From f3ce8eeb83ce45728ed3e33d5d1cff6664ecb84e Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Thu, 25 Apr 2019 14:27:57 -0700 Subject: [PATCH] fix(ivy): property bindings use correct indices (#30129) - Extracts and documents code that will be common to interpolation instructions - Ensures that binding indices are updated at the proper time during compilation - Adds additional tests Related #30011 PR Close #30129 --- .../r3_view_compiler_binding_spec.ts | 2 +- .../src/compiler_util/expression_converter.ts | 60 ++++++- .../compiler/src/render3/view/template.ts | 71 +++++--- .../instructions/property_interpolation.ts | 72 ++++---- .../core/test/acceptance/properties_spec.ts | 154 +++++++++++++++++- tools/public_api_guard/core/core.d.ts | 20 +-- 6 files changed, 305 insertions(+), 74 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index 78c9a377a8..7fa4977f60 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -470,7 +470,7 @@ describe('compiler compliance: bindings', () => { … if (rf & 2) { i0.ɵɵselect(0); - i0.ɵɵpropertyInterpolateV("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"); + i0.ɵɵpropertyInterpolateV("title", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); i0.ɵɵselect(1); i0.ɵɵpropertyInterpolate8("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); i0.ɵɵselect(2); diff --git a/packages/compiler/src/compiler_util/expression_converter.ts b/packages/compiler/src/compiler_util/expression_converter.ts index f902f0884c..b98b0090ba 100644 --- a/packages/compiler/src/compiler_util/expression_converter.ts +++ b/packages/compiler/src/compiler_util/expression_converter.ts @@ -155,16 +155,12 @@ export function convertPropertyBinding( localResolver = new DefaultLocalResolver(); } const currValExpr = createCurrValueExpr(bindingId); - const stmts: o.Statement[] = []; const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction); const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression); + const stmts: o.Statement[] = getStatementsFromVisitor(visitor, bindingId); - if (visitor.temporaryCount) { - for (let i = 0; i < visitor.temporaryCount; i++) { - stmts.push(temporaryDeclaration(bindingId, i)); - } - } else if (form == BindingForm.TrySimple) { + if (visitor.temporaryCount === 0 && form == BindingForm.TrySimple) { return new ConvertPropertyBindingResult([], outputExpr); } @@ -172,6 +168,58 @@ export function convertPropertyBinding( return new ConvertPropertyBindingResult(stmts, currValExpr); } +/** + * Given some expression, such as a binding or interpolation expression, and a context expression to + * look values up on, visit each facet of the given expression resolving values from the context + * expression such that a list of arguments can be derived from the found values that can be used as + * arguments to an external update instruction. + * + * @param localResolver The resolver to use to look up expressions by name appropriately + * @param contextVariableExpression The expression representing the context variable used to create + * the final argument expressions + * @param expressionWithArgumentsToExtract The expression to visit to figure out what values need to + * be resolved and what arguments list to build. + * @param bindingId A name prefix used to create temporary variable names if they're needed for the + * arguments generated + * @returns An array of expressions that can be passed as arguments to instruction expressions like + * `o.importExpr(R3.propertyInterpolate).callFn(result)` + */ +export function convertUpdateArguments( + localResolver: LocalResolver, contextVariableExpression: o.Expression, + expressionWithArgumentsToExtract: cdAst.AST, bindingId: string) { + const visitor = + new _AstToIrVisitor(localResolver, contextVariableExpression, bindingId, undefined); + const outputExpr: o.InvokeFunctionExpr = + expressionWithArgumentsToExtract.visit(visitor, _Mode.Expression); + + const stmts = getStatementsFromVisitor(visitor, bindingId); + + // Removing the first argument, because it was a length for ViewEngine, not Ivy. + let args = outputExpr.args.slice(1); + if (expressionWithArgumentsToExtract instanceof cdAst.Interpolation) { + // If we're dealing with an interpolation of 1 value with an empty prefix and suffix, reduce the + // args returned to just the value, because we're going to pass it to a special instruction. + const strings = expressionWithArgumentsToExtract.strings; + if (args.length === 3 && strings[0] === '' && strings[1] === '') { + // Single argument interpolate instructions. + args = [args[1]]; + } else if (args.length >= 19) { + // 19 or more arguments must be passed to the `interpolateV`-style instructions, which accept + // an array of arguments + args = [o.literalArr(args)]; + } + } + return {stmts, args}; +} + +function getStatementsFromVisitor(visitor: _AstToIrVisitor, bindingId: string) { + const stmts: o.Statement[] = []; + for (let i = 0; i < visitor.temporaryCount; i++) { + stmts.push(temporaryDeclaration(bindingId, i)); + } + return stmts; +} + function convertBuiltins(converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST { const visitor = new _BuiltinAstConverter(converterFactory); return ast.visit(visitor); diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index c737cb95ae..fa751dac6e 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -7,7 +7,7 @@ */ import {flatten, sanitizeIdentifier} from '../../compile_metadata'; -import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; +import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding, convertUpdateArguments} from '../../compiler_util/expression_converter'; import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, ParsedEventType, PropertyRead} from '../../expression_parser/ast'; @@ -750,23 +750,13 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (inputType === BindingType.Property) { if (value instanceof Interpolation) { - // Interpolated properties - const {currValExpr} = convertPropertyBinding( - this, implicit, value, this.bindingContext(), BindingForm.TrySimple); - - let args: o.Expression[] = (currValExpr as any).args; - args.shift(); // ViewEngine required a count, we don't need that. - - // For interpolations like attr="{{foo}}", we don't need ["", foo, ""], just [foo]. - if (args.length === 3 && isEmptyStringExpression(args[0]) && - isEmptyStringExpression(args[2])) { - args = [args[1]]; - } - this.updateInstruction( - elementIndex, input.sourceSpan, propertyInterpolate(args.length), () => { - return [o.literal(attrName), ...args, ...params]; - }); + elementIndex, input.sourceSpan, getPropertyInterpolationExpression(value), + () => + [o.literal(attrName), + ...this.getUpdateInstructionArguments(o.variable(CONTEXT_NAME), value), + ...params]); + } else { // Bound, un-interpolated properties this.updateInstruction(elementIndex, input.sourceSpan, R3.property, () => { @@ -1076,6 +1066,21 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver o.importExpr(R3.bind).callFn([valExpr]); } + /** + * Gets a list of argument expressions to pass to an update instruction expression. Also updates + * the temp variables state with temp variables that were identified as needing to be created + * while visiting the arguments. + * @param contextExpression The expression for the context variable used to create arguments + * @param value The original expression we will be resolving an arguments list from. + */ + private getUpdateInstructionArguments(contextExpression: o.Expression, value: AST): + o.Expression[] { + const {args, stmts} = + convertUpdateArguments(this, contextExpression, value, this.bindingContext()); + this._tempVariables.push(...stmts); + return args; + } + private matchDirectives(tagName: string, elOrTpl: t.Element|t.Template) { if (this.directiveMatcher) { const selector = createCssSelector(tagName, getAttrsForDirectiveMatching(elOrTpl)); @@ -1646,16 +1651,12 @@ function interpolate(args: o.Expression[]): o.Expression { return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); } -function isEmptyStringExpression(exp: o.Expression) { - return exp instanceof o.LiteralExpr && exp.value === ''; -} - -function propertyInterpolate(argsLength: number) { - if (argsLength % 2 !== 1) { - error(`Invalid propertyInterpolate argument length ${argsLength}`); - } - - switch (argsLength) { +/** + * Gets the instruction to generate for an interpolated property + * @param interpolation An Interpolation AST + */ +function getPropertyInterpolationExpression(interpolation: Interpolation) { + switch (getInterpolationArgsLength(interpolation)) { case 1: return R3.propertyInterpolate; case 3: @@ -1679,6 +1680,22 @@ function propertyInterpolate(argsLength: number) { } } +/** + * Gets the number of arguments expected to be passed to a generated instruction in the case of + * interpolation instructions. + * @param interpolation An interpolation ast + */ +function getInterpolationArgsLength(interpolation: Interpolation) { + const {expressions, strings} = interpolation; + if (expressions.length === 1 && strings.length === 2 && strings[0] === '' && strings[1] === '') { + // If the interpolation has one interpolated value, but the prefix and suffix are both empty + // strings, we only pass one argument, to a special instruction like `propertyInterpolate` or + // `textInterpolate`. + return 1; + } else { + return expressions.length + strings.length; + } +} /** * Options that can be used to modify how a template is parsed by `parseTemplate()`. */ diff --git a/packages/core/src/render3/instructions/property_interpolation.ts b/packages/core/src/render3/instructions/property_interpolation.ts index 8204a9c8b4..67984b7c8e 100644 --- a/packages/core/src/render3/instructions/property_interpolation.ts +++ b/packages/core/src/render3/instructions/property_interpolation.ts @@ -7,6 +7,7 @@ */ import {assertEqual, assertLessThan} from '../../util/assert'; import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings'; +import {SanitizerFn} from '../interfaces/sanitization'; import {BINDING_INDEX, TVIEW} from '../interfaces/view'; import {getLView, getSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; @@ -15,6 +16,7 @@ import {renderStringify} from '../util/misc_utils'; import {TsickleIssue1009, elementPropertyInternal, storeBindingMetadata} from './shared'; + /** * Create interpolation bindings with a variable number of expressions. * @@ -290,12 +292,6 @@ export function ɵɵinterpolation8( /// NEW INSTRUCTIONS ///////////////////////////////////////////////////////////////////// - -/** - * Shared reference to a string, used in `ɵɵpropertyInterpolate`. - */ -const EMPTY_STRING = ''; - /** * * Update an interpolated property on an element with a lone bound value @@ -321,11 +317,13 @@ const EMPTY_STRING = ''; * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ -export function ɵɵpropertyInterpolate(propName: string, v0: any): TsickleIssue1009 { - ɵɵpropertyInterpolate1(propName, EMPTY_STRING, v0, EMPTY_STRING); +export function ɵɵpropertyInterpolate( + propName: string, v0: any, sanitizer?: SanitizerFn): TsickleIssue1009 { + ɵɵpropertyInterpolate1(propName, '', v0, '', sanitizer); return ɵɵpropertyInterpolate; } @@ -354,13 +352,15 @@ export function ɵɵpropertyInterpolate(propName: string, v0: any): TsickleIssue * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate1( - propName: string, prefix: string, v0: any, suffix: string): TsickleIssue1009 { + propName: string, prefix: string, v0: any, suffix: string, + sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); - elementPropertyInternal(index, propName, ɵɵinterpolation1(prefix, v0, suffix)); + elementPropertyInternal(index, propName, ɵɵinterpolation1(prefix, v0, suffix), sanitizer); return ɵɵpropertyInterpolate1; } @@ -390,14 +390,15 @@ export function ɵɵpropertyInterpolate1( * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate2( - propName: string, prefix: string, v0: any, i0: string, v1: any, - suffix: string): TsickleIssue1009 { + propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, + sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); - elementPropertyInternal(index, propName, ɵɵinterpolation2(prefix, v0, i0, v1, suffix)); + elementPropertyInternal(index, propName, ɵɵinterpolation2(prefix, v0, i0, v1, suffix), sanitizer); return ɵɵpropertyInterpolate2; } @@ -430,14 +431,16 @@ export function ɵɵpropertyInterpolate2( * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate3( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, - suffix: string): TsickleIssue1009 { + suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); - elementPropertyInternal(index, propName, ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix)); + elementPropertyInternal( + index, propName, ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix), sanitizer); return ɵɵpropertyInterpolate3; } @@ -472,15 +475,16 @@ export function ɵɵpropertyInterpolate3( * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate4( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, suffix: string): TsickleIssue1009 { + v3: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); elementPropertyInternal( - index, propName, ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix)); + index, propName, ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix), sanitizer); return ɵɵpropertyInterpolate4; } @@ -517,15 +521,17 @@ export function ɵɵpropertyInterpolate4( * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate5( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, suffix: string): TsickleIssue1009 { + v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); elementPropertyInternal( - index, propName, ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix)); + index, propName, ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix), + sanitizer); return ɵɵpropertyInterpolate5; } @@ -564,16 +570,18 @@ export function ɵɵpropertyInterpolate5( * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate6( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): TsickleIssue1009 { + v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, + sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); elementPropertyInternal( - index, propName, - ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix)); + index, propName, ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix), + sanitizer); return ɵɵpropertyInterpolate6; } @@ -614,17 +622,19 @@ export function ɵɵpropertyInterpolate6( * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate7( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, - suffix: string): TsickleIssue1009 { + v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, + sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); elementPropertyInternal( index, propName, - ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix)); + ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix), + sanitizer); return ɵɵpropertyInterpolate7; } @@ -667,17 +677,19 @@ export function ɵɵpropertyInterpolate7( * @param i6 Static value used for concatenation only. * @param v7 Value checked for change. * @param suffix Static value used for concatenation only. + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ export function ɵɵpropertyInterpolate8( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string): TsickleIssue1009 { + suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); elementPropertyInternal( index, propName, - ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix)); + ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix), + sanitizer); return ɵɵpropertyInterpolate8; } @@ -707,12 +719,14 @@ export function ɵɵpropertyInterpolate8( * @param values The a collection of values and the strings inbetween those values, beginning with a * string prefix and ending with a string suffix. * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) + * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ -export function ɵɵpropertyInterpolateV(propName: string, values: any[]): TsickleIssue1009 { +export function ɵɵpropertyInterpolateV( + propName: string, values: any[], sanitizer?: SanitizerFn): TsickleIssue1009 { const index = getSelectedIndex(); - elementPropertyInternal(index, propName, ɵɵinterpolationV(values)); + elementPropertyInternal(index, propName, ɵɵinterpolationV(values), sanitizer); return ɵɵpropertyInterpolateV; } diff --git a/packages/core/test/acceptance/properties_spec.ts b/packages/core/test/acceptance/properties_spec.ts index d8e591d265..3f1cb66f17 100644 --- a/packages/core/test/acceptance/properties_spec.ts +++ b/packages/core/test/acceptance/properties_spec.ts @@ -10,8 +10,9 @@ import {Component, Input} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; +import {of } from 'rxjs'; -describe('elementProperty', () => { +describe('property instructions', () => { it('should bind to properties whose names do not correspond to their attribute names', () => { @Component({template: ''}) class MyComp { @@ -33,6 +34,25 @@ describe('elementProperty', () => { expect(labelNode.nativeElement.getAttribute('for')).toBe('some-textarea'); }); + it('should not allow unsanitary urls in bound properties', () => { + @Component({ + template: ` + + ` + }) + class App { + naughty = 'javascript:alert("haha, I am taking over your computer!!!");'; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const img = fixture.nativeElement.querySelector('img'); + + expect(img.src.indexOf('unsafe:')).toBe(0); + }); + + it('should not map properties whose names do not correspond to their attribute names, ' + 'if they correspond to inputs', () => { @@ -60,4 +80,136 @@ describe('elementProperty', () => { expect(myCompNode.nativeElement.getAttribute('for')).toBeFalsy(); expect(myCompNode.componentInstance.for).toBe('hej'); }); + + it('should handle all flavors of interpolated properties', () => { + @Component({ + template: ` +
+
+
+
+
+
+
+
+
+
+ ` + }) + class App { + one = 1; + two = 2; + three = 3; + four = 4; + five = 5; + six = 6; + seven = 7; + eight = 8; + nine = 9; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const titles = Array.from(fixture.nativeElement.querySelectorAll('div[title]')) + .map((div: HTMLDivElement) => div.title); + + expect(titles).toEqual([ + 'a1b2c3d4e5f6g7h8i9j', + 'a1b2c3d4e5f6g7h8i', + 'a1b2c3d4e5f6g7h', + 'a1b2c3d4e5f6g', + 'a1b2c3d4e5f', + 'a1b2c3d4e', + 'a1b2c3d', + 'a1b2c', + 'a1b', + '1', + ]); + }); + + it('should handle pipes in interpolated properties', () => { + @Component({ + template: ` + + ` + }) + class App { + details = of ({ + title: 'cool image', + url: 'http://somecooldomain:1234/cool_image.png', + }); + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const img: HTMLImageElement = fixture.nativeElement.querySelector('img'); + expect(img.src).toBe('http://somecooldomain:1234/cool_image.png'); + expect(img.title).toBe('cool image'); + }); + + // From https://angular-team.atlassian.net/browse/FW-1287 + it('should handle multiple elvis operators', () => { + @Component({ + template: ` + + ` + }) + class App { + /** Clearly this is a doctor of heavy metals. */ + leadSurgeon = { + getCommonInfo() { + return {getPhotoUrl() { return 'http://somecooldomain:1234/cool_image.png'; }}; + } + }; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const img = fixture.nativeElement.querySelector('img'); + + expect(img.src).toBe('http://somecooldomain:1234/cool_image.png'); + }); + + it('should not allow unsanitary urls in interpolated properties', () => { + @Component({ + template: ` + + ` + }) + class App { + naughty = 'javascript:alert("haha, I am taking over your computer!!!");'; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const img: HTMLImageElement = fixture.nativeElement.querySelector('img'); + + expect(img.src.indexOf('unsafe:')).toBe(0); + }); + + it('should not allow unsanitary urls in interpolated properties, even if you are tricky', () => { + @Component({ + template: ` + + ` + }) + class App { + ja = 'ja'; + va = 'va'; + naughty = 'alert("I am a h4xx0rz1!!");'; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const img = fixture.nativeElement.querySelector('img'); + + expect(img.src.indexOf('unsafe:')).toBe(0); + }); }); diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 25ad65f835..26b76420a6 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -943,25 +943,25 @@ export declare function ɵɵprojectionDef(selectors?: CssSelectorList[]): void; export declare function ɵɵproperty(propName: string, value: T, sanitizer?: SanitizerFn | null, nativeOnly?: boolean): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate(propName: string, v0: any): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate(propName: string, v0: any, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate1(propName: string, prefix: string, v0: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate1(propName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate2(propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate2(propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate3(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate3(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate4(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate4(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate5(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate5(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate6(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate6(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate7(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate7(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolate8(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string): TsickleIssue1009; +export declare function ɵɵpropertyInterpolate8(propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009; -export declare function ɵɵpropertyInterpolateV(propName: string, values: any[]): TsickleIssue1009; +export declare function ɵɵpropertyInterpolateV(propName: string, values: any[], sanitizer?: SanitizerFn): TsickleIssue1009; export declare function ɵɵProvidersFeature(providers: Provider[], viewProviders?: Provider[]): (definition: DirectiveDef) => void;