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

PR Close #24938
This commit is contained in:
Matias Niemelä
2018-07-23 08:49:52 -07:00
committed by Victor Berchet
parent 13f3157823
commit 169e9dd2c8
21 changed files with 963 additions and 490 deletions
@@ -48,12 +48,12 @@ export class LargeTableComponent {
{
if (rf2 & RenderFlags.Create) {
E(0, 'td');
s(c0);
s(null, c0);
{ T(1); }
e();
}
if (rf2 & RenderFlags.Update) {
sp(0, 0, cell.row % 2 ? '' : 'grey');
sp(0, 0, null, cell.row % 2 ? '' : 'grey');
t(1, b(cell.value));
}
}
+2 -2
View File
@@ -41,7 +41,7 @@ export class TreeComponent {
template: function(rf: RenderFlags, ctx: TreeComponent) {
if (rf & RenderFlags.Create) {
E(0, 'span');
s(c0);
s(null, c0);
{ T(1); }
e();
C(2);
@@ -114,7 +114,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
E(0, 'tree');
{
E(1, 'span');
s(c1);
s(null, c1);
{ T(2); }
e();
C(3);
@@ -53,7 +53,7 @@ describe('compiler compliance', () => {
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵE(0, "div", $c1$);
$r3$.ɵs(null, $c2$);
$r3$.ɵs($c2$);
$r3$.ɵNS();
$r3$.ɵE(1, "svg");
$r3$.ɵEe(2, "circle", $c3$);
@@ -103,7 +103,7 @@ describe('compiler compliance', () => {
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵE(0, "div", $c1$);
$r3$.ɵs(null, $c2$);
$r3$.ɵs($c2$);
$r3$.ɵNM();
$r3$.ɵE(1, "math");
$r3$.ɵEe(2, "infinity");
@@ -153,7 +153,7 @@ describe('compiler compliance', () => {
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵE(0, "div", $c1$);
$r3$.ɵs(null, $c2$);
$r3$.ɵs($c2$);
$r3$.ɵT(1, "Hello ");
$r3$.ɵE(2, "b");
$r3$.ɵT(3, "World");
@@ -329,8 +329,8 @@ describe('compiler compliance', () => {
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
const template = `
const _c0 = ["background-color"];
const _c1 = ["error"];
const _c0 = ["error"];
const _c1 = ["background-color"];
…
MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
factory:function MyComponent_Factory(){
@@ -43,11 +43,11 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) {
$r3$.ɵE(0, "div");
$r3$.ɵs();
$r3$.ɵs(null, null, $r3$.ɵzss);
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵsm(0, $ctx$.myStyleExp);
$r3$.ɵsm(0, null, $ctx$.myStyleExp);
$r3$.ɵsa(0);
}
}
@@ -96,15 +96,15 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) {
$r3$.ɵE(0, "div");
$r3$.ɵs(_c0);
$r3$.ɵs(null, _c0, $r3$.ɵzss);
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵsm(0, $ctx$.myStyleExp);
$r3$.ɵsm(0, null, $ctx$.myStyleExp);
$r3$.ɵsp(0, 1, $ctx$.myWidth);
$r3$.ɵsp(0, 2, $ctx$.myHeight);
$r3$.ɵsa(0);
$r3$.ɵa(0, "style", $r3$.ɵb("border-width: 10px"));
$r3$.ɵa(0, "style", $r3$.ɵb("border-width: 10px"), $r3$.ɵzs);
}
}
});
@@ -113,6 +113,59 @@ describe('compiler compliance: styling', () => {
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should assign a sanitizer instance to the element style allocation instruction if any url-based properties are detected',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div [style.background-image]="myImage">\`
})
export class MyComponent {
myImage = 'url(foo.jpg)';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
const _c0 = ["background-image"];
export class MyComponent {
constructor() {
this.myImage = 'url(foo.jpg)';
}
}
MyComponent.ngComponentDef = i0.ɵdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory() {
return new MyComponent();
},
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵE(0, "div");
i0.ɵs(null, _c0, i0.ɵzss);
i0.ɵe();
}
if (rf & 2) {
i0.ɵsp(0, 0, ctx.myImage);
i0.ɵsa(0);
}
}
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
});
describe('[class]', () => {
@@ -144,7 +197,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵsm(0,null,$ctx$.myClassExp);
$r3$.ɵsm(0,$ctx$.myClassExp);
$r3$.ɵsa(0);
}
}
@@ -193,11 +246,11 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) {
$r3$.ɵE(0, "div");
$r3$.ɵs(null, _c0);
$r3$.ɵs(_c0);
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵsm(0, null, $ctx$.myClassExp);
$r3$.ɵsm(0, $ctx$.myClassExp);
$r3$.ɵcp(0, 1, $ctx$.yesToApple);
$r3$.ɵcp(0, 2, $ctx$.yesToOrange);
$r3$.ɵsa(0);
@@ -234,8 +287,8 @@ describe('compiler compliance: styling', () => {
};
const template = `
const _c0 = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
const _c1 = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
const _c0 = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
const _c1 = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
…
MyComponent.ngComponentDef = i0.ɵdefineComponent({
type: MyComponent,
@@ -251,7 +304,7 @@ describe('compiler compliance: styling', () => {
}
if (rf & 2) {
$r3$.ɵa(0, "class", $r3$.ɵb("round"));
$r3$.ɵa(0, "style", $r3$.ɵb("height:100px"));
$r3$.ɵa(0, "style", $r3$.ɵb("height:100px"), $r3$.ɵzs);
}
}
});
@@ -154,4 +154,12 @@ export class Identifiers {
// Reserve slots for pure functions
static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE};
// sanitization-related functions
static sanitizeHtml: o.ExternalReference = {name: 'ɵzh', moduleName: CORE};
static sanitizeStyle: o.ExternalReference = {name: 'ɵzs', moduleName: CORE};
static defaultStyleSanitizer: o.ExternalReference = {name: 'ɵzss', moduleName: CORE};
static sanitizeResourceUrl: o.ExternalReference = {name: 'ɵzr', moduleName: CORE};
static sanitizeScript: o.ExternalReference = {name: 'ɵzc', moduleName: CORE};
static sanitizeUrl: o.ExternalReference = {name: 'ɵzu', moduleName: CORE};
}
+91 -21
View File
@@ -368,10 +368,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
});
let hasMapBasedStyling = false;
for (let i = 0; i < styleInputs.length; i++) {
const input = styleInputs[i];
const isMapBasedStyleBinding = i === 0 && input.name === 'style';
if (!isMapBasedStyleBinding && !stylesIndexMap.hasOwnProperty(input.name)) {
if (isMapBasedStyleBinding) {
hasMapBasedStyling = true;
} else if (!stylesIndexMap.hasOwnProperty(input.name)) {
stylesIndexMap[input.name] = currStyleIndex++;
}
}
@@ -384,9 +387,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
}
// in the event that a [style] binding is used then sanitization will
// always be imported because it is not possible to know ahead of time
// whether style bindings will use or not use any sanitizable properties
// that isStyleSanitizable() will detect
let useDefaultStyleSanitizer = hasMapBasedStyling;
// this will build the instructions so that they fall into the following syntax
// => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2]
Object.keys(stylesIndexMap).forEach(prop => {
useDefaultStyleSanitizer = useDefaultStyleSanitizer || isStyleSanitizable(prop);
initialStyleDeclarations.push(o.literal(prop));
});
@@ -473,18 +483,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (hasStylingInstructions) {
const paramsList: (o.Expression)[] = [];
if (initialStyleDeclarations.length) {
// the template compiler handles initial style (e.g. style="foo") values
// in a special command called `elementStyle` so that the initial styles
// can be processed during runtime. These initial styles values are bound to
// a constant because the inital style values do not change (since they're static).
paramsList.push(
this.constantPool.getConstLiteral(o.literalArr(initialStyleDeclarations), true));
} else if (initialClassDeclarations.length) {
// no point in having an extra `null` value unless there are follow-up params
paramsList.push(o.NULL_EXPR);
}
if (initialClassDeclarations.length) {
// the template compiler handles initial class styling (e.g. class="foo") values
// in a special command called `elementClass` so that the initial class
@@ -492,6 +490,26 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// a constant because the inital class values do not change (since they're static).
paramsList.push(
this.constantPool.getConstLiteral(o.literalArr(initialClassDeclarations), true));
} else if (initialStyleDeclarations.length || useDefaultStyleSanitizer) {
// no point in having an extra `null` value unless there are follow-up params
paramsList.push(o.NULL_EXPR);
}
if (initialStyleDeclarations.length) {
// the template compiler handles initial style (e.g. style="foo") values
// in a special command called `elementStyle` so that the initial styles
// can be processed during runtime. These initial styles values are bound to
// a constant because the inital style values do not change (since they're static).
paramsList.push(
this.constantPool.getConstLiteral(o.literalArr(initialStyleDeclarations), true));
} else if (useDefaultStyleSanitizer) {
// no point in having an extra `null` value unless there are follow-up params
paramsList.push(o.NULL_EXPR);
}
if (useDefaultStyleSanitizer) {
paramsList.push(o.importExpr(R3.defaultStyleSanitizer));
}
this._creationCode.push(o.importExpr(R3.elementStyling).callFn(paramsList).toStmt());
@@ -532,13 +550,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const stylingInput = mapBasedStyleInput || mapBasedClassInput;
if (stylingInput) {
const params: o.Expression[] = [];
if (mapBasedStyleInput) {
params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true));
} else if (mapBasedClassInput) {
params.push(o.NULL_EXPR);
}
if (mapBasedClassInput) {
params.push(this.convertPropertyBinding(implicit, mapBasedClassInput.value, true));
} else if (mapBasedStyleInput) {
params.push(o.NULL_EXPR);
}
if (mapBasedStyleInput) {
params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true));
}
this.instruction(
this._bindingCode, stylingInput.sourceSpan, R3.elementStylingMap, indexLiteral,
@@ -551,11 +569,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
for (i; i < styleInputs.length; i++) {
const input = styleInputs[i];
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
const params = [convertedBinding];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
if (sanitizationRef) {
params.push(sanitizationRef);
}
const key = input.name;
const styleIndex: number = stylesIndexMap[key] !;
this.instruction(
this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral,
o.literal(styleIndex), convertedBinding);
o.literal(styleIndex), ...params);
}
lastInputCommand = styleInputs[styleInputs.length - 1];
@@ -566,11 +590,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
for (i; i < classInputs.length; i++) {
const input = classInputs[i];
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
const params = [convertedBinding];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
if (sanitizationRef) {
params.push(sanitizationRef);
}
const key = input.name;
const classIndex: number = classesIndexMap[key] !;
this.instruction(
this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral,
o.literal(classIndex), convertedBinding);
o.literal(classIndex), ...params);
}
lastInputCommand = classInputs[classInputs.length - 1];
@@ -588,12 +618,19 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
const instruction = mapBindingToInstruction(input.type);
if (instruction) {
const params = [convertedBinding];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
if (sanitizationRef) {
params.push(sanitizationRef);
}
// TODO(chuckj): runtime: security context?
this.instruction(
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), convertedBinding);
o.literal(input.name), ...params);
} else {
this._unsupported(`binding type ${input.type}`);
}
@@ -1061,3 +1098,36 @@ export function makeBindingParser(): BindingParser {
function isClassBinding(input: t.BoundAttribute): boolean {
return input.name == 'className' || input.name == 'class';
}
function resolveSanitizationFn(input: t.BoundAttribute, context: core.SecurityContext) {
switch (context) {
case core.SecurityContext.HTML:
return o.importExpr(R3.sanitizeHtml);
case core.SecurityContext.SCRIPT:
return o.importExpr(R3.sanitizeScript);
case core.SecurityContext.STYLE:
// the compiler does not fill in an instruction for [style.prop?] binding
// values because the style algorithm knows internally what props are subject
// to sanitization (only [attr.style] values are explicitly sanitized)
return input.type === BindingType.Attribute ? o.importExpr(R3.sanitizeStyle) : null;
case core.SecurityContext.URL:
return o.importExpr(R3.sanitizeUrl);
case core.SecurityContext.RESOURCE_URL:
return o.importExpr(R3.sanitizeResourceUrl);
default:
return null;
}
}
function isStyleSanitizable(prop: string): boolean {
switch (prop) {
case 'background-image':
case 'background':
case 'border-image':
case 'filter':
case 'list-style':
case 'list-style-image':
return true;
}
return false;
}
@@ -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
+32 -26
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);
}
//////////////////////////
@@ -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
};
File diff suppressed because it is too large Load Diff
+143
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;
}
+17 -131
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);
@@ -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;
}
@@ -539,6 +539,9 @@
{
"name": "getRootView"
},
{
"name": "getStyleSanitizer"
},
{
"name": "getStylingContext"
},
@@ -557,6 +560,9 @@
{
"name": "getValue"
},
{
"name": "hasValueChanged"
},
{
"name": "hostElement"
},
@@ -668,6 +674,9 @@
{
"name": "pointers"
},
{
"name": "prepareInitialFlag"
},
{
"name": "projectionNodeStack"
},
@@ -284,7 +284,7 @@ describe('elements', () => {
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵs(null, c1);
$r3$.ɵs(c1);
$r3$.ɵe();
}
if (rf & 2) {
@@ -323,7 +323,7 @@ describe('elements', () => {
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵs(c0);
$r3$.ɵs(null, c0);
$r3$.ɵe();
}
if (rf & 2) {
@@ -356,8 +356,8 @@ describe('elements', () => {
it('should bind to many and keep order', () => {
type $MyComponent$ = MyComponent;
const c0 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red'];
const c1 = ['foo'];
const c0 = ['foo'];
const c1 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red'];
@Component({
selector: 'my-component',
@@ -416,7 +416,7 @@ describe('elements', () => {
$r3$.ɵe();
}
if (rf & 2) {
$r3$.ɵsm(0, ctx.styleExp, ctx.classExp);
$r3$.ɵsm(0, ctx.classExp, ctx.styleExp);
$r3$.ɵsa(0);
}
}
@@ -51,7 +51,7 @@ describe('compiler sanitization', () => {
if (rf & 2) {
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵsanitizeHtml);
$r3$.ɵp(0, 'hidden', $r3$.ɵb(ctx.hidden));
$r3$.ɵsp(0, 0, ctx.style, $r3$.ɵsanitizeStyle);
$r3$.ɵsp(0, 0, ctx.style);
$r3$.ɵsa(0);
$r3$.ɵp(1, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
$r3$.ɵa(1, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
+1 -1
View File
@@ -213,7 +213,7 @@ describe('exports', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
elementStyling(null, [InitialStylingFlags.VALUES_MODE, 'red', true]);
elementStyling([InitialStylingFlags.VALUES_MODE, 'red', true]);
elementEnd();
elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
elementEnd();
+78 -11
View File
@@ -14,8 +14,10 @@ import {bind, container, element, elementAttribute, elementEnd, elementProperty,
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node';
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass';
import {defaultStyleSanitizer, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
import {StyleSanitizeFn} from '../../src/sanitization/style_sanitizer';
import {NgForOf} from './common_with_def';
import {ComponentFixture, TemplateFixture} from './render_util';
@@ -27,9 +29,10 @@ describe('instructions', () => {
elementEnd();
}
function createDiv(initialStyles?: (string | number)[]) {
function createDiv(initialStyles?: (string | number)[], styleSanitizer?: StyleSanitizeFn) {
elementStart(0, 'div');
elementStyling(initialStyles && Array.isArray(initialStyles) ? initialStyles : null);
elementStyling(
[], initialStyles && Array.isArray(initialStyles) ? initialStyles : null, styleSanitizer);
elementEnd();
}
@@ -190,39 +193,87 @@ describe('instructions', () => {
});
describe('elementStyleProp', () => {
it('should use sanitizer function', () => {
const t = new TemplateFixture(() => { return createDiv(['background-image']); });
it('should automatically sanitize unless a bypass operation is applied', () => {
const t = new TemplateFixture(
() => { return createDiv(['background-image'], defaultStyleSanitizer); });
t.update(() => {
elementStyleProp(0, 0, 'url("http://server")', sanitizeStyle);
elementStyleProp(0, 0, 'url("http://server")');
elementStylingApply(0);
});
// nothing is set because sanitizer suppresses it.
expect(t.html).toEqual('<div></div>');
t.update(() => {
elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle);
elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server2")'));
elementStylingApply(0);
});
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
.toEqual('url("http://server")');
.toEqual('url("http://server2")');
});
it('should not re-apply the style value even if it is a newly bypassed again', () => {
const sanitizerInterceptor = new MockSanitizerInterceptor();
const t = createTemplateFixtureWithSanitizer(
() => createDiv(['background-image'], sanitizerInterceptor.getStyleSanitizer()),
sanitizerInterceptor);
t.update(() => {
elementStyleProp(0, 0, bypassSanitizationTrustStyle('apple'));
elementStylingApply(0);
});
expect(sanitizerInterceptor.lastValue !).toEqual('apple');
sanitizerInterceptor.lastValue = null;
t.update(() => {
elementStyleProp(0, 0, bypassSanitizationTrustStyle('apple'));
elementStylingApply(0);
});
expect(sanitizerInterceptor.lastValue).toEqual(null);
});
});
describe('elementStyleMap', () => {
function createDivWithStyle() {
elementStart(0, 'div');
elementStyling(['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
elementStyling([], ['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
elementEnd();
}
it('should add style', () => {
const fixture = new TemplateFixture(createDivWithStyle);
fixture.update(() => {
elementStylingMap(0, {'background-color': 'red'});
elementStylingMap(0, null, {'background-color': 'red'});
elementStylingApply(0);
});
expect(fixture.html).toEqual('<div style="height: 10px; background-color: red;"></div>');
});
it('should sanitize new styles that may contain `url` properties', () => {
const detectedValues: string[] = [];
const sanitizerInterceptor =
new MockSanitizerInterceptor(value => { detectedValues.push(value); });
const fixture = createTemplateFixtureWithSanitizer(
() => createDiv([], sanitizerInterceptor.getStyleSanitizer()), sanitizerInterceptor);
fixture.update(() => {
elementStylingMap(0, null, {
'background-image': 'background-image',
'background': 'background',
'border-image': 'border-image',
'list-style': 'list-style',
'list-style-image': 'list-style-image',
'filter': 'filter',
'width': 'width'
});
elementStylingApply(0);
});
const props = detectedValues.sort();
expect(props).toEqual([
'background', 'background-image', 'border-image', 'filter', 'list-style', 'list-style-image'
]);
});
});
describe('elementClass', () => {
@@ -235,7 +286,7 @@ describe('instructions', () => {
it('should add class', () => {
const fixture = new TemplateFixture(createDivWithStyling);
fixture.update(() => {
elementStylingMap(0, null, 'multiple classes');
elementStylingMap(0, 'multiple classes');
elementStylingApply(0);
});
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
@@ -504,7 +555,23 @@ class LocalMockSanitizer implements Sanitizer {
bypassSecurityTrustResourceUrl(value: string) { return new LocalSanitizedValue(value); }
}
class MockSanitizerInterceptor {
public lastValue: string|null = null;
constructor(private _interceptorFn?: ((value: any) => any)|null) {}
getStyleSanitizer() { return defaultStyleSanitizer; }
sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null|any): string|null {
if (this._interceptorFn) {
this._interceptorFn(value);
}
return this.lastValue = value;
}
}
function stripStyleWsCharacters(value: string): string {
// color: blue; => color:blue
return value.replace(/;/g, '').replace(/:\s+/g, ':');
}
function createTemplateFixtureWithSanitizer(buildFn: () => any, sanitizer: Sanitizer) {
return new TemplateFixture(buildFn, () => {}, null, null, sanitizer);
}
@@ -748,7 +748,7 @@ describe('render3 integration test', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(['border-color']);
elementStyling(null, ['border-color']);
elementEnd();
}
if (rf & RenderFlags.Update) {
@@ -767,7 +767,7 @@ describe('render3 integration test', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(['font-size']);
elementStyling(null, ['font-size']);
elementEnd();
}
if (rf & RenderFlags.Update) {
@@ -788,7 +788,7 @@ describe('render3 integration test', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(null, ['active']);
elementStyling(['active']);
elementEnd();
}
if (rf & RenderFlags.Update) {
@@ -814,7 +814,7 @@ describe('render3 integration test', () => {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
elementStyling(
null, ['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
elementEnd();
}
if (rf & RenderFlags.Update) {
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass';
import {sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
describe('sanitization', () => {
class Wrap {
@@ -64,4 +65,4 @@ describe('sanitization', () => {
expect(() => sanitizeScript(bypassSanitizationTrustHtml('true'))).toThrowError(ERROR);
expect(sanitizeScript(bypassSanitizationTrustScript('true'))).toEqual('true');
});
});
});