/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview * * NOTE (gbrodman) this file is taken from the open source version located at * https://github.com/google/closure-templates/blob/63f34802b543dadb72a078b006a0e7e4dbc71d7e/javascript/soyutils_usegoog.js * and slightly modified so that it compiles. * * Utility functions and classes for Soy gencode * *
* This file contains utilities that should only be called by Soy-generated * JS code. Please do not use these functions directly from * your hand-written code. Their names all start with '$$', or exist within the * soydata.VERY_UNSAFE namespace. * *
TODO(lukes): ensure that the above pattern is actually followed * consistently. * */ goog.provide('soy'); goog.provide('soy.asserts'); goog.provide('soy.esc'); goog.provide('soydata'); goog.provide('soydata.SanitizedHtml'); goog.provide('soydata.VERY_UNSAFE'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.debug'); goog.require('goog.format'); goog.require('goog.html.SafeHtml'); goog.require('goog.html.SafeScript'); goog.require('goog.html.SafeStyle'); goog.require('goog.html.SafeStyleSheet'); goog.require('goog.html.SafeUrl'); goog.require('goog.html.TrustedResourceUrl'); goog.require('goog.html.uncheckedconversions'); goog.require('goog.i18n.BidiFormatter'); goog.require('goog.i18n.bidi'); goog.require('goog.object'); goog.require('goog.soy.data.SanitizedContent'); goog.require('goog.soy.data.SanitizedContentKind'); goog.require('goog.soy.data.SanitizedCss'); goog.require('goog.soy.data.SanitizedHtml'); goog.require('goog.soy.data.SanitizedHtmlAttribute'); goog.require('goog.soy.data.SanitizedJs'); goog.require('goog.soy.data.SanitizedTrustedResourceUri'); goog.require('goog.soy.data.SanitizedUri'); goog.require('goog.soy.data.UnsanitizedText'); goog.require('goog.string'); goog.require('goog.string.Const'); // ----------------------------------------------------------------------------- // soydata: Defines typed strings, e.g. an HTML string {@code "ac"} is // semantically distinct from the plain text string {@code "ac"} and smart // templates can take that distinction into account. /** * Checks whether a given value is of a given content kind. * * @param {*} value The value to be examined. * @param {goog.soy.data.SanitizedContentKind} contentKind The desired content * kind. * @return {boolean} Whether the given value is of the given kind. * @private */ soydata.isContentKind_ = function(value, contentKind) { // TODO(user): This function should really include the assert on // value.constructor that is currently sprinkled at most of the call sites. // Unfortunately, that would require a (debug-mode-only) switch statement. // TODO(user): Perhaps we should get rid of the contentKind property // altogether and only at the constructor. return value != null && value.contentKind === contentKind; }; /** * Returns a given value's contentDir property, constrained to a * goog.i18n.bidi.Dir value or null. Returns null if the value is null, * undefined, a primitive or does not have a contentDir property, or the * property's value is not 1 (for LTR), -1 (for RTL), or 0 (for neutral). * * @param {*} value The value whose contentDir property, if any, is to * be returned. * @return {?goog.i18n.bidi.Dir} The contentDir property. */ soydata.getContentDir = function(value) { if (value != null) { switch (value.contentDir) { case goog.i18n.bidi.Dir.LTR: return goog.i18n.bidi.Dir.LTR; case goog.i18n.bidi.Dir.RTL: return goog.i18n.bidi.Dir.RTL; case goog.i18n.bidi.Dir.NEUTRAL: return goog.i18n.bidi.Dir.NEUTRAL; } } return null; }; /** * This class is only a holder for `soydata.SanitizedHtml.from`. Do not * instantiate or extend it. Use `goog.soy.data.SanitizedHtml` instead. * * @constructor * @extends {goog.soy.data.SanitizedHtml} * @abstract */ soydata.SanitizedHtml = function() { soydata.SanitizedHtml.base(this, 'constructor'); // Throws an exception. }; goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedHtml); /** * Returns a SanitizedHtml object for a particular value. The content direction * is preserved. * * This HTML-escapes the value unless it is already SanitizedHtml or SafeHtml. * * @param {*} value The value to convert. If it is already a SanitizedHtml * object, it is left alone. * @return {!goog.soy.data.SanitizedHtml} A SanitizedHtml object derived from * the stringified value. It is escaped unless the input is SanitizedHtml or * SafeHtml. */ soydata.SanitizedHtml.from = function(value) { // The check is soydata.isContentKind_() inlined for performance. if (value != null && value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); return /** @type {!goog.soy.data.SanitizedHtml} */ (value); } if (value instanceof goog.html.SafeHtml) { return soydata.VERY_UNSAFE.ordainSanitizedHtml( goog.html.SafeHtml.unwrap(value), value.getDirection()); } return soydata.VERY_UNSAFE.ordainSanitizedHtml( soy.esc.$$escapeHtmlHelper(String(value)), soydata.getContentDir(value)); }; /** * Empty string, used as a type in Soy templates. * @enum {string} * @private */ soydata.$$EMPTY_STRING_ = { VALUE: '' }; /** * Creates a factory for SanitizedContent types. * * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can * instantiate Sanitized* classes, without making the Sanitized* constructors * publicly usable. Requiring all construction to use the VERY_UNSAFE names * helps callers and their reviewers easily tell that creating SanitizedContent * is not always safe and calls for careful review. * * @param {function(new: T)} ctor A constructor. * @return {!function(*, ?goog.i18n.bidi.Dir=): T} A factory that takes * content and an optional content direction and returns a new instance. If * the content direction is undefined, ctor.prototype.contentDir is used. * @template T * @private */ soydata.$$makeSanitizedContentFactory_ = function(ctor) { /** * @param {string} content * @constructor * @extends {goog.soy.data.SanitizedContent} */ function InstantiableCtor(content) { /** @override */ this.content = content; } InstantiableCtor.prototype = ctor.prototype; /** * Creates a ctor-type SanitizedContent instance. * * @param {*} content The content to put in the instance. * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction. If * undefined, ctor.prototype.contentDir is used. * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually * of type T above (ctor's type, a descendant of SanitizedContent), but * there is no way to express that here. */ function sanitizedContentFactory(content, opt_contentDir) { var result = new InstantiableCtor(String(content)); if (opt_contentDir !== undefined) { result.contentDir = opt_contentDir; } return result; } return sanitizedContentFactory; }; /** * Creates a factory for SanitizedContent types that should always have their * default directionality. * * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can * instantiate Sanitized* classes, without making the Sanitized* constructors * publicly usable. Requiring all construction to use the VERY_UNSAFE names * helps callers and their reviewers easily tell that creating SanitizedContent * is not always safe and calls for careful review. * * @param {function(new: T, string)} ctor A constructor. * @return {!function(*): T} A factory that takes content and returns a new * instance (with default directionality, i.e. ctor.prototype.contentDir). * @template T * @private */ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_ = function(ctor) { /** * @param {string} content * @constructor * @extends {goog.soy.data.SanitizedContent} */ function InstantiableCtor(content) { /** @override */ this.content = content; } InstantiableCtor.prototype = ctor.prototype; /** * Creates a ctor-type SanitizedContent instance. * * @param {*} content The content to put in the instance. * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually * of type T above (ctor's type, a descendant of SanitizedContent), but * there is no way to express that here. */ function sanitizedContentFactory(content) { var result = new InstantiableCtor(String(content)); return result; } return sanitizedContentFactory; }; // ----------------------------------------------------------------------------- // Sanitized content ordainers. Please use these with extreme caution (with the // exception of markUnsanitizedText). A good recommendation is to limit usage // of these to just a handful of files in your source tree where usages can be // carefully audited. /** * Protects a string from being used in an noAutoescaped context. * * This is useful for content where there is significant risk of accidental * unescaped usage in a Soy template. A great case is for user-controlled * data that has historically been a source of vulernabilities. * * @param {*} content Text to protect. * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if * unknown and thus to be estimated when necessary. Default: null. * @return {!goog.soy.data.UnsanitizedText} A wrapper that is rejected by the * Soy noAutoescape print directive. */ soydata.markUnsanitizedText = function(content, opt_contentDir) { return new goog.soy.data.UnsanitizedText(content, opt_contentDir); }; /** * Takes a leap of faith that the provided content is "safe" HTML. * * @param {*} content A string of HTML that can safely be embedded in * a PCDATA context in your app. If you would be surprised to find that an * HTML sanitizer produced `s` (e.g. it runs code or fetches bad URLs) * and you wouldn't write a template that produces `s` on security or * privacy grounds, then don't pass `s` here. * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if * unknown and thus to be estimated when necessary. Default: null. * @return {!goog.soy.data.SanitizedHtml} Sanitized content wrapper that * indicates to Soy not to escape when printed as HTML. */ soydata.VERY_UNSAFE.ordainSanitizedHtml = soydata.$$makeSanitizedContentFactory_(goog.soy.data.SanitizedHtml); /** * Takes a leap of faith that the provided content is "safe" (non-attacker- * controlled, XSS-free) Javascript. * * @param {*} content Javascript source that when evaluated does not * execute any attacker-controlled scripts. * @return {!goog.soy.data.SanitizedJs} Sanitized content wrapper that indicates * to Soy not to escape when printed as Javascript source. */ soydata.VERY_UNSAFE.ordainSanitizedJs = soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( goog.soy.data.SanitizedJs); /** * Takes a leap of faith that the provided content is "safe" to use as a URI * in a Soy template. * * This creates a Soy SanitizedContent object which indicates to Soy there is * no need to escape it when printed as a URI (e.g. in an href or src * attribute), such as if it's already been encoded or if it's a Javascript: * URI. * * @param {*} content A chunk of URI that the caller knows is safe to * emit in a template. * @return {!goog.soy.data.SanitizedUri} Sanitized content wrapper that * indicates to Soy not to escape or filter when printed in URI context. */ soydata.VERY_UNSAFE.ordainSanitizedUri = soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( goog.soy.data.SanitizedUri); /** * Takes a leap of faith that the provided content is "safe" to use as a * TrustedResourceUri in a Soy template. * * This creates a Soy SanitizedContent object which indicates to Soy there is * no need to filter it when printed as a TrustedResourceUri. * * @param {*} content A chunk of TrustedResourceUri such as that the caller * knows is safe to emit in a template. * @return {!goog.soy.data.SanitizedTrustedResourceUri} Sanitized content * wrapper that indicates to Soy not to escape or filter when printed in * TrustedResourceUri context. */ soydata.VERY_UNSAFE.ordainSanitizedTrustedResourceUri = soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( goog.soy.data.SanitizedTrustedResourceUri); /** * Takes a leap of faith that the provided content is "safe" to use as an * HTML attribute. * * @param {*} content An attribute name and value, such as * {@code dir="ltr"}. * @return {!goog.soy.data.SanitizedHtmlAttribute} Sanitized content wrapper * that indicates to Soy not to escape when printed as an HTML attribute. */ soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute = soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( goog.soy.data.SanitizedHtmlAttribute); /** * Takes a leap of faith that the provided content is "safe" to use as CSS * in a style block. * * @param {*} content CSS, such as `color:#c3d9ff`. * @return {!goog.soy.data.SanitizedCss} Sanitized CSS wrapper that indicates to * Soy there is no need to escape or filter when printed in CSS context. */ soydata.VERY_UNSAFE.ordainSanitizedCss = soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( goog.soy.data.SanitizedCss); // ----------------------------------------------------------------------------- // Soy-generated utilities in the soy namespace. Contains implementations for // common soyfunctions (e.g. keys()) and escaping/print directives. /** * Whether the locale is right-to-left. * * @type {boolean} */ soy.$$IS_LOCALE_RTL = goog.i18n.bidi.IS_RTL; /** * Builds an augmented map. The returned map will contain mappings from both * the base map and the additional map. If the same key appears in both, then * the value from the additional map will be visible, while the value from the * base map will be hidden. The base map will be used, but not modified. * * @param {!Object} baseMap The original map to augment. * @param {!Object} additionalMap A map containing the additional mappings. * @return {!Object} An augmented map containing both the original and * additional mappings. */ soy.$$augmentMap = function(baseMap, additionalMap) { return soy.$$assignDefaults(soy.$$assignDefaults({}, additionalMap), baseMap); }; /** * Copies extra properties into an object if they do not already exist. The * destination object is mutated in the process. * * @param {!Object} obj The destination object to update. * @param {!Object} defaults An object with default properties to apply. * @return {!Object} The destination object for convenience. */ soy.$$assignDefaults = function(obj, defaults) { for (var key in defaults) { if (!(key in obj)) { obj[key] = defaults[key]; } } return obj; }; /** * Checks that the given map key is a string. * *
This is used to validate keys for legacy object map literals.
*
* @param {*} key Key to check.
* @return {string} The given key.
*/
soy.$$checkLegacyObjectMapLiteralKey = function(key) {
if ((typeof key) != 'string') {
throw Error(
'Map literal\'s key expression must evaluate to string' +
' (encountered type "' + (typeof key) + '").');
}
return key;
};
/**
* Gets the keys in a map as an array. There are no guarantees on the order.
* @param {Object} map The map to get the keys of.
* @return {!Array Important: This function must always be called with a string constant.
*
* If Closure Compiler is not being used, then this is just this identity
* function. If Closure Compiler is being used, then each call to this function
* will be replaced with a short string constant, which will be consistent per
* input name.
*
* @param {string} delTemplateName The delegate template name for which to get a
* consistent unique id.
* @return {string} A unique id that is consistent per input name.
*
* @idGenerator {consistent}
*/
soy.$$getDelTemplateId = function(delTemplateName) {
return delTemplateName;
};
/**
* Map from registered delegate template key to the priority of the
* implementation.
* @type {Object}
* @private
*/
soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {};
/**
* Map from registered delegate template key to the implementation function.
* @type {Object}
* @private
*/
soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {};
/**
* Registers a delegate implementation. If the same delegate template key (id
* and variant) has been registered previously, then priority values are
* compared and only the higher priority implementation is stored (if
* priorities are equal, an error is thrown).
*
* @param {string} delTemplateId The delegate template id.
* @param {string} delTemplateVariant The delegate template variant (can be
* empty string).
* @param {number} delPriority The implementation's priority value.
* @param {Function} delFn The implementation function.
*/
soy.$$registerDelegateFn = function(
delTemplateId, delTemplateVariant, delPriority, delFn) {
var mapKey = 'key_' + delTemplateId + ':' + delTemplateVariant;
var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey];
if (currPriority === undefined || delPriority > currPriority) {
// Registering new or higher-priority function: replace registry entry.
soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey] = delPriority;
soy.$$DELEGATE_REGISTRY_FUNCTIONS_[mapKey] = delFn;
} else if (delPriority == currPriority) {
// Registering same-priority function: error.
throw Error(
'Encountered two active delegates with the same priority ("' +
delTemplateId + ':' + delTemplateVariant + '").');
} else {
// Registering lower-priority function: do nothing.
}
};
/**
* Retrieves the (highest-priority) implementation that has been registered for
* a given delegate template key (id and variant). If no implementation has
* been registered for the key, then the fallback is the same id with empty
* variant. If the fallback is also not registered, and allowsEmptyDefault is
* true, then returns an implementation that is equivalent to an empty template
* (i.e. rendered output would be empty string).
*
* @param {string} delTemplateId The delegate template id.
* @param {string} delTemplateVariant The delegate template variant (can be
* empty string).
* @param {boolean} allowsEmptyDefault Whether to default to the empty template
* function if there's no active implementation.
* @return {Function} The retrieved implementation function.
*/
soy.$$getDelegateFn = function(
delTemplateId, delTemplateVariant, allowsEmptyDefault) {
var delFn =
soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':' + delTemplateVariant];
if (!delFn && delTemplateVariant != '') {
// Fallback to empty variant.
delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':'];
}
if (delFn) {
return delFn;
} else if (allowsEmptyDefault) {
return soy.$$EMPTY_TEMPLATE_FN_;
} else {
throw Error(
'Found no active impl for delegate call to "' + delTemplateId +
(delTemplateVariant ? ':' + delTemplateVariant : '') +
'" (and delcall does not set allowemptydefault="true").');
}
};
/**
* Private helper soy.$$getDelegateFn(). This is the empty template function
* that is returned whenever there's no delegate implementation found.
*
* @param {Object
* Escapes HTML special characters so that the value will not prematurely end
* the body of a tag like {@code }.
*
* Will normalize known safe HTML to make sure that sanitized HTML (which could
* contain an innocuous {@code } don't prematurely end an RCDATA
* element.
*
* @param {?} value The string-like value to be escaped. May not be a string,
* but the value will be coerced to a string.
* @return {string} An escaped version of value.
*/
soy.$$escapeHtmlRcdata = function(value) {
if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) {
goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml);
return soy.esc.$$normalizeHtmlHelper(value.getContent());
}
return soy.esc.$$escapeHtmlHelper(value);
};
/**
* Matches any/only HTML5 void elements' start tags.
* See http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
* @type {RegExp}
* @private
*/
soy.$$HTML5_VOID_ELEMENTS_ = new RegExp(
'^<(?:area|base|br|col|command|embed|hr|img|input' +
'|keygen|link|meta|param|source|track|wbr)\\b');
/**
* Removes HTML tags from a string of known safe HTML.
* If opt_tagWhitelist is not specified or is empty, then
* the result can be used as an attribute value.
*
* @param {*} value The HTML to be escaped. May not be a string, but the
* value will be coerced to a string.
* @param {Object and
from
// breaking the layout of containing HTML.
return html + finalCloseTags;
};
/**
* Make sure that tag boundaries are not broken by Safe CSS when embedded in a
* {@code