Everyone must have seen the message for inline HTML into DOM in angular. Yes we want to show trusted message from server or we can inline our constant and svg icons because we simply wants to colourize them.
Sanitze and DomSanitizer
Angular gives classes for clearing content form various malicious junk
abstract class Sanitizer {
abstract sanitize(context: SecurityContext, value: string | {}): string | null
}
abstract class DomSanitizer implements Sanitizer {
abstract sanitize(context: SecurityContext, value: string | SafeValue): string | null
abstract bypassSecurityTrustHtml(value: string): SafeHtml
abstract bypassSecurityTrustStyle(value: string): SafeStyle
abstract bypassSecurityTrustScript(value: string): SafeScript
abstract bypassSecurityTrustUrl(value: string): SafeUrl
abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl
}
Angular make use of concrete private class i.e DomSanitizerImpl while building DOM tree
The service introduces the concept of Safevalue. Actually it is a wrappper class around the string. When the rendere added up the content via binding, whether it be a innerHTML, @HostBinding or src its value goes through sanitize method. If it is received in safeValue, then the underlying string get returned simply. Angular has not been sanitizing library specially. So we can expect that framwork is not taking any risks. The code of SVG gets turn into an empty string, inline styles are removed etc.
Proper Safehtml pipe
With this DOMPurify a pipe can be made that not only mark values as safe but will actually make them also. We can run it via DOMPurify.sanitize method and can mark it safe with provided context
@Pipe({name: 'dompurify'})
export class NgDompurifyPipe implements PipeTransform {
constructor(private readonly domSanitizer: DomSanitizer) {}
transform(
value: {} | string | null,
context: SecurityContext = SecurityContext.HTML,
): SafeValue | null {
return this.bypassSecurityTrust(context, DOMPurify.sanitize(value));
}
private bypassSecurityTrust(
context: SecurityContext,
purifiedValue: string,
): SafeValue | null {
switch (context) {
case SecurityContext.HTML:
return this.domSanitizer.bypassSecurityTrustHtml(purifiedValue);
case SecurityContext.STYLE:
return this.domSanitizer.bypassSecurityTrustStyle(purifiedValue);
case SecurityContext.SCRIPT:
return this.domSanitizer.bypassSecurityTrustScript(purifiedValue);
case SecurityContext.URL:
return this.domSanitizer.bypassSecurityTrustUrl(purifiedValue);
case SecurityContext.RESOURCE_URL:
return this.domSanitizer.bypassSecurityTrustResourceUrl(purifiedValue);
default:
return null;
}
}
}
DoPurifyDomSanitizer
We can create another class by simply extending DomSanitizer that will delegate sanitizing to DOMPurify. We can start with Sanitizer service and we can use it in both pipe and our DOMSanitizer. Adding token will allow users to set up DOMPurify the way it is need for application.
@NgModule({
// ...
providers: [
{
provide: DOMPURIFY_CONFIG,
useValue: {FORBID_ATTR: ['id']},
},
],
// ...
})
export class AppModule {}
@Injectable({
providedIn: 'root',
})
export class NgDompurifySanitizer extends Sanitizer {
constructor(
@Inject(DOMPURIFY_CONFIG)
private readonly config: NgDompurifyConfig,
) {
super();
}
sanitize(
_context: SecurityContext,
value: {} | string | null,
config: NgDompurifyConfig = this.config,
): string {
return sanitize(String(value || ''), config);
}
Now we are aware of how DomSanitizer works and we can no longer mask the issue of inlining arbitary content. Now we can use SVG or server generated messages when we provide style sanitizing function.