diff options
Diffstat (limited to 'assets/viz/2/goog/html/safestyle.js')
-rw-r--r-- | assets/viz/2/goog/html/safestyle.js | 181 |
1 files changed, 146 insertions, 35 deletions
diff --git a/assets/viz/2/goog/html/safestyle.js b/assets/viz/2/goog/html/safestyle.js index a1f87cb..506a475 100644 --- a/assets/viz/2/goog/html/safestyle.js +++ b/assets/viz/2/goog/html/safestyle.js @@ -22,6 +22,7 @@ goog.provide('goog.html.SafeStyle'); goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('goog.html.SafeUrl'); goog.require('goog.string'); goog.require('goog.string.Const'); goog.require('goog.string.TypedString'); @@ -42,19 +43,19 @@ goog.require('goog.string.TypedString'); * is immutable; hence only a default instance corresponding to the empty string * can be obtained via constructor invocation. * - * A SafeStyle's string representation ({@link #getTypedStringValue()}) can - * safely: + * SafeStyle's string representation can safely be: * <ul> - * <li>Be interpolated as the entire content of a *quoted* HTML style - * attribute, or before already existing properties. The SafeStyle string - * *must be HTML-attribute-escaped* (where " and ' are escaped) before + * <li>Interpolated as the content of a *quoted* HTML style attribute. + * However, the SafeStyle string *must be HTML-attribute-escaped* before * interpolation. - * <li>Be interpolated as the entire content of a {}-wrapped block within a - * stylesheet, or before already existing properties. The SafeStyle string - * should not be escaped before interpolation. SafeStyle's contract also - * guarantees that the string will not be able to introduce new properties - * or elide existing ones. - * <li>Be assigned to the style property of a DOM node. The SafeStyle string + * <li>Interpolated as the content of a {}-wrapped block within a stylesheet. + * '<' characters in the SafeStyle string *must be CSS-escaped* before + * interpolation. The SafeStyle string is also guaranteed not to be able + * to introduce new properties or elide existing ones. + * <li>Interpolated as the content of a {}-wrapped block within an HTML + * <style> element. '<' characters in the SafeStyle string + * *must be CSS-escaped* before interpolation. + * <li>Assigned to the style property of a DOM node. The SafeStyle string * should not be escaped before being assigned to the property. * </ul> * @@ -84,7 +85,7 @@ goog.require('goog.string.TypedString'); * appended to {@code background:url("}, the resulting string may result in * the execution of a malicious script. * - * TODO(user): Consider whether we should implement UTF-8 interchange + * TODO(mlourenco): Consider whether we should implement UTF-8 interchange * validity checks and blacklisting of newlines (including Unicode ones) and * other whitespace characters (\t, \f). Document here if so and also update * SafeStyle.fromConstant(). @@ -126,7 +127,7 @@ goog.html.SafeStyle = function() { /** * A type marker used to implement additional run-time type checking. * @see goog.html.SafeStyle#unwrap - * @const + * @const {!Object} * @private */ this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = @@ -312,7 +313,7 @@ goog.html.SafeStyle.EMPTY = /** - * The innocuous string generated by goog.html.SafeUrl.create when passed + * The innocuous string generated by goog.html.SafeStyle.create when passed * an unsafe value. * @const {string} */ @@ -320,8 +321,20 @@ goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez'; /** + * A single property value. + * @typedef {string|!goog.string.Const|!goog.html.SafeUrl} + */ +goog.html.SafeStyle.PropertyValue; + + +/** * Mapping of property names to their values. - * @typedef {!Object<string, goog.string.Const|string>} + * We don't support numbers even though some values might be numbers (e.g. + * line-height or 0 for any length). The reason is that most numeric values need + * units (e.g. '1px') and allowing numbers could cause users forgetting about + * them. + * @typedef {!Object<string, ?goog.html.SafeStyle.PropertyValue| + * ?Array<!goog.html.SafeStyle.PropertyValue>>} */ goog.html.SafeStyle.PropertyMap; @@ -331,9 +344,12 @@ goog.html.SafeStyle.PropertyMap; * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to * their values, for example {'margin': '1px'}. Names must consist of * [-_a-zA-Z0-9]. Values might be strings consisting of - * [-,.'"%_!# a-zA-Z0-9], where " and ' must be properly balanced. - * Other values must be wrapped in goog.string.Const. Null value causes - * skipping the property. + * [-,.'"%_!# a-zA-Z0-9], where " and ' must be properly balanced. We also + * allow simple functions like rgb() and url() which sanitizes its contents. + * Other values must be wrapped in goog.string.Const. URLs might be passed + * as goog.html.SafeUrl which will be wrapped into url(""). We also support + * array whose elements are joined with ' '. Null value causes skipping the + * property. * @return {!goog.html.SafeStyle} * @throws {Error} If invalid name is provided. * @throws {goog.asserts.AssertionError} If invalid value is provided. With @@ -350,19 +366,11 @@ goog.html.SafeStyle.create = function(map) { if (value == null) { continue; } - if (value instanceof goog.string.Const) { - value = goog.string.Const.unwrap(value); - // These characters can be used to change context and we don't want that - // even with const values. - goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].'); - } else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) { - goog.asserts.fail( - 'String value allows only [-,."\'%_!# a-zA-Z0-9], rgb() and ' + - 'rgba(), got: ' + value); - value = goog.html.SafeStyle.INNOCUOUS_STRING; - } else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) { - goog.asserts.fail('String value requires balanced quotes, got: ' + value); - value = goog.html.SafeStyle.INNOCUOUS_STRING; + if (goog.isArray(value)) { + value = goog.array.map(value, goog.html.SafeStyle.sanitizePropertyValue_) + .join(' '); + } else { + value = goog.html.SafeStyle.sanitizePropertyValue_(value); } style += name + ':' + value + ';'; } @@ -376,6 +384,50 @@ goog.html.SafeStyle.create = function(map) { /** + * Checks and converts value to string. + * @param {!goog.html.SafeStyle.PropertyValue} value + * @return {string} + * @private + */ +goog.html.SafeStyle.sanitizePropertyValue_ = function(value) { + if (value instanceof goog.html.SafeUrl) { + var url = goog.html.SafeUrl.unwrap(value); + return 'url("' + url.replace(/</g, '%3c').replace(/[\\"]/g, '\\$&') + '")'; + } + var result = value instanceof goog.string.Const ? + goog.string.Const.unwrap(value) : + goog.html.SafeStyle.sanitizePropertyValueString_(String(value)); + // These characters can be used to change context and we don't want that even + // with const values. + goog.asserts.assert(!/[{;}]/.test(result), 'Value does not allow [{;}].'); + return result; +}; + + +/** + * Checks string value. + * @param {string} value + * @return {string} + * @private + */ +goog.html.SafeStyle.sanitizePropertyValueString_ = function(value) { + var valueWithoutFunctions = + value.replace(goog.html.SafeUrl.FUNCTIONS_RE_, '$1') + .replace(goog.html.SafeUrl.URL_RE_, 'url'); + if (!goog.html.SafeStyle.VALUE_RE_.test(valueWithoutFunctions)) { + goog.asserts.fail( + 'String value allows only ' + goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ + + ' and simple functions, got: ' + value); + return goog.html.SafeStyle.INNOCUOUS_STRING; + } else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) { + goog.asserts.fail('String value requires balanced quotes, got: ' + value); + return goog.html.SafeStyle.INNOCUOUS_STRING; + } + return goog.html.SafeStyle.sanitizeUrl_(value); +}; + + +/** * Checks that quotes (" and ') are properly balanced inside a string. Assumes * that neither escape (\) nor any other character that could result in * breaking out of a string parsing context are allowed; @@ -400,7 +452,13 @@ goog.html.SafeStyle.hasBalancedQuotes_ = function(value) { }; -// Keep in sync with the error string in create(). +/** + * Characters allowed in goog.html.SafeStyle.VALUE_RE_. + * @private {string} + */ +goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ = '[-,."\'%_!# a-zA-Z0-9]'; + + /** * Regular expression for safe values. * @@ -411,13 +469,66 @@ goog.html.SafeStyle.hasBalancedQuotes_ = function(value) { * (e.g. background-attachment or font-family) and hence could allow * multiple values to get injected, but that should pose no risk of XSS. * - * The rgb() and rgba() expression checks only for XSS safety, not for CSS - * validity. + * The expression checks only for XSS safety, not for CSS validity. * @const {!RegExp} * @private */ goog.html.SafeStyle.VALUE_RE_ = - /^([-,."'%_!# a-zA-Z0-9]+|(?:rgb|hsl)a?\([0-9.%, ]+\))$/; + new RegExp('^' + goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ + '+$'); + + +/** + * Regular expression for url(). We support URLs allowed by + * https://www.w3.org/TR/css-syntax-3/#url-token-diagram without using escape + * sequences. Use percent-encoding if you need to use special characters like + * backslash. + * @private @const {!RegExp} + */ +goog.html.SafeUrl.URL_RE_ = new RegExp( + '\\b(url\\([ \t\n]*)(' + + '\'[ -&(-\\[\\]-~]*\'' + // Printable characters except ' and \. + '|"[ !#-\\[\\]-~]*"' + // Printable characters except " and \. + '|[!#-&*-\\[\\]-~]*' + // Printable characters except [ "'()\\]. + ')([ \t\n]*\\))', + 'g'); + + +/** + * Regular expression for simple functions. + * @private @const {!RegExp} + */ +goog.html.SafeUrl.FUNCTIONS_RE_ = new RegExp( + '\\b(hsl|hsla|rgb|rgba|(rotate|scale|translate)(X|Y|Z|3d)?)' + + '\\([-0-9a-z.%, ]+\\)', + 'g'); + + +/** + * Sanitize URLs inside url(). + * + * NOTE: We could also consider using CSS.escape once that's available in the + * browsers. However, loosely matching URL e.g. with url\(.*\) and then escaping + * the contents would result in a slightly different language than CSS leading + * to confusion of users. E.g. url(")") is valid in CSS but it would be invalid + * as seen by our parser. On the other hand, url(\) is invalid in CSS but our + * parser would be fine with it. + * + * @param {string} value Untrusted CSS property value. + * @return {string} + * @private + */ +goog.html.SafeStyle.sanitizeUrl_ = function(value) { + return value.replace( + goog.html.SafeUrl.URL_RE_, function(match, before, url, after) { + var quote = ''; + url = url.replace(/^(['"])(.*)\1$/, function(match, start, inside) { + quote = start; + return inside; + }); + var sanitized = goog.html.SafeUrl.sanitize(url).getTypedStringValue(); + return before + quote + sanitized + quote + after; + }); +}; /** |