How do I resolve sanitizing unsafe URL value with blob value? - javascript

I'm having this sanitize URL error in Angular, I've seen many solutions, but I've not been able to use them in my scenario, that's why I'm asking the question.
Here is my function:
#Input()
maxFileSize: number = 1000000;
public fileChangeEvent(fileInput: any) {
if (fileInput.target.files && fileInput.target.files[0]) {
const reader = new FileReader();
reader.onload = (e: any) => {
if (e.target.result) {
if (e.target.result.length < this.maxFileSize) {
this.value = e.target.result;
} else {
alert(`Logo size ${e.target.result.length} cannot exceed ${this.maxFileSize} bytes.`);
}
}
};
reader.readAsDataURL(fileInput.target.files[0]);
}
}
I've tried this.sanitizer.bypassSecurityTrustUrl(url), but in my case, fileInput.target.files[0] is a blob, so I always get an error when wrapping it worth the sanitizer function.
I'm using it in the view like this:
<ng-container *ngIf="value">
<div class="input-group-append">
<div class="img-thumbnail">
<img src="{{value}}" alt="Preview" />
</div>
<button class="btn btn-danger btn-lg" type="button" (click)="clearLogo()">
<i class="fa fa-trash" aria-hidden="true"></i>
Delete
</button>
</div>
</ng-container>
enter code here
I've also tried [src]="{{value}}", but that did not work as well.
I'm getting this error:
WARNING: sanitizing unsafe URL value
Please where am I getting it wrong?

I hope this fixes your issue.
constructor(private sanitizer:DomSanitizer){...}
....
let file = event.target.files[0];
let blob = new Blob([file], { type: file.type });
let url = window.URL.createObjectURL(blob);
this.value = this.sanitizer.bypassSecurityTrustUrl(url);

Related

How to render an Image loaded from postgres Database in angular

I'm trying to render dinamically and image linked to an Ad in angular, the problem is that even if in tag the 'src' parameter seems to be right it can't show the image.
This is my code in Angular
ngOnChanges(changes: SimpleChanges): void {
console.log("ngOnChanges: " + changes);
console.log(this.ads.length);
for(let i = 0; i< this.ads.length; i++){
this.service.getImage(this.ads[i]).subscribe(blobList => this.ads[i].images = blobList).add( () => {
console.log("Byte: " + this.ads[i].images.at(0));
let div = document.getElementById("ad" + this.ads[i].id) as HTMLDivElement;
console.log(div);
let bytea: ArrayBuffer = this.ads[i].images.at(0) as ArrayBuffer;
const buffer = Buffer.from(bytea);
const blob = new Blob([buffer], {type: 'image/png'});
const image = this.createImageFromBlob(blob);
div.appendChild(image);
});
}
}
This is the function I use to create an HTMLImageElement from a BLOB
public createImageFromBlob(blob: Blob): HTMLImageElement {
const image = new Image();
const url = URL.createObjectURL(blob);
image.src = url;
return image;
}
This is the Ad interface
export interface Ad{
id: number;
title: string;
description: string;
user: User;
property: Property;
price: number;
mq: number;
status: string;
city: string;
images: ArrayBuffer[];
}
This is my code in HTML
<span *ngIf="!isEmpty()">
<span *ngFor="let ad of ads">
<span *ngIf="canShow(ad.status)">
<div class="container justify-content-center">
<div class="card jumbotron">
<div class="text-center badge-info badge" style="margin-top: -2%;" id="statusTitle">
<h2>{{ad.status.toUpperCase()}}</h2>
</div>
<div class="row no-gutters m-3">
<div class="col-lg-5 col-md-7 col-md-12" id="ad{{ad.id}}">
</div>
<div class="col">
<div class="card-block px-2">
<h3 class="card-title">{{ad.title}}</h3>
<p class="card-text">{{ad.description}}</p>
{{ad.price}}
<hr>
{{ad.user.nickname}}
</div>
</div>
</div>
<div class="card-footer w-100 text-muted">
Leggi annuncio
</div>
</div>
</div>
<hr>
</span>
</span>
</span>
I'm adding the java springboot controller that I use to take data from the database. This method returns a List of Byte[] 'cause I have bytea on postgres database.
#GetMapping("/getImage")
public List<byte[]> getImage(HttpServletRequest request, #RequestParam String adId){
List<Image> imageList = DBManager.getInstance().getImageDao().findByAdId(Integer.parseInt(adId));
List<byte[]> imgList = new ArrayList<>();
for (Image image : imageList) {
imgList.add(image.getData());
}
return imgList;
}
And this is the method I use in Angular to call the java server.
getImage(adId: number): Observable<Blob[]> {
return this.http.get<Blob[]>('http://localhost:8080/image', {params: {adId: adId}, responseType: 'json'});
}
As you can see i'm trying to inject the HTMLImageElement in a div linked through an ID.
I tried some other methods but this one is the only that gave me a correct Blob, but on website it only shows the "Image not Found" icon.
image
I can't figure out what I'm doing wrong, any help will be very appreciated, thanks.
I resolved ot converting the byte[] to blob but only adding the base64 to image/jpeg encoding string, thank you all, hope this will help someone.
for(let i = 0; i< this.ads.length; i++){
this.service.getImage(this.ads[i].id).subscribe(blobList => this.ads[i].images = blobList).add( () => {
let image = document.getElementById("img" + this.ads[i].id) as HTMLImageElement;
if(this.ads[i].images.at(0) == null){
image.src = "https://fakeimg.pl/400x250/?text=No%20image"
}else{
image.src = 'data:image/jpeg;base64,' + this.ads[i].images.at(0);
}
});
}

Changing image in child component will update higher most html img tag in parent component

Video
I made a short 15 second Youtube video demonstrating the bug since its rather hard to explain here is video: https://www.youtube.com/watch?v=YXykw2oSh8E --- please ignore my terrible editing skills
Short Summary
I will summarize the error here: If I have two image tags and I am trying to change the image of the lower one that is located lower in the html file, the image on the higher most img tag will be changed as opposed to the one I selected even though the logic is in a seperate component instance
Code layout
In my code I have two <app-image-display> components stacked on top of one another. When I try to update the image of the one with the name of coverPhoto it will update the image in the one named profile. If I move the coverPhoto named instance higher in the html file than profile named instance and try to change profile it will update coverPhoto.
Parent
I have a parent component that looks like this:
....
<div *ngIf="company">
<app-image-display name="profile" id="profile" #profile [image]="company.profileUrl" [uploadPath]='companyProfilePath' (imageUpdated)="updateImage($event, CompanyImage.PROFILE)"></app-image-display>
<app-image-display name="coverPhoto" id="coverPhoto" #coverPhoto [image]="company.coverPhotoUrl" [uploadPath]='companyCoverPath' (imageUpdated)="updateImage($event, CompanyImage.COVER_PHOTO_URL)"></app-image-display>
</div>
.....
You can see it contains two separate instances of the same component called ImageDisplay. One of the instances is designed to handle a profile image and the other a cover photo. I put in temporary logic to show me which on is the cover photo just to be explicit as seen in the video. This component is designed to show the use the previously saved image then allow them to view a new image (tempImage) before saving it to the db. The ImageDisplayComponent looks like the following:
Image Display Html
<img [src]="image" width="200" *ngIf="!tempImage" height="200" class=" img-thumbnail" alt="Responsive
image" onerror="this.src = 'TEMP_IMG_URL_GOES_HERE'">
<img [src]="tempImage" *ngIf="tempImage" width="200" height="200" class=" img-thumbnail"
alt="Responsive image">
<span *ngIf="uploadPath.includes('cover')">Cover Image</span>
<app-image-picker *ngIf="!percentage" (imageSelected)="imageSelected($event) (tempImageSelected)="tempSelected($event)"></app-image-picker>
Image Display TS
And the .ts looks like this:
...
export class ImageDisplayComponent implements OnInit {
#Output() imageUpdated: EventEmitter<string> = new EventEmitter();
#Input() image: string = '';
#Input() uploadPath: string
tempImage: string;
percentage: Observable<number>;
selectedPictureFile: File;
task: AngularFireUploadTask;
constructor(
private dbs: AngularFireStorage,
private toastService: ToastService
) { }
ngOnInit(): void {
console.log(this.uploadPath)
}
imageSelected(image: File) {
this.selectedPictureFile = image;
this.fileUpload(this.uploadPath, this.selectedPictureFile);
}
async fileUpload(path: string, file: File): Promise<void> {
const ref = this.dbs.ref(path);
this.task = this.dbs.upload(path, file);
this.percentage = this.task.percentageChanges();
let imageUrl = '';
from(this.task).pipe(
switchMap(() => ref.getDownloadURL()),
map((img) => imageUrl = img),
finalize(() => delete this.percentage)
).subscribe(() => {
this.imageUpdated.emit(imageUrl)
},(error) => {
this.toastService.show(`${error.message}`, {
delay: 3000,
autohide: true
});
});
}
tempSelected(imageUrl: string) {
this.tempImage = imageUrl;
}
}
...
ImagePickerComponent Html
app-image-picker looks like the following:
<div style="margin: 5px">
<label for="file-upload" class="custom-file-upload">
{{labelText}}
</label>
<input type="file" id="file-upload" (change)="onPictureSelected($event)" accept=".png, .jpg">
<div class="row">
<button *ngIf="pictureSelected" class="btn btn-info m-1" [disabled]="!pictureSelected" (click)="uploadPicture()">
Save
</button>
<button *ngIf="pictureSelected" class="btn btn-warn m-1" [disabled]="!pictureSelected" (click)="cancel()">
Cancel
</button>
</div>
ImagePickerComponent TS
This is what the onPictureSelected function looks like:
// image selection and verification.
onPictureSelected(event) {
this.selectedPictureFile = event.target.files[0] as File;
const reader = new FileReader();
reader.readAsDataURL(this.selectedPictureFile);
if (!this.imageService.isImage(this.selectedPictureFile.name)) {
this.pictureSelected = false;
} else if (!this.imageService.isAllowedSize(this.selectedPictureFile.size)) {
this.pictureSelected = false;
} else {
// tslint:disable-next-line:no-shadowed-variable
reader.onloadend = (event: any) => {
if (event.target) {
this.selectedPictureURL = event.target.result;
this.tempImageSelected.emit(this.selectedPictureURL);
}
};
this.pictureSelected = true;
}
}
I am on Angular version 9.0.3

Property 'split' does not exist on type 'ArrayBuffer' in angular

I was trying to read a CSV file using Filereader and want to convert the content into an array. I was able to get the CSV file properly, but whenever I want to convert the content of CSV file into an array I have this error.
Why do I have this error and how can i solve it?
ERROR in src/app/app.component.ts(31,10): error TS2314: Generic type 'Array<T>' requires 1 type argument(s).
src/app/app.component.ts(31,21): error TS2339: Property 'split' does not exist on type 'string | ArrayBuffer'.
Property 'split' does not exist on type 'ArrayBuffer'.
Here is my app.component.html file:
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">
<img src="/docs/4.0/assets/brand/bootstrap-solid.svg" width="30" height="30" class="d-inline-block align-top"
alt="">
Floor Plan
</a>
</nav>
<div class="card m-5">
<div class="row row-5">
<div class="col-4">
<div class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input" id="inputGroupFile04" (change)="upload($event.target)">
<label class="custom-file-label" for="inputGroupFile04">Choose file</label>
</div>
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" class="btn btn-primary btn-sm">Upload </button>
</div>
</div>
</div>
<div class="col-8 border border-primary" >
{{csvContent}}
</div>
</div>
</div>
here is my app.component.ts file:
export class AppComponent {
fileToUpload: File = null;
title = 'floor-plan';
csvContent: string[] = []
upload(input: HTMLInputElement) {
const files = input.files;
var content = this.csvContent;
if (files && files.length) {
const fileToRead = files[0];
const fileReader = new FileReader();
fileReader.onload = (event) => {
this.csvContent = (event.target as FileReader).result.split('\n').map((data) => {
return data.split(',')
})
}
fileReader.readAsText(fileToRead, "UTF-8");
}
}
}
The type of the FileReader result property is tricky for TypeScript, because it depends on what method you've called elsewhere in the code.
In your case, you're calling readAsText so you know that result contains a string, not an ArrayBuffer, but TypeScript doesn't know that.
You'll need a type guard or type assertion. For instance, with a type guard:
fileReader.onload = (event) => {
const result = fileReader.result;
if (typeof result !== "string') {
throw new Error("Unexpected result from FileReader");
}
this.csvContent = result.split('\n').map((data) => {
return data.split(',')
})
};
Or with a type assertion:
fileReader.onload = (event) => {
this.csvContent = (fileReader.result as string).split('\n').map((data) => {
return data.split(',')
})
};
In both of the examples above, I've used fileReader rather than event.target since the onload handler closes over it.
FileReader.result can be either a string or an ArrayBuffer along its life. It starts being an ArrayBuffer, then, after the file is read, it gets converted to a string. The problem is, Typescript isn't happy about applying the .split() method to something that can be an ArrayBuffer at some point in time.
It will be a string at this point of the script execution; you know that, but Typescript doesn't.
In order to tell this to Typescript, cast it to a string like so :
this.csvContent = <string>((event.target as FileReader).result).split(...)
Then Typescript should stop complaining :)

Angular4- Displaying item in JSON that isn't always present in

Still building off of this question, to pull in media data from the tweet JSON that I've made a call to. At first, I thought it would be as simple as iterating through the JSON, but the entities.media.media_url are not always present, and will return undefined errors for the tweets that don't contain them. I tried creating a method that would pull the data if it exists, but I'm not able to get it to iterate.
tweets.component.html
<div class="tweet-container">
<div *ngFor="let item of tweetsdata" class="tweets-card">
<div class="tweet-text">
<p class="tweet-date">{{item.created_at | slice:0:10}}</p>
<p>{{item.text}}</p>
<!-- <img src={{getImage()}}> -->
<!-- <p>hello: {{item.entities?.media[0].media_url}}</p> -->
<p>{{getImage()}}</p>
<div class="interact-tweet">
<i class="fa fa-reply" aria-hidden="true"></i>
<i class="fa fa-retweet" aria-hidden="true"></i>
<i class="fa fa-heart" aria-hidden="true"></i>
</div>
</div>
</div>
</div>
tweets.component.ts
...
searchcall() {
const headers = new Headers();
// const searchterm = 'query=' + this.searchquery;
const searchterm = 'query=from%3Adailymuse%20%23' + this.searchquery;
headers.append('Content-Type', 'application/X-www-form-urlencoded');
this.http.post('https://localhost:3000/search', searchterm, {headers: headers}).subscribe((res) => {
this.tweetsdata = [
res.json().data.statuses[0],
res.json().data.statuses[1],
res.json().data.statuses[2],
res.json().data.statuses[3],
res.json().data.statuses[4]
];
console.log(this.tweetsdata);
this.screen_name = this.tweetsdata[0].user.screen_name;
this.user = this.tweetsdata[0].user.name;
this.profile_img_url = this.tweetsdata[0].user.profile_image_url_https;
this.user_id = this.tweetsdata[0].user.id_str;
});
}
getImage() {
if (this.tweetsdata.entities) // will keep returning undefined because it's not pointing to a specific tweet in the array, tweetsdata
{
const imgURL = this.tweetsdata.entities.media[0].media_url;
console.log(imgURL);
return imgURL;
} else {
console.log('nope');
}
}
I think *ngIf would make the most sense to use, but I'm unsure how to set up the statement to check if the key exists first, then present the link. This is what I've tried:
<p *ngIf="item.entities">{{item.entities.media[0].media_url}}</p>
Which technically works if there is an attached image present, but it breaks the code for the following tweets that don't contain an image. Am I setting up the if statement correctly?

Parse JS : Error: Tried to encode an unsaved file

This error happens when I try to upload two files simultaneously that call the same controller for submission. Here is the relevant code:
<ion-list id="licenseDetailsInReview-list4" class=" ">
<ion-item class="item-divider " id="licenseDetails-list-item-divider5">Image 1
<div ng-if="doc.status!='approved'" class="edit">
<div class="button button-clear button-small has-file button-black " type="button">
<span style="color:black" class="image1"><i class="icon ion-edit icon-accessory custom"></i></span>
<input type="file" accept="image/*;capture=camera" name="{{doctype.doctype.documentType}}" onchange="angular.element(this).scope().$parent.uplCtrl.fileChanged(this.files, this,1)">
</div>
</div>
</ion-item>
<div class="thumbimg">
<span class="imgdet1"><div ng-if ="doc.thumb1"><img src="{{doc.thumb1.url}}"></div>
<div ng-if="!doc.thumb1"><i class="icon ion-image" style="font-size: 64px; color: rgb(136, 136, 136); vertical-align: middle;"></i></div>
</div>
</ion-list>
<ion-list id="licenseDetailsInReview-list4" class=" ">
<ion-item class="item-divider " id="licenseDetails-list-item-divider5">Image 2
<div ng-if="doc.status!='approved'" class="edit">
<div class="button button-clear button-small has-file button-black edit" type="button">
<span style="color:black" class="image2"><i class="icon ion-edit icon-accessory custom"></i></span>
<input type="file" accept="image/*;capture=camera" name="{{doctype.doctype.documentType}}" onchange="angular.element(this).scope().$parent.uplCtrl.fileChanged(this.files, this,2)">
</div>
</div>
</ion-item>
<div class="thumbimg">
<span class="imgdet2"><div ng-if ="doc.thumb2"><img src="{{doc.thumb2.url}}"></div>
<div ng-if="!doc.thumb2"><i class="icon ion-image" style="font-size: 64px; color: rgb(136, 136, 136); vertical-align: middle;"></i></div>
</div>
</ion-list>
And the controller methods :
fileChanged(files, type, i) {
const self = this;
const file = files[0];
console.log(type.name, files);
window.URL = window.URL || window.webkitURL;
self['files'] = self['files'] || {};
self['files'][type.name] = {
file: file,
blob: window.URL.createObjectURL(file)
};
var typeHolder = document.querySelector('.image' + i).innerHTML = "Uploading File.." + file.name;
this.$scope.$apply();
this.submit(self.$scope.user, i)
}
And the submit method:
submit(user, i) {
console.log('user', user);
const self = this;
//var UserDocument = Parse.Object.extend('UserDocument');
this.$scope.mutex =0;
var promises = [];
for (self.$scope.doctype.documentType in this.files) {
console.log("Files", this.files)
if (this.files.hasOwnProperty(self.$scope.doctype.documentType)) {
var parseFile = new Parse.File(self.$scope.doctype.documentType + i +'.' + this.files[self.$scope.doctype.documentType].file.name.split('.').pop(), this.files[self.$scope.doctype.documentType].file);
if (!self.$scope.doc) {
var objUserDocType = new this.UserDocumentType();
console.log("reached here");
var docType = self.$scope.usd.find(o => o.documentType == self.$scope.doctype.documentType);
objUserDocType.id = docType.objectId;
this.objUserDoc.set('docType', objUserDocType);
}
// console.log("reached here too!");
this.objUserDoc.set('owner', Parse.User.current());
//objUserDoc.set('status', 'inReview');
this.objUserDoc.set('image' + i, parseFile);
self.$scope.submitted = 1;
var p = this.objUserDoc.save().....
So when I try to upload one image before the other one has been saved, I get the error:
upload.controller.js:110 error objUserDoc Error: Tried to encode an unsaved file.
at encode (http://localhost:3000/app-1db45d.js:92569:14)
at ParseObjectSubclass._getSaveJSON (http://localhost:3000/app-1db45d.js:88168:40)
at ParseObjectSubclass._getSaveParams (http://localhost:3000/app-1db45d.js:88176:24)
at task (http://localhost:3000/app-1db45d.js:89758:34)
at TaskQueue.enqueue (http://localhost:3000/app-1db45d.js:94528:10)
at Object.save (http://localhost:3000/app-1db45d.js:89768:31)
at ParsePromise.wrappedResolvedCallback (http://localhost:3000/app-1db45d.js:90767:44)
at ParsePromise.resolve (http://localhost:3000/app-1db45d.js:90705:37)
at ParsePromise.<anonymous> (http://localhost:3000/app-1db45d.js:90777:30)
at ParsePromise.wrappedResolvedCallback (http://localhost:3000/app-1db45d.js:90767:44)
What can I do to resolve this??
thanks in advance
Two key ideas are needed: (a) we must save the file, then save the referencing object, and (b) to do N file-object saves, accumulate the save promises first, then execute all of them with Promise.all().
I tried to re-arrange your code to illustrate, but with an important caveat: I don't understand the app, nor can I see the controller surrounding all this, so the code here must be checked carefully to insure that it correctly refers to the controller level objects, including $scope.
// save a parse file, then the parse object that refers to it
function saveUserDocWithFile(objUserDoc, doctype) {
var parseFile = new Parse.File(doctype.documentType + i +'.' + this.files[doctype.documentType].file.name.split('.').pop(), this.files[doctype.documentType].file);
// first save the file, then update and save containing object
this.$scope.submitted = 1;
return parseFile.save().then(function() {
objUserDoc.set('image' + i, parseFile);
return objUserDoc.save();
});
}
With that, the submit function is simpler. It just needs to create an array of promises and execute them (using Promise.all()).
submit(user, i) {
console.log('user', user);
const self = this;
//var UserDocument = Parse.Object.extend('UserDocument');
self.$scope.mutex =0;
var promises = [];
for (self.$scope.doctype.documentType in self.files) {
console.log("Files", self.files)
if (self.files.hasOwnProperty(self.$scope.doctype.documentType)) {
if (!self.$scope.doc) {
self.objUserDoc.set('docType', objUserDocType());
}
self.objUserDoc.set('owner', Parse.User.current());
var promise = saveUserDocWithFile(self.objUserDoc, self.$scope.doctype);
promises.push(promise);
}
}
return Promise.all(promises);
}
// factor out objUserDocType creation for readability
function objUserDocType() {
var objUserDocType = new this.UserDocumentType();
var docType = this.$scope.usd.find(o => o.documentType == this.$scope.doctype.documentType);
objUserDocType.id = docType.objectId;
return objUserDocType;
}

Categories