fix(ivy): sanitization for Host Bindings (#27939)

This commit adds sanitization for `elementProperty` and `elementAttribute` instructions used in `hostBindings` function, similar to what we already have in the `template` function. Main difference is the fact that for some attributes (like "href" and "src") we can't define which SecurityContext they belong to (URL vs RESOURCE_URL) in Compiler, since information in Directive selector may not be enough to calculate it. In order to resolve the problem, Compiler injects slightly different sanitization function which detects proper Security Context at runtime.

PR Close #27939
This commit is contained in:
Andrew Kushnir
2019-01-03 10:04:06 -08:00
committed by Kara Erickson
parent 1de4031d9c
commit c3aa24c3f9
14 changed files with 430 additions and 48 deletions

View File

@ -213,4 +213,6 @@ export class Identifiers {
o.ExternalReference = {name: 'ɵsanitizeResourceUrl', moduleName: CORE};
static sanitizeScript: o.ExternalReference = {name: 'ɵsanitizeScript', moduleName: CORE};
static sanitizeUrl: o.ExternalReference = {name: 'ɵsanitizeUrl', moduleName: CORE};
static sanitizeUrlOrResourceUrl:
o.ExternalReference = {name: 'ɵsanitizeUrlOrResourceUrl', moduleName: CORE};
}

View File

@ -30,7 +30,7 @@ import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, type
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
import {StylingBuilder, StylingInstruction} from './styling_builder';
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, prepareEventListenerParameters, renderFlagCheckIfStmt} from './template';
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template';
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
const EMPTY_ARRAY: any[] = [];
@ -698,15 +698,45 @@ function createHostBindingsFunction(
const value = binding.expression.visit(valueConverter);
const bindingExpr = bindingFn(bindingContext, value);
const {bindingName, instruction, extraParams} = getBindingNameAndInstruction(binding);
const {bindingName, instruction, isAttribute} = getBindingNameAndInstruction(binding);
const securityContexts =
bindingParser
.calcPossibleSecurityContexts(meta.selector || '', bindingName, isAttribute)
.filter(context => context !== core.SecurityContext.NONE);
let sanitizerFn: o.ExternalExpr|null = null;
if (securityContexts.length) {
if (securityContexts.length === 2 &&
securityContexts.indexOf(core.SecurityContext.URL) > -1 &&
securityContexts.indexOf(core.SecurityContext.RESOURCE_URL) > -1) {
// Special case for some URL attributes (such as "src" and "href") that may be a part of
// different security contexts. In this case we use special santitization function and
// select the actual sanitizer at runtime based on a tag name that is provided while
// invoking sanitization function.
sanitizerFn = o.importExpr(R3.sanitizeUrlOrResourceUrl);
} else {
sanitizerFn = resolveSanitizationFn(securityContexts[0], isAttribute);
}
}
const instructionParams: o.Expression[] = [
elVarExp, o.literal(bindingName), o.importExpr(R3.bind).callFn([bindingExpr.currValExpr])
];
if (sanitizerFn) {
instructionParams.push(sanitizerFn);
}
if (!isAttribute) {
if (!sanitizerFn) {
// append `null` in front of `nativeOnly` flag if no sanitizer fn defined
instructionParams.push(o.literal(null));
}
// host bindings must have nativeOnly prop set to true
instructionParams.push(o.literal(true));
}
updateStatements.push(...bindingExpr.stmts);
updateStatements.push(
o.importExpr(instruction).callFn(instructionParams.concat(extraParams)).toStmt());
updateStatements.push(o.importExpr(instruction).callFn(instructionParams).toStmt());
}
}
@ -777,10 +807,9 @@ function createStylingStmt(
}
function getBindingNameAndInstruction(binding: ParsedProperty):
{bindingName: string, instruction: o.ExternalReference, extraParams: o.Expression[]} {
{bindingName: string, instruction: o.ExternalReference, isAttribute: boolean} {
let bindingName = binding.name;
let instruction !: o.ExternalReference;
const extraParams: o.Expression[] = [];
// Check to see if this is an attr binding or a property binding
const attrMatches = bindingName.match(ATTR_REGEX);
@ -797,13 +826,9 @@ function getBindingNameAndInstruction(binding: ParsedProperty):
} else {
instruction = R3.elementProperty;
}
extraParams.push(
o.literal(null), // TODO: This should be a sanitizer fn (FW-785)
o.literal(true) // host bindings must have nativeOnly prop set to true
);
}
return {bindingName, instruction, extraParams};
return {bindingName, instruction, isAttribute: !!attrMatches};
}
function createHostListeners(

View File

@ -701,7 +701,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
} else if (instruction) {
const params: any[] = [];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
const isAttributeBinding = input.type === BindingType.Attribute;
const sanitizationRef = resolveSanitizationFn(input.securityContext, isAttributeBinding);
if (sanitizationRef) params.push(sanitizationRef);
// TODO(chuckj): runtime: security context
@ -1571,7 +1572,7 @@ export function makeBindingParser(
new Parser(new Lexer()), interpolationConfig, new DomElementSchemaRegistry(), null, []);
}
function resolveSanitizationFn(input: t.BoundAttribute, context: core.SecurityContext) {
export function resolveSanitizationFn(context: core.SecurityContext, isAttribute?: boolean) {
switch (context) {
case core.SecurityContext.HTML:
return o.importExpr(R3.sanitizeHtml);
@ -1581,7 +1582,7 @@ function resolveSanitizationFn(input: t.BoundAttribute, context: core.SecurityCo
// 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;
return isAttribute ? o.importExpr(R3.sanitizeStyle) : null;
case core.SecurityContext.URL:
return o.importExpr(R3.sanitizeUrl);
case core.SecurityContext.RESOURCE_URL:

View File

@ -305,6 +305,12 @@ export class BindingParser {
}
}
calcPossibleSecurityContexts(selector: string, propName: string, isAttribute: boolean):
SecurityContext[] {
const prop = this._schemaRegistry.getMappedPropName(propName);
return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
}
private _parseAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, targetEvents: ParsedEvent[]) {
const matches = splitAtPeriod(name, [name, '']);