feat(ivy): bridge compile instructions to include sanitization helpers (#24938)
PR Close #24938
This commit is contained in:
parent
13f3157823
commit
169e9dd2c8
|
@ -48,12 +48,12 @@ export class LargeTableComponent {
|
||||||
{
|
{
|
||||||
if (rf2 & RenderFlags.Create) {
|
if (rf2 & RenderFlags.Create) {
|
||||||
E(0, 'td');
|
E(0, 'td');
|
||||||
s(c0);
|
s(null, c0);
|
||||||
{ T(1); }
|
{ T(1); }
|
||||||
e();
|
e();
|
||||||
}
|
}
|
||||||
if (rf2 & RenderFlags.Update) {
|
if (rf2 & RenderFlags.Update) {
|
||||||
sp(0, 0, cell.row % 2 ? '' : 'grey');
|
sp(0, 0, null, cell.row % 2 ? '' : 'grey');
|
||||||
t(1, b(cell.value));
|
t(1, b(cell.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ export class TreeComponent {
|
||||||
template: function(rf: RenderFlags, ctx: TreeComponent) {
|
template: function(rf: RenderFlags, ctx: TreeComponent) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
E(0, 'span');
|
E(0, 'span');
|
||||||
s(c0);
|
s(null, c0);
|
||||||
{ T(1); }
|
{ T(1); }
|
||||||
e();
|
e();
|
||||||
C(2);
|
C(2);
|
||||||
|
@ -114,7 +114,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) {
|
||||||
E(0, 'tree');
|
E(0, 'tree');
|
||||||
{
|
{
|
||||||
E(1, 'span');
|
E(1, 'span');
|
||||||
s(c1);
|
s(null, c1);
|
||||||
{ T(2); }
|
{ T(2); }
|
||||||
e();
|
e();
|
||||||
C(3);
|
C(3);
|
||||||
|
|
|
@ -53,7 +53,7 @@ describe('compiler compliance', () => {
|
||||||
template: function MyComponent_Template(rf, ctx) {
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, "div", $c1$);
|
$r3$.ɵE(0, "div", $c1$);
|
||||||
$r3$.ɵs(null, $c2$);
|
$r3$.ɵs($c2$);
|
||||||
$r3$.ɵNS();
|
$r3$.ɵNS();
|
||||||
$r3$.ɵE(1, "svg");
|
$r3$.ɵE(1, "svg");
|
||||||
$r3$.ɵEe(2, "circle", $c3$);
|
$r3$.ɵEe(2, "circle", $c3$);
|
||||||
|
@ -103,7 +103,7 @@ describe('compiler compliance', () => {
|
||||||
template: function MyComponent_Template(rf, ctx) {
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, "div", $c1$);
|
$r3$.ɵE(0, "div", $c1$);
|
||||||
$r3$.ɵs(null, $c2$);
|
$r3$.ɵs($c2$);
|
||||||
$r3$.ɵNM();
|
$r3$.ɵNM();
|
||||||
$r3$.ɵE(1, "math");
|
$r3$.ɵE(1, "math");
|
||||||
$r3$.ɵEe(2, "infinity");
|
$r3$.ɵEe(2, "infinity");
|
||||||
|
@ -153,7 +153,7 @@ describe('compiler compliance', () => {
|
||||||
template: function MyComponent_Template(rf, ctx) {
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, "div", $c1$);
|
$r3$.ɵE(0, "div", $c1$);
|
||||||
$r3$.ɵs(null, $c2$);
|
$r3$.ɵs($c2$);
|
||||||
$r3$.ɵT(1, "Hello ");
|
$r3$.ɵT(1, "Hello ");
|
||||||
$r3$.ɵE(2, "b");
|
$r3$.ɵE(2, "b");
|
||||||
$r3$.ɵT(3, "World");
|
$r3$.ɵT(3, "World");
|
||||||
|
@ -329,8 +329,8 @@ describe('compiler compliance', () => {
|
||||||
|
|
||||||
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
|
||||||
const template = `
|
const template = `
|
||||||
const _c0 = ["background-color"];
|
const _c0 = ["error"];
|
||||||
const _c1 = ["error"];
|
const _c1 = ["background-color"];
|
||||||
…
|
…
|
||||||
MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
|
MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
|
||||||
factory:function MyComponent_Factory(){
|
factory:function MyComponent_Factory(){
|
||||||
|
|
|
@ -43,11 +43,11 @@ describe('compiler compliance: styling', () => {
|
||||||
template: function MyComponent_Template(rf, $ctx$) {
|
template: function MyComponent_Template(rf, $ctx$) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, "div");
|
$r3$.ɵE(0, "div");
|
||||||
$r3$.ɵs();
|
$r3$.ɵs(null, null, $r3$.ɵzss);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵsm(0, $ctx$.myStyleExp);
|
$r3$.ɵsm(0, null, $ctx$.myStyleExp);
|
||||||
$r3$.ɵsa(0);
|
$r3$.ɵsa(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,15 +96,15 @@ describe('compiler compliance: styling', () => {
|
||||||
template: function MyComponent_Template(rf, $ctx$) {
|
template: function MyComponent_Template(rf, $ctx$) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, "div");
|
$r3$.ɵE(0, "div");
|
||||||
$r3$.ɵs(_c0);
|
$r3$.ɵs(null, _c0, $r3$.ɵzss);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵsm(0, $ctx$.myStyleExp);
|
$r3$.ɵsm(0, null, $ctx$.myStyleExp);
|
||||||
$r3$.ɵsp(0, 1, $ctx$.myWidth);
|
$r3$.ɵsp(0, 1, $ctx$.myWidth);
|
||||||
$r3$.ɵsp(0, 2, $ctx$.myHeight);
|
$r3$.ɵsp(0, 2, $ctx$.myHeight);
|
||||||
$r3$.ɵsa(0);
|
$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);
|
const result = compile(files, angularFiles);
|
||||||
expectEmit(result.source, template, 'Incorrect template');
|
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]', () => {
|
describe('[class]', () => {
|
||||||
|
@ -144,7 +197,7 @@ describe('compiler compliance: styling', () => {
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵsm(0,null,$ctx$.myClassExp);
|
$r3$.ɵsm(0,$ctx$.myClassExp);
|
||||||
$r3$.ɵsa(0);
|
$r3$.ɵsa(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,11 +246,11 @@ describe('compiler compliance: styling', () => {
|
||||||
template: function MyComponent_Template(rf, $ctx$) {
|
template: function MyComponent_Template(rf, $ctx$) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, "div");
|
$r3$.ɵE(0, "div");
|
||||||
$r3$.ɵs(null, _c0);
|
$r3$.ɵs(_c0);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵsm(0, null, $ctx$.myClassExp);
|
$r3$.ɵsm(0, $ctx$.myClassExp);
|
||||||
$r3$.ɵcp(0, 1, $ctx$.yesToApple);
|
$r3$.ɵcp(0, 1, $ctx$.yesToApple);
|
||||||
$r3$.ɵcp(0, 2, $ctx$.yesToOrange);
|
$r3$.ɵcp(0, 2, $ctx$.yesToOrange);
|
||||||
$r3$.ɵsa(0);
|
$r3$.ɵsa(0);
|
||||||
|
@ -234,8 +287,8 @@ describe('compiler compliance: styling', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
const _c0 = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
|
const _c0 = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
|
||||||
const _c1 = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
|
const _c1 = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
|
||||||
…
|
…
|
||||||
MyComponent.ngComponentDef = i0.ɵdefineComponent({
|
MyComponent.ngComponentDef = i0.ɵdefineComponent({
|
||||||
type: MyComponent,
|
type: MyComponent,
|
||||||
|
@ -251,7 +304,7 @@ describe('compiler compliance: styling', () => {
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵa(0, "class", $r3$.ɵb("round"));
|
$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
|
// Reserve slots for pure functions
|
||||||
static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE};
|
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};
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,10 +368,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let hasMapBasedStyling = false;
|
||||||
for (let i = 0; i < styleInputs.length; i++) {
|
for (let i = 0; i < styleInputs.length; i++) {
|
||||||
const input = styleInputs[i];
|
const input = styleInputs[i];
|
||||||
const isMapBasedStyleBinding = i === 0 && input.name === 'style';
|
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++;
|
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
|
// this will build the instructions so that they fall into the following syntax
|
||||||
// => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2]
|
// => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2]
|
||||||
Object.keys(stylesIndexMap).forEach(prop => {
|
Object.keys(stylesIndexMap).forEach(prop => {
|
||||||
|
useDefaultStyleSanitizer = useDefaultStyleSanitizer || isStyleSanitizable(prop);
|
||||||
initialStyleDeclarations.push(o.literal(prop));
|
initialStyleDeclarations.push(o.literal(prop));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -473,18 +483,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
if (hasStylingInstructions) {
|
if (hasStylingInstructions) {
|
||||||
const paramsList: (o.Expression)[] = [];
|
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) {
|
if (initialClassDeclarations.length) {
|
||||||
// the template compiler handles initial class styling (e.g. class="foo") values
|
// the template compiler handles initial class styling (e.g. class="foo") values
|
||||||
// in a special command called `elementClass` so that the initial class
|
// 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).
|
// a constant because the inital class values do not change (since they're static).
|
||||||
paramsList.push(
|
paramsList.push(
|
||||||
this.constantPool.getConstLiteral(o.literalArr(initialClassDeclarations), true));
|
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());
|
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;
|
const stylingInput = mapBasedStyleInput || mapBasedClassInput;
|
||||||
if (stylingInput) {
|
if (stylingInput) {
|
||||||
const params: o.Expression[] = [];
|
const params: o.Expression[] = [];
|
||||||
if (mapBasedStyleInput) {
|
|
||||||
params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true));
|
|
||||||
} else if (mapBasedClassInput) {
|
|
||||||
params.push(o.NULL_EXPR);
|
|
||||||
}
|
|
||||||
if (mapBasedClassInput) {
|
if (mapBasedClassInput) {
|
||||||
params.push(this.convertPropertyBinding(implicit, mapBasedClassInput.value, true));
|
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.instruction(
|
||||||
this._bindingCode, stylingInput.sourceSpan, R3.elementStylingMap, indexLiteral,
|
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++) {
|
for (i; i < styleInputs.length; i++) {
|
||||||
const input = styleInputs[i];
|
const input = styleInputs[i];
|
||||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
|
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 key = input.name;
|
||||||
const styleIndex: number = stylesIndexMap[key] !;
|
const styleIndex: number = stylesIndexMap[key] !;
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral,
|
this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral,
|
||||||
o.literal(styleIndex), convertedBinding);
|
o.literal(styleIndex), ...params);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastInputCommand = styleInputs[styleInputs.length - 1];
|
lastInputCommand = styleInputs[styleInputs.length - 1];
|
||||||
|
@ -566,11 +590,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
for (i; i < classInputs.length; i++) {
|
for (i; i < classInputs.length; i++) {
|
||||||
const input = classInputs[i];
|
const input = classInputs[i];
|
||||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
|
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 key = input.name;
|
||||||
const classIndex: number = classesIndexMap[key] !;
|
const classIndex: number = classesIndexMap[key] !;
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral,
|
this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral,
|
||||||
o.literal(classIndex), convertedBinding);
|
o.literal(classIndex), ...params);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastInputCommand = classInputs[classInputs.length - 1];
|
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 convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||||
|
|
||||||
const instruction = mapBindingToInstruction(input.type);
|
const instruction = mapBindingToInstruction(input.type);
|
||||||
if (instruction) {
|
if (instruction) {
|
||||||
|
const params = [convertedBinding];
|
||||||
|
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
|
||||||
|
if (sanitizationRef) {
|
||||||
|
params.push(sanitizationRef);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(chuckj): runtime: security context?
|
// TODO(chuckj): runtime: security context?
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
|
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||||
o.literal(input.name), convertedBinding);
|
o.literal(input.name), ...params);
|
||||||
} else {
|
} else {
|
||||||
this._unsupported(`binding type ${input.type}`);
|
this._unsupported(`binding type ${input.type}`);
|
||||||
}
|
}
|
||||||
|
@ -1061,3 +1098,36 @@ export function makeBindingParser(): BindingParser {
|
||||||
function isClassBinding(input: t.BoundAttribute): boolean {
|
function isClassBinding(input: t.BoundAttribute): boolean {
|
||||||
return input.name == 'className' || input.name == 'class';
|
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';
|
} from './render3/index';
|
||||||
export {NgModuleDef as ɵNgModuleDef} from './metadata/ng_module';
|
export {NgModuleDef as ɵNgModuleDef} from './metadata/ng_module';
|
||||||
export {
|
export {
|
||||||
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
|
|
||||||
bypassSanitizationTrustStyle as ɵbypassSanitizationTrustStyle,
|
|
||||||
bypassSanitizationTrustScript as ɵbypassSanitizationTrustScript,
|
|
||||||
bypassSanitizationTrustUrl as ɵbypassSanitizationTrustUrl,
|
|
||||||
bypassSanitizationTrustResourceUrl as ɵbypassSanitizationTrustResourceUrl,
|
|
||||||
sanitizeHtml as ɵsanitizeHtml,
|
sanitizeHtml as ɵsanitizeHtml,
|
||||||
sanitizeStyle as ɵsanitizeStyle,
|
sanitizeStyle as ɵsanitizeStyle,
|
||||||
sanitizeUrl as ɵsanitizeUrl,
|
sanitizeUrl as ɵsanitizeUrl,
|
||||||
sanitizeResourceUrl as ɵsanitizeResourceUrl,
|
sanitizeResourceUrl as ɵsanitizeResourceUrl,
|
||||||
} from './sanitization/sanitization';
|
} 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
|
// clang-format on
|
||||||
|
|
|
@ -10,6 +10,7 @@ import './ng_dev_mode';
|
||||||
|
|
||||||
import {QueryList} from '../linker';
|
import {QueryList} from '../linker';
|
||||||
import {Sanitizer} from '../sanitization/security';
|
import {Sanitizer} from '../sanitization/security';
|
||||||
|
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||||
|
|
||||||
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual} from './assert';
|
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual} from './assert';
|
||||||
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
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 {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||||
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
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 {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
|
||||||
import {ViewRef} from './view_ref';
|
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
|
* (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
|
* specifically for element styling--the index must be the next index after the element
|
||||||
* index.)
|
* 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.
|
* @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
|
* 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]`)
|
* 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
|
* 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).
|
* 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>(
|
export function elementStyling<T>(
|
||||||
|
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||||
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||||
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null): void {
|
styleSanitizer?: StyleSanitizeFn | null): void {
|
||||||
const lElement = currentElementNode !;
|
const lElement = currentElementNode !;
|
||||||
const tNode = lElement.tNode;
|
const tNode = lElement.tNode;
|
||||||
if (!tNode.stylingTemplate) {
|
if (!tNode.stylingTemplate) {
|
||||||
// initialize the styling template.
|
// initialize the styling template.
|
||||||
tNode.stylingTemplate = createStylingContextTemplate(styleDeclarations, classDeclarations);
|
tNode.stylingTemplate =
|
||||||
|
createStylingContextTemplate(classDeclarations, styleDeclarations, styleSanitizer);
|
||||||
}
|
}
|
||||||
if (styleDeclarations && styleDeclarations.length ||
|
if (styleDeclarations && styleDeclarations.length ||
|
||||||
classDeclarations && classDeclarations.length) {
|
classDeclarations && classDeclarations.length) {
|
||||||
|
@ -1377,22 +1382,23 @@ export function elementStylingApply<T>(index: number): void {
|
||||||
* renaming as part of minification.
|
* renaming as part of minification.
|
||||||
* @param value New value to write (null to remove).
|
* @param value New value to write (null to remove).
|
||||||
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
|
* @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
|
* Note that when a suffix is provided then the underlying sanitizer will
|
||||||
* sanitization.
|
* be ignored.
|
||||||
*/
|
*/
|
||||||
export function elementStyleProp<T>(
|
export function elementStyleProp<T>(
|
||||||
index: number, styleIndex: number, value: T | null, suffix?: string): void;
|
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 {
|
|
||||||
let valueToAdd: string|null = null;
|
let valueToAdd: string|null = null;
|
||||||
if (value) {
|
if (value) {
|
||||||
valueToAdd =
|
if (suffix) {
|
||||||
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
|
// when a suffix is applied then it will bypass
|
||||||
if (typeof suffixOrSanitizer == 'string') {
|
// sanitization entirely (b/c a new string is created)
|
||||||
valueToAdd = valueToAdd + suffixOrSanitizer;
|
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);
|
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
|
* (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
|
* specifically for element styling--the index must be the next index after the element
|
||||||
* index.)
|
* 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.
|
* @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
|
* Any missing classes (that have already been applied to the element beforehand) will be
|
||||||
* removed (unset) from the element's list of CSS classes.
|
* 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>(
|
export function elementStylingMap<T>(
|
||||||
index: number, styles: {[styleName: string]: any} | null,
|
index: number, classes: {[key: string]: any} | string | null,
|
||||||
classes?: {[key: string]: any} | string | null): void {
|
styles?: {[styleName: string]: any} | null): void {
|
||||||
updateStylingMap(getStylingContext(index), styles, classes);
|
updateStylingMap(getStylingContext(index), classes, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {defineInjectable, defineInjector,} from '../../di/defs';
|
import {defineInjectable, defineInjector,} from '../../di/defs';
|
||||||
import {inject} from '../../di/injector';
|
import {inject} from '../../di/injector';
|
||||||
import * as r3 from '../index';
|
import * as r3 from '../index';
|
||||||
|
import * as sanitization from '../../sanitization/sanitization';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,4 +89,11 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||||
'ɵt': r3.t,
|
'ɵt': r3.t,
|
||||||
'ɵV': r3.V,
|
'ɵV': r3.V,
|
||||||
'ɵv': r3.v,
|
'ɵv': r3.v,
|
||||||
|
|
||||||
|
'ɵzh': sanitization.sanitizeHtml,
|
||||||
|
'ɵzs': sanitization.sanitizeStyle,
|
||||||
|
'ɵzss': sanitization.defaultStyleSanitizer,
|
||||||
|
'ɵzr': sanitization.sanitizeResourceUrl,
|
||||||
|
'ɵzc': sanitization.sanitizeScript,
|
||||||
|
'ɵzu': sanitization.sanitizeUrl
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,10 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||||
import {InitialStylingFlags} from './interfaces/definition';
|
import {InitialStylingFlags} from './interfaces/definition';
|
||||||
import {LElementNode} from './interfaces/node';
|
import {LElementNode} from './interfaces/node';
|
||||||
import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The styling context acts as a styling manifest (shaped as an array) for determining which
|
* 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`
|
* styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp`
|
||||||
|
@ -51,42 +53,44 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* context = [
|
* context = [
|
||||||
|
* element,
|
||||||
|
* styleSanitizer | null,
|
||||||
* [null, '100px', '200px', true], // property names are not needed since they have already been
|
* [null, '100px', '200px', true], // property names are not needed since they have already been
|
||||||
* written to DOM.
|
* written to DOM.
|
||||||
*
|
*
|
||||||
|
* configMasterVal,
|
||||||
* 1, // this instructs how many `style` values there are so that class index values can be
|
* 1, // this instructs how many `style` values there are so that class index values can be
|
||||||
* offsetted
|
* offsetted
|
||||||
*
|
* 'last class string applied',
|
||||||
* configMasterVal,
|
|
||||||
*
|
|
||||||
* // 3
|
|
||||||
* 'width',
|
|
||||||
* pointers(1, 12); // Point to static `width`: `100px` and multi `width`.
|
|
||||||
* null,
|
|
||||||
*
|
*
|
||||||
* // 6
|
* // 6
|
||||||
* 'height',
|
* 'width',
|
||||||
* pointers(2, 15); // Point to static `height`: `200px` and multi `height`.
|
* pointers(1, 15); // Point to static `width`: `100px` and multi `width`.
|
||||||
* null,
|
* null,
|
||||||
*
|
*
|
||||||
* // 9
|
* // 9
|
||||||
* 'foo',
|
* 'height',
|
||||||
* pointers(1, 18); // Point to static `foo`: `true` and multi `foo`.
|
* pointers(2, 18); // Point to static `height`: `200px` and multi `height`.
|
||||||
* null,
|
* null,
|
||||||
*
|
*
|
||||||
* // 12
|
* // 12
|
||||||
* 'width',
|
* 'foo',
|
||||||
* pointers(1, 3); // Point to static `width`: `100px` and single `width`.
|
* pointers(1, 21); // Point to static `foo`: `true` and multi `foo`.
|
||||||
* null,
|
* null,
|
||||||
*
|
*
|
||||||
* // 15
|
* // 15
|
||||||
* 'height',
|
* 'width',
|
||||||
* pointers(2, 6); // Point to static `height`: `200px` and single `height`.
|
* pointers(1, 6); // Point to static `width`: `100px` and single `width`.
|
||||||
* null,
|
* null,
|
||||||
*
|
*
|
||||||
* // 18
|
* // 18
|
||||||
|
* 'height',
|
||||||
|
* pointers(2, 9); // Point to static `height`: `200px` and single `height`.
|
||||||
|
* null,
|
||||||
|
*
|
||||||
|
* // 21
|
||||||
* 'foo',
|
* 'foo',
|
||||||
* pointers(3, 9); // Point to static `foo`: `true` and single `foo`.
|
* pointers(3, 12); // Point to static `foo`: `true` and single `foo`.
|
||||||
* null,
|
* null,
|
||||||
* ]
|
* ]
|
||||||
*
|
*
|
||||||
|
@ -111,36 +115,41 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces
|
||||||
* `updateStylingMap` can include new CSS properties that will be added to the context).
|
* `updateStylingMap` can include new CSS properties that will be added to the context).
|
||||||
*/
|
*/
|
||||||
export interface StylingContext extends
|
export interface StylingContext extends
|
||||||
Array<InitialStyles|number|string|boolean|LElementNode|null> {
|
Array<InitialStyles|number|string|boolean|LElementNode|StyleSanitizeFn|null> {
|
||||||
/**
|
/**
|
||||||
* Location of element that is used as a target for this context.
|
* Location of element that is used as a target for this context.
|
||||||
*/
|
*/
|
||||||
[0]: LElementNode|null;
|
[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.
|
* 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)
|
* 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
|
* mixed together (using bit shifting) with a index value which tells the starting index value
|
||||||
* of where the multi style entries begin.
|
* 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
|
* 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
|
* 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.
|
* 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
|
* 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.
|
* So that the algorithm can exit early incase the string has not changed.
|
||||||
*/
|
*/
|
||||||
[4]: string|null;
|
[5]: string|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,31 +168,35 @@ export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
|
||||||
*/
|
*/
|
||||||
export const enum StylingFlags {
|
export const enum StylingFlags {
|
||||||
// Implies no configurations
|
// Implies no configurations
|
||||||
None = 0b00,
|
None = 0b000,
|
||||||
// Whether or not the entry or context itself is dirty
|
// Whether or not the entry or context itself is dirty
|
||||||
Dirty = 0b01,
|
Dirty = 0b001,
|
||||||
// Whether or not this is a class-based assignment
|
// 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
|
// The max amount of bits used to represent these configuration values
|
||||||
BitCountSize = 2,
|
BitCountSize = 3,
|
||||||
// There are only two bits here
|
// There are only three bits here
|
||||||
BitMask = 0b11
|
BitMask = 0b111
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
|
/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
|
||||||
export const enum StylingIndex {
|
export const enum StylingIndex {
|
||||||
// Position of where the initial styles are stored in the styling context
|
// Position of where the initial styles are stored in the styling context
|
||||||
ElementPosition = 0,
|
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
|
// 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`)
|
// 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
|
// 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
|
// 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
|
// 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
|
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
|
||||||
FlagsOffset = 0,
|
FlagsOffset = 0,
|
||||||
PropertyOffset = 1,
|
PropertyOffset = 1,
|
||||||
|
@ -191,9 +204,9 @@ export const enum StylingIndex {
|
||||||
// Size of each multi or single entry (flag + prop + value)
|
// Size of each multi or single entry (flag + prop + value)
|
||||||
Size = 3,
|
Size = 3,
|
||||||
// Each flag has a binary digit length of this value
|
// 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
|
// 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
|
* class will be applied to the element as an initial class since it's true
|
||||||
*/
|
*/
|
||||||
export function createStylingContextTemplate(
|
export function createStylingContextTemplate(
|
||||||
|
initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||||
initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||||
initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null): StylingContext {
|
styleSanitizer?: StyleSanitizeFn | null): StylingContext {
|
||||||
const initialStylingValues: InitialStyles = [null];
|
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
|
// we use two maps since a class name might collide with a CSS style prop
|
||||||
const stylesLookup: {[key: string]: number} = {};
|
const stylesLookup: {[key: string]: number} = {};
|
||||||
|
@ -314,7 +328,7 @@ export function createStylingContextTemplate(
|
||||||
|
|
||||||
const indexForMulti = i * StylingIndex.Size + multiStart;
|
const indexForMulti = i * StylingIndex.Size + multiStart;
|
||||||
const indexForSingle = i * StylingIndex.Size + singleStart;
|
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));
|
setFlag(context, indexForSingle, pointers(initialFlag, indexForInitial, indexForMulti));
|
||||||
setProp(context, indexForSingle, prop);
|
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
|
* @param context The styling context that will be updated with the
|
||||||
* newly provided style values.
|
* 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 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(
|
export function updateStylingMap(
|
||||||
context: StylingContext, styles: {[key: string]: any} | null,
|
context: StylingContext, classes: {[key: string]: any} | string | null,
|
||||||
classes?: {[key: string]: any} | string | null): void {
|
styles?: {[key: string]: any} | null): void {
|
||||||
let classNames: string[] = EMPTY_ARR;
|
let classNames: string[] = EMPTY_ARR;
|
||||||
let applyAllClasses = false;
|
let applyAllClasses = false;
|
||||||
let ignoreAllClassUpdates = false;
|
let ignoreAllClassUpdates = false;
|
||||||
|
@ -407,10 +421,10 @@ export function updateStylingMap(
|
||||||
const prop = getProp(context, ctxIndex);
|
const prop = getProp(context, ctxIndex);
|
||||||
if (prop === newProp) {
|
if (prop === newProp) {
|
||||||
const value = getValue(context, ctxIndex);
|
const value = getValue(context, ctxIndex);
|
||||||
if (value !== newValue) {
|
const flag = getPointers(context, ctxIndex);
|
||||||
|
if (hasValueChanged(flag, value, newValue)) {
|
||||||
setValue(context, ctxIndex, newValue);
|
setValue(context, ctxIndex, newValue);
|
||||||
|
|
||||||
const flag = getPointers(context, ctxIndex);
|
|
||||||
const initialValue = getInitialValue(context, flag);
|
const initialValue = getInitialValue(context, flag);
|
||||||
|
|
||||||
// there is no point in setting this to dirty if the previously
|
// there is no point in setting this to dirty if the previously
|
||||||
|
@ -437,7 +451,8 @@ export function updateStylingMap(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we only care to do this if the insertion is in the middle
|
// 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;
|
dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,6 +483,7 @@ export function updateStylingMap(
|
||||||
// this means that there are left-over properties in the context that
|
// this means that there are left-over properties in the context that
|
||||||
// were not detected in the context during the loop above. In that
|
// were not detected in the context during the loop above. In that
|
||||||
// case we want to add the new entries into the list
|
// case we want to add the new entries into the list
|
||||||
|
const sanitizer = getStyleSanitizer(context);
|
||||||
while (propIndex < propLimit) {
|
while (propIndex < propLimit) {
|
||||||
const isClassBased = propIndex >= classesStartIndex;
|
const isClassBased = propIndex >= classesStartIndex;
|
||||||
if (ignoreAllClassUpdates && isClassBased) break;
|
if (ignoreAllClassUpdates && isClassBased) break;
|
||||||
|
@ -476,7 +492,7 @@ export function updateStylingMap(
|
||||||
const prop = isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex];
|
const prop = isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex];
|
||||||
const value: string|boolean =
|
const value: string|boolean =
|
||||||
isClassBased ? (applyAllClasses ? true : classes[prop]) : styles[prop];
|
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);
|
context.push(flag, prop, value);
|
||||||
propIndex++;
|
propIndex++;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
@ -508,7 +524,7 @@ export function updateStyleProp(
|
||||||
const currFlag = getPointers(context, singleIndex);
|
const currFlag = getPointers(context, singleIndex);
|
||||||
|
|
||||||
// didn't change ... nothing to make a note of
|
// 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)
|
// the value will always get updated (even if the dirty flag is skipped)
|
||||||
setValue(context, singleIndex, value);
|
setValue(context, singleIndex, value);
|
||||||
const indexForMulti = getMultiOrSingleIndex(currFlag);
|
const indexForMulti = getMultiOrSingleIndex(currFlag);
|
||||||
|
@ -573,6 +589,7 @@ export function renderStyling(
|
||||||
if (isContextDirty(context)) {
|
if (isContextDirty(context)) {
|
||||||
const native = context[StylingIndex.ElementPosition] !.native;
|
const native = context[StylingIndex.ElementPosition] !.native;
|
||||||
const multiStartIndex = getMultiStartIndex(context);
|
const multiStartIndex = getMultiStartIndex(context);
|
||||||
|
const styleSanitizer = getStyleSanitizer(context);
|
||||||
for (let i = StylingIndex.SingleStylesStartPosition; i < context.length;
|
for (let i = StylingIndex.SingleStylesStartPosition; i < context.length;
|
||||||
i += StylingIndex.Size) {
|
i += StylingIndex.Size) {
|
||||||
// there is no point in rendering styles that have not changed on screen
|
// there is no point in rendering styles that have not changed on screen
|
||||||
|
@ -607,7 +624,8 @@ export function renderStyling(
|
||||||
if (isClassBased) {
|
if (isClassBased) {
|
||||||
setClass(native, prop, valueToApply ? true : false, renderer, classStore);
|
setClass(native, prop, valueToApply ? true : false, renderer, classStore);
|
||||||
} else {
|
} 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);
|
setDirty(context, i, false);
|
||||||
}
|
}
|
||||||
|
@ -631,7 +649,8 @@ export function renderStyling(
|
||||||
*/
|
*/
|
||||||
function setStyle(
|
function setStyle(
|
||||||
native: any, prop: string, value: string | null, renderer: Renderer3,
|
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) {
|
if (store) {
|
||||||
store[prop] = value;
|
store[prop] = value;
|
||||||
} else if (value) {
|
} else if (value) {
|
||||||
|
@ -697,6 +716,12 @@ function isClassBased(context: StylingContext, index: number): boolean {
|
||||||
return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class;
|
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) {
|
function pointers(configFlag: number, staticIndex: number, dynamicIndex: number) {
|
||||||
return (configFlag & StylingFlags.BitMask) | (staticIndex << StylingFlags.BitCountSize) |
|
return (configFlag & StylingFlags.BitMask) | (staticIndex << StylingFlags.BitCountSize) |
|
||||||
(dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize));
|
(dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize));
|
||||||
|
@ -721,6 +746,10 @@ function getMultiStartIndex(context: StylingContext): number {
|
||||||
return getMultiOrSingleIndex(context[StylingIndex.MasterFlagPosition]) as 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) {
|
function setProp(context: StylingContext, index: number, prop: string) {
|
||||||
context[index + StylingIndex.PropertyOffset] = prop;
|
context[index + StylingIndex.PropertyOffset] = prop;
|
||||||
}
|
}
|
||||||
|
@ -808,7 +837,8 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition:
|
||||||
const singleFlag = getPointers(context, singleIndex);
|
const singleFlag = getPointers(context, singleIndex);
|
||||||
const initialIndexForSingle = getInitialIndex(singleFlag);
|
const initialIndexForSingle = getInitialIndex(singleFlag);
|
||||||
const flagValue = (isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None) |
|
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);
|
const updatedFlag = pointers(flagValue, initialIndexForSingle, i);
|
||||||
setFlag(context, singleIndex, updatedFlag);
|
setFlag(context, singleIndex, updatedFlag);
|
||||||
}
|
}
|
||||||
|
@ -816,14 +846,14 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition:
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertNewMultiProperty(
|
function insertNewMultiProperty(
|
||||||
context: StylingContext, index: number, classBased: boolean, name: string,
|
context: StylingContext, index: number, classBased: boolean, name: string, flag: number,
|
||||||
value: string | boolean): void {
|
value: string | boolean): void {
|
||||||
const doShift = index < context.length;
|
const doShift = index < context.length;
|
||||||
|
|
||||||
// prop does not exist in the list, add it in
|
// prop does not exist in the list, add it in
|
||||||
context.splice(
|
context.splice(
|
||||||
index, 0, StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), name,
|
index, 0, flag | StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None),
|
||||||
value);
|
name, value);
|
||||||
|
|
||||||
if (doShift) {
|
if (doShift) {
|
||||||
// because the value was inserted midway into the array then we
|
// 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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -9,63 +9,13 @@
|
||||||
import {getCurrentSanitizer} from '../render3/instructions';
|
import {getCurrentSanitizer} from '../render3/instructions';
|
||||||
import {stringify} from '../render3/util';
|
import {stringify} from '../render3/util';
|
||||||
|
|
||||||
|
import {BypassType, allowSanitizationBypass} from './bypass';
|
||||||
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
||||||
import {SecurityContext} from './security';
|
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';
|
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
|
* 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) {
|
if (s) {
|
||||||
return s.sanitize(SecurityContext.HTML, unsafeHtml) || '';
|
return s.sanitize(SecurityContext.HTML, unsafeHtml) || '';
|
||||||
}
|
}
|
||||||
if (unsafeHtml instanceof String && (unsafeHtml as TrustedHtmlString)[BRAND] === 'Html') {
|
if (allowSanitizationBypass(unsafeHtml, BypassType.Html)) {
|
||||||
return unsafeHtml.toString();
|
return unsafeHtml.toString();
|
||||||
}
|
}
|
||||||
return _sanitizeHtml(document, stringify(unsafeHtml));
|
return _sanitizeHtml(document, stringify(unsafeHtml));
|
||||||
|
@ -109,7 +59,7 @@ export function sanitizeStyle(unsafeStyle: any): string {
|
||||||
if (s) {
|
if (s) {
|
||||||
return s.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
|
return s.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
|
||||||
}
|
}
|
||||||
if (unsafeStyle instanceof String && (unsafeStyle as TrustedStyleString)[BRAND] === 'Style') {
|
if (allowSanitizationBypass(unsafeStyle, BypassType.Style)) {
|
||||||
return unsafeStyle.toString();
|
return unsafeStyle.toString();
|
||||||
}
|
}
|
||||||
return _sanitizeStyle(stringify(unsafeStyle));
|
return _sanitizeStyle(stringify(unsafeStyle));
|
||||||
|
@ -134,7 +84,7 @@ export function sanitizeUrl(unsafeUrl: any): string {
|
||||||
if (s) {
|
if (s) {
|
||||||
return s.sanitize(SecurityContext.URL, unsafeUrl) || '';
|
return s.sanitize(SecurityContext.URL, unsafeUrl) || '';
|
||||||
}
|
}
|
||||||
if (unsafeUrl instanceof String && (unsafeUrl as TrustedUrlString)[BRAND] === 'Url') {
|
if (allowSanitizationBypass(unsafeUrl, BypassType.Url)) {
|
||||||
return unsafeUrl.toString();
|
return unsafeUrl.toString();
|
||||||
}
|
}
|
||||||
return _sanitizeUrl(stringify(unsafeUrl));
|
return _sanitizeUrl(stringify(unsafeUrl));
|
||||||
|
@ -154,8 +104,7 @@ export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
|
||||||
if (s) {
|
if (s) {
|
||||||
return s.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '';
|
return s.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '';
|
||||||
}
|
}
|
||||||
if (unsafeResourceUrl instanceof String &&
|
if (allowSanitizationBypass(unsafeResourceUrl, BypassType.ResourceUrl)) {
|
||||||
(unsafeResourceUrl as TrustedResourceUrlString)[BRAND] === 'ResourceUrl') {
|
|
||||||
return unsafeResourceUrl.toString();
|
return unsafeResourceUrl.toString();
|
||||||
}
|
}
|
||||||
throw new Error('unsafe value used in a resource URL context (see http://g.co/ng/security#xss)');
|
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) {
|
if (s) {
|
||||||
return s.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
|
return s.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
|
||||||
}
|
}
|
||||||
if (unsafeScript instanceof String && (unsafeScript as TrustedScriptString)[BRAND] === 'Script') {
|
if (allowSanitizationBypass(unsafeScript, BypassType.Script)) {
|
||||||
return unsafeScript.toString();
|
return unsafeScript.toString();
|
||||||
}
|
}
|
||||||
throw new Error('unsafe value used in a script context');
|
throw new Error('unsafe value used in a script context');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark `html` string as 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)
|
||||||
* 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 {
|
export const defaultStyleSanitizer = (function(prop: string, value?: string): string | boolean {
|
||||||
return bypassSanitizationTrustString(trustedHtml, 'Html');
|
if (value === undefined) {
|
||||||
}
|
return prop === 'background-image' || prop === 'background' || prop === 'border-image' ||
|
||||||
/**
|
prop === 'filter' || prop === 'filter' || prop === 'list-style' ||
|
||||||
* Mark `style` string as trusted.
|
prop === 'list-style-image';
|
||||||
*
|
}
|
||||||
* 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');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return sanitizeStyle(value);
|
||||||
function bypassSanitizationTrustString(trustedString: string, mode: 'Html'): TrustedHtmlString;
|
} as StyleSanitizeFn);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -101,3 +101,19 @@ export function _sanitizeStyle(value: string): string {
|
||||||
|
|
||||||
return 'unsafe';
|
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": "getRootView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getStyleSanitizer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getStylingContext"
|
"name": "getStylingContext"
|
||||||
},
|
},
|
||||||
|
@ -557,6 +560,9 @@
|
||||||
{
|
{
|
||||||
"name": "getValue"
|
"name": "getValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "hasValueChanged"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "hostElement"
|
"name": "hostElement"
|
||||||
},
|
},
|
||||||
|
@ -668,6 +674,9 @@
|
||||||
{
|
{
|
||||||
"name": "pointers"
|
"name": "pointers"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "prepareInitialFlag"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "projectionNodeStack"
|
"name": "projectionNodeStack"
|
||||||
},
|
},
|
||||||
|
|
|
@ -284,7 +284,7 @@ describe('elements', () => {
|
||||||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'div');
|
$r3$.ɵE(0, 'div');
|
||||||
$r3$.ɵs(null, c1);
|
$r3$.ɵs(c1);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
|
@ -323,7 +323,7 @@ describe('elements', () => {
|
||||||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵE(0, 'div');
|
$r3$.ɵE(0, 'div');
|
||||||
$r3$.ɵs(c0);
|
$r3$.ɵs(null, c0);
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
|
@ -356,8 +356,8 @@ describe('elements', () => {
|
||||||
it('should bind to many and keep order', () => {
|
it('should bind to many and keep order', () => {
|
||||||
type $MyComponent$ = MyComponent;
|
type $MyComponent$ = MyComponent;
|
||||||
|
|
||||||
const c0 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red'];
|
const c0 = ['foo'];
|
||||||
const c1 = ['foo'];
|
const c1 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red'];
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-component',
|
selector: 'my-component',
|
||||||
|
@ -416,7 +416,7 @@ describe('elements', () => {
|
||||||
$r3$.ɵe();
|
$r3$.ɵe();
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵsm(0, ctx.styleExp, ctx.classExp);
|
$r3$.ɵsm(0, ctx.classExp, ctx.styleExp);
|
||||||
$r3$.ɵsa(0);
|
$r3$.ɵsa(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe('compiler sanitization', () => {
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵsanitizeHtml);
|
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵsanitizeHtml);
|
||||||
$r3$.ɵp(0, 'hidden', $r3$.ɵb(ctx.hidden));
|
$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$.ɵsa(0);
|
||||||
$r3$.ɵp(1, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
$r3$.ɵp(1, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
||||||
$r3$.ɵa(1, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
$r3$.ɵa(1, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl);
|
||||||
|
|
|
@ -213,7 +213,7 @@ describe('exports', () => {
|
||||||
function Template(rf: RenderFlags, ctx: any) {
|
function Template(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'div');
|
elementStart(0, 'div');
|
||||||
elementStyling(null, [InitialStylingFlags.VALUES_MODE, 'red', true]);
|
elementStyling([InitialStylingFlags.VALUES_MODE, 'red', true]);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
|
elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
|
|
|
@ -14,8 +14,10 @@ import {bind, container, element, elementAttribute, elementEnd, elementProperty,
|
||||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node';
|
import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node';
|
||||||
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
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 {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
||||||
|
import {StyleSanitizeFn} from '../../src/sanitization/style_sanitizer';
|
||||||
|
|
||||||
import {NgForOf} from './common_with_def';
|
import {NgForOf} from './common_with_def';
|
||||||
import {ComponentFixture, TemplateFixture} from './render_util';
|
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||||
|
@ -27,9 +29,10 @@ describe('instructions', () => {
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDiv(initialStyles?: (string | number)[]) {
|
function createDiv(initialStyles?: (string | number)[], styleSanitizer?: StyleSanitizeFn) {
|
||||||
elementStart(0, 'div');
|
elementStart(0, 'div');
|
||||||
elementStyling(initialStyles && Array.isArray(initialStyles) ? initialStyles : null);
|
elementStyling(
|
||||||
|
[], initialStyles && Array.isArray(initialStyles) ? initialStyles : null, styleSanitizer);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,39 +193,87 @@ describe('instructions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('elementStyleProp', () => {
|
describe('elementStyleProp', () => {
|
||||||
it('should use sanitizer function', () => {
|
it('should automatically sanitize unless a bypass operation is applied', () => {
|
||||||
const t = new TemplateFixture(() => { return createDiv(['background-image']); });
|
const t = new TemplateFixture(
|
||||||
|
() => { return createDiv(['background-image'], defaultStyleSanitizer); });
|
||||||
t.update(() => {
|
t.update(() => {
|
||||||
elementStyleProp(0, 0, 'url("http://server")', sanitizeStyle);
|
elementStyleProp(0, 0, 'url("http://server")');
|
||||||
elementStylingApply(0);
|
elementStylingApply(0);
|
||||||
});
|
});
|
||||||
// nothing is set because sanitizer suppresses it.
|
// nothing is set because sanitizer suppresses it.
|
||||||
expect(t.html).toEqual('<div></div>');
|
expect(t.html).toEqual('<div></div>');
|
||||||
|
|
||||||
t.update(() => {
|
t.update(() => {
|
||||||
elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle);
|
elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server2")'));
|
||||||
elementStylingApply(0);
|
elementStylingApply(0);
|
||||||
});
|
});
|
||||||
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
|
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', () => {
|
describe('elementStyleMap', () => {
|
||||||
function createDivWithStyle() {
|
function createDivWithStyle() {
|
||||||
elementStart(0, 'div');
|
elementStart(0, 'div');
|
||||||
elementStyling(['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
|
elementStyling([], ['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should add style', () => {
|
it('should add style', () => {
|
||||||
const fixture = new TemplateFixture(createDivWithStyle);
|
const fixture = new TemplateFixture(createDivWithStyle);
|
||||||
fixture.update(() => {
|
fixture.update(() => {
|
||||||
elementStylingMap(0, {'background-color': 'red'});
|
elementStylingMap(0, null, {'background-color': 'red'});
|
||||||
elementStylingApply(0);
|
elementStylingApply(0);
|
||||||
});
|
});
|
||||||
expect(fixture.html).toEqual('<div style="height: 10px; background-color: red;"></div>');
|
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', () => {
|
describe('elementClass', () => {
|
||||||
|
@ -235,7 +286,7 @@ describe('instructions', () => {
|
||||||
it('should add class', () => {
|
it('should add class', () => {
|
||||||
const fixture = new TemplateFixture(createDivWithStyling);
|
const fixture = new TemplateFixture(createDivWithStyling);
|
||||||
fixture.update(() => {
|
fixture.update(() => {
|
||||||
elementStylingMap(0, null, 'multiple classes');
|
elementStylingMap(0, 'multiple classes');
|
||||||
elementStylingApply(0);
|
elementStylingApply(0);
|
||||||
});
|
});
|
||||||
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
|
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
|
||||||
|
@ -504,7 +555,23 @@ class LocalMockSanitizer implements Sanitizer {
|
||||||
bypassSecurityTrustResourceUrl(value: string) { return new LocalSanitizedValue(value); }
|
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 {
|
function stripStyleWsCharacters(value: string): string {
|
||||||
// color: blue; => color:blue
|
// color: blue; => color:blue
|
||||||
return value.replace(/;/g, '').replace(/:\s+/g, ':');
|
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) {
|
function Template(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'span');
|
elementStart(0, 'span');
|
||||||
elementStyling(['border-color']);
|
elementStyling(null, ['border-color']);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
|
@ -767,7 +767,7 @@ describe('render3 integration test', () => {
|
||||||
function Template(rf: RenderFlags, ctx: any) {
|
function Template(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'span');
|
elementStart(0, 'span');
|
||||||
elementStyling(['font-size']);
|
elementStyling(null, ['font-size']);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
|
@ -788,7 +788,7 @@ describe('render3 integration test', () => {
|
||||||
function Template(rf: RenderFlags, ctx: any) {
|
function Template(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'span');
|
elementStart(0, 'span');
|
||||||
elementStyling(null, ['active']);
|
elementStyling(['active']);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
|
@ -814,7 +814,7 @@ describe('render3 integration test', () => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'span');
|
elementStart(0, 'span');
|
||||||
elementStyling(
|
elementStyling(
|
||||||
null, ['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
|
['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
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
|
* 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', () => {
|
describe('sanitization', () => {
|
||||||
class Wrap {
|
class Wrap {
|
||||||
|
@ -64,4 +65,4 @@ describe('sanitization', () => {
|
||||||
expect(() => sanitizeScript(bypassSanitizationTrustHtml('true'))).toThrowError(ERROR);
|
expect(() => sanitizeScript(bypassSanitizationTrustHtml('true'))).toThrowError(ERROR);
|
||||||
expect(sanitizeScript(bypassSanitizationTrustScript('true'))).toEqual('true');
|
expect(sanitizeScript(bypassSanitizationTrustScript('true'))).toEqual('true');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue