Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/elements/dom-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import '../utils/boot.js';
import { resolveUrl, pathFromUrl } from '../utils/resolve-url.js';
import { strictTemplatePolicy } from '../utils/settings.js';

let modules = {};
let lcModules = {};
let modules = Object.create(null);
let lcModules = Object.create(null);
/**
* Sets a dom-module into the global registry by id.
*
Expand Down
11 changes: 8 additions & 3 deletions lib/mixins/element-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,12 @@ export const ElementMixin = dedupingMixin(base => {
function processElementStyles(klass, template, is, baseURI) {
if (!builtCSS) {
const templateStyles = template.content.querySelectorAll('style');
const stylesWithImports = stylesFromTemplate(template);
const useDomModules = !strictTemplatePolicy || allowTemplateFromDomModule;
const stylesWithImports = useDomModules ?
stylesFromTemplate(template) :
Array.from(templateStyles);
// insert styles from <link rel="import" type="css"> at the top of the template
const linkedStyles = stylesFromModuleImports(is);
const linkedStyles = useDomModules ? stylesFromModuleImports(is) : [];
const firstTemplateChild = template.content.firstElementChild;
for (let idx = 0; idx < linkedStyles.length; idx++) {
let s = linkedStyles[idx];
Expand Down Expand Up @@ -550,7 +553,9 @@ export const ElementMixin = dedupingMixin(base => {
if (meta) {
this._importPath = pathFromUrl(meta.url);
} else {
const module = DomModule.import(/** @type {PolymerElementConstructor} */ (this).is);
const module = (!strictTemplatePolicy || allowTemplateFromDomModule) ?
DomModule.import(/** @type {PolymerElementConstructor} */ (this).is) :
null;
this._importPath = (module && module.assetpath) ||
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.importPath;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/mixins/properties-changed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ interface PropertiesChanged {
* @param attribute Attribute name to reflect to.
* @param value Property value to refect.
*/
_propertyToAttribute(property: string, attribute?: string, value?: any): void;
_propertyToAttribute(property: string, attribute?: string, value?: any, skipSanitization?: boolean): void;

/**
* Sets a typed value to an HTML attribute on a node.
Expand All @@ -274,7 +274,7 @@ interface PropertiesChanged {
* @param value Value to serialize.
* @param attribute Attribute name to serialize to.
*/
_valueToNodeAttribute(node: Element|null, value: any, attribute: string): void;
_valueToNodeAttribute(node: Element|null, value: any, attribute: string, skipSanitization?: boolean): void;

/**
* Converts a typed JavaScript value to a string.
Expand Down
13 changes: 9 additions & 4 deletions lib/mixins/properties-changed.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../utils/boot.js';
import { dedupingMixin } from '../utils/mixin.js';
import { microTask } from '../utils/async.js';
import { wrap } from '../utils/wrap.js';
import { sanitizeDOMValue } from '../utils/settings.js';

/** @const {!AsyncInterface} */
const microtask = microTask;
Expand Down Expand Up @@ -501,11 +502,12 @@ export const PropertiesChanged = dedupingMixin(
* @return {void}
* @override
*/
_propertyToAttribute(property, attribute, value) {
_propertyToAttribute(property, attribute, value, skipSanitization) {
this.__serializing = true;
value = (arguments.length < 3) ? this[property] : value;
this._valueToNodeAttribute(/** @type {!HTMLElement} */(this), value,
attribute || this.constructor.attributeNameForProperty(property));
attribute || this.constructor.attributeNameForProperty(property),
skipSanitization);
this.__serializing = false;
}

Expand All @@ -523,11 +525,14 @@ export const PropertiesChanged = dedupingMixin(
* @return {void}
* @override
*/
_valueToNodeAttribute(node, value, attribute) {
const str = this._serializeValue(value);
_valueToNodeAttribute(node, value, attribute, skipSanitization) {
if (attribute === 'class' || attribute === 'name' || attribute === 'slot') {
node = /** @type {?Element} */(wrap(node));
}
if (!skipSanitization && sanitizeDOMValue) {
value = sanitizeDOMValue(value, attribute, 'attribute', /** @type {!Node} */ (node));
}
const str = this._serializeValue(value);
if (str === undefined) {
node.removeAttribute(attribute);
} else {
Expand Down
31 changes: 28 additions & 3 deletions lib/mixins/property-effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import '../utils/boot.js';
import { wrap } from '../utils/wrap.js';
import { dedupingMixin } from '../utils/mixin.js';
import { root, isAncestor, isDescendant, get, translate, isPath, set, normalize } from '../utils/path.js';
import { root, isAncestor, isDescendant, get, translate, isPath, set, normalize, split } from '../utils/path.js';
/* for notify, reflect */
import { camelToDashCase, dashToCamelCase } from '../utils/case-map.js';
import { PropertyAccessors } from './property-accessors.js';
Expand Down Expand Up @@ -45,6 +45,22 @@ const COMPUTE_INFO = '__computeInfo';
/** @const {!RegExp} */
const capitalAttributeRegex = /[A-Z]/;

const BLOCKED_PATH_SEGMENTS = {
'__proto__': true,
'constructor': true,
'prototype': true,
};

function hasBlockedPathSegment(path) {
const parts = split(path);
for (let i = 0; i < parts.length; i++) {
if (BLOCKED_PATH_SEGMENTS[parts[i]]) {
return true;
}
}
return false;
}

/**
* @typedef {{
* name: (string | undefined),
Expand Down Expand Up @@ -408,7 +424,7 @@ function runReflectEffect(inst, property, props, oldProps, info) {
if (sanitizeDOMValue) {
value = sanitizeDOMValue(value, info.attrName, 'attribute', /** @type {Node} */(inst));
}
inst._propertyToAttribute(property, info.attrName, value);
inst._propertyToAttribute(property, info.attrName, value, true);
}

/**
Expand Down Expand Up @@ -805,7 +821,7 @@ function runBindingEffect(inst, path, props, oldProps, info, hasPaths, nodeList)
*/
function applyBindingValue(inst, node, binding, part, value) {
value = computeBindingValue(node, value, binding, part);
if (sanitizeDOMValue) {
if (sanitizeDOMValue && binding.kind !== 'attribute') {
value = sanitizeDOMValue(value, binding.target, binding.kind, node);
}
if (binding.kind == 'attribute') {
Expand Down Expand Up @@ -1584,6 +1600,9 @@ export const PropertyEffects = dedupingMixin(superClass => {
* @protected
*/
_setPendingPropertyOrPath(path, value, shouldNotify, isPathNotification) {
if (hasBlockedPathSegment(path)) {
return false;
}
if (isPathNotification ||
root(Array.isArray(path) ? path[0] : path) !== path) {
// Dirty check changes being set to a path against the actual object,
Expand Down Expand Up @@ -1981,6 +2000,9 @@ export const PropertyEffects = dedupingMixin(superClass => {
linkPaths(to, from) {
to = normalize(to);
from = normalize(from);
if (hasBlockedPathSegment(to) || hasBlockedPathSegment(from)) {
return;
}
this.__dataLinkedPaths = this.__dataLinkedPaths || {};
this.__dataLinkedPaths[to] = from;
}
Expand All @@ -1998,6 +2020,9 @@ export const PropertyEffects = dedupingMixin(superClass => {
*/
unlinkPaths(path) {
path = normalize(path);
if (hasBlockedPathSegment(path)) {
return;
}
if (this.__dataLinkedPaths) {
delete this.__dataLinkedPaths[path];
}
Expand Down
2 changes: 1 addition & 1 deletion lib/mixins/template-stamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ export const TemplateStamp = dedupingMixin(
// NOTE: ShadyDom optimization indicating there is an insertion point
dom.__noInsertionPoint = !templateInfo.hasInsertionPoint;
let nodes = dom.nodeList = new Array(nodeInfo.length);
dom.$ = {};
dom.$ = Object.create(null);
for (let i=0, l=nodeInfo.length, info; (i<l) && (info=nodeInfo[i]); i++) {
let node = nodes[i] = findTemplateNode(dom, info);
applyIdToMap(this, dom.$, node, info);
Expand Down
6 changes: 3 additions & 3 deletions lib/utils/html-tag.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export {html};
*
* This allows you to write a Polymer Template in JavaScript.
*
* Templates can be composed by interpolating `HTMLTemplateElement`s in
* expressions in the JavaScript template literal. The nested template's
* `innerHTML` is included in the containing template. The only other
* Templates can be composed by interpolating `HTMLTemplateElement`s created by
* Polymer's `html` tag in expressions in the JavaScript template literal. The
* nested template's `innerHTML` is included in the containing template. The only other
* values allowed in expressions are those returned from `htmlLiteral`
* which ensures only literal values from JS source ever reach the HTML, to
* guard against XSS risks.
Expand Down
19 changes: 15 additions & 4 deletions lib/utils/html-tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
import './boot.js';

// Track templates created by Polymer's html tag. Nested template interpolation
// is only safe when the nested template came from the same trusted
// construction path, and WeakSet membership cannot be forged externally.
const trustedTemplates = new WeakSet();

/**
* Our TrustedTypePolicy for HTML which is declared using the Polymer html
* template tag function.
Expand Down Expand Up @@ -68,6 +73,11 @@ function literalValue(value) {
*/
function htmlValue(value) {
if (value instanceof HTMLTemplateElement) {
if (!trustedTemplates.has(value)) {
throw new Error(
`untrusted template value passed to Polymer's html function: ${value}`
);
}
// This might be an mXSS risk – mainly in the case where this template
// contains untrusted content that was believed to be sanitized.
// However we can't just use the XMLSerializer here because it misencodes
Expand All @@ -89,10 +99,10 @@ function htmlValue(value) {
*
* This allows you to write a Polymer Template in JavaScript.
*
* Templates can be composed by interpolating `HTMLTemplateElement`s in
* expressions in the JavaScript template literal. The nested template's
* `innerHTML` is included in the containing template. The only other
* values allowed in expressions are those returned from `htmlLiteral`
* Templates can be composed by interpolating `HTMLTemplateElement`s created by
* Polymer's `html` tag in expressions in the JavaScript template literal. The
* nested template's `innerHTML` is included in the containing template. The
* only other values allowed in expressions are those returned from `htmlLiteral`
* which ensures only literal values from JS source ever reach the HTML, to
* guard against XSS risks.
*
Expand Down Expand Up @@ -127,6 +137,7 @@ export const html = function html(strings, ...values) {
value = policy.createHTML(value);
}
template.innerHTML = value;
trustedTemplates.add(template);
return template;
};

Expand Down
21 changes: 21 additions & 0 deletions lib/utils/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
import './boot.js';

const BLOCKED_PATH_SEGMENTS = {
'__proto__': true,
'constructor': true,
'prototype': true,
};

function hasBlockedPathSegment(parts) {
for (let i=0; i<parts.length; i++) {
if (BLOCKED_PATH_SEGMENTS[parts[i]]) {
return true;
}
}
return false;
}

/**
* Module with utilities for manipulating structured data path strings.
*
Expand Down Expand Up @@ -191,6 +206,9 @@ export function split(path) {
export function get(root, path, info) {
let prop = root;
let parts = split(path);
if (hasBlockedPathSegment(parts)) {
return;
}
// Loop over path parts[0..n-1] and dereference
for (let i=0; i<parts.length; i++) {
if (!prop) {
Expand All @@ -217,6 +235,9 @@ export function get(root, path, info) {
export function set(root, path, value) {
let prop = root;
let parts = split(path);
if (hasBlockedPathSegment(parts)) {
return;
}
let last = parts[parts.length-1];
if (parts.length > 1) {
// Loop over path parts[0..n-2] and dereference
Expand Down
Loading