perf(core): make sanitization tree-shakable in Ivy mode (#31934)
In VE the `Sanitizer` is always available in `BrowserModule` because the VE retrieves it using injection. In Ivy the injection is optional and we have instructions instead of component definition arrays. The implication of this is that in Ivy the instructions can pull in the sanitizer only when they are working with a property which is known to be unsafe. Because the Injection is optional this works even if no Sanitizer is present. So in Ivy we first use the sanitizer which is pulled in by the instruction, unless one is available through the `Injector` then we use that one instead. This PR does few things: 1) It makes `Sanitizer` optional in Ivy. 2) It makes `DomSanitizer` tree shakable. 3) It aligns the semantics of Ivy `Sanitizer` with that of the Ivy sanitization rules. 4) It refactors `DomSanitizer` to use same functions as Ivy sanitization for consistency. PR Close #31934
This commit is contained in:

committed by
Andrew Kushnir

parent
40b28742a9
commit
2e4d17f3a9
@ -29,16 +29,23 @@ export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
|
||||
{provide: DOCUMENT, useFactory: _document, deps: []},
|
||||
];
|
||||
|
||||
const BROWSER_SANITIZATION_PROVIDERS__PRE_R3__: StaticProvider[] = [
|
||||
{provide: Sanitizer, useExisting: DomSanitizer},
|
||||
{provide: DomSanitizer, useClass: DomSanitizerImpl, deps: [DOCUMENT]},
|
||||
];
|
||||
|
||||
/**
|
||||
* @codeGenApi
|
||||
*/
|
||||
export const BROWSER_SANITIZATION_PROVIDERS__POST_R3__ = [];
|
||||
|
||||
/**
|
||||
* @security Replacing built-in sanitization providers exposes the application to XSS risks.
|
||||
* Attacker-controlled data introduced by an unsanitized provider could expose your
|
||||
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
|
||||
* @publicApi
|
||||
*/
|
||||
export const BROWSER_SANITIZATION_PROVIDERS: StaticProvider[] = [
|
||||
{provide: Sanitizer, useExisting: DomSanitizer},
|
||||
{provide: DomSanitizer, useClass: DomSanitizerImpl, deps: [DOCUMENT]},
|
||||
];
|
||||
export const BROWSER_SANITIZATION_PROVIDERS = BROWSER_SANITIZATION_PROVIDERS__PRE_R3__;
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {BROWSER_SANITIZATION_PROVIDERS as ɵBROWSER_SANITIZATION_PROVIDERS, INTERNAL_BROWSER_PLATFORM_PROVIDERS as ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS, initDomAdapter as ɵinitDomAdapter} from './browser';
|
||||
export {BROWSER_SANITIZATION_PROVIDERS as ɵBROWSER_SANITIZATION_PROVIDERS, BROWSER_SANITIZATION_PROVIDERS__POST_R3__ as ɵBROWSER_SANITIZATION_PROVIDERS__POST_R3__, INTERNAL_BROWSER_PLATFORM_PROVIDERS as ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS, initDomAdapter as ɵinitDomAdapter} from './browser';
|
||||
export {BrowserDomAdapter as ɵBrowserDomAdapter} from './browser/browser_adapter';
|
||||
export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './browser/location/browser_platform_location';
|
||||
export {TRANSITION_ID as ɵTRANSITION_ID} from './browser/server-transition';
|
||||
|
@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Inject, Injectable, Sanitizer, SecurityContext, ɵ_sanitizeHtml as _sanitizeHtml, ɵ_sanitizeStyle as _sanitizeStyle, ɵ_sanitizeUrl as _sanitizeUrl} from '@angular/core';
|
||||
|
||||
import {Inject, Injectable, Injector, Sanitizer, SecurityContext, forwardRef, ɵBypassType as BypassType, ɵ_sanitizeHtml as _sanitizeHtml, ɵ_sanitizeStyle as _sanitizeStyle, ɵ_sanitizeUrl as _sanitizeUrl, ɵallowSanitizationBypassAndThrow as allowSanitizationBypassOrThrow, ɵbypassSanitizationTrustHtml as bypassSanitizationTrustHtml, ɵbypassSanitizationTrustResourceUrl as bypassSanitizationTrustResourceUrl, ɵbypassSanitizationTrustScript as bypassSanitizationTrustScript, ɵbypassSanitizationTrustStyle as bypassSanitizationTrustStyle, ɵbypassSanitizationTrustUrl as bypassSanitizationTrustUrl, ɵgetSanitizationBypassType as getSanitizationBypassType, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core';
|
||||
|
||||
export {SecurityContext};
|
||||
|
||||
@ -87,6 +86,7 @@ export interface SafeResourceUrl extends SafeValue {}
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
@Injectable({providedIn: 'root', useExisting: forwardRef(() => DomSanitizerImpl)})
|
||||
export abstract class DomSanitizer implements Sanitizer {
|
||||
/**
|
||||
* Sanitizes a value for use in the given SecurityContext.
|
||||
@ -143,8 +143,11 @@ export abstract class DomSanitizer implements Sanitizer {
|
||||
abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl;
|
||||
}
|
||||
|
||||
export function domSanitizerImplFactory(injector: Injector) {
|
||||
return new DomSanitizerImpl(injector.get(DOCUMENT));
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({providedIn: 'root', useFactory: domSanitizerImplFactory, deps: [Injector]})
|
||||
export class DomSanitizerImpl extends DomSanitizer {
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) { super(); }
|
||||
|
||||
@ -154,29 +157,30 @@ export class DomSanitizerImpl extends DomSanitizer {
|
||||
case SecurityContext.NONE:
|
||||
return value as string;
|
||||
case SecurityContext.HTML:
|
||||
if (value instanceof SafeHtmlImpl) return value.changingThisBreaksApplicationSecurity;
|
||||
this.checkNotSafeValue(value, 'HTML');
|
||||
if (allowSanitizationBypassOrThrow(value, BypassType.Html)) {
|
||||
return unwrapSafeValue(value);
|
||||
}
|
||||
return _sanitizeHtml(this._doc, String(value));
|
||||
case SecurityContext.STYLE:
|
||||
if (value instanceof SafeStyleImpl) return value.changingThisBreaksApplicationSecurity;
|
||||
this.checkNotSafeValue(value, 'Style');
|
||||
if (allowSanitizationBypassOrThrow(value, BypassType.Style)) {
|
||||
return unwrapSafeValue(value);
|
||||
}
|
||||
return _sanitizeStyle(value as string);
|
||||
case SecurityContext.SCRIPT:
|
||||
if (value instanceof SafeScriptImpl) return value.changingThisBreaksApplicationSecurity;
|
||||
this.checkNotSafeValue(value, 'Script');
|
||||
if (allowSanitizationBypassOrThrow(value, BypassType.Script)) {
|
||||
return unwrapSafeValue(value);
|
||||
}
|
||||
throw new Error('unsafe value used in a script context');
|
||||
case SecurityContext.URL:
|
||||
if (value instanceof SafeResourceUrlImpl || value instanceof SafeUrlImpl) {
|
||||
// Allow resource URLs in URL contexts, they are strictly more trusted.
|
||||
return value.changingThisBreaksApplicationSecurity;
|
||||
const type = getSanitizationBypassType(value);
|
||||
if (allowSanitizationBypassOrThrow(value, BypassType.Url)) {
|
||||
return unwrapSafeValue(value);
|
||||
}
|
||||
this.checkNotSafeValue(value, 'URL');
|
||||
return _sanitizeUrl(String(value));
|
||||
case SecurityContext.RESOURCE_URL:
|
||||
if (value instanceof SafeResourceUrlImpl) {
|
||||
return value.changingThisBreaksApplicationSecurity;
|
||||
if (allowSanitizationBypassOrThrow(value, BypassType.ResourceUrl)) {
|
||||
return unwrapSafeValue(value);
|
||||
}
|
||||
this.checkNotSafeValue(value, 'ResourceURL');
|
||||
throw new Error(
|
||||
'unsafe value used in a resource URL context (see http://g.co/ng/security#xss)');
|
||||
default:
|
||||
@ -184,48 +188,13 @@ export class DomSanitizerImpl extends DomSanitizer {
|
||||
}
|
||||
}
|
||||
|
||||
private checkNotSafeValue(value: any, expectedType: string) {
|
||||
if (value instanceof SafeValueImpl) {
|
||||
throw new Error(
|
||||
`Required a safe ${expectedType}, got a ${value.getTypeName()} ` +
|
||||
`(see http://g.co/ng/security#xss)`);
|
||||
}
|
||||
bypassSecurityTrustHtml(value: string): SafeHtml { return bypassSanitizationTrustHtml(value); }
|
||||
bypassSecurityTrustStyle(value: string): SafeStyle { return bypassSanitizationTrustStyle(value); }
|
||||
bypassSecurityTrustScript(value: string): SafeScript {
|
||||
return bypassSanitizationTrustScript(value);
|
||||
}
|
||||
|
||||
bypassSecurityTrustHtml(value: string): SafeHtml { return new SafeHtmlImpl(value); }
|
||||
bypassSecurityTrustStyle(value: string): SafeStyle { return new SafeStyleImpl(value); }
|
||||
bypassSecurityTrustScript(value: string): SafeScript { return new SafeScriptImpl(value); }
|
||||
bypassSecurityTrustUrl(value: string): SafeUrl { return new SafeUrlImpl(value); }
|
||||
bypassSecurityTrustUrl(value: string): SafeUrl { return bypassSanitizationTrustUrl(value); }
|
||||
bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl {
|
||||
return new SafeResourceUrlImpl(value);
|
||||
return bypassSanitizationTrustResourceUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SafeValueImpl implements SafeValue {
|
||||
constructor(public changingThisBreaksApplicationSecurity: string) {
|
||||
// empty
|
||||
}
|
||||
|
||||
abstract getTypeName(): string;
|
||||
|
||||
toString() {
|
||||
return `SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity}` +
|
||||
` (see http://g.co/ng/security#xss)`;
|
||||
}
|
||||
}
|
||||
|
||||
class SafeHtmlImpl extends SafeValueImpl implements SafeHtml {
|
||||
getTypeName() { return 'HTML'; }
|
||||
}
|
||||
class SafeStyleImpl extends SafeValueImpl implements SafeStyle {
|
||||
getTypeName() { return 'Style'; }
|
||||
}
|
||||
class SafeScriptImpl extends SafeValueImpl implements SafeScript {
|
||||
getTypeName() { return 'Script'; }
|
||||
}
|
||||
class SafeUrlImpl extends SafeValueImpl implements SafeUrl {
|
||||
getTypeName() { return 'URL'; }
|
||||
}
|
||||
class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
|
||||
getTypeName() { return 'ResourceURL'; }
|
||||
}
|
||||
|
Reference in New Issue
Block a user