Angular 5 DomSanitizer with Hyperlinks - html

I am using a WYSIWYG editor (CKEditor) and trying to render the content with Angular 5.
What I am trying to figure out is the proper way to use DomSanitizer in Angular 5. The problem I am facing is that Hyperlinks are not working (are not "clickable") in the resulting sanitized HTML.
I am using the following Typescript code to return a safeHtml content:
public getSafeContent(): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(this.page.content);
}
and using it in my template this way:
<div [innerHTML]="getSafeContent()"></div>
This will render the HTML with all inline styles intact, but hyperlinks won't work.
I tried doing this instead:
public getSafeContent(): SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, this.page.content);
}
Which results in that Hyperlinks actually works, but inlines styles are not.
Is there a way to get both styles and hyperlinks to work with sanitized content?
Update
This is what the page looks like in Chrome dev tools:
<div _ngcontent-c22="" class="row">
<div _ngcontent-c22="" class="col-lg-12">
<div _ngcontent-c22="">
<p>google</p>
</div>
</div>
</div>
and the google link is not clickable.

bypassSecurityTrustHtml allows <script> tags in the content. For URLs you need bypassSecurityTrustUrl. See here: https://angular.io/api/platform-browser/DomSanitizer#bypassSecurityTrustUrl.
I've never tried stacking the bypassXXX methods, so I don't know if you can do something like this bypassSecurityTrustUrl(bypassSecurityTrustHtml(myContent)) but I would guess probably not since each method takes a string but returns an object (of type SafeHtml/SafeUrl), so it can't be used as the input to the stacked function call which expects a string.
So, you may need to parse the contents, pass each URL into the bypassSecurityTrustUrl and then combine everything back together again.
Update
I just looked at the sanitize method. I haven't tried this, but something like this might work:
this.sanitizer.sanitize(SecurityContext.HTML, this.sanitizer.bypassSecurityTrustUrl(myContent));
since sanitize can take a SafeValue as an input. The inner bypassSecurityTrustUrl sanitizes the URLs and returns a SafeUrl, which is unwrapped by the outer sanitize and used as input to make it HTML safe. Like I said, I haven't tried it, but it looks good in theory...

in .ts pipe for 'URL' sanitizer
import { Component, Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'sanitizeUrl' })
export class SafeUrlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
in .html
<div [innerHTML]="Content | sanitizeUrl| sanitizeHtml">
</div>
pipe for 'HTML' sanitizer
import { Component, Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({
name: 'sanitizeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {}
transform(value) {
return this.sanitized.bypassSecurityTrustHtml(value);
}
}
Please consider the above solution. this will apply both pipes without disturbing any style and link click event at same time

I used DOMPurify library to sanitize the DOM. DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
I created angular PureTextPipe pipe to sanitize the raw content
import { Pipe, PipeTransform } from '#angular/core';
import DOMPurify from 'dompurify';
#Pipe({
name: 'pureText',
pure: true
})
export class PureTextPipe implements PipeTransform {
transform(str: any): any {
let res = str;
if (str === null || str === undefined || str === '' || str === 0) {
res = '--';
}
return DOMPurify.sanitize(res);
}
}
Now to use this pipe you just need to add it to HTML like:
<div [innerHTML]="yourRawData | pureText"></div>
Documentation of DOMPurify at
https://www.npmjs.com/package/dompurify
import DOMPurify from 'dompurify';
var clean = DOMPurify.sanitize(dirty);

I have found a working solution to this. With this both the URL and the style would work. It is again with the use of "bypassSecurityTrustHtml". It's surprising how using this in html or TS does not make the link work, but using this in a seperate file as a pipe makes this work like a charm.
Here is what I did
Create a Custom Pipe
.safe.dom.pipe
import { Component, Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl } from '#angular/platform-browser';
#Pipe({
name: 'safeDom'
})
export class SafeDomPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
switch (type) {
case 'html': return this.sanitizer.bypassSecurityTrustHtml(value);
case 'style': return this.sanitizer.bypassSecurityTrustStyle(value);
case 'script': return this.sanitizer.bypassSecurityTrustScript(value);
case 'url': return this.sanitizer.bypassSecurityTrustUrl(value);
case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value);
default: throw new Error(`Invalid safe type specified: ${type}`);
}
}
}`
In Case you have a common module in your project that is included where you require to implement it, you can declare and export this package from there. Else just declare this pipe in the module you want to implement this.
import { SafeDomPipe } from './pipes/safe.dom.pipe';
#NgModule({
imports: [
.....
],
exports: [
....
SafeDomPipe
....
],
declarations: [
....
SafeDomPipe
....
])
Now in Html you can directly use this pipe.
<div [innerHtml]="data | safeDom: 'html' "> </div>
This would allow both link and style.

If you're data content is consisting of HTML with Hyperlinks. But we want to sanitize with hyperlink working. This worked for me:
HTML
<div [innerHTML]="getSafeContent()"></div>
TS:
public getSafeContent(): SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, this.page.content);
}

This works for me:
Component:
content = '<b>Hello World</b><p style=\'font-size:14pt\'>
<a href=\'http://www.google.com\'>Go to Google</a></p>Test123';
public getSafeContent(): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(this.content);
}
HTML:
<div [innerHTML]="getSafeContent()"></div>
Link works and inline styles are intact

Related

DomSanitizer doesn't parse text to HTML

I am working on an Angular project and I am taking data from a database where it has data with HTML tags. To put it in the HTML I can do that through this code:
[innerHTML]="element.texto"
What Im looking for is to use it on the ts to put the text on a pdf. Im using DomSanitizer but the output is wrong and does not transform it on the HTML. This is the exaple code im using:
import { DomSanitizer } from "#angular/platform-browser";
///
constructor(
private sanitizer:DomSanitizer
) { }
dataTransformer() {
let textoHTML = "<p>Hello <b>World</b></p>"
alert(this.sanitizer.bypassSecurityTrustHtml(textoHTML)) //It shows: SafeValue must use [property]=binding: <p>Hello <b>World</b></p> (see http://g.co/ng/security#xss)
}
In the other hand, I have used the following code for parseing the code but with a wrong solution.
this.sanitizer.bypassSecurityTrustHtml(textoHTML)['changingThisBreaksApplicationSecurity'] //It shows: <p>Hello <b>World</b></p>
What im looking for is: Hello World
Thanks!
you can use pipe
[innerHTML]="element.texto | safeHtml "
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Pipe({
name: 'safeHtml',
pure: false
})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {
}
transform(value: any): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
#erfan-farhadi has given a reusable code.
Use the following as quick fix
Change your ts file to something like this
import { DomSanitizer } from "#angular/platform-browser";
sanitizedValue: undefined; // member var to hold sanitized HTML
constructor(
private sanitizer:DomSanitizer
) { }
dataTransformer() {
let textoHTML = "<p>Hello <b>World</b></p>"
this.sanitizedValue = this.sanitizer.bypassSecurityTrustHtml(textoHTML));
}
/// ...
In html template use like following
[innerHTML]="sanitizedValue"

ViewEncapsulation.None not working with innertHTML

I'm actually developing an angular application and I have to put an [innerHTML] element in a div.
My code
Like that :
something.component.html
<section class="mx-auto" *ngFor="let publication of publication">
<div [innerHTML]="publication.content"></div>
</section>
So in ts :
something.component.ts
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Subscription } from 'rxjs';
import { ActivatedRoute } from '#angular/router';
import { Title, Meta } from '#angular/platform-browser';
import { Publication } from '../publication.model';
import { PublicationsService } from '../publication.service';
#Component({
selector: 'app-free-publication',
templateUrl: './something.component.html',
styleUrls: ['./something.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FreePublicationComponent implements OnInit {
publication: Publication[] = [];
suggestions: Publication[] = [];
private routeSub: Subscription;
getId: any;
isLoading = false;
constructor(public publicationsService: PublicationsService, private route: ActivatedRoute, private titleService: Title, private meta: Meta) {
this.getId = this.route.url['_value'][1].path;
this.getId = + this.getId;
}
ngOnInit() {
this.isLoading = true;
// main publication
this.routeSub = this.route.params.subscribe(params => {
this.publicationsService.getPublication(params['publicationId']).then(dataPublication => {
for (let i = 0; (dataPublication.content.match(/wp-content/g) || []).length; i++) {
dataPublication.content = dataPublication.content.replace('https://aurelienbamde.com/wp-content/', 'assets/content/');
}
this.titleService.setTitle(dataPublication.title);
this.meta.addTag({ name: 'keywords', content: dataPublication.post_tag });
this.publication = [dataPublication];
});
});
}
}
And my innertHTML do not return the style of the html doc that I send.
My tests
With a console.log() at the end of ngOnInit, I can see my html with all of the styles attributs, but by inspecting the div of the innerHTML, there is no style inside.
My question
So I well implement ViewEncapsulation.None as you see, there is an action on other elements, so it works, but not on my innerHTML.
Do you have any idea, problem of version ? Or coworking with others elements ?
Thanks in advance for your time !
And I wish you success in your projects.
You must bypass the security imposed by angular for dangerous content (HTML content not generated by the app). There is a service, called DomSanitizer that enables you to declare a content as safe, preventing angular to filter potentially harm things to be used like styles, classes, tags etc. You basically need to pass your content through this sanitizer using a pipe:
<div [innerHTML]="dangerousContent | safeHtml"></div>
Your SafeHtmlPipe would be something like this:
#Pipe({name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value)
}
}
There are other bypassSecurityTrust* methods in DomSanitizer:
bypassSecurityTrustScript
bypassSecurityTrustStyle
bypassSecurityTrustUrl
bypassSecurityTrustResourceUrl
You can find more info in Angular docs.

How do i write a function that detects and replaces urls in a loaded JSON?

Im trying to write a function that replaces all urls in a Json with a button that leads to another function. The loaded Json with the buttons is shown on my website then.
In my component.ts the part where a Json is detected looks like that:
} else {
this.downloading = false;
this.container = <aContainer>json;
this.loadedJson = JSON.stringify(json, null, 2);
And in the component.html the JSON gets shown on my website:
h3>Unrecognized JSON data:</h3>
<pre>{{loadedJson}}</pre>
I wanted to try it with a regExp like that to replace everything with the string: url://
let regExp = /url:\/\//
Does anybody know how i can implement code that recognizes the urls and than replaces them with a button that leads to antoher function?
Thank You!
You can create a pipe to transform the loadedJSON like below and [innerHTML] property binding
import { PipeTransform, Pipe } from "#angular/core";
import { DomSanitizer } from "#angular/platform-browser";
#Pipe({
name: "transformURL",
pure: false
})
export class TransformURLPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) { }
transform(value: any): any {
if (value.length === 0) {
return value;
}
return this.sanitizer.bypassSecurityTrustHtml(
value.replace(/url:\/\//g, "<button type='button'>Test</button>")
);
}
}
HTML
<p [innerHTML]=" loadedJSON | transformURL"></p>

Display view from external HTML

I would like to display a view on page from HTML from external database using Angular 2.
For example I have a html string in component.ts like below:
htmlString = '<div style="color: red">Hello world!</div>';
And this is my html file:
<div [innerHTML]="htmlString"></div>
Instead to get red font "Hello word!" sentence it returns me only tags on my page. How can I fix this issue?
i think you also need to put a pipe to allow html in string (safe html) ..
something like:
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({
name: 'safeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { }
transform(value: any, args?: any): any {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
and use like this:
<div [innerHTML]="htmlString | safeHtml"></div>

Are global variables accessible in Angular 2 html template directly?

So I put in app.settings like
public static get DateFormat(): string { return 'MM/DD/YYYY';}
and then in one of my html template of a component I want to do something like this.
<input [(ngModel)]="Holiday" [date-format]="AppSettings.DateFormat"/>
In component I have
import { AppSettings } from '../../../app.settings';
Right now it's not accessible like this in html template. Is there any way?
No, the scope for code in the template is the component instance. You need to assign the value to a field in the component, or add a getter or method that forwards to the global variable in order to be able to use it from the template.
import { AppSettings } from '../../../app.settings';
...
export class MyComponent {
get dateFormat() {
return AppSettings.DateFormat;
}
}
then you can use it like
<input [(ngModel)]="Holiday" [date-format]="dateFormat"/>
It seems hacky but you can use pipe. It saves you from repeating injection or variable binding for each component.
#Pipe({
name: 'settings',
})
export class GlobalVariablePipe implements PipeTransform {
transform(value: any): object {
return AppSettings;
}
}
Then, once imported in your module, you can simply use the pipe as follows:
{{(''|settings).DateFormat}}
To the best of my knowledge, no, and that's by design. Templates are hooked up in conjunction with components, that's how they derive their scope and thereby access to bindable data. It's possible it could be hacked around, but even if you figure it out, this is a path you should not follow.
I do this sort of thing with a class:
class AppConfig {}
AppConfig.Globals = {
TEST_VAL: 'hey now here is the value'
};
export { AppConfig }
And then use it like so:
import { Component } from '#angular/core';
import { AppConfig } from '../../app/app.config';
class HomeComponent {
constructor() {
this.test_val = AppConfig.Globals.TEST_VAL;
}
}
HomeComponent.annotations = [
new Component ( {
templateUrl: './views/home/home.component.html'
} )
];
export { HomeComponent }
In the template:
{{test_val}}
Works well for me.
It seems an old topic. However here is my 2 cents. I used a service instead and it's working. I come up with something like this : <input [(ngModel)]="Holiday" [date-format]="appSettingService.DateFormat"/>. The inconvenient of this solution, you have DI the service.
Here is what I did :
appSettingService.ts
import { Injectable } from '#angular/core';
#Injectable({
})
export class AppSettingService{
constructor() { }
public DateFormat(): string { return 'MM/DD/YYYY';}
...
In your component :
...
constructor(public appSettingService : AppSettingService ) { }
...
And finally your in your template:
<input [(ngModel)]="Holiday" [date-format]="appSettingService.DateFormat"/>
I'm looking for a way to use the value without the suffixe like this:
<input [(ngModel)]="Holiday" [date-format]="DateFormat"/>
Step 1. Define global json globals.json
{
"LABEL": "Your Text"
}
Step 2. Define Injectable Globals class globals.ts and add to providers in app.module.ts
import { Injectable } from '#angular/core';
import jsonGlobals from './globals.json';
#Injectable()
export class Globals {
prop: any = jsonGlobals;
}
in app.module.ts
providers: [Globals]
Step 3. Inject in component's constructor
constructor(public globals: Globals) { }
Step 4. Add following compiler properties in tsconfig.json
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
Step 5. Use in HTML templates
{{globals.prop.LABEL}}