diff --git a/modules/benchmarks/src/largetable/render3/table.ts b/modules/benchmarks/src/largetable/render3/table.ts
index 852538c4b2..9893af24b7 100644
--- a/modules/benchmarks/src/largetable/render3/table.ts
+++ b/modules/benchmarks/src/largetable/render3/table.ts
@@ -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));
}
}
diff --git a/modules/benchmarks/src/tree/render3/tree.ts b/modules/benchmarks/src/tree/render3/tree.ts
index 3fc03b2f81..70b9d43ea2 100644
--- a/modules/benchmarks/src/tree/render3/tree.ts
+++ b/modules/benchmarks/src/tree/render3/tree.ts
@@ -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);
diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
index 6fd0a1c9ed..3d549e7665 100644
--- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
@@ -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(){
diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
index 24ddf9447d..00aa063cae 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
@@ -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: \`
\`
+ })
+ 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);
}
}
});
diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts
index ae60d396a1..5e1aaabd46 100644
--- a/packages/compiler/src/render3/r3_identifiers.ts
+++ b/packages/compiler/src/render3/r3_identifiers.ts
@@ -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};
}
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index e50f2a5ce9..a2a937d7ea 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -368,10 +368,13 @@ export class TemplateDefinitionBuilder implements t.Visitor
, 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, 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, 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, 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, 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, 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, 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, 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;
+}
diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts
index d51c1d5468..c88234bda0 100644
--- a/packages/core/src/core_render3_private_export.ts
+++ b/packages/core/src/core_render3_private_export.ts
@@ -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
diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts
index 187ada2015..eba6b90974 100644
--- a/packages/core/src/render3/instructions.ts
+++ b/packages/core/src/render3/instructions.ts
@@ -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(
* (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(
+ 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(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(
- index: number, styleIndex: number, value: T | null, suffix?: string): void;
-export function elementStyleProp(
- index: number, styleIndex: number, value: T | null, sanitizer?: SanitizerFn): void;
-export function elementStyleProp(
- 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(
* (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(
- 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);
}
//////////////////////////
diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts
index 65e122c5f5..9a2137d1d8 100644
--- a/packages/core/src/render3/jit/environment.ts
+++ b/packages/core/src/render3/jit/environment.ts
@@ -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
};
diff --git a/packages/core/src/render3/styling.ts b/packages/core/src/render3/styling.ts
index d4e2d049da..aa46782341 100644
--- a/packages/core/src/render3/styling.ts
+++ b/packages/core/src/render3/styling.ts
@@ -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 {
+ Array {
/**
* 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 { [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;
+}
diff --git a/packages/core/src/sanitization/bypass.ts b/packages/core/src/sanitization/bypass.ts
new file mode 100644
index 0000000000..a8b812e225
--- /dev/null
+++ b/packages/core/src/sanitization/bypass.ts
@@ -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;
+}
diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts
index ecd0203456..c5fac7314a 100644
--- a/packages/core/src/sanitization/sanitization.ts
+++ b/packages/core/src/sanitization/sanitization.ts
@@ -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);
diff --git a/packages/core/src/sanitization/style_sanitizer.ts b/packages/core/src/sanitization/style_sanitizer.ts
index c335781048..88a03e8e98 100644
--- a/packages/core/src/sanitization/style_sanitizer.ts
+++ b/packages/core/src/sanitization/style_sanitizer.ts
@@ -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;
+}
diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json
index 86469d9074..2957b13643 100644
--- a/packages/core/test/bundling/todo/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json
@@ -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"
},
diff --git a/packages/core/test/render3/compiler_canonical/elements_spec.ts b/packages/core/test/render3/compiler_canonical/elements_spec.ts
index 1d1c8dd41a..1eb490df7e 100644
--- a/packages/core/test/render3/compiler_canonical/elements_spec.ts
+++ b/packages/core/test/render3/compiler_canonical/elements_spec.ts
@@ -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);
}
}
diff --git a/packages/core/test/render3/compiler_canonical/sanitize_spec.ts b/packages/core/test/render3/compiler_canonical/sanitize_spec.ts
index 43fea1b77b..dd2e4b11cb 100644
--- a/packages/core/test/render3/compiler_canonical/sanitize_spec.ts
+++ b/packages/core/test/render3/compiler_canonical/sanitize_spec.ts
@@ -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);
diff --git a/packages/core/test/render3/exports_spec.ts b/packages/core/test/render3/exports_spec.ts
index 8db0c81559..b357572fd5 100644
--- a/packages/core/test/render3/exports_spec.ts
+++ b/packages/core/test/render3/exports_spec.ts
@@ -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();
diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts
index 2e3f74a812..e95b47ba57 100644
--- a/packages/core/test/render3/instructions_spec.ts
+++ b/packages/core/test/render3/instructions_spec.ts
@@ -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('');
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('');
});
+
+ 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('');
@@ -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);
+}
diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts
index 1f1182e012..c1e7440471 100644
--- a/packages/core/test/render3/integration_spec.ts
+++ b/packages/core/test/render3/integration_spec.ts
@@ -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) {
diff --git a/packages/core/test/render3/styling_spec.ts b/packages/core/test/render3/styling_spec.ts
index c7c558d6f1..46415d86ba 100644
--- a/packages/core/test/render3/styling_spec.ts
+++ b/packages/core/test/render3/styling_spec.ts
@@ -10,6 +10,8 @@ import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/def
import {LElementNode} from '../../src/render3/interfaces/node';
import {Renderer3} from '../../src/render3/interfaces/renderer';
import {StylingContext, StylingFlags, StylingIndex, allocStylingContext, createStylingContextTemplate, isContextDirty, renderStyling as _renderStyling, setContextDirty, updateClassProp, updateStyleProp, updateStylingMap} from '../../src/render3/styling';
+import {defaultStyleSanitizer} from '../../src/sanitization/sanitization';
+import {StyleSanitizeFn} from '../../src/sanitization/style_sanitizer';
import {renderToHtml} from './render_util';
@@ -18,9 +20,9 @@ describe('styling', () => {
beforeEach(() => { element = {} as any; });
function initContext(
- styles?: (number | string)[] | null,
- classes?: (string | number | boolean)[] | null): StylingContext {
- return allocStylingContext(element, createStylingContextTemplate(styles, classes));
+ styles?: (number | string)[] | null, classes?: (string | number | boolean)[] | null,
+ sanitizer?: StyleSanitizeFn | null): StylingContext {
+ return allocStylingContext(element, createStylingContextTemplate(classes, styles, sanitizer));
}
function renderStyles(context: StylingContext, renderer?: Renderer3) {
@@ -55,14 +57,23 @@ describe('styling', () => {
}
function updateClasses(context: StylingContext, classes: string | {[key: string]: any} | null) {
- updateStylingMap(context, null, classes);
+ updateStylingMap(context, classes, null);
}
- function cleanStyle(a: number = 0, b: number = 0): number { return _clean(a, b, false); }
+ function updateStyles(context: StylingContext, styles: {[key: string]: any} | null) {
+ updateStylingMap(context, null, styles);
+ }
+
+ function cleanStyle(a: number = 0, b: number = 0): number { return _clean(a, b, false, false); }
+
+ function cleanStyleWithSanitization(a: number = 0, b: number = 0): number {
+ return _clean(a, b, false, true);
+ }
function cleanClass(a: number, b: number) { return _clean(a, b, true); }
- function _clean(a: number = 0, b: number = 0, isClassBased: boolean): number {
+ function _clean(
+ a: number = 0, b: number = 0, isClassBased: boolean, sanitizable?: boolean): number {
let num = 0;
if (a) {
num |= a << StylingFlags.BitCountSize;
@@ -73,24 +84,32 @@ describe('styling', () => {
if (isClassBased) {
num |= StylingFlags.Class;
}
+ if (sanitizable) {
+ num |= StylingFlags.Sanitize;
+ }
return num;
}
- function _dirty(a: number = 0, b: number = 0, isClassBased: boolean): number {
- return _clean(a, b, isClassBased) | StylingFlags.Dirty;
+ function _dirty(
+ a: number = 0, b: number = 0, isClassBased: boolean, sanitizable?: boolean): number {
+ return _clean(a, b, isClassBased, sanitizable) | StylingFlags.Dirty;
}
function dirtyStyle(a: number = 0, b: number = 0): number {
return _dirty(a, b, false) | StylingFlags.Dirty;
}
+ function dirtyStyleWithSanitization(a: number = 0, b: number = 0): number {
+ return _dirty(a, b, false, true);
+ }
+
function dirtyClass(a: number, b: number) { return _dirty(a, b, true); }
describe('styles', () => {
describe('createStylingContextTemplate', () => {
it('should initialize empty template', () => {
const template = initContext();
- expect(template).toEqual([element, [null], cleanStyle(0, 5), 0, null]);
+ expect(template).toEqual([element, null, [null], cleanStyle(0, 6), 0, null]);
});
it('should initialize static styles', () => {
@@ -98,28 +117,29 @@ describe('styling', () => {
initContext([InitialStylingFlags.VALUES_MODE, 'color', 'red', 'width', '10px']);
expect(template).toEqual([
element,
+ null,
[null, 'red', '10px'],
- dirtyStyle(0, 11), //
+ dirtyStyle(0, 12), //
0,
null,
- // #5
- cleanStyle(1, 11),
+ // #6
+ cleanStyle(1, 12),
'color',
null,
- // #8
- cleanStyle(2, 14),
+ // #9
+ cleanStyle(2, 15),
'width',
null,
- // #11
- dirtyStyle(1, 5),
+ // #12
+ dirtyStyle(1, 6),
'color',
null,
- // #14
- dirtyStyle(2, 8),
+ // #15
+ dirtyStyle(2, 9),
'width',
null,
]);
@@ -132,7 +152,7 @@ describe('styling', () => {
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
- elementStyling([
+ elementStyling([], [
'width', 'height', 'opacity', //
InitialStylingFlags.VALUES_MODE, 'width', '100px', 'height', '100px', 'opacity',
'0.5'
@@ -160,30 +180,30 @@ describe('styling', () => {
it('should build a list of multiple styling values', () => {
const getStyles = trackStylesFactory();
const stylingContext = initContext();
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
- updateStylingMap(stylingContext, {height: '200px'});
+ updateStyles(stylingContext, {height: '200px'});
expect(getStyles(stylingContext)).toEqual({width: null, height: '200px'});
});
it('should evaluate the delta between style changes when rendering occurs', () => {
const stylingContext =
initContext(['width', 'height', InitialStylingFlags.VALUES_MODE, 'width', '100px']);
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
height: '200px',
});
expect(renderStyles(stylingContext)).toEqual({width: '100px', height: '200px'});
expect(renderStyles(stylingContext)).toEqual({});
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
expect(renderStyles(stylingContext)).toEqual({height: '100px'});
updateStyleProp(stylingContext, 1, '100px');
expect(renderStyles(stylingContext)).toEqual({});
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
@@ -193,7 +213,7 @@ describe('styling', () => {
it('should update individual values on a set of styles', () => {
const getStyles = trackStylesFactory();
const stylingContext = initContext(['width', 'height']);
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
@@ -205,7 +225,7 @@ describe('styling', () => {
const stylingContext = initContext();
expect(isContextDirty(stylingContext)).toBeFalsy();
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
@@ -213,13 +233,13 @@ describe('styling', () => {
setContextDirty(stylingContext, false);
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
expect(isContextDirty(stylingContext)).toBeFalsy();
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '200px',
height: '100px',
});
@@ -228,7 +248,7 @@ describe('styling', () => {
it('should only mark itself as updated when any single properties have been applied', () => {
const stylingContext = initContext(['height']);
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
height: '100px',
});
@@ -258,7 +278,7 @@ describe('styling', () => {
opacity: '0',
});
- updateStylingMap(stylingContext, {width: '200px', height: '200px'});
+ updateStyles(stylingContext, {width: '200px', height: '200px'});
expect(getStyles(stylingContext)).toEqual({
width: '200px',
@@ -282,7 +302,7 @@ describe('styling', () => {
opacity: '0',
});
- updateStylingMap(stylingContext, {});
+ updateStyles(stylingContext, {});
expect(getStyles(stylingContext)).toEqual({
width: '100px',
@@ -295,68 +315,70 @@ describe('styling', () => {
const stylingContext = initContext(['width', 'height']);
const getStyles = trackStylesFactory();
- updateStylingMap(stylingContext, {width: '100px', height: '100px'});
+ updateStyles(stylingContext, {width: '100px', height: '100px'});
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 11), //
+ dirtyStyle(0, 12), //
2,
null,
- // #5
- cleanStyle(0, 11),
+ // #6
+ cleanStyle(0, 12),
'width',
null,
- // #8
- cleanStyle(0, 14),
+ // #9
+ cleanStyle(0, 15),
'height',
null,
- // #11
- dirtyStyle(0, 5),
+ // #12
+ dirtyStyle(0, 6),
'width',
'100px',
- // #14
- dirtyStyle(0, 8),
+ // #15
+ dirtyStyle(0, 9),
'height',
'100px',
]);
getStyles(stylingContext);
- updateStylingMap(stylingContext, {width: '200px', opacity: '0'});
+ updateStyles(stylingContext, {width: '200px', opacity: '0'});
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 11), //
+ dirtyStyle(0, 12), //
2,
null,
- // #5
- cleanStyle(0, 11),
+ // #6
+ cleanStyle(0, 12),
'width',
null,
- // #8
- cleanStyle(0, 17),
+ // #9
+ cleanStyle(0, 18),
'height',
null,
- // #11
- dirtyStyle(0, 5),
+ // #12
+ dirtyStyle(0, 6),
'width',
'200px',
- // #14
+ // #15
dirtyStyle(),
'opacity',
'0',
- // #17
- dirtyStyle(0, 8),
+ // #18
+ dirtyStyle(0, 9),
'height',
null,
]);
@@ -364,69 +386,71 @@ describe('styling', () => {
getStyles(stylingContext);
expect(stylingContext).toEqual([
element,
+ null,
[null],
- cleanStyle(0, 11), //
+ cleanStyle(0, 12), //
2,
null,
- // #5
- cleanStyle(0, 11),
+ // #6
+ cleanStyle(0, 12),
'width',
null,
- // #8
- cleanStyle(0, 17),
+ // #9
+ cleanStyle(0, 18),
'height',
null,
- // #11
- cleanStyle(0, 5),
+ // #12
+ cleanStyle(0, 6),
'width',
'200px',
- // #14
+ // #15
cleanStyle(),
'opacity',
'0',
- // #17
- cleanStyle(0, 8),
+ // #18
+ cleanStyle(0, 9),
'height',
null,
]);
- updateStylingMap(stylingContext, {width: null});
+ updateStyles(stylingContext, {width: null});
updateStyleProp(stylingContext, 0, '300px');
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 11), //
+ dirtyStyle(0, 12), //
2,
null,
- // #5
- dirtyStyle(0, 11),
+ // #6
+ dirtyStyle(0, 12),
'width',
'300px',
- // #8
- cleanStyle(0, 17),
+ // #9
+ cleanStyle(0, 18),
'height',
null,
- // #11
- cleanStyle(0, 5),
+ // #12
+ cleanStyle(0, 6),
'width',
null,
- // #14
+ // #15
dirtyStyle(),
'opacity',
null,
- // #17
- cleanStyle(0, 8),
+ // #18
+ cleanStyle(0, 9),
'height',
null,
]);
@@ -436,33 +460,34 @@ describe('styling', () => {
updateStyleProp(stylingContext, 0, null);
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 11), //
+ dirtyStyle(0, 12), //
2,
null,
- // #5
- dirtyStyle(0, 11),
+ // #6
+ dirtyStyle(0, 12),
'width',
null,
- // #8
- cleanStyle(0, 17),
+ // #9
+ cleanStyle(0, 18),
'height',
null,
- // #11
- cleanStyle(0, 5),
+ // #12
+ cleanStyle(0, 6),
'width',
null,
- // #14
+ // #15
cleanStyle(),
'opacity',
null,
- // #17
- cleanStyle(0, 8),
+ // #18
+ cleanStyle(0, 9),
'height',
null,
]);
@@ -473,116 +498,119 @@ describe('styling', () => {
const stylingContext = initContext(['lineHeight']);
const getStyles = trackStylesFactory();
- updateStylingMap(stylingContext, {width: '100px', height: '100px', opacity: '0.5'});
+ updateStyles(stylingContext, {width: '100px', height: '100px', opacity: '0.5'});
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 8), //
+ dirtyStyle(0, 9), //
1,
null,
- // #5
- cleanStyle(0, 17),
+ // #6
+ cleanStyle(0, 18),
'lineHeight',
null,
- // #8
+ // #9
dirtyStyle(),
'width',
'100px',
- // #11
+ // #12
dirtyStyle(),
'height',
'100px',
- // #14
+ // #15
dirtyStyle(),
'opacity',
'0.5',
- // #17
- cleanStyle(0, 5),
+ // #18
+ cleanStyle(0, 6),
'lineHeight',
null,
]);
getStyles(stylingContext);
- updateStylingMap(stylingContext, {});
+ updateStyles(stylingContext, {});
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 8), //
+ dirtyStyle(0, 9), //
1,
null,
- // #5
- cleanStyle(0, 17),
+ // #6
+ cleanStyle(0, 18),
'lineHeight',
null,
- // #8
+ // #9
dirtyStyle(),
'width',
null,
- // #11
+ // #12
dirtyStyle(),
'height',
null,
- // #14
+ // #15
dirtyStyle(),
'opacity',
null,
- // #17
- cleanStyle(0, 5),
+ // #18
+ cleanStyle(0, 6),
'lineHeight',
null,
]);
getStyles(stylingContext);
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
borderWidth: '5px',
});
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 8), //
+ dirtyStyle(0, 9), //
1,
null,
- // #5
- cleanStyle(0, 20),
+ // #6
+ cleanStyle(0, 21),
'lineHeight',
null,
- // #8
+ // #9
dirtyStyle(),
'borderWidth',
'5px',
- // #11
+ // #12
cleanStyle(),
'width',
null,
- // #14
+ // #15
cleanStyle(),
'height',
null,
- // #17
+ // #18
cleanStyle(),
'opacity',
null,
- // #20
- cleanStyle(0, 5),
+ // #21
+ cleanStyle(0, 6),
'lineHeight',
null,
]);
@@ -591,83 +619,85 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 8), //
+ dirtyStyle(0, 9), //
1,
null,
- // #5
- dirtyStyle(0, 20),
+ // #6
+ dirtyStyle(0, 21),
'lineHeight',
'200px',
- // #8
+ // #9
dirtyStyle(),
'borderWidth',
'5px',
- // #11
+ // #12
cleanStyle(),
'width',
null,
- // #14
+ // #15
cleanStyle(),
'height',
null,
- // #17
+ // #18
cleanStyle(),
'opacity',
null,
- // #20
- cleanStyle(0, 5),
+ // #21
+ cleanStyle(0, 6),
'lineHeight',
null,
]);
- updateStylingMap(stylingContext, {borderWidth: '15px', borderColor: 'red'});
+ updateStyles(stylingContext, {borderWidth: '15px', borderColor: 'red'});
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 8), //
+ dirtyStyle(0, 9), //
1,
null,
- // #5
- dirtyStyle(0, 23),
+ // #6
+ dirtyStyle(0, 24),
'lineHeight',
'200px',
- // #8
+ // #9
dirtyStyle(),
'borderWidth',
'15px',
- // #11
+ // #12
dirtyStyle(),
'borderColor',
'red',
- // #14
+ // #15
cleanStyle(),
'width',
null,
- // #17
+ // #18
cleanStyle(),
'height',
null,
- // #20
+ // #21
cleanStyle(),
'opacity',
null,
- // #23
- cleanStyle(0, 5),
+ // #24
+ cleanStyle(0, 6),
'lineHeight',
null,
]);
@@ -677,7 +707,7 @@ describe('styling', () => {
const getStyles = trackStylesFactory();
const stylingContext = initContext(['height']);
- updateStylingMap(stylingContext, {
+ updateStyles(stylingContext, {
width: '100px',
});
@@ -685,23 +715,24 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
+ null,
[null],
- dirtyStyle(0, 8), //
+ dirtyStyle(0, 9), //
1,
null,
- // #5
- dirtyStyle(0, 11),
+ // #6
+ dirtyStyle(0, 12),
'height',
'200px',
- // #5
+ // #6
dirtyStyle(),
'width',
'100px',
- // #11
- cleanStyle(0, 5),
+ // #12
+ cleanStyle(0, 6),
'height',
null,
]);
@@ -710,27 +741,140 @@ describe('styling', () => {
expect(stylingContext).toEqual([
element,
+ null,
[null],
- cleanStyle(0, 8), //
+ cleanStyle(0, 9), //
1,
null,
- // #5
- cleanStyle(0, 11),
+ // #6
+ cleanStyle(0, 12),
'height',
'200px',
- // #5
+ // #6
cleanStyle(),
'width',
'100px',
- // #11
- cleanStyle(0, 5),
+ // #12
+ cleanStyle(0, 6),
'height',
null,
]);
});
+
+ it('should mark styles that may contain url values as being sanitizable (when a sanitizer is passed in)',
+ () => {
+ const getStyles = trackStylesFactory();
+ const initialStyles = ['border-image', 'border-width'];
+ const styleSanitizer = defaultStyleSanitizer;
+ const stylingContext = initContext(initialStyles, null, styleSanitizer);
+
+ updateStyleProp(stylingContext, 0, 'url(foo.jpg)');
+ updateStyleProp(stylingContext, 1, '100px');
+
+ expect(stylingContext).toEqual([
+ element,
+ styleSanitizer,
+ [null],
+ dirtyStyle(0, 12), //
+ 2,
+ null,
+
+ // #6
+ dirtyStyleWithSanitization(0, 12),
+ 'border-image',
+ 'url(foo.jpg)',
+
+ // #9
+ dirtyStyle(0, 15),
+ 'border-width',
+ '100px',
+
+ // #12
+ cleanStyleWithSanitization(0, 6),
+ 'border-image',
+ null,
+
+ // #15
+ cleanStyle(0, 9),
+ 'border-width',
+ null,
+ ]);
+
+ updateStyles(stylingContext, {'background-image': 'unsafe'});
+
+ expect(stylingContext).toEqual([
+ element,
+ styleSanitizer,
+ [null],
+ dirtyStyle(0, 12), //
+ 2,
+ null,
+
+ // #6
+ dirtyStyleWithSanitization(0, 15),
+ 'border-image',
+ 'url(foo.jpg)',
+
+ // #9
+ dirtyStyle(0, 18),
+ 'border-width',
+ '100px',
+
+ // #12
+ dirtyStyleWithSanitization(0, 0),
+ 'background-image',
+ 'unsafe',
+
+ // #15
+ cleanStyleWithSanitization(0, 6),
+ 'border-image',
+ null,
+
+ // #18
+ cleanStyle(0, 9),
+ 'border-width',
+ null,
+ ]);
+
+ getStyles(stylingContext);
+
+ expect(stylingContext).toEqual([
+ element,
+ styleSanitizer,
+ [null],
+ cleanStyle(0, 12), //
+ 2,
+ null,
+
+ // #6
+ cleanStyleWithSanitization(0, 15),
+ 'border-image',
+ 'url(foo.jpg)',
+
+ // #9
+ cleanStyle(0, 18),
+ 'border-width',
+ '100px',
+
+ // #12
+ cleanStyleWithSanitization(0, 0),
+ 'background-image',
+ 'unsafe',
+
+ // #15
+ cleanStyleWithSanitization(0, 6),
+ 'border-image',
+ null,
+
+ // #18
+ cleanStyle(0, 9),
+ 'border-width',
+ null,
+ ]);
+ });
});
});
@@ -739,20 +883,20 @@ describe('styling', () => {
const template =
initContext(null, [InitialStylingFlags.VALUES_MODE, 'one', true, 'two', true]);
expect(template).toEqual([
- element, [null, true, true], dirtyStyle(0, 11), //
+ element, null, [null, true, true], dirtyStyle(0, 12), //
0, null,
- // #5
- cleanClass(1, 11), 'one', null,
+ // #6
+ cleanClass(1, 12), 'one', null,
- // #8
- cleanClass(2, 14), 'two', null,
+ // #9
+ cleanClass(2, 15), 'two', null,
- // #11
- dirtyClass(1, 5), 'one', null,
+ // #12
+ dirtyClass(1, 6), 'one', null,
- // #14
- dirtyClass(2, 8), 'two', null
+ // #15
+ dirtyClass(2, 9), 'two', null
]);
});
@@ -787,10 +931,10 @@ describe('styling', () => {
const stylingContext = initContext(null, ['guy']);
expect(getClasses(stylingContext)).toEqual({});
- updateStylingMap(stylingContext, null, 'foo bar guy');
+ updateStylingMap(stylingContext, 'foo bar guy');
expect(getClasses(stylingContext)).toEqual({'foo': true, 'bar': true, 'guy': true});
- updateStylingMap(stylingContext, null, 'foo man');
+ updateStylingMap(stylingContext, 'foo man');
updateClassProp(stylingContext, 0, true);
expect(getClasses(stylingContext))
.toEqual({'foo': true, 'man': true, 'bar': false, 'guy': true});
@@ -803,109 +947,111 @@ describe('styling', () => {
const stylingContext = initContext(initialStyles, initialClasses);
expect(stylingContext).toEqual([
element,
+ null,
[null, '100px', true],
- dirtyStyle(0, 17), //
+ dirtyStyle(0, 18), //
2,
null,
- // #5
- cleanStyle(1, 17),
+ // #6
+ cleanStyle(1, 18),
'width',
null,
- // #8
- cleanStyle(0, 20),
+ // #9
+ cleanStyle(0, 21),
'height',
null,
- // #11
- cleanClass(2, 23),
+ // #12
+ cleanClass(2, 24),
'wide',
null,
- // #14
- cleanClass(0, 26),
+ // #15
+ cleanClass(0, 27),
'tall',
null,
- // #17
- dirtyStyle(1, 5),
+ // #18
+ dirtyStyle(1, 6),
'width',
null,
- // #20
- cleanStyle(0, 8),
+ // #21
+ cleanStyle(0, 9),
'height',
null,
- // #23
- dirtyClass(2, 11),
+ // #24
+ dirtyClass(2, 12),
'wide',
null,
- // #26
- cleanClass(0, 14),
+ // #27
+ cleanClass(0, 15),
'tall',
null,
]);
expect(getStylesAndClasses(stylingContext)).toEqual([{width: '100px'}, {wide: true}]);
- updateStylingMap(stylingContext, {width: '200px', opacity: '0.5'}, 'tall round');
+ updateStylingMap(stylingContext, 'tall round', {width: '200px', opacity: '0.5'});
expect(stylingContext).toEqual([
element,
+ null,
[null, '100px', true],
- dirtyStyle(0, 17), //
+ dirtyStyle(0, 18), //
2,
'tall round',
- // #5
- cleanStyle(1, 17),
+ // #6
+ cleanStyle(1, 18),
'width',
null,
- // #8
- cleanStyle(0, 32),
+ // #9
+ cleanStyle(0, 33),
'height',
null,
- // #11
- cleanClass(2, 29),
+ // #12
+ cleanClass(2, 30),
'wide',
null,
- // #14
- cleanClass(0, 23),
+ // #15
+ cleanClass(0, 24),
'tall',
null,
- // #17
- dirtyStyle(1, 5),
+ // #18
+ dirtyStyle(1, 6),
'width',
'200px',
- // #20
+ // #21
dirtyStyle(0, 0),
'opacity',
'0.5',
- // #23
- dirtyClass(0, 14),
+ // #24
+ dirtyClass(0, 15),
'tall',
true,
- // #26
+ // #27
dirtyClass(0, 0),
'round',
true,
- // #29
- cleanClass(2, 11),
+ // #30
+ cleanClass(2, 12),
'wide',
null,
- // #32
- cleanStyle(0, 8),
+ // #33
+ cleanStyle(0, 9),
'height',
null,
]);
@@ -914,63 +1060,64 @@ describe('styling', () => {
{width: '200px', opacity: '0.5'}, {tall: true, round: true, wide: true}
]);
- updateStylingMap(stylingContext, {width: '500px'}, {tall: true, wide: true});
+ updateStylingMap(stylingContext, {tall: true, wide: true}, {width: '500px'});
updateStyleProp(stylingContext, 0, '300px');
expect(stylingContext).toEqual([
element,
+ null,
[null, '100px', true],
- dirtyStyle(0, 17), //
+ dirtyStyle(0, 18), //
2,
null,
- // #5
- dirtyStyle(1, 17),
+ // #6
+ dirtyStyle(1, 18),
'width',
'300px',
- // #8
- cleanStyle(0, 32),
+ // #9
+ cleanStyle(0, 33),
'height',
null,
- // #11
- cleanClass(2, 23),
+ // #12
+ cleanClass(2, 24),
'wide',
null,
- // #14
- cleanClass(0, 20),
+ // #15
+ cleanClass(0, 21),
'tall',
null,
- // #17
- cleanStyle(1, 5),
+ // #18
+ cleanStyle(1, 6),
'width',
'500px',
- // #20
- cleanClass(0, 14),
+ // #21
+ cleanClass(0, 15),
'tall',
true,
- // #23
- cleanClass(2, 11),
+ // #24
+ cleanClass(2, 12),
'wide',
true,
- // #26
+ // #27
dirtyClass(0, 0),
'round',
null,
- // #29
+ // #30
dirtyStyle(0, 0),
'opacity',
null,
- // #32
- cleanStyle(0, 8),
+ // #33
+ cleanStyle(0, 9),
'height',
null,
]);
diff --git a/packages/core/test/sanitization/sanatization_spec.ts b/packages/core/test/sanitization/sanatization_spec.ts
index 6c9f31e8c0..c1663f4aff 100644
--- a/packages/core/test/sanitization/sanatization_spec.ts
+++ b/packages/core/test/sanitization/sanatization_spec.ts
@@ -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');
});
-});
\ No newline at end of file
+});