Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds unsafeCss for composing values into css #474

Merged
merged 3 commits into from
Jan 24, 2019
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added
* [Maintenance] Added script to publish dev releases automatically ([#476](https://github.com/Polymer/lit-element/pull/476)).
* Adds `unsafeCss` for composing "unsafe" values into `css`. Note, `CSSResult` is no longer constructable. ([#451](https://github.com/Polymer/lit-element/issues/451) and [#471](https://github.com/Polymer/lit-element/issues/471)).

### Fixed
* Fixed a bug where we broke compatibility with closure compiler's property renaming optimizations. JSCompiler_renameProperty can't be a module export ([#465](https://github.com/Polymer/lit-element/pull/465)).
Expand Down
33 changes: 29 additions & 4 deletions src/lib/css-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ found at http://polymer.github.io/PATENTS.txt
export const supportsAdoptingStyleSheets =
('adoptedStyleSheets' in Document.prototype);

const constructionToken = Symbol();

export class CSSResult {

_styleSheet?: CSSStyleSheet|null;

readonly cssText: string;

constructor(cssText: string) { this.cssText = cssText; }
constructor(cssText: string, safeToken: symbol) {
if (safeToken !== constructionToken) {
throw new Error('CSSResult is not constructable. Use `unsafeCss` or `css` instead.');
}
this.cssText = cssText;
}

// Note, this is a getter so that it's lazy. In practice, this means
// stylesheets are not created until the first element instance is made.
Expand All @@ -37,20 +44,38 @@ export class CSSResult {
}
}

/**
* Wrap a value for interpolation in a css tagged template literal.
*
* This is unsafe because untrusted CSS text can be used to phone home
* or exfiltrate data to an attacker controlled site. Take care to only use
* this with trusted input.
*/
export const unsafeCss = (value: unknown) => {
return new CSSResult(String(value), constructionToken);
};

const textFromCSSResult = (value: CSSResult) => {
if (value instanceof CSSResult) {
return value.cssText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider mentioning unsafeCss in the error message here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

} else {
throw new Error(
`Value passed to 'css' function must be a 'css' function result: ${
value}.`);
value}. Use 'unsafeCss' to pass non-literal values, but
take care to ensure page security.` );
}
};

/**
* Template tag which which can be used with LitElement's `style` property to
* set element styles. For security reasons, only literal string values may be
* used. To incorporate non-literal values `unsafeCss` may be used inside a
* template string part.
*/
export const css =
(strings: TemplateStringsArray, ...values: CSSResult[]): CSSResult => {
(strings: TemplateStringsArray, ...values: CSSResult[]) => {
const cssText = values.reduce(
(acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1],
strings[0]);
return new CSSResult(cssText);
return new CSSResult(cssText, constructionToken);
};
29 changes: 29 additions & 0 deletions src/test/lit-element_styling_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import '@webcomponents/shadycss/apply-shim.min.js';

import {
css,
unsafeCss,
CSSResult,
html as htmlWithStyles,
LitElement,
} from '../lit-element.js';
Expand Down Expand Up @@ -432,6 +434,33 @@ suite('Static get styles', () => {
assert.throws(() => { css`div { border: ${`2px solid blue;` as any}}`; });
});

test('`CSSResult` cannot be constructed', async () => {
// Note, this is done for security, instead use `css` or `unsafeCss`
assert.throws(() => { new CSSResult('throw', Symbol()); });
});

test('Any value can be used in `css` when included with `unsafeCss`', async () => {
const name = generateElementName();
const someVar = `2px solid blue`;
customElements.define(name, class extends LitElement {
static get styles() {
return css`div {
border: ${unsafeCss(someVar)};
}`;
}

render() {
return htmlWithStyles`
<div>Testing</div>`;
}
});
const el = document.createElement(name);
container.appendChild(el);
await (el as LitElement).updateComplete;
const div = el.shadowRoot!.querySelector('div');
assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px');
});

test('styles in render compose with `static get styles`', async () => {
const name = generateElementName();
customElements.define(name, class extends LitElement {
Expand Down