UNPKG

closure-builder

Version:

Simple Closure, Soy and JavaScript Build system

1,490 lines (1,308 loc) 112 kB
/* * 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 * Utility functions and classes for Soy. * * <p> * The top portion of this file contains utilities for Soy users:<ul> * <li> soy.StringBuilder: Compatible with the 'stringbuilder' code style. * <li> soy.renderElement: Render template and set as innerHTML of an element. * <li> soy.renderAsFragment: Render template and return as HTML fragment. * </ul> * * <p> * The bottom portion of this file contains utilities that should only be called * by Soy-generated JS code. Please do not use these functions directly from * your hand-writen code. Their names all start with '$$'. * * @author Garrett Boyer * @author Mike Samuel * @author Kai Huang * @author Aharon Lanin */ // COPIED FROM nogoog_shim.js // Create closure namespaces. var goog = goog || {}; goog.DEBUG = false; goog.inherits = function(childCtor, parentCtor) { /** @constructor */ function tempCtor() {}; tempCtor.prototype = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype; childCtor.prototype = new tempCtor(); childCtor.prototype.constructor = childCtor; /** * Calls superclass constructor/method. * @param {!Object} me Should always be "this". * @param {string} methodName * @param {...*} var_args * @return {?} The return value of the superclass method/constructor. */ childCtor.base = function(me, methodName, var_args) { var args = Array.prototype.slice.call(arguments, 2); return parentCtor.prototype[methodName].apply(me, args); }; }; // Just enough browser detection for this file. if (!goog.userAgent) { goog.userAgent = (function() { var userAgent = ""; if ("undefined" !== typeof navigator && navigator && "string" == typeof navigator.userAgent) { userAgent = navigator.userAgent; } var isOpera = userAgent.indexOf('Opera') == 0; return { jscript: { /** * @type {boolean} */ HAS_JSCRIPT: 'ScriptEngine' in this }, /** * @type {boolean} */ OPERA: isOpera, /** * @type {boolean} */ IE: !isOpera && userAgent.indexOf('MSIE') != -1, /** * @type {boolean} */ WEBKIT: !isOpera && userAgent.indexOf('WebKit') != -1 }; })(); } if (!goog.asserts) { goog.asserts = { /** * @param {*} condition Condition to check. */ assert: function (condition) { if (!condition) { throw Error('Assertion error'); } }, /** * @param {...*} var_args */ fail: function (var_args) {} }; } // Stub out the document wrapper used by renderAs*. if (!goog.dom) { goog.dom = {}; /** * @param {Document=} d * @constructor */ goog.dom.DomHelper = function(d) { this.document_ = d || document; }; /** * @return {!Document} */ goog.dom.DomHelper.prototype.getDocument = function() { return this.document_; }; /** * Creates a new element. * @param {string} name Tag name. * @return {!Element} */ goog.dom.DomHelper.prototype.createElement = function(name) { return this.document_.createElement(name); }; /** * Creates a new document fragment. * @return {!DocumentFragment} */ goog.dom.DomHelper.prototype.createDocumentFragment = function() { return this.document_.createDocumentFragment(); }; } if (!goog.format) { goog.format = { insertWordBreaks: function(str, maxCharsBetweenWordBreaks) { str = String(str); var resultArr = []; var resultArrLen = 0; // These variables keep track of important state inside str. var isInTag = false; // whether we're inside an HTML tag var isMaybeInEntity = false; // whether we might be inside an HTML entity var numCharsWithoutBreak = 0; // number of chars since last word break var flushIndex = 0; // index of first char not yet flushed to resultArr for (var i = 0, n = str.length; i < n; ++i) { var charCode = str.charCodeAt(i); // If hit maxCharsBetweenWordBreaks, and not space next, then add <wbr>. if (numCharsWithoutBreak >= maxCharsBetweenWordBreaks && // space charCode != 32) { resultArr[resultArrLen++] = str.substring(flushIndex, i); flushIndex = i; resultArr[resultArrLen++] = goog.format.WORD_BREAK; numCharsWithoutBreak = 0; } if (isInTag) { // If inside an HTML tag and we see '>', it's the end of the tag. if (charCode == 62) { isInTag = false; } } else if (isMaybeInEntity) { switch (charCode) { // Inside an entity, a ';' is the end of the entity. // The entity that just ended counts as one char, so increment // numCharsWithoutBreak. case 59: // ';' isMaybeInEntity = false; ++numCharsWithoutBreak; break; // If maybe inside an entity and we see '<', we weren't actually in // an entity. But now we're inside and HTML tag. case 60: // '<' isMaybeInEntity = false; isInTag = true; break; // If maybe inside an entity and we see ' ', we weren't actually in // an entity. Just correct the state and reset the // numCharsWithoutBreak since we just saw a space. case 32: // ' ' isMaybeInEntity = false; numCharsWithoutBreak = 0; break; } } else { // !isInTag && !isInEntity switch (charCode) { // When not within a tag or an entity and we see '<', we're now // inside an HTML tag. case 60: // '<' isInTag = true; break; // When not within a tag or an entity and we see '&', we might be // inside an entity. case 38: // '&' isMaybeInEntity = true; break; // When we see a space, reset the numCharsWithoutBreak count. case 32: // ' ' numCharsWithoutBreak = 0; break; // When we see a non-space, increment the numCharsWithoutBreak. default: ++numCharsWithoutBreak; break; } } } // Flush the remaining chars at the end of the string. resultArr[resultArrLen++] = str.substring(flushIndex); return resultArr.join(''); }, /** * String inserted as a word break by insertWordBreaks(). Safari requires * <wbr></wbr>, Opera needs the &shy; entity, though this will give a * visible hyphen at breaks. IE8+ use a zero width space. Other browsers * just use <wbr>. * @type {string} * @private */ WORD_BREAK: goog.userAgent.WEBKIT ? '<wbr></wbr>' : goog.userAgent.OPERA ? '&shy;' : goog.userAgent.IE ? '&#8203;' : '<wbr>' }; } if (!goog.i18n) { goog.i18n = { bidi: {} }; } /** * Constant that defines whether or not the current locale is an RTL locale. * * @type {boolean} */ goog.i18n.bidi.IS_RTL = false; /** * Directionality enum. * @enum {number} */ goog.i18n.bidi.Dir = { /** * Left-to-right. */ LTR: 1, /** * Right-to-left. */ RTL: -1, /** * Neither left-to-right nor right-to-left. */ NEUTRAL: 0, /** * A historical misnomer for NEUTRAL. * @deprecated For "neutral", use NEUTRAL; for "unknown", use null. */ UNKNOWN: 0 }; /** * Convert a directionality given in various formats to a goog.i18n.bidi.Dir * constant. Useful for interaction with different standards of directionality * representation. * * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given * in one of the following formats: * 1. A goog.i18n.bidi.Dir constant. * 2. A number (positive = LTR, negative = RTL, 0 = neutral). * 3. A boolean (true = RTL, false = LTR). * 4. A null for unknown directionality. * @param {boolean=} opt_noNeutral Whether a givenDir of zero or * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in * order to preserve legacy behavior. * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the * given directionality. If given null, returns null (i.e. unknown). */ goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) { if (typeof givenDir == 'number') { // This includes the non-null goog.i18n.bidi.Dir case. return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : givenDir < 0 ? goog.i18n.bidi.Dir.RTL : opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL; } else if (givenDir == null) { return null; } else { // Must be typeof givenDir == 'boolean'. return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; } }; /** * Estimates the directionality of a string based on relative word counts. * If the number of RTL words is above a certain percentage of the total number * of strongly directional words, returns RTL. * Otherwise, if any words are strongly or weakly LTR, returns LTR. * Otherwise, returns NEUTRAL. * Numbers are counted as weakly LTR. * @param {string} str The string to be checked. * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. * Default: false. * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. */ goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) { var rtlCount = 0; var totalCount = 0; var hasWeaklyLtr = false; var tokens = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml). split(soyshim.$$bidiWordSeparatorRe_); for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (soyshim.$$bidiRtlDirCheckRe_.test(token)) { rtlCount++; totalCount++; } else if (soyshim.$$bidiIsRequiredLtrRe_.test(token)) { hasWeaklyLtr = true; } else if (soyshim.$$bidiLtrCharRe_.test(token)) { totalCount++; } else if (soyshim.$$bidiHasNumeralsRe_.test(token)) { hasWeaklyLtr = true; } } return totalCount == 0 ? (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) : (rtlCount / totalCount > soyshim.$$bidiRtlDetectionThreshold_ ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR); }; /** * Utility class for formatting text for display in a potentially * opposite-directionality context without garbling. Provides the following * functionality: * * @param {goog.i18n.bidi.Dir|number|boolean|null} dir The context * directionality, in one of the following formats: * 1. A goog.i18n.bidi.Dir constant. NEUTRAL is treated the same as null, * i.e. unknown, for backward compatibility with legacy calls. * 2. A number (positive = LTR, negative = RTL, 0 = unknown). * 3. A boolean (true = RTL, false = LTR). * 4. A null for unknown directionality. * @constructor */ goog.i18n.BidiFormatter = function(dir) { /** * The overall directionality of the context in which the formatter is being * used. * @type {?goog.i18n.bidi.Dir} * @private */ this.dir_ = goog.i18n.bidi.toDir(dir, true /* opt_noNeutral */); }; /** * @return {?goog.i18n.bidi.Dir} The context directionality. */ goog.i18n.BidiFormatter.prototype.getContextDir = function() { return this.dir_; }; /** * Returns 'dir="ltr"' or 'dir="rtl"', depending on the given directionality, if * it is not the same as the context directionality. Otherwise, returns the * empty string. * * @param {goog.i18n.bidi.Dir} dir A directionality. * @return {string} 'dir="rtl"' for RTL text in non-RTL context; 'dir="ltr"' for * LTR text in non-LTR context; else, the empty string. */ goog.i18n.BidiFormatter.prototype.knownDirAttr = function(dir) { return !dir || dir == this.dir_ ? '' : dir < 0 ? 'dir="rtl"' : 'dir="ltr"'; }; /** * Returns the trailing horizontal edge, i.e. "right" or "left", depending on * the global bidi directionality. * @return {string} "left" for RTL context and "right" otherwise. */ goog.i18n.BidiFormatter.prototype.endEdge = function () { return this.dir_ < 0 ? 'left' : 'right'; }; /** * Returns the Unicode BiDi mark matching the context directionality (LRM for * LTR context directionality, RLM for RTL context directionality), or the * empty string for unknown context directionality. * * @return {string} LRM for LTR context directionality and RLM for RTL context * directionality. */ goog.i18n.BidiFormatter.prototype.mark = function () { return ( (this.dir_ > 0) ? '\u200E' /*LRM*/ : (this.dir_ < 0) ? '\u200F' /*RLM*/ : ''); }; /** * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) * if the directionality or the exit directionality of {@code text} are opposite * to the context directionality. Otherwise returns the empty string. * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes * in text, making the logic suitable for HTML and HTML-escaped text. * @param {?goog.i18n.bidi.Dir} textDir {@code text}'s overall directionality, * or null if unknown and needs to be estimated. * @param {string} text The text whose directionality is to be estimated. * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. * Default: false. * @return {string} A Unicode bidi mark matching the context directionality, or * the empty string when either the context directionality is unknown or * neither the text's overall nor its exit directionality is opposite to it. */ goog.i18n.BidiFormatter.prototype.markAfterKnownDir = function ( textDir, text, opt_isHtml) { if (textDir == null) { textDir = goog.i18n.bidi.estimateDirection(text, opt_isHtml); } return ( this.dir_ > 0 && (textDir < 0 || soyshim.$$bidiIsRtlExitText_(text, opt_isHtml)) ? '\u200E' : // LRM this.dir_ < 0 && (textDir > 0 || soyshim.$$bidiIsLtrExitText_(text, opt_isHtml)) ? '\u200F' : // RLM ''); }; /** * Formats an HTML string for use in HTML output of the context directionality, * so an opposite-directionality string is neither garbled nor garbles what * follows it. * * @param {?goog.i18n.bidi.Dir} textDir {@code str}'s overall directionality, or * null if unknown and needs to be estimated. * @param {string} str The input text (HTML or HTML-escaped). * @param {boolean=} placeholder This argument exists for consistency with the * Closure Library. Specifying it has no effect. * @return {string} The input text after applying the above processing. */ goog.i18n.BidiFormatter.prototype.spanWrapWithKnownDir = function( textDir, str, placeholder) { if (textDir == null) { textDir = goog.i18n.bidi.estimateDirection(str, true); } var reset = this.markAfterKnownDir(textDir, str, true); if (textDir > 0 && this.dir_ <= 0) { str = '<span dir="ltr">' + str + '</span>'; } else if (textDir < 0 && this.dir_ >= 0) { str = '<span dir="rtl">' + str + '</span>'; } return str + reset; }; /** * Returns the leading horizontal edge, i.e. "left" or "right", depending on * the global bidi directionality. * @return {string} "right" for RTL context and "left" otherwise. */ goog.i18n.BidiFormatter.prototype.startEdge = function () { return this.dir_ < 0 ? 'right' : 'left'; }; /** * Formats an HTML-escaped string for use in HTML output of the context * directionality, so an opposite-directionality string is neither garbled nor * garbles what follows it. * As opposed to {@link #spanWrapWithKnownDir}, this makes use of unicode bidi * formatting characters. In HTML, it should only be used inside attribute * values and elements that do not allow markup, e.g. an 'option' tag. * * @param {?goog.i18n.bidi.Dir} textDir {@code str}'s overall directionality, or * null if unknown and needs to be estimated. * @param {string} str The input text (HTML-escaped). * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. * Default: false. * @return {string} The input text after applying the above processing. */ goog.i18n.BidiFormatter.prototype.unicodeWrapWithKnownDir = function( textDir, str, opt_isHtml) { if (textDir == null) { textDir = goog.i18n.bidi.estimateDirection(str, opt_isHtml); } var reset = this.markAfterKnownDir(textDir, str, opt_isHtml); if (textDir > 0 && this.dir_ <= 0) { str = '\u202A' + str + '\u202C'; } else if (textDir < 0 && this.dir_ >= 0) { str = '\u202B' + str + '\u202C'; } return str + reset; }; if (!goog.string) { goog.string = { /** * Converts \r\n, \r, and \n to <br>s * @param {*} str The string in which to convert newlines. * @param {boolean=} opt_xml Whether to use XML compatible tags. * @return {string} A copy of {@code str} with converted newlines. */ newLineToBr: function(str, opt_xml) { str = String(str); // This quick test helps in the case when there are no chars to replace, // in the worst case this makes barely a difference to the time taken. if (!goog.string.NEWLINE_TO_BR_RE_.test(str)) { return str; } return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>'); }, urlEncode: encodeURIComponent, /** * Regular expression used within newlineToBr(). * @type {RegExp} * @private */ NEWLINE_TO_BR_RE_: /[\r\n]/ }; } /** * Utility class to facilitate much faster string concatenation in IE, * using Array.join() rather than the '+' operator. For other browsers * we simply use the '+' operator. * * @param {Object|number|string|boolean=} opt_a1 Optional first initial item * to append. * @param {...Object|number|string|boolean} var_args Other initial items to * append, e.g., new goog.string.StringBuffer('foo', 'bar'). * @constructor */ goog.string.StringBuffer = function(opt_a1, var_args) { /** * Internal buffer for the string to be concatenated. * @type {string|Array} * @private */ this.buffer_ = goog.userAgent.jscript.HAS_JSCRIPT ? [] : ''; if (opt_a1 != null) { this.append.apply(this, arguments); } }; /** * Length of internal buffer (faster than calling buffer_.length). * Only used for IE. * @type {number} * @private */ goog.string.StringBuffer.prototype.bufferLength_ = 0; /** * Appends one or more items to the string. * * Calling this with null, undefined, or empty arguments is an error. * * @param {Object|number|string|boolean} a1 Required first string. * @param {Object|number|string|boolean=} opt_a2 Optional second string. * @param {...Object|number|string|boolean} var_args Other items to append, * e.g., sb.append('foo', 'bar', 'baz'). * @return {goog.string.StringBuffer} This same StringBuilder object. */ goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) { if (goog.userAgent.jscript.HAS_JSCRIPT) { if (opt_a2 == null) { // no second argument (note: undefined == null) // Array assignment is 2x faster than Array push. Also, use a1 // directly to avoid arguments instantiation, another 2x improvement. this.buffer_[this.bufferLength_++] = a1; } else { var arr = /**@type {Array.<number|string|boolean>}*/(this.buffer_); arr.push.apply(arr, arguments); this.bufferLength_ = this.buffer_.length; } } else { // Use a1 directly to avoid arguments instantiation for single-arg case. this.buffer_ += a1; if (opt_a2 != null) { // no second argument (note: undefined == null) for (var i = 1; i < arguments.length; i++) { this.buffer_ += arguments[i]; } } } return this; }; /** * Clears the string. */ goog.string.StringBuffer.prototype.clear = function() { if (goog.userAgent.jscript.HAS_JSCRIPT) { this.buffer_.length = 0; // reuse array to avoid creating new object this.bufferLength_ = 0; } else { this.buffer_ = ''; } }; /** * Returns the concatenated string. * * @return {string} The concatenated string. */ goog.string.StringBuffer.prototype.toString = function() { if (goog.userAgent.jscript.HAS_JSCRIPT) { var str = this.buffer_.join(''); // Given a string with the entire contents, simplify the StringBuilder by // setting its contents to only be this string, rather than many fragments. this.clear(); if (str) { this.append(str); } return str; } else { return /** @type {string} */ (this.buffer_); } }; if (!goog.soy) goog.soy = { /** * Helper function to render a Soy template and then set the * output string as the innerHTML of an element. It is recommended * to use this helper function instead of directly setting * innerHTML in your hand-written code, so that it will be easier * to audit the code for cross-site scripting vulnerabilities. * * @param {Function} template The Soy template defining element's content. * @param {Object=} opt_templateData The data for the template. * @param {Object=} opt_injectedData The injected data for the template. * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM * nodes will be created. */ renderAsElement: function( template, opt_templateData, opt_injectedData, opt_dom) { return /** @type {!Element} */ (soyshim.$$renderWithWrapper_( template, opt_templateData, opt_dom, true /* asElement */, opt_injectedData)); }, /** * Helper function to render a Soy template into a single node or * a document fragment. If the rendered HTML string represents a * single node, then that node is returned (note that this is * *not* a fragment, despite them name of the method). Otherwise a * document fragment is returned containing the rendered nodes. * * @param {Function} template The Soy template defining element's content. * @param {Object=} opt_templateData The data for the template. * @param {Object=} opt_injectedData The injected data for the template. * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM * nodes will be created. * @return {!Node} The resulting node or document fragment. */ renderAsFragment: function( template, opt_templateData, opt_injectedData, opt_dom) { return soyshim.$$renderWithWrapper_( template, opt_templateData, opt_dom, false /* asElement */, opt_injectedData); }, /** * Helper function to render a Soy template and then set the output string as * the innerHTML of an element. It is recommended to use this helper function * instead of directly setting innerHTML in your hand-written code, so that it * will be easier to audit the code for cross-site scripting vulnerabilities. * * NOTE: New code should consider using goog.soy.renderElement instead. * * @param {Element} element The element whose content we are rendering. * @param {Function} template The Soy template defining the element's content. * @param {Object=} opt_templateData The data for the template. * @param {Object=} opt_injectedData The injected data for the template. */ renderElement: function( element, template, opt_templateData, opt_injectedData) { element.innerHTML = template(opt_templateData, null, opt_injectedData); }, data: {} }; /** * A type of textual content. * * This is an enum of type Object so that these values are unforgeable. * * @enum {!Object} */ goog.soy.data.SanitizedContentKind = { /** * A snippet of HTML that does not start or end inside a tag, comment, entity, * or DOCTYPE; and that does not contain any executable code * (JS, {@code <object>}s, etc.) from a different trust domain. */ HTML: goog.DEBUG ? {sanitizedContentKindHtml: true} : {}, /** * Executable Javascript code or expression, safe for insertion in a * script-tag or event handler context, known to be free of any * attacker-controlled scripts. This can either be side-effect-free * Javascript (such as JSON) or Javascript that's entirely under Google's * control. */ JS: goog.DEBUG ? {sanitizedContentJsChars: true} : {}, /** * A sequence of code units that can appear between quotes (either kind) in a * JS program without causing a parse error, and without causing any side * effects. * <p> * The content should not contain unescaped quotes, newlines, or anything else * that would cause parsing to fail or to cause a JS parser to finish the * string its parsing inside the content. * <p> * The content must also not end inside an escape sequence ; no partial octal * escape sequences or odd number of '{@code \}'s at the end. */ JS_STR_CHARS: goog.DEBUG ? {sanitizedContentJsStrChars: true} : {}, /** A properly encoded portion of a URI. */ URI: goog.DEBUG ? {sanitizedContentUri: true} : {}, /** * Repeated attribute names and values. For example, * {@code dir="ltr" foo="bar" onclick="trustedFunction()" checked}. */ ATTRIBUTES: goog.DEBUG ? {sanitizedContentHtmlAttribute: true} : {}, // TODO: Consider separating rules, declarations, and values into // separate types, but for simplicity, we'll treat explicitly blessed // SanitizedContent as allowed in all of these contexts. /** * A CSS3 declaration, property, value or group of semicolon separated * declarations. */ CSS: goog.DEBUG ? {sanitizedContentCss: true} : {}, /** * Unsanitized plain-text content. * * This is effectively the "null" entry of this enum, and is sometimes used * to explicitly mark content that should never be used unescaped. Since any * string is safe to use as text, being of ContentKind.TEXT makes no * guarantees about its safety in any other context such as HTML. */ TEXT: goog.DEBUG ? {sanitizedContentKindText: true} : {} }; /** * A string-like object that carries a content-type and a content direction. * * IMPORTANT! Do not create these directly, nor instantiate the subclasses. * Instead, use a trusted, centrally reviewed library as endorsed by your team * to generate these objects. Otherwise, you risk accidentally creating * SanitizedContent that is attacker-controlled and gets evaluated unescaped in * templates. * * @constructor */ goog.soy.data.SanitizedContent = function() { throw Error('Do not instantiate directly'); }; /** * The context in which this content is safe from XSS attacks. * @type {goog.soy.data.SanitizedContentKind} */ goog.soy.data.SanitizedContent.prototype.contentKind; /** * The content's direction; null if unknown and thus to be estimated when * necessary. * @type {?goog.i18n.bidi.Dir} */ goog.soy.data.SanitizedContent.prototype.contentDir = null; /** * The already-safe content. * @type {string} */ goog.soy.data.SanitizedContent.prototype.content; /** @override */ goog.soy.data.SanitizedContent.prototype.toString = function() { return this.content; }; var soy = { esc: {} }; var soydata = {}; soydata.VERY_UNSAFE = {}; var soyshim = { $$DEFAULT_TEMPLATE_DATA_: {} }; /** * Helper function to render a Soy template into a single node or a document * fragment. If the rendered HTML string represents a single node, then that * node is returned. Otherwise a document fragment is created and returned * (wrapped in a DIV element if #opt_singleNode is true). * * @param {Function} template The Soy template defining the element's content. * @param {Object=} opt_templateData The data for the template. * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM * nodes will be created. * @param {boolean=} opt_asElement Whether to wrap the fragment in an * element if the template does not render a single element. If true, * result is always an Element. * @param {Object=} opt_injectedData The injected data for the template. * @return {!Node} The resulting node or document fragment. * @private */ soyshim.$$renderWithWrapper_ = function( template, opt_templateData, opt_dom, opt_asElement, opt_injectedData) { var dom = opt_dom || document; var wrapper = dom.createElement('div'); wrapper.innerHTML = template( opt_templateData || soyshim.$$DEFAULT_TEMPLATE_DATA_, undefined, opt_injectedData); // If the template renders as a single element, return it. if (wrapper.childNodes.length == 1) { var firstChild = wrapper.firstChild; if (!opt_asElement || firstChild.nodeType == 1 /* Element */) { return /** @type {!Node} */ (firstChild); } } // If we're forcing it to be a single element, return the wrapper DIV. if (opt_asElement) { return wrapper; } // Otherwise, create and return a fragment. var fragment = dom.createDocumentFragment(); while (wrapper.firstChild) { fragment.appendChild(wrapper.firstChild); } return fragment; }; /** * Strips str of any HTML mark-up and escapes. Imprecise in several ways, but * precision is not very important, since the result is only meant to be used * for directionality detection. * Based on goog.i18n.bidi.stripHtmlIfNeeded_(). * @param {string} str The string to be stripped. * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. * Default: false. * @return {string} The stripped string. * @private */ soyshim.$$bidiStripHtmlIfNecessary_ = function(str, opt_isHtml) { return opt_isHtml ? str.replace(soyshim.$$BIDI_HTML_SKIP_RE_, '') : str; }; /** * Simplified regular expression for am HTML tag (opening or closing) or an HTML * escape - the things we want to skip over in order to ignore their ltr * characters. * Copied from goog.i18n.bidi.htmlSkipReg_. * @type {RegExp} * @private */ soyshim.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g; /** * A practical pattern to identify strong LTR character. This pattern is not * theoretically correct according to unicode standard. It is simplified for * performance and small code size. * Copied from goog.i18n.bidi.ltrChars_. * @type {string} * @private */ soyshim.$$bidiLtrChars_ = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF'; /** * A practical pattern to identify strong RTL character. This pattern is not * theoretically correct according to unicode standard. It is simplified for * performance and small code size. * Copied from goog.i18n.bidi.rtlChars_. * @type {string} * @private */ soyshim.$$bidiRtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC'; /** * Regular expressions to check if a piece of text is of RTL directionality * on first character with strong directionality. * Based on goog.i18n.bidi.rtlDirCheckRe_. * @type {RegExp} * @private */ soyshim.$$bidiRtlDirCheckRe_ = new RegExp( '^[^' + soyshim.$$bidiLtrChars_ + ']*[' + soyshim.$$bidiRtlChars_ + ']'); /** * Regular expression to check for LTR characters. * Based on goog.i18n.bidi.ltrCharReg_. * @type {RegExp} * @private */ soyshim.$$bidiLtrCharRe_ = new RegExp('[' + soyshim.$$bidiLtrChars_ + ']'); /** * Regular expression to check if a string looks like something that must * always be LTR even in RTL text, e.g. a URL. When estimating the * directionality of text containing these, we treat these as weakly LTR, * like numbers. * Copied from goog.i18n.bidi.isRequiredLtrRe_. * @type {RegExp} * @private */ soyshim.$$bidiIsRequiredLtrRe_ = /^http:\/\/.*/; /** * Regular expression to check if a string contains any numerals. Used to * differentiate between completely neutral strings and those containing * numbers, which are weakly LTR. * Copied from goog.i18n.bidi.hasNumeralsRe_. * @type {RegExp} * @private */ soyshim.$$bidiHasNumeralsRe_ = /\d/; /** * Regular expression to split a string into "words" for directionality * estimation based on relative word counts. * Copied from goog.i18n.bidi.wordSeparatorRe_. * @type {RegExp} * @private */ soyshim.$$bidiWordSeparatorRe_ = /\s+/; /** * This constant controls threshold of rtl directionality. * Copied from goog.i18n.bidi.rtlDetectionThreshold_. * @type {number} * @private */ soyshim.$$bidiRtlDetectionThreshold_ = 0.40; /** * Regular expressions to check if the last strongly-directional character in a * piece of text is LTR. * Based on goog.i18n.bidi.ltrExitDirCheckRe_. * @type {RegExp} * @private */ soyshim.$$bidiLtrExitDirCheckRe_ = new RegExp( '[' + soyshim.$$bidiLtrChars_ + '][^' + soyshim.$$bidiRtlChars_ + ']*$'); /** * Regular expressions to check if the last strongly-directional character in a * piece of text is RTL. * Based on goog.i18n.bidi.rtlExitDirCheckRe_. * @type {RegExp} * @private */ soyshim.$$bidiRtlExitDirCheckRe_ = new RegExp( '[' + soyshim.$$bidiRtlChars_ + '][^' + soyshim.$$bidiLtrChars_ + ']*$'); /** * Check if the exit directionality a piece of text is LTR, i.e. if the last * strongly-directional character in the string is LTR. * Based on goog.i18n.bidi.endsWithLtr(). * @param {string} str string being checked. * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. * Default: false. * @return {boolean} Whether LTR exit directionality was detected. * @private */ soyshim.$$bidiIsLtrExitText_ = function(str, opt_isHtml) { str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml); return soyshim.$$bidiLtrExitDirCheckRe_.test(str); }; /** * Check if the exit directionality a piece of text is RTL, i.e. if the last * strongly-directional character in the string is RTL. * Based on goog.i18n.bidi.endsWithRtl(). * @param {string} str string being checked. * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. * Default: false. * @return {boolean} Whether RTL exit directionality was detected. * @private */ soyshim.$$bidiIsRtlExitText_ = function(str, opt_isHtml) { str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml); return soyshim.$$bidiRtlExitDirCheckRe_.test(str); }; // ============================================================================= // COPIED FROM soyutils_usegoog.js // ----------------------------------------------------------------------------- // StringBuilder (compatible with the 'stringbuilder' code style). /** * Utility class to facilitate much faster string concatenation in IE, * using Array.join() rather than the '+' operator. For other browsers * we simply use the '+' operator. * * @param {Object} var_args Initial items to append, * e.g., new soy.StringBuilder('foo', 'bar'). * @constructor */ soy.StringBuilder = goog.string.StringBuffer; // ----------------------------------------------------------------------------- // soydata: Defines typed strings, e.g. an HTML string {@code "a<b>c"} is // semantically distinct from the plain text string {@code "a<b>c"} and smart // templates can take that distinction into account. /** * A type of textual content. * * This is an enum of type Object so that these values are unforgeable. * * @enum {!Object} */ soydata.SanitizedContentKind = goog.soy.data.SanitizedContentKind; /** * Checks whether a given value is of a given content kind. * * @param {*} value The value to be examined. * @param {soydata.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; }; /** * Content of type {@link soydata.SanitizedContentKind.HTML}. * * The content is 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 {@code s} (e.g. it runs code or fetches bad URLs) and * you wouldn't write a template that produces {@code s} on security or privacy * grounds, then don't pass {@code s} here. The default content direction is * unknown, i.e. to be estimated when necessary. * * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.SanitizedHtml = function() { goog.soy.data.SanitizedContent.call(this); // Throws an exception. }; goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedContent); /** @override */ soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML; /** * Returns a SanitizedHtml object for a particular value. The content direction * is preserved. * * This HTML-escapes the value unless it is already SanitizedHtml. * * @param {*} value The value to convert. If it is already a SanitizedHtml * object, it is left alone. * @return {!soydata.SanitizedHtml} A SanitizedHtml object derived from the * stringified value. It is escaped unless the input is SanitizedHtml. */ soydata.SanitizedHtml.from = function(value) { // The check is soydata.isContentKind() inlined for performance. if (value != null && value.contentKind === soydata.SanitizedContentKind.HTML) { goog.asserts.assert(value.constructor === soydata.SanitizedHtml); return /** @type {!soydata.SanitizedHtml} */ (value); } return soydata.VERY_UNSAFE.ordainSanitizedHtml( soy.esc.$$escapeHtmlHelper(String(value)), soydata.getContentDir(value)); }; /** * Content of type {@link soydata.SanitizedContentKind.JS}. * * The content is Javascript source that when evaluated does not execute any * attacker-controlled scripts. The content direction is LTR. * * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.SanitizedJs = function() { goog.soy.data.SanitizedContent.call(this); // Throws an exception. }; goog.inherits(soydata.SanitizedJs, goog.soy.data.SanitizedContent); /** @override */ soydata.SanitizedJs.prototype.contentKind = soydata.SanitizedContentKind.JS; /** @override */ soydata.SanitizedJs.prototype.contentDir = goog.i18n.bidi.Dir.LTR; /** * Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}. * * The content can be safely inserted as part of a single- or double-quoted * string without terminating the string. The default content direction is * unknown, i.e. to be estimated when necessary. * * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.SanitizedJsStrChars = function() { goog.soy.data.SanitizedContent.call(this); // Throws an exception. }; goog.inherits(soydata.SanitizedJsStrChars, goog.soy.data.SanitizedContent); /** @override */ soydata.SanitizedJsStrChars.prototype.contentKind = soydata.SanitizedContentKind.JS_STR_CHARS; /** * Content of type {@link soydata.SanitizedContentKind.URI}. * * The content is a URI chunk that the caller knows is safe to emit in a * template. The content direction is LTR. * * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.SanitizedUri = function() { goog.soy.data.SanitizedContent.call(this); // Throws an exception. }; goog.inherits(soydata.SanitizedUri, goog.soy.data.SanitizedContent); /** @override */ soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI; /** @override */ soydata.SanitizedUri.prototype.contentDir = goog.i18n.bidi.Dir.LTR; /** * Content of type {@link soydata.SanitizedContentKind.ATTRIBUTES}. * * The content should be safely embeddable within an open tag, such as a * key="value" pair. The content direction is LTR. * * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.SanitizedHtmlAttribute = function() { goog.soy.data.SanitizedContent.call(this); // Throws an exception. }; goog.inherits(soydata.SanitizedHtmlAttribute, goog.soy.data.SanitizedContent); /** @override */ soydata.SanitizedHtmlAttribute.prototype.contentKind = soydata.SanitizedContentKind.ATTRIBUTES; /** @override */ soydata.SanitizedHtmlAttribute.prototype.contentDir = goog.i18n.bidi.Dir.LTR; /** * Content of type {@link soydata.SanitizedContentKind.CSS}. * * The content is non-attacker-exploitable CSS, such as {@code color:#c3d9ff}. * The content direction is LTR. * * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.SanitizedCss = function() { goog.soy.data.SanitizedContent.call(this); // Throws an exception. }; goog.inherits(soydata.SanitizedCss, goog.soy.data.SanitizedContent); /** @override */ soydata.SanitizedCss.prototype.contentKind = soydata.SanitizedContentKind.CSS; /** @override */ soydata.SanitizedCss.prototype.contentDir = goog.i18n.bidi.Dir.LTR; /** * Unsanitized plain text string. * * While all strings are effectively safe to use as a plain text, there are no * guarantees about safety in any other context such as HTML. This is * sometimes used to mark that should never be used unescaped. * * @param {*} content Plain text with no guarantees. * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if * unknown and thus to be estimated when necessary. Default: null. * @constructor * @extends {goog.soy.data.SanitizedContent} */ soydata.UnsanitizedText = function(content, opt_contentDir) { /** @override */ this.content = String(content); this.contentDir = opt_contentDir != null ? opt_contentDir : null; }; goog.inherits(soydata.UnsanitizedText, goog.soy.data.SanitizedContent); /** @override */ soydata.UnsanitizedText.prototype.contentKind = soydata.SanitizedContentKind.TEXT; /** * 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) { /** @type {function(new: goog.soy.data.SanitizedContent)} */ function InstantiableCtor() {} 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(); result.content = 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) { /** @type {function(new: goog.soy.data.SanitizedContent)} */ function InstantiableCtor() {} 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(); result.content = 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 {!soydata.UnsanitizedText} A wrapper that is rejected by the * Soy noAutoescape print directive. */ soydata.markUnsanitizedText = function(content, opt_contentDir) { return new soydata.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 {@code s} (e.g. it runs code or fetches bad URLs) * and you wouldn't write a template that produces {@code s} on security or * privacy grounds, then don't pass {@code 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 {!soydata.SanitizedHtml} Sanitized content wrapper that * indicates to Soy not to escape when printed as HTML. */ soydata.VERY_UNSAFE.ordainSanitizedHtml = soydata.$$makeSanitizedContentFactory_(soydata.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 {!soydata.SanitizedJs} Sanitized content wrapper that indicates to * Soy not to escape when printed as Javascript source. */ soydata.VERY_UNSAFE.ordainSanitizedJs = soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( soydata.SanitizedJs); // TODO: This function is probably necessary, either externally or internally // as an implementation detail. Generally, plain text will always work here, // as there's no harm to unescaping the string and then re-escaping when // finally printed. /** * Takes a leap of faith that the provided content can be safely embedded in * a Javascript string without re-esacping. * * @param {*} content Content that can be safely inserted as part of a * single- or double-quoted string without terminating the string. * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if * unknown and thus to be estimated when necessary. Default: null. * @return {!soydata.SanitizedJsStrChars} Sanitized content wrapper that * indicates to Soy not to escape when printed in a JS string. */ soydata.VERY_UNSAFE.ordainSanitizedJsStrChars = soydata.$$makeSanitizedContentFactory_(soydata.SanitizedJsStrChars); /** * 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 *