lib/goog/html/safehtml.js

1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15
16/**
17 * @fileoverview The SafeHtml type and its builders.
18 *
19 * TODO(xtof): Link to document stating type contract.
20 */
21
22goog.provide('goog.html.SafeHtml');
23
24goog.require('goog.array');
25goog.require('goog.asserts');
26goog.require('goog.dom.TagName');
27goog.require('goog.dom.tags');
28goog.require('goog.html.SafeStyle');
29goog.require('goog.html.SafeStyleSheet');
30goog.require('goog.html.SafeUrl');
31goog.require('goog.html.TrustedResourceUrl');
32goog.require('goog.i18n.bidi.Dir');
33goog.require('goog.i18n.bidi.DirectionalString');
34goog.require('goog.object');
35goog.require('goog.string');
36goog.require('goog.string.Const');
37goog.require('goog.string.TypedString');
38
39
40
41/**
42 * A string that is safe to use in HTML context in DOM APIs and HTML documents.
43 *
44 * A SafeHtml is a string-like object that carries the security type contract
45 * that its value as a string will not cause untrusted script execution when
46 * evaluated as HTML in a browser.
47 *
48 * Values of this type are guaranteed to be safe to use in HTML contexts,
49 * such as, assignment to the innerHTML DOM property, or interpolation into
50 * a HTML template in HTML PC_DATA context, in the sense that the use will not
51 * result in a Cross-Site-Scripting vulnerability.
52 *
53 * Instances of this type must be created via the factory methods
54 * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),
55 * etc and not by invoking its constructor. The constructor intentionally
56 * takes no parameters and the type is immutable; hence only a default instance
57 * corresponding to the empty string can be obtained via constructor invocation.
58 *
59 * @see goog.html.SafeHtml#create
60 * @see goog.html.SafeHtml#htmlEscape
61 * @constructor
62 * @final
63 * @struct
64 * @implements {goog.i18n.bidi.DirectionalString}
65 * @implements {goog.string.TypedString}
66 */
67goog.html.SafeHtml = function() {
68 /**
69 * The contained value of this SafeHtml. The field has a purposely ugly
70 * name to make (non-compiled) code that attempts to directly access this
71 * field stand out.
72 * @private {string}
73 */
74 this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
75
76 /**
77 * A type marker used to implement additional run-time type checking.
78 * @see goog.html.SafeHtml#unwrap
79 * @const
80 * @private
81 */
82 this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
83 goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
84
85 /**
86 * This SafeHtml's directionality, or null if unknown.
87 * @private {?goog.i18n.bidi.Dir}
88 */
89 this.dir_ = null;
90};
91
92
93/**
94 * @override
95 * @const
96 */
97goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
98
99
100/** @override */
101goog.html.SafeHtml.prototype.getDirection = function() {
102 return this.dir_;
103};
104
105
106/**
107 * @override
108 * @const
109 */
110goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
111
112
113/**
114 * Returns this SafeHtml's value a string.
115 *
116 * IMPORTANT: In code where it is security relevant that an object's type is
117 * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of
118 * this method. If in doubt, assume that it's security relevant. In particular,
119 * note that goog.html functions which return a goog.html type do not guarantee
120 * that the returned instance is of the right type. For example:
121 *
122 * <pre>
123 * var fakeSafeHtml = new String('fake');
124 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
125 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
126 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
127 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
128 * // instanceof goog.html.SafeHtml.
129 * </pre>
130 *
131 * @see goog.html.SafeHtml#unwrap
132 * @override
133 */
134goog.html.SafeHtml.prototype.getTypedStringValue = function() {
135 return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
136};
137
138
139if (goog.DEBUG) {
140 /**
141 * Returns a debug string-representation of this value.
142 *
143 * To obtain the actual string value wrapped in a SafeHtml, use
144 * {@code goog.html.SafeHtml.unwrap}.
145 *
146 * @see goog.html.SafeHtml#unwrap
147 * @override
148 */
149 goog.html.SafeHtml.prototype.toString = function() {
150 return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
151 '}';
152 };
153}
154
155
156/**
157 * Performs a runtime check that the provided object is indeed a SafeHtml
158 * object, and returns its value.
159 * @param {!goog.html.SafeHtml} safeHtml The object to extract from.
160 * @return {string} The SafeHtml object's contained string, unless the run-time
161 * type check fails. In that case, {@code unwrap} returns an innocuous
162 * string, or, if assertions are enabled, throws
163 * {@code goog.asserts.AssertionError}.
164 */
165goog.html.SafeHtml.unwrap = function(safeHtml) {
166 // Perform additional run-time type-checking to ensure that safeHtml is indeed
167 // an instance of the expected type. This provides some additional protection
168 // against security bugs due to application code that disables type checks.
169 // Specifically, the following checks are performed:
170 // 1. The object is an instance of the expected type.
171 // 2. The object is not an instance of a subclass.
172 // 3. The object carries a type marker for the expected type. "Faking" an
173 // object requires a reference to the type marker, which has names intended
174 // to stand out in code reviews.
175 if (safeHtml instanceof goog.html.SafeHtml &&
176 safeHtml.constructor === goog.html.SafeHtml &&
177 safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
178 goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
179 return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
180 } else {
181 goog.asserts.fail('expected object of type SafeHtml, got \'' +
182 safeHtml + '\'');
183 return 'type_error:SafeHtml';
184 }
185};
186
187
188/**
189 * Shorthand for union of types that can sensibly be converted to strings
190 * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).
191 * @private
192 * @typedef {string|number|boolean|!goog.string.TypedString|
193 * !goog.i18n.bidi.DirectionalString}
194 */
195goog.html.SafeHtml.TextOrHtml_;
196
197
198/**
199 * Returns HTML-escaped text as a SafeHtml object.
200 *
201 * If text is of a type that implements
202 * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new
203 * {@code SafeHtml} object is set to {@code text}'s directionality, if known.
204 * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,
205 * {@code null}).
206 *
207 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
208 * the parameter is of type SafeHtml it is returned directly (no escaping
209 * is done).
210 * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
211 */
212goog.html.SafeHtml.htmlEscape = function(textOrHtml) {
213 if (textOrHtml instanceof goog.html.SafeHtml) {
214 return textOrHtml;
215 }
216 var dir = null;
217 if (textOrHtml.implementsGoogI18nBidiDirectionalString) {
218 dir = textOrHtml.getDirection();
219 }
220 var textAsString;
221 if (textOrHtml.implementsGoogStringTypedString) {
222 textAsString = textOrHtml.getTypedStringValue();
223 } else {
224 textAsString = String(textOrHtml);
225 }
226 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
227 goog.string.htmlEscape(textAsString), dir);
228};
229
230
231/**
232 * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
233 * &lt;br&gt;.
234 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
235 * the parameter is of type SafeHtml it is returned directly (no escaping
236 * is done).
237 * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
238 */
239goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {
240 if (textOrHtml instanceof goog.html.SafeHtml) {
241 return textOrHtml;
242 }
243 var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
244 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
245 goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),
246 html.getDirection());
247};
248
249
250/**
251 * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
252 * &lt;br&gt; and escaping whitespace to preserve spatial formatting. Character
253 * entity #160 is used to make it safer for XML.
254 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
255 * the parameter is of type SafeHtml it is returned directly (no escaping
256 * is done).
257 * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
258 */
259goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
260 textOrHtml) {
261 if (textOrHtml instanceof goog.html.SafeHtml) {
262 return textOrHtml;
263 }
264 var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
265 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
266 goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),
267 html.getDirection());
268};
269
270
271/**
272 * Coerces an arbitrary object into a SafeHtml object.
273 *
274 * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same
275 * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and
276 * HTML-escaped. If {@code textOrHtml} is of a type that implements
277 * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is
278 * preserved.
279 *
280 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to
281 * coerce.
282 * @return {!goog.html.SafeHtml} The resulting SafeHtml object.
283 * @deprecated Use goog.html.SafeHtml.htmlEscape.
284 */
285goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
286
287
288/**
289 * @const
290 * @private
291 */
292goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
293
294
295/**
296 * Set of attributes containing URL as defined at
297 * http://www.w3.org/TR/html5/index.html#attributes-1.
298 * @private @const {!Object<string,boolean>}
299 */
300goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet('action', 'cite',
301 'data', 'formaction', 'href', 'manifest', 'poster', 'src');
302
303
304/**
305 * Tags which are unsupported via create(). They might be supported via a
306 * tag-specific create method. These are tags which might require a
307 * TrustedResourceUrl in one of their attributes or a restricted type for
308 * their content.
309 * @private @const {!Object<string,boolean>}
310 */
311goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
312 goog.dom.TagName.EMBED, goog.dom.TagName.IFRAME, goog.dom.TagName.LINK,
313 goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT, goog.dom.TagName.STYLE,
314 goog.dom.TagName.TEMPLATE);
315
316
317/**
318 * @typedef {string|number|goog.string.TypedString|
319 * goog.html.SafeStyle.PropertyMap}
320 * @private
321 */
322goog.html.SafeHtml.AttributeValue_;
323
324
325/**
326 * Creates a SafeHtml content consisting of a tag with optional attributes and
327 * optional content.
328 *
329 * For convenience tag names and attribute names are accepted as regular
330 * strings, instead of goog.string.Const. Nevertheless, you should not pass
331 * user-controlled values to these parameters. Note that these parameters are
332 * syntactically validated at runtime, and invalid values will result in
333 * an exception.
334 *
335 * Example usage:
336 *
337 * goog.html.SafeHtml.create('br');
338 * goog.html.SafeHtml.create('div', {'class': 'a'});
339 * goog.html.SafeHtml.create('p', {}, 'a');
340 * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));
341 *
342 * goog.html.SafeHtml.create('span', {
343 * 'style': {'margin': '0'}
344 * });
345 *
346 * To guarantee SafeHtml's type contract is upheld there are restrictions on
347 * attribute values and tag names.
348 *
349 * - For attributes which contain script code (on*), a goog.string.Const is
350 * required.
351 * - For attributes which contain style (style), a goog.html.SafeStyle or a
352 * goog.html.SafeStyle.PropertyMap is required.
353 * - For attributes which are interpreted as URLs (e.g. src, href) a
354 * goog.html.SafeUrl, goog.string.Const or string is required. If a string
355 * is passed, it will be sanitized with SafeUrl.sanitize().
356 * - For tags which can load code, more specific goog.html.SafeHtml.create*()
357 * functions must be used. Tags which can load code and are not supported by
358 * this function are embed, iframe, link, object, script, style, and template.
359 *
360 * @param {string} tagName The name of the tag. Only tag names consisting of
361 * [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed.
362 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
363 * opt_attributes Mapping from attribute names to their values. Only
364 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
365 * undefined causes the attribute to be omitted.
366 * @param {!goog.html.SafeHtml.TextOrHtml_|
367 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
368 * HTML-escape and put inside the tag. This must be empty for void tags
369 * like <br>. Array elements are concatenated.
370 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
371 * @throws {Error} If invalid tag name, attribute name, or attribute value is
372 * provided.
373 * @throws {goog.asserts.AssertionError} If content for void tag is provided.
374 */
375goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
376 if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
377 throw Error('Invalid tag name <' + tagName + '>.');
378 }
379 if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
380 throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
381 }
382 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
383 tagName, opt_attributes, opt_content);
384};
385
386
387/**
388 * Creates a SafeHtml representing an iframe tag.
389 *
390 * By default the sandbox attribute is set to an empty value, which is the most
391 * secure option, as it confers the iframe the least privileges. If this
392 * is too restrictive then granting individual privileges is the preferable
393 * option. Unsetting the attribute entirely is the least secure option and
394 * should never be done unless it's stricly necessary.
395 *
396 * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src
397 * attribute. If null or undefined src will not be set.
398 * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
399 * If null or undefined srcdoc will not be set.
400 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
401 * opt_attributes Mapping from attribute names to their values. Only
402 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
403 * undefined causes the attribute to be omitted.
404 * @param {!goog.html.SafeHtml.TextOrHtml_|
405 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
406 * HTML-escape and put inside the tag. Array elements are concatenated.
407 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
408 * @throws {Error} If invalid tag name, attribute name, or attribute value is
409 * provided. If opt_attributes contains the src or srcdoc attributes.
410 */
411goog.html.SafeHtml.createIframe = function(
412 opt_src, opt_srcdoc, opt_attributes, opt_content) {
413 var fixedAttributes = {};
414 fixedAttributes['src'] = opt_src || null;
415 fixedAttributes['srcdoc'] = opt_srcdoc || null;
416 var defaultAttributes = {'sandbox': ''};
417 var attributes = goog.html.SafeHtml.combineAttributes(
418 fixedAttributes, defaultAttributes, opt_attributes);
419 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
420 'iframe', attributes, opt_content);
421};
422
423
424/**
425 * Creates a SafeHtml representing a style tag. The type attribute is set
426 * to "text/css".
427 * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
428 * styleSheet Content to put inside the tag. Array elements are
429 * concatenated.
430 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
431 * opt_attributes Mapping from attribute names to their values. Only
432 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
433 * undefined causes the attribute to be omitted.
434 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
435 * @throws {Error} If invalid attribute name or attribute value is provided. If
436 * opt_attributes contains the type attribute.
437 */
438goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {
439 var fixedAttributes = {'type': 'text/css'};
440 var defaultAttributes = {};
441 var attributes = goog.html.SafeHtml.combineAttributes(
442 fixedAttributes, defaultAttributes, opt_attributes);
443
444 var content = '';
445 styleSheet = goog.array.concat(styleSheet);
446 for (var i = 0; i < styleSheet.length; i++) {
447 content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);
448 }
449 // Convert to SafeHtml so that it's not HTML-escaped.
450 var htmlContent = goog.html.SafeHtml
451 .createSafeHtmlSecurityPrivateDoNotAccessOrElse(
452 content, goog.i18n.bidi.Dir.NEUTRAL);
453 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
454 'style', attributes, htmlContent);
455};
456
457
458/**
459 * @param {string} tagName The tag name.
460 * @param {string} name The attribute name.
461 * @param {!goog.html.SafeHtml.AttributeValue_} value The attribute value.
462 * @return {string} A "name=value" string.
463 * @throws {Error} If attribute value is unsafe for the given tag and attribute.
464 * @private
465 */
466goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
467 // If it's goog.string.Const, allow any valid attribute name.
468 if (value instanceof goog.string.Const) {
469 value = goog.string.Const.unwrap(value);
470 } else if (name.toLowerCase() == 'style') {
471 value = goog.html.SafeHtml.getStyleValue_(value);
472 } else if (/^on/i.test(name)) {
473 // TODO(jakubvrana): Disallow more attributes with a special meaning.
474 throw Error('Attribute "' + name +
475 '" requires goog.string.Const value, "' + value + '" given.');
476 // URL attributes handled differently accroding to tag.
477 } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
478 if (value instanceof goog.html.TrustedResourceUrl) {
479 value = goog.html.TrustedResourceUrl.unwrap(value);
480 } else if (value instanceof goog.html.SafeUrl) {
481 value = goog.html.SafeUrl.unwrap(value);
482 } else if (goog.isString(value)) {
483 value = goog.html.SafeUrl.sanitize(value).getTypedStringValue();
484 } else {
485 throw Error('Attribute "' + name + '" on tag "' + tagName +
486 '" requires goog.html.SafeUrl, goog.string.Const, or string,' +
487 ' value "' + value + '" given.');
488 }
489 }
490
491 // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
492 // HTML-escaping.
493 if (value.implementsGoogStringTypedString) {
494 // Ok to call getTypedStringValue() since there's no reliance on the type
495 // contract for security here.
496 value = value.getTypedStringValue();
497 }
498
499 goog.asserts.assert(goog.isString(value) || goog.isNumber(value),
500 'String or number value expected, got ' +
501 (typeof value) + ' with value: ' + value);
502 return name + '="' + goog.string.htmlEscape(String(value)) + '"';
503};
504
505
506/**
507 * Gets value allowed in "style" attribute.
508 * @param {goog.html.SafeHtml.AttributeValue_} value It could be SafeStyle or a
509 * map which will be passed to goog.html.SafeStyle.create.
510 * @return {string} Unwrapped value.
511 * @throws {Error} If string value is given.
512 * @private
513 */
514goog.html.SafeHtml.getStyleValue_ = function(value) {
515 if (!goog.isObject(value)) {
516 throw Error('The "style" attribute requires goog.html.SafeStyle or map ' +
517 'of style properties, ' + (typeof value) + ' given: ' + value);
518 }
519 if (!(value instanceof goog.html.SafeStyle)) {
520 // Process the property bag into a style object.
521 value = goog.html.SafeStyle.create(value);
522 }
523 return goog.html.SafeStyle.unwrap(value);
524};
525
526
527/**
528 * Creates a SafeHtml content with known directionality consisting of a tag with
529 * optional attributes and optional content.
530 * @param {!goog.i18n.bidi.Dir} dir Directionality.
531 * @param {string} tagName
532 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
533 * @param {!goog.html.SafeHtml.TextOrHtml_|
534 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
535 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
536 */
537goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes,
538 opt_content) {
539 var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
540 html.dir_ = dir;
541 return html;
542};
543
544
545/**
546 * Creates a new SafeHtml object by concatenating values.
547 * @param {...(!goog.html.SafeHtml.TextOrHtml_|
548 * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
549 * @return {!goog.html.SafeHtml}
550 */
551goog.html.SafeHtml.concat = function(var_args) {
552 var dir = goog.i18n.bidi.Dir.NEUTRAL;
553 var content = '';
554
555 /**
556 * @param {!goog.html.SafeHtml.TextOrHtml_|
557 * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
558 */
559 var addArgument = function(argument) {
560 if (goog.isArray(argument)) {
561 goog.array.forEach(argument, addArgument);
562 } else {
563 var html = goog.html.SafeHtml.htmlEscape(argument);
564 content += goog.html.SafeHtml.unwrap(html);
565 var htmlDir = html.getDirection();
566 if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
567 dir = htmlDir;
568 } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
569 dir = null;
570 }
571 }
572 };
573
574 goog.array.forEach(arguments, addArgument);
575 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
576 content, dir);
577};
578
579
580/**
581 * Creates a new SafeHtml object with known directionality by concatenating the
582 * values.
583 * @param {!goog.i18n.bidi.Dir} dir Directionality.
584 * @param {...(!goog.html.SafeHtml.TextOrHtml_|
585 * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array
586 * arguments would be processed recursively.
587 * @return {!goog.html.SafeHtml}
588 */
589goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
590 var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
591 html.dir_ = dir;
592 return html;
593};
594
595
596/**
597 * Type marker for the SafeHtml type, used to implement additional run-time
598 * type checking.
599 * @const {!Object}
600 * @private
601 */
602goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
603
604
605/**
606 * Package-internal utility method to create SafeHtml instances.
607 *
608 * @param {string} html The string to initialize the SafeHtml object with.
609 * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
610 * constructed, or null if unknown.
611 * @return {!goog.html.SafeHtml} The initialized SafeHtml object.
612 * @package
613 */
614goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
615 html, dir) {
616 return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
617 html, dir);
618};
619
620
621/**
622 * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
623 * method exists only so that the compiler can dead code eliminate static
624 * fields (like EMPTY) when they're not accessed.
625 * @param {string} html
626 * @param {?goog.i18n.bidi.Dir} dir
627 * @return {!goog.html.SafeHtml}
628 * @private
629 */
630goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
631 html, dir) {
632 this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
633 this.dir_ = dir;
634 return this;
635};
636
637
638/**
639 * Like create() but does not restrict which tags can be constructed.
640 *
641 * @param {string} tagName Tag name. Set or validated by caller.
642 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
643 * @param {(!goog.html.SafeHtml.TextOrHtml_|
644 * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
645 * @return {!goog.html.SafeHtml}
646 * @throws {Error} If invalid or unsafe attribute name or value is provided.
647 * @throws {goog.asserts.AssertionError} If content for void tag is provided.
648 * @package
649 */
650goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse =
651 function(tagName, opt_attributes, opt_content) {
652 var dir = null;
653 var result = '<' + tagName;
654
655 if (opt_attributes) {
656 for (var name in opt_attributes) {
657 if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
658 throw Error('Invalid attribute name "' + name + '".');
659 }
660 var value = opt_attributes[name];
661 if (!goog.isDefAndNotNull(value)) {
662 continue;
663 }
664 result += ' ' +
665 goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
666 }
667 }
668
669 var content = opt_content;
670 if (!goog.isDefAndNotNull(content)) {
671 content = [];
672 } else if (!goog.isArray(content)) {
673 content = [content];
674 }
675
676 if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
677 goog.asserts.assert(!content.length,
678 'Void tag <' + tagName + '> does not allow content.');
679 result += '>';
680 } else {
681 var html = goog.html.SafeHtml.concat(content);
682 result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
683 dir = html.getDirection();
684 }
685
686 var dirAttribute = opt_attributes && opt_attributes['dir'];
687 if (dirAttribute) {
688 if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
689 // If the tag has the "dir" attribute specified then its direction is
690 // neutral because it can be safely used in any context.
691 dir = goog.i18n.bidi.Dir.NEUTRAL;
692 } else {
693 dir = null;
694 }
695 }
696
697 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
698 result, dir);
699};
700
701
702/**
703 * @param {!Object<string, string>} fixedAttributes
704 * @param {!Object<string, string>} defaultAttributes
705 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
706 * opt_attributes Optional attributes passed to create*().
707 * @return {!Object<string, goog.html.SafeHtml.AttributeValue_>}
708 * @throws {Error} If opt_attributes contains an attribute with the same name
709 * as an attribute in fixedAttributes.
710 * @package
711 */
712goog.html.SafeHtml.combineAttributes = function(
713 fixedAttributes, defaultAttributes, opt_attributes) {
714 var combinedAttributes = {};
715 var name;
716
717 for (name in fixedAttributes) {
718 goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
719 combinedAttributes[name] = fixedAttributes[name];
720 }
721 for (name in defaultAttributes) {
722 goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
723 combinedAttributes[name] = defaultAttributes[name];
724 }
725
726 for (name in opt_attributes) {
727 var nameLower = name.toLowerCase();
728 if (nameLower in fixedAttributes) {
729 throw Error('Cannot override "' + nameLower + '" attribute, got "' +
730 name + '" with value "' + opt_attributes[name] + '"');
731 }
732 if (nameLower in defaultAttributes) {
733 delete combinedAttributes[nameLower];
734 }
735 combinedAttributes[name] = opt_attributes[name];
736 }
737
738 return combinedAttributes;
739};
740
741
742/**
743 * A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
744 * @const {!goog.html.SafeHtml}
745 */
746goog.html.SafeHtml.DOCTYPE_HTML =
747 goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
748 '<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
749
750
751/**
752 * A SafeHtml instance corresponding to the empty string.
753 * @const {!goog.html.SafeHtml}
754 */
755goog.html.SafeHtml.EMPTY =
756 goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
757 '', goog.i18n.bidi.Dir.NEUTRAL);