feat(ivy): provide sanitization methods which can be tree shaken (#22540)
By providing a top level sanitization methods (rather than service) the compiler can generate calls into the methods only when needed. This makes the methods tree shakable. PR Close #22540
This commit is contained in:

committed by
Kara Erickson

parent
538f1d980f
commit
6d1367d297
@ -6,10 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '@angular/core';
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {InertBodyHelper} from './inert_body';
|
||||
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
|
||||
import {_sanitizeUrl, sanitizeSrcset} from './url_sanitizer';
|
||||
|
||||
function tagSet(tags: string): {[k: string]: boolean} {
|
||||
const res: {[k: string]: boolean} = {};
|
||||
@ -143,21 +142,17 @@ class SanitizingHtmlSerializer {
|
||||
for (let i = 0; i < elAttrs.length; i++) {
|
||||
const elAttr = elAttrs.item(i);
|
||||
const attrName = elAttr.name;
|
||||
let value = elAttr.value;
|
||||
const lower = attrName.toLowerCase();
|
||||
if (!VALID_ATTRS.hasOwnProperty(lower)) {
|
||||
this.sanitizedSomething = true;
|
||||
continue;
|
||||
}
|
||||
let value = elAttr.value;
|
||||
// TODO(martinprobst): Special case image URIs for data:image/...
|
||||
if (URI_ATTRS[lower]) value = sanitizeUrl(value);
|
||||
if (URI_ATTRS[lower]) value = _sanitizeUrl(value);
|
||||
if (SRCSET_ATTRS[lower]) value = sanitizeSrcset(value);
|
||||
this.buf.push(' ');
|
||||
this.buf.push(attrName);
|
||||
this.buf.push('="');
|
||||
this.buf.push(encodeEntities(value));
|
||||
this.buf.push('"');
|
||||
};
|
||||
this.buf.push(' ', attrName, '="', encodeEntities(value), '"');
|
||||
}
|
||||
this.buf.push('>');
|
||||
}
|
||||
|
||||
@ -173,7 +168,9 @@ class SanitizingHtmlSerializer {
|
||||
private chars(chars: string) { this.buf.push(encodeEntities(chars)); }
|
||||
|
||||
checkClobberedElement(node: Node, nextNode: Node): Node {
|
||||
if (nextNode && node.contains(nextNode)) {
|
||||
if (nextNode &&
|
||||
(node.compareDocumentPosition(nextNode) &
|
||||
Node.DOCUMENT_POSITION_CONTAINED_BY) === Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
||||
throw new Error(
|
||||
`Failed to sanitize html because the element is clobbered: ${(node as Element).outerHTML}`);
|
||||
}
|
||||
@ -214,7 +211,7 @@ let inertBodyHelper: InertBodyHelper;
|
||||
* Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
|
||||
* the DOM in a browser environment.
|
||||
*/
|
||||
export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
|
||||
export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
|
||||
let inertBodyElement: HTMLElement|null = null;
|
||||
try {
|
||||
inertBodyHelper = inertBodyHelper || new InertBodyHelper(defaultDoc);
|
||||
@ -259,8 +256,8 @@ export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
|
||||
}
|
||||
|
||||
function getTemplateContent(el: Node): Node|null {
|
||||
return 'content' in el && isTemplateElement(el) ? (<any>el).content : null;
|
||||
return 'content' in el && isTemplateElement(el) ? el.content : null;
|
||||
}
|
||||
function isTemplateElement(el: Node): boolean {
|
||||
function isTemplateElement(el: Node): el is HTMLTemplateElement {
|
||||
return el.nodeType === Node.ELEMENT_NODE && el.nodeName === 'TEMPLATE';
|
||||
}
|
||||
|
237
packages/core/src/sanitization/sanitization.ts
Normal file
237
packages/core/src/sanitization/sanitization.ts
Normal file
@ -0,0 +1,237 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {stringify} from '../render3/util';
|
||||
|
||||
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
||||
import {_sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
|
||||
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
||||
|
||||
const BRAND = '__SANITIZER_TRUSTED_BRAND__';
|
||||
|
||||
/**
|
||||
* A branded trusted string used with sanitization.
|
||||
*
|
||||
* See: {@link TrustedHtmlString}, {@link TrustedResourceUrlString}, {@link TrustedScriptString},
|
||||
* {@link TrustedStyleString}, {@link TrustedUrlString}
|
||||
*/
|
||||
export interface TrustedString extends String {
|
||||
'__SANITIZER_TRUSTED_BRAND__': 'Html'|'Style'|'Script'|'Url'|'ResourceUrl';
|
||||
}
|
||||
|
||||
/**
|
||||
* A branded trusted string used with sanitization of `html` strings.
|
||||
*
|
||||
* See: {@link bypassSanitizationTrustHtml} and {@link htmlSanitizer}.
|
||||
*/
|
||||
export interface TrustedHtmlString extends TrustedString { '__SANITIZER_TRUSTED_BRAND__': 'Html'; }
|
||||
|
||||
/**
|
||||
* A branded trusted string used with sanitization of `style` strings.
|
||||
*
|
||||
* See: {@link bypassSanitizationTrustStyle} and {@link styleSanitizer}.
|
||||
*/
|
||||
export interface TrustedStyleString extends TrustedString {
|
||||
'__SANITIZER_TRUSTED_BRAND__': 'Style';
|
||||
}
|
||||
|
||||
/**
|
||||
* A branded trusted string used with sanitization of `url` strings.
|
||||
*
|
||||
* See: {@link bypassSanitizationTrustScript} and {@link scriptSanitizer}.
|
||||
*/
|
||||
export interface TrustedScriptString extends TrustedString {
|
||||
'__SANITIZER_TRUSTED_BRAND__': 'Script';
|
||||
}
|
||||
|
||||
/**
|
||||
* A branded trusted string used with sanitization of `url` strings.
|
||||
*
|
||||
* See: {@link bypassSanitizationTrustUrl} and {@link urlSanitizer}.
|
||||
*/
|
||||
export interface TrustedUrlString extends TrustedString { '__SANITIZER_TRUSTED_BRAND__': 'Url'; }
|
||||
|
||||
/**
|
||||
* A branded trusted string used with sanitization of `resourceUrl` strings.
|
||||
*
|
||||
* See: {@link bypassSanitizationTrustResourceUrl} and {@link resourceUrlSanitizer}.
|
||||
*/
|
||||
export interface TrustedResourceUrlString extends TrustedString {
|
||||
'__SANITIZER_TRUSTED_BRAND__': 'ResourceUrl';
|
||||
}
|
||||
|
||||
/**
|
||||
* An `html` sanitizer which converts untrusted `html` **string** into trusted string by removing
|
||||
* dangerous content.
|
||||
*
|
||||
* This method parses the `html` and locates potentially dangerous content (such as urls and
|
||||
* javascript) and removes it.
|
||||
*
|
||||
* It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustHtml}.
|
||||
*
|
||||
* @param unsafeHtml untrusted `html`, typically from the user.
|
||||
* @returns `html` string which is safe to display to user, because all of the dangerous javascript
|
||||
* and urls have been removed.
|
||||
*/
|
||||
export function sanitizeHtml(unsafeHtml: any): string {
|
||||
if (unsafeHtml instanceof String && (unsafeHtml as TrustedHtmlString)[BRAND] === 'Html') {
|
||||
return unsafeHtml.toString();
|
||||
}
|
||||
return _sanitizeHtml(document, stringify(unsafeHtml));
|
||||
}
|
||||
|
||||
/**
|
||||
* A `style` sanitizer which converts untrusted `style` **string** into trusted string by removing
|
||||
* dangerous content.
|
||||
*
|
||||
* This method parses the `style` and locates potentially dangerous content (such as urls and
|
||||
* javascript) and removes it.
|
||||
*
|
||||
* It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustStyle}.
|
||||
*
|
||||
* @param unsafeStyle untrusted `style`, typically from the user.
|
||||
* @returns `style` string which is safe to bind to the `style` properties, because all of the
|
||||
* dangerous javascript and urls have been removed.
|
||||
*/
|
||||
export function sanitizeStyle(unsafeStyle: any): string {
|
||||
if (unsafeStyle instanceof String && (unsafeStyle as TrustedStyleString)[BRAND] === 'Style') {
|
||||
return unsafeStyle.toString();
|
||||
}
|
||||
return _sanitizeStyle(stringify(unsafeStyle));
|
||||
}
|
||||
|
||||
/**
|
||||
* A `url` sanitizer which converts untrusted `url` **string** into trusted string by removing
|
||||
* dangerous
|
||||
* content.
|
||||
*
|
||||
* This method parses the `url` and locates potentially dangerous content (such as javascript) and
|
||||
* removes it.
|
||||
*
|
||||
* It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustUrl}.
|
||||
*
|
||||
* @param unsafeUrl untrusted `url`, typically from the user.
|
||||
* @returns `url` string which is safe to bind to the `src` properties such as `<img src>`, because
|
||||
* all of the dangerous javascript has been removed.
|
||||
*/
|
||||
export function sanitizeUrl(unsafeUrl: any): string {
|
||||
if (unsafeUrl instanceof String && (unsafeUrl as TrustedUrlString)[BRAND] === 'Url') {
|
||||
return unsafeUrl.toString();
|
||||
}
|
||||
return _sanitizeUrl(stringify(unsafeUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* A `url` sanitizer which only lets trusted `url`s through.
|
||||
*
|
||||
* This passes only `url`s marked trusted by calling {@link bypassSanitizationTrustResourceUrl}.
|
||||
*
|
||||
* @param unsafeResourceUrl untrusted `url`, typically from the user.
|
||||
* @returns `url` string which is safe to bind to the `src` properties such as `<img src>`, because
|
||||
* only trusted `url`s have been allowed to pass.
|
||||
*/
|
||||
export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
|
||||
if (unsafeResourceUrl instanceof String &&
|
||||
(unsafeResourceUrl as TrustedResourceUrlString)[BRAND] === 'ResourceUrl') {
|
||||
return unsafeResourceUrl.toString();
|
||||
}
|
||||
throw new Error('unsafe value used in a resource URL context (see http://g.co/ng/security#xss)');
|
||||
}
|
||||
|
||||
/**
|
||||
* A `script` sanitizer which only lets trusted javascript through.
|
||||
*
|
||||
* This passes only `script`s marked trusted by calling {@link bypassSanitizationTrustScript}.
|
||||
*
|
||||
* @param unsafeScript untrusted `script`, typically from the user.
|
||||
* @returns `url` string which is safe to bind to the `<script>` element such as `<img src>`,
|
||||
* because only trusted `scripts`s have been allowed to pass.
|
||||
*/
|
||||
export function sanitizeScript(unsafeScript: any): string {
|
||||
if (unsafeScript instanceof String && (unsafeScript as TrustedScriptString)[BRAND] === 'Script') {
|
||||
return unsafeScript.toString();
|
||||
}
|
||||
throw new Error('unsafe value used in a script context');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark `html` string as trusted.
|
||||
*
|
||||
* This function wraps the trusted string in `String` and brands it in a way which makes it
|
||||
* recognizable to {@link htmlSanitizer} to be trusted implicitly.
|
||||
*
|
||||
* @param trustedHtml `html` string which needs to be implicitly trusted.
|
||||
* @returns a `html` `String` which has been branded to be implicitly trusted.
|
||||
*/
|
||||
export function bypassSanitizationTrustHtml(trustedHtml: string): TrustedHtmlString {
|
||||
return bypassSanitizationTrustString(trustedHtml, 'Html');
|
||||
}
|
||||
/**
|
||||
* Mark `style` string as trusted.
|
||||
*
|
||||
* This function wraps the trusted string in `String` and brands it in a way which makes it
|
||||
* recognizable to {@link styleSanitizer} to be trusted implicitly.
|
||||
*
|
||||
* @param trustedStyle `style` string which needs to be implicitly trusted.
|
||||
* @returns a `style` `String` which has been branded to be implicitly trusted.
|
||||
*/
|
||||
export function bypassSanitizationTrustStyle(trustedStyle: string): TrustedStyleString {
|
||||
return bypassSanitizationTrustString(trustedStyle, 'Style');
|
||||
}
|
||||
/**
|
||||
* Mark `script` string as trusted.
|
||||
*
|
||||
* This function wraps the trusted string in `String` and brands it in a way which makes it
|
||||
* recognizable to {@link scriptSanitizer} to be trusted implicitly.
|
||||
*
|
||||
* @param trustedScript `script` string which needs to be implicitly trusted.
|
||||
* @returns a `script` `String` which has been branded to be implicitly trusted.
|
||||
*/
|
||||
export function bypassSanitizationTrustScript(trustedScript: string): TrustedScriptString {
|
||||
return bypassSanitizationTrustString(trustedScript, 'Script');
|
||||
}
|
||||
/**
|
||||
* Mark `url` string as trusted.
|
||||
*
|
||||
* This function wraps the trusted string in `String` and brands it in a way which makes it
|
||||
* recognizable to {@link urlSanitizer} to be trusted implicitly.
|
||||
*
|
||||
* @param trustedUrl `url` string which needs to be implicitly trusted.
|
||||
* @returns a `url` `String` which has been branded to be implicitly trusted.
|
||||
*/
|
||||
export function bypassSanitizationTrustUrl(trustedUrl: string): TrustedUrlString {
|
||||
return bypassSanitizationTrustString(trustedUrl, 'Url');
|
||||
}
|
||||
/**
|
||||
* Mark `url` string as trusted.
|
||||
*
|
||||
* This function wraps the trusted string in `String` and brands it in a way which makes it
|
||||
* recognizable to {@link resourceUrlSanitizer} to be trusted implicitly.
|
||||
*
|
||||
* @param trustedResourceUrl `url` string which needs to be implicitly trusted.
|
||||
* @returns a `url` `String` which has been branded to be implicitly trusted.
|
||||
*/
|
||||
export function bypassSanitizationTrustResourceUrl(trustedResourceUrl: string):
|
||||
TrustedResourceUrlString {
|
||||
return bypassSanitizationTrustString(trustedResourceUrl, 'ResourceUrl');
|
||||
}
|
||||
|
||||
|
||||
function bypassSanitizationTrustString(trustedString: string, mode: 'Html'): TrustedHtmlString;
|
||||
function bypassSanitizationTrustString(trustedString: string, mode: 'Style'): TrustedStyleString;
|
||||
function bypassSanitizationTrustString(trustedString: string, mode: 'Script'): TrustedScriptString;
|
||||
function bypassSanitizationTrustString(trustedString: string, mode: 'Url'): TrustedUrlString;
|
||||
function bypassSanitizationTrustString(
|
||||
trustedString: string, mode: 'ResourceUrl'): TrustedResourceUrlString;
|
||||
function bypassSanitizationTrustString(
|
||||
trustedString: string,
|
||||
mode: 'Html' | 'Style' | 'Script' | 'Url' | 'ResourceUrl'): TrustedString {
|
||||
const trusted = new String(trustedString) as TrustedString;
|
||||
trusted[BRAND] = mode;
|
||||
return trusted;
|
||||
}
|
@ -6,9 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '@angular/core';
|
||||
|
||||
import {sanitizeUrl} from './url_sanitizer';
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {_sanitizeUrl} from './url_sanitizer';
|
||||
|
||||
|
||||
/**
|
||||
@ -83,14 +82,14 @@ function hasBalancedQuotes(value: string) {
|
||||
* Sanitizes the given untrusted CSS style property value (i.e. not an entire object, just a single
|
||||
* value) and returns a value that is safe to use in a browser environment.
|
||||
*/
|
||||
export function sanitizeStyle(value: string): string {
|
||||
export function _sanitizeStyle(value: string): string {
|
||||
value = String(value).trim(); // Make sure it's actually a string.
|
||||
if (!value) return '';
|
||||
|
||||
// Single url(...) values are supported, but only for URLs that sanitize cleanly. See above for
|
||||
// reasoning behind this.
|
||||
const urlMatch = value.match(URL_RE);
|
||||
if ((urlMatch && sanitizeUrl(urlMatch[1]) === urlMatch[1]) ||
|
||||
if ((urlMatch && _sanitizeUrl(urlMatch[1]) === urlMatch[1]) ||
|
||||
value.match(SAFE_STYLE_VALUE) && hasBalancedQuotes(value)) {
|
||||
return value; // Safe style values.
|
||||
}
|
||||
|
@ -6,8 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '@angular/core';
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
|
||||
/**
|
||||
* A pattern that recognizes a commonly useful subset of URLs that are safe.
|
||||
@ -44,7 +43,7 @@ const SAFE_SRCSET_PATTERN = /^(?:(?:https?|file):|[^&:/?#]*(?:[/?#]|$))/gi;
|
||||
const DATA_URL_PATTERN =
|
||||
/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+\/]+=*$/i;
|
||||
|
||||
export function sanitizeUrl(url: string): string {
|
||||
export function _sanitizeUrl(url: string): string {
|
||||
url = String(url);
|
||||
if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url;
|
||||
|
||||
@ -57,5 +56,5 @@ export function sanitizeUrl(url: string): string {
|
||||
|
||||
export function sanitizeSrcset(srcset: string): string {
|
||||
srcset = String(srcset);
|
||||
return srcset.split(',').map((srcset) => sanitizeUrl(srcset.trim())).join(', ');
|
||||
return srcset.split(',').map((srcset) => _sanitizeUrl(srcset.trim())).join(', ');
|
||||
}
|
||||
|
Reference in New Issue
Block a user