feat(ivy): bridge compile instructions to include sanitization helpers (#24938)

PR Close #24938
This commit is contained in:
Matias Niemelä
2018-07-11 10:58:18 -07:00
committed by Victor Berchet
parent 13f3157823
commit 169e9dd2c8
21 changed files with 963 additions and 490 deletions

View File

@ -102,14 +102,16 @@ export {
} from './render3/index';
export {NgModuleDef as ɵNgModuleDef} from './metadata/ng_module';
export {
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
bypassSanitizationTrustStyle as ɵbypassSanitizationTrustStyle,
bypassSanitizationTrustScript as ɵbypassSanitizationTrustScript,
bypassSanitizationTrustUrl as ɵbypassSanitizationTrustUrl,
bypassSanitizationTrustResourceUrl as ɵbypassSanitizationTrustResourceUrl,
sanitizeHtml as ɵsanitizeHtml,
sanitizeStyle as ɵsanitizeStyle,
sanitizeUrl as ɵsanitizeUrl,
sanitizeResourceUrl as ɵsanitizeResourceUrl,
} from './sanitization/sanitization';
export {
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
bypassSanitizationTrustStyle as ɵbypassSanitizationTrustStyle,
bypassSanitizationTrustScript as ɵbypassSanitizationTrustScript,
bypassSanitizationTrustUrl as ɵbypassSanitizationTrustUrl,
bypassSanitizationTrustResourceUrl as ɵbypassSanitizationTrustResourceUrl,
} from './sanitization/bypass';
// clang-format on

View File

@ -10,6 +10,7 @@ import './ng_dev_mode';
import {QueryList} from '../linker';
import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual} from './assert';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
@ -25,7 +26,7 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, Curre
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {StylingContext, StylingIndex, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling';
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling';
import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
import {ViewRef} from './view_ref';
@ -1291,25 +1292,29 @@ export function elementClassProp<T>(
* (Note that this is not the element index, but rather an index value allocated
* specifically for element styling--the index must be the next index after the element
* index.)
* @param styleDeclarations A key/value array of CSS styles that will be registered on the element.
* Each individual style will be used on the element as long as it is not overridden
* by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`)
* bindings. If a style binding changes its value to null then the initial styling
* values that are passed in here will be applied to the element (if matched).
* @param classDeclarations A key/value array of CSS classes that will be registered on the element.
* Each individual style will be used on the element as long as it is not overridden
* by any classes placed on the element by multiple (`[class]`) or singular (`[class.named]`)
* bindings. If a class binding changes its value to a falsy value then the matching initial
* class value that are passed in here will be applied to the element (if matched).
* @param styleDeclarations A key/value array of CSS styles that will be registered on the element.
* Each individual style will be used on the element as long as it is not overridden
* by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`)
* bindings. If a style binding changes its value to null then the initial styling
* values that are passed in here will be applied to the element (if matched).
* @param styleSanitizer An optional sanitizer function that will be used (if provided)
* to sanitize the any CSS property values that are applied to the element (during rendering).
*/
export function elementStyling<T>(
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null): void {
styleSanitizer?: StyleSanitizeFn | null): void {
const lElement = currentElementNode !;
const tNode = lElement.tNode;
if (!tNode.stylingTemplate) {
// initialize the styling template.
tNode.stylingTemplate = createStylingContextTemplate(styleDeclarations, classDeclarations);
tNode.stylingTemplate =
createStylingContextTemplate(classDeclarations, styleDeclarations, styleSanitizer);
}
if (styleDeclarations && styleDeclarations.length ||
classDeclarations && classDeclarations.length) {
@ -1377,22 +1382,23 @@ export function elementStylingApply<T>(index: number): void {
* renaming as part of minification.
* @param value New value to write (null to remove).
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
* @param sanitizer An optional function used to transform the value typically used for
* sanitization.
* Note that when a suffix is provided then the underlying sanitizer will
* be ignored.
*/
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null, suffix?: string): void;
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null, sanitizer?: SanitizerFn): void;
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null,
suffixOrSanitizer?: string | SanitizerFn): void {
index: number, styleIndex: number, value: T | null, suffix?: string): void {
let valueToAdd: string|null = null;
if (value) {
valueToAdd =
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
if (typeof suffixOrSanitizer == 'string') {
valueToAdd = valueToAdd + suffixOrSanitizer;
if (suffix) {
// when a suffix is applied then it will bypass
// sanitization entirely (b/c a new string is created)
valueToAdd = stringify(value) + suffix;
} else {
// sanitization happens by dealing with a String value
// this means that the string value will be passed through
// into the style rendering later (which is where the value
// will be sanitized before it is applied)
valueToAdd = value as any as string;
}
}
updateElementStyleProp(getStylingContext(index), styleIndex, valueToAdd);
@ -1412,17 +1418,17 @@ export function elementStyleProp<T>(
* (Note that this is not the element index, but rather an index value allocated
* specifically for element styling--the index must be the next index after the element
* index.)
* @param styles A key/value style map of the styles that will be applied to the given element.
* Any missing styles (that have already been applied to the element beforehand) will be
* removed (unset) from the element's styling.
* @param classes A key/value style map of CSS classes that will be added to the given element.
* Any missing classes (that have already been applied to the element beforehand) will be
* removed (unset) from the element's list of CSS classes.
* @param styles A key/value style map of the styles that will be applied to the given element.
* Any missing styles (that have already been applied to the element beforehand) will be
* removed (unset) from the element's styling.
*/
export function elementStylingMap<T>(
index: number, styles: {[styleName: string]: any} | null,
classes?: {[key: string]: any} | string | null): void {
updateStylingMap(getStylingContext(index), styles, classes);
index: number, classes: {[key: string]: any} | string | null,
styles?: {[styleName: string]: any} | null): void {
updateStylingMap(getStylingContext(index), classes, styles);
}
//////////////////////////

View File

@ -9,6 +9,7 @@
import {defineInjectable, defineInjector,} from '../../di/defs';
import {inject} from '../../di/injector';
import * as r3 from '../index';
import * as sanitization from '../../sanitization/sanitization';
/**
@ -88,4 +89,11 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵt': r3.t,
'ɵV': r3.V,
'ɵv': r3.v,
'ɵzh': sanitization.sanitizeHtml,
'ɵzs': sanitization.sanitizeStyle,
'ɵzss': sanitization.defaultStyleSanitizer,
'ɵzr': sanitization.sanitizeResourceUrl,
'ɵzc': sanitization.sanitizeScript,
'ɵzu': sanitization.sanitizeUrl
};

View File

@ -6,10 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {InitialStylingFlags} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
/**
* The styling context acts as a styling manifest (shaped as an array) for determining which
* styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp`
@ -51,42 +53,44 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
*
* ```
* context = [
* element,
* styleSanitizer | null,
* [null, '100px', '200px', true], // property names are not needed since they have already been
* written to DOM.
*
* configMasterVal,
* 1, // this instructs how many `style` values there are so that class index values can be
* offsetted
*
* configMasterVal,
*
* // 3
* 'width',
* pointers(1, 12); // Point to static `width`: `100px` and multi `width`.
* null,
* 'last class string applied',
*
* // 6
* 'height',
* pointers(2, 15); // Point to static `height`: `200px` and multi `height`.
* 'width',
* pointers(1, 15); // Point to static `width`: `100px` and multi `width`.
* null,
*
* // 9
* 'foo',
* pointers(1, 18); // Point to static `foo`: `true` and multi `foo`.
* 'height',
* pointers(2, 18); // Point to static `height`: `200px` and multi `height`.
* null,
*
* // 12
* 'width',
* pointers(1, 3); // Point to static `width`: `100px` and single `width`.
* 'foo',
* pointers(1, 21); // Point to static `foo`: `true` and multi `foo`.
* null,
*
* // 15
* 'height',
* pointers(2, 6); // Point to static `height`: `200px` and single `height`.
* 'width',
* pointers(1, 6); // Point to static `width`: `100px` and single `width`.
* null,
*
* // 18
* 'height',
* pointers(2, 9); // Point to static `height`: `200px` and single `height`.
* null,
*
* // 21
* 'foo',
* pointers(3, 9); // Point to static `foo`: `true` and single `foo`.
* pointers(3, 12); // Point to static `foo`: `true` and single `foo`.
* null,
* ]
*
@ -111,36 +115,41 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
* `updateStylingMap` can include new CSS properties that will be added to the context).
*/
export interface StylingContext extends
Array<InitialStyles|number|string|boolean|LElementNode|null> {
Array<InitialStyles|number|string|boolean|LElementNode|StyleSanitizeFn|null> {
/**
* Location of element that is used as a target for this context.
*/
[0]: LElementNode|null;
/**
* The style sanitizer that is used within this context
*/
[1]: StyleSanitizeFn|null;
/**
* Location of initial data shared by all instances of this style.
*/
[1]: InitialStyles;
[2]: InitialStyles;
/**
* A numeric value representing the configuration status (whether the context is dirty or not)
* mixed together (using bit shifting) with a index value which tells the starting index value
* of where the multi style entries begin.
*/
[2]: number;
[3]: number;
/**
* A numeric value representing the class index offset value. Whenever a single class is
* applied (using `elementClassProp`) it should have an styling index value that doesn't
* need to take into account any style values that exist in the context.
*/
[3]: number;
[4]: number;
/**
* The last CLASS STRING VALUE that was interpreted by elementStylingMap. This is cached
* So that the algorithm can exit early incase the string has not changed.
*/
[4]: string|null;
[5]: string|null;
}
/**
@ -159,31 +168,35 @@ export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
*/
export const enum StylingFlags {
// Implies no configurations
None = 0b00,
None = 0b000,
// Whether or not the entry or context itself is dirty
Dirty = 0b01,
Dirty = 0b001,
// Whether or not this is a class-based assignment
Class = 0b10,
Class = 0b010,
// Whether or not a sanitizer was applied to this property
Sanitize = 0b100,
// The max amount of bits used to represent these configuration values
BitCountSize = 2,
// There are only two bits here
BitMask = 0b11
BitCountSize = 3,
// There are only three bits here
BitMask = 0b111
}
/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
export const enum StylingIndex {
// Position of where the initial styles are stored in the styling context
ElementPosition = 0,
// Position of where the style sanitizer is stored within the styling context
StyleSanitizerPosition = 1,
// Position of where the initial styles are stored in the styling context
InitialStylesPosition = 1,
InitialStylesPosition = 2,
// Index of location where the start of single properties are stored. (`updateStyleProp`)
MasterFlagPosition = 2,
MasterFlagPosition = 3,
// Index of location where the class index offset value is located
ClassOffsetPosition = 3,
ClassOffsetPosition = 4,
// Position of where the last string-based CSS class value was stored
CachedCssClassString = 4,
CachedCssClassString = 5,
// Location of single (prop) value entries are stored within the context
SingleStylesStartPosition = 5,
SingleStylesStartPosition = 6,
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
FlagsOffset = 0,
PropertyOffset = 1,
@ -191,9 +204,9 @@ export const enum StylingIndex {
// Size of each multi or single entry (flag + prop + value)
Size = 3,
// Each flag has a binary digit length of this value
BitCountSize = 15, // (32 - 1) / 2 = ~15
BitCountSize = 14, // (32 - 3) / 2 = ~14
// The binary digit value as a mask
BitMask = 0b111111111111111 // 15 bits
BitMask = 0b11111111111111 // 14 bits
}
/**
@ -233,10 +246,11 @@ export function allocStylingContext(
* class will be applied to the element as an initial class since it's true
*/
export function createStylingContextTemplate(
initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null): StylingContext {
styleSanitizer?: StyleSanitizeFn | null): StylingContext {
const initialStylingValues: InitialStyles = [null];
const context: StylingContext = [null, initialStylingValues, 0, 0, null];
const context: StylingContext = [null, styleSanitizer || null, initialStylingValues, 0, 0, null];
// we use two maps since a class name might collide with a CSS style prop
const stylesLookup: {[key: string]: number} = {};
@ -314,7 +328,7 @@ export function createStylingContextTemplate(
const indexForMulti = i * StylingIndex.Size + multiStart;
const indexForSingle = i * StylingIndex.Size + singleStart;
const initialFlag = isClassBased ? StylingFlags.Class : StylingFlags.None;
const initialFlag = prepareInitialFlag(prop, isClassBased, styleSanitizer || null);
setFlag(context, indexForSingle, pointers(initialFlag, indexForInitial, indexForMulti));
setProp(context, indexForSingle, prop);
@ -347,12 +361,12 @@ const EMPTY_OBJ: {[key: string]: any} = {};
*
* @param context The styling context that will be updated with the
* newly provided style values.
* @param styles The key/value map of CSS styles that will be used for the update.
* @param classes The key/value map of CSS class names that will be used for the update.
* @param styles The key/value map of CSS styles that will be used for the update.
*/
export function updateStylingMap(
context: StylingContext, styles: {[key: string]: any} | null,
classes?: {[key: string]: any} | string | null): void {
context: StylingContext, classes: {[key: string]: any} | string | null,
styles?: {[key: string]: any} | null): void {
let classNames: string[] = EMPTY_ARR;
let applyAllClasses = false;
let ignoreAllClassUpdates = false;
@ -407,10 +421,10 @@ export function updateStylingMap(
const prop = getProp(context, ctxIndex);
if (prop === newProp) {
const value = getValue(context, ctxIndex);
if (value !== newValue) {
const flag = getPointers(context, ctxIndex);
if (hasValueChanged(flag, value, newValue)) {
setValue(context, ctxIndex, newValue);
const flag = getPointers(context, ctxIndex);
const initialValue = getInitialValue(context, flag);
// there is no point in setting this to dirty if the previously
@ -437,7 +451,8 @@ export function updateStylingMap(
}
} else {
// we only care to do this if the insertion is in the middle
insertNewMultiProperty(context, ctxIndex, isClassBased, newProp, newValue);
const newFlag = prepareInitialFlag(newProp, isClassBased, getStyleSanitizer(context));
insertNewMultiProperty(context, ctxIndex, isClassBased, newProp, newFlag, newValue);
dirty = true;
}
}
@ -468,6 +483,7 @@ export function updateStylingMap(
// this means that there are left-over properties in the context that
// were not detected in the context during the loop above. In that
// case we want to add the new entries into the list
const sanitizer = getStyleSanitizer(context);
while (propIndex < propLimit) {
const isClassBased = propIndex >= classesStartIndex;
if (ignoreAllClassUpdates && isClassBased) break;
@ -476,7 +492,7 @@ export function updateStylingMap(
const prop = isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex];
const value: string|boolean =
isClassBased ? (applyAllClasses ? true : classes[prop]) : styles[prop];
const flag = StylingFlags.Dirty | (isClassBased ? StylingFlags.Class : StylingFlags.None);
const flag = prepareInitialFlag(prop, isClassBased, sanitizer) | StylingFlags.Dirty;
context.push(flag, prop, value);
propIndex++;
dirty = true;
@ -508,7 +524,7 @@ export function updateStyleProp(
const currFlag = getPointers(context, singleIndex);
// didn't change ... nothing to make a note of
if (currValue !== value) {
if (hasValueChanged(currFlag, currValue, value)) {
// the value will always get updated (even if the dirty flag is skipped)
setValue(context, singleIndex, value);
const indexForMulti = getMultiOrSingleIndex(currFlag);
@ -573,6 +589,7 @@ export function renderStyling(
if (isContextDirty(context)) {
const native = context[StylingIndex.ElementPosition] !.native;
const multiStartIndex = getMultiStartIndex(context);
const styleSanitizer = getStyleSanitizer(context);
for (let i = StylingIndex.SingleStylesStartPosition; i < context.length;
i += StylingIndex.Size) {
// there is no point in rendering styles that have not changed on screen
@ -607,7 +624,8 @@ export function renderStyling(
if (isClassBased) {
setClass(native, prop, valueToApply ? true : false, renderer, classStore);
} else {
setStyle(native, prop, valueToApply as string | null, renderer, styleStore);
const sanitizer = (flag & StylingFlags.Sanitize) ? styleSanitizer : null;
setStyle(native, prop, valueToApply as string | null, renderer, sanitizer, styleStore);
}
setDirty(context, i, false);
}
@ -631,7 +649,8 @@ export function renderStyling(
*/
function setStyle(
native: any, prop: string, value: string | null, renderer: Renderer3,
store?: {[key: string]: any}) {
sanitizer: StyleSanitizeFn | null, store?: {[key: string]: any}) {
value = sanitizer && value ? sanitizer(prop, value) : value;
if (store) {
store[prop] = value;
} else if (value) {
@ -697,6 +716,12 @@ function isClassBased(context: StylingContext, index: number): boolean {
return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class;
}
function isSanitizable(context: StylingContext, index: number): boolean {
const adjustedIndex =
index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index;
return ((context[adjustedIndex] as number) & StylingFlags.Sanitize) == StylingFlags.Sanitize;
}
function pointers(configFlag: number, staticIndex: number, dynamicIndex: number) {
return (configFlag & StylingFlags.BitMask) | (staticIndex << StylingFlags.BitCountSize) |
(dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize));
@ -721,6 +746,10 @@ function getMultiStartIndex(context: StylingContext): number {
return getMultiOrSingleIndex(context[StylingIndex.MasterFlagPosition]) as number;
}
function getStyleSanitizer(context: StylingContext): StyleSanitizeFn|null {
return context[StylingIndex.StyleSanitizerPosition];
}
function setProp(context: StylingContext, index: number, prop: string) {
context[index + StylingIndex.PropertyOffset] = prop;
}
@ -808,7 +837,8 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition:
const singleFlag = getPointers(context, singleIndex);
const initialIndexForSingle = getInitialIndex(singleFlag);
const flagValue = (isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None) |
(isClassBased(context, singleIndex) ? StylingFlags.Class : StylingFlags.None);
(isClassBased(context, singleIndex) ? StylingFlags.Class : StylingFlags.None) |
(isSanitizable(context, singleIndex) ? StylingFlags.Sanitize : StylingFlags.None);
const updatedFlag = pointers(flagValue, initialIndexForSingle, i);
setFlag(context, singleIndex, updatedFlag);
}
@ -816,14 +846,14 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition:
}
function insertNewMultiProperty(
context: StylingContext, index: number, classBased: boolean, name: string,
context: StylingContext, index: number, classBased: boolean, name: string, flag: number,
value: string | boolean): void {
const doShift = index < context.length;
// prop does not exist in the list, add it in
context.splice(
index, 0, StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), name,
value);
index, 0, flag | StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None),
name, value);
if (doShift) {
// because the value was inserted midway into the array then we
@ -839,3 +869,30 @@ function valueExists(value: string | null | boolean, isClassBased?: boolean) {
}
return value !== null;
}
function prepareInitialFlag(
name: string, isClassBased: boolean, sanitizer?: StyleSanitizeFn | null) {
if (isClassBased) {
return StylingFlags.Class;
} else if (sanitizer && sanitizer(name)) {
return StylingFlags.Sanitize;
}
return StylingFlags.None;
}
function hasValueChanged(
flag: number, a: string | boolean | null, b: string | boolean | null): boolean {
const isClassBased = flag & StylingFlags.Class;
const hasValues = a && b;
const usesSanitizer = flag & StylingFlags.Sanitize;
// the toString() comparison ensures that a value is checked
// ... otherwise (during sanitization bypassing) the === comparsion
// would fail since a new String() instance is created
if (!isClassBased && hasValues && usesSanitizer) {
// we know for sure we're dealing with strings at this point
return (a as string).toString() !== (b as string).toString();
}
// everything else is safe to check with a normal equality check
return a !== b;
}

View File

@ -0,0 +1,143 @@
/**
* @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
*/
const BRAND = '__SANITIZER_TRUSTED_BRAND__';
export const enum BypassType {
Url = 'Url',
Html = 'Html',
ResourceUrl = 'ResourceUrl',
Script = 'Script',
Style = 'Style',
}
/**
* A branded trusted string used with sanitization.
*
* See: {@link TrustedHtmlString}, {@link TrustedResourceUrlString}, {@link TrustedScriptString},
* {@link TrustedStyleString}, {@link TrustedUrlString}
*/
export interface TrustedString extends String { [BRAND]: BypassType; }
/**
* A branded trusted string used with sanitization of `html` strings.
*
* See: {@link bypassSanitizationTrustHtml} and {@link htmlSanitizer}.
*/
export interface TrustedHtmlString extends TrustedString { [BRAND]: BypassType.Html; }
/**
* A branded trusted string used with sanitization of `style` strings.
*
* See: {@link bypassSanitizationTrustStyle} and {@link styleSanitizer}.
*/
export interface TrustedStyleString extends TrustedString { [BRAND]: BypassType.Style; }
/**
* A branded trusted string used with sanitization of `url` strings.
*
* See: {@link bypassSanitizationTrustScript} and {@link scriptSanitizer}.
*/
export interface TrustedScriptString extends TrustedString { [BRAND]: BypassType.Script; }
/**
* A branded trusted string used with sanitization of `url` strings.
*
* See: {@link bypassSanitizationTrustUrl} and {@link urlSanitizer}.
*/
export interface TrustedUrlString extends TrustedString { [BRAND]: BypassType.Url; }
/**
* A branded trusted string used with sanitization of `resourceUrl` strings.
*
* See: {@link bypassSanitizationTrustResourceUrl} and {@link resourceUrlSanitizer}.
*/
export interface TrustedResourceUrlString extends TrustedString { [BRAND]: BypassType.ResourceUrl; }
export function allowSanitizationBypass(value: any, type: BypassType): boolean {
return (value instanceof String && (value as TrustedStyleString)[BRAND] === type) ? true : false;
}
/**
* 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, BypassType.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, BypassType.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, BypassType.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, BypassType.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, BypassType.ResourceUrl);
}
function bypassSanitizationTrustString(
trustedString: string, mode: BypassType.Html): TrustedHtmlString;
function bypassSanitizationTrustString(
trustedString: string, mode: BypassType.Style): TrustedStyleString;
function bypassSanitizationTrustString(
trustedString: string, mode: BypassType.Script): TrustedScriptString;
function bypassSanitizationTrustString(
trustedString: string, mode: BypassType.Url): TrustedUrlString;
function bypassSanitizationTrustString(
trustedString: string, mode: BypassType.ResourceUrl): TrustedResourceUrlString;
function bypassSanitizationTrustString(trustedString: string, mode: BypassType): TrustedString {
const trusted = new String(trustedString) as TrustedString;
trusted[BRAND] = mode;
return trusted;
}

View File

@ -9,63 +9,13 @@
import {getCurrentSanitizer} from '../render3/instructions';
import {stringify} from '../render3/util';
import {BypassType, allowSanitizationBypass} from './bypass';
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
import {SecurityContext} from './security';
import {_sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
import {StyleSanitizeFn, _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
@ -85,7 +35,7 @@ export function sanitizeHtml(unsafeHtml: any): string {
if (s) {
return s.sanitize(SecurityContext.HTML, unsafeHtml) || '';
}
if (unsafeHtml instanceof String && (unsafeHtml as TrustedHtmlString)[BRAND] === 'Html') {
if (allowSanitizationBypass(unsafeHtml, BypassType.Html)) {
return unsafeHtml.toString();
}
return _sanitizeHtml(document, stringify(unsafeHtml));
@ -109,7 +59,7 @@ export function sanitizeStyle(unsafeStyle: any): string {
if (s) {
return s.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
}
if (unsafeStyle instanceof String && (unsafeStyle as TrustedStyleString)[BRAND] === 'Style') {
if (allowSanitizationBypass(unsafeStyle, BypassType.Style)) {
return unsafeStyle.toString();
}
return _sanitizeStyle(stringify(unsafeStyle));
@ -134,7 +84,7 @@ export function sanitizeUrl(unsafeUrl: any): string {
if (s) {
return s.sanitize(SecurityContext.URL, unsafeUrl) || '';
}
if (unsafeUrl instanceof String && (unsafeUrl as TrustedUrlString)[BRAND] === 'Url') {
if (allowSanitizationBypass(unsafeUrl, BypassType.Url)) {
return unsafeUrl.toString();
}
return _sanitizeUrl(stringify(unsafeUrl));
@ -154,8 +104,7 @@ export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
if (s) {
return s.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '';
}
if (unsafeResourceUrl instanceof String &&
(unsafeResourceUrl as TrustedResourceUrlString)[BRAND] === 'ResourceUrl') {
if (allowSanitizationBypass(unsafeResourceUrl, BypassType.ResourceUrl)) {
return unsafeResourceUrl.toString();
}
throw new Error('unsafe value used in a resource URL context (see http://g.co/ng/security#xss)');
@ -175,85 +124,22 @@ export function sanitizeScript(unsafeScript: any): string {
if (s) {
return s.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
}
if (unsafeScript instanceof String && (unsafeScript as TrustedScriptString)[BRAND] === 'Script') {
if (allowSanitizationBypass(unsafeScript, BypassType.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.
* The default style sanitizer will handle sanitization for style properties by
* sanitizing any CSS property that can include a `url` value (usually image-based properties)
*/
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');
}
export const defaultStyleSanitizer = (function(prop: string, value?: string): string | boolean {
if (value === undefined) {
return prop === 'background-image' || prop === 'background' || prop === 'border-image' ||
prop === 'filter' || prop === 'filter' || prop === 'list-style' ||
prop === 'list-style-image';
}
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;
}
return sanitizeStyle(value);
} as StyleSanitizeFn);

View File

@ -101,3 +101,19 @@ export function _sanitizeStyle(value: string): string {
return 'unsafe';
}
/**
* Used to intercept and sanitize style values before they are written to the renderer.
*
* This function is designed to be called in two modes. When a value is not provided
* then the function will return a boolean whether a property will be sanitized later.
* If a value is provided then the sanitized version of that will be returned.
*/
export interface StyleSanitizeFn {
/** This mode is designed to instruct whether the property will be used for sanitization
* at a later point */
(prop: string): boolean;
/** This mode is designed to sanitize the provided value */
(prop: string, value: string): string;
}