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(user): 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 or goog.string.Const is required.
355 * - For tags which can load code, more specific goog.html.SafeHtml.create*()
356 * functions must be used. Tags which can load code and are not supported by
357 * this function are embed, iframe, link, object, script, style, and template.
358 *
359 * @param {string} tagName The name of the tag. Only tag names consisting of
360 * [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed.
361 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
362 * opt_attributes Mapping from attribute names to their values. Only
363 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
364 * undefined causes the attribute to be omitted.
365 * @param {!goog.html.SafeHtml.TextOrHtml_|
366 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
367 * HTML-escape and put inside the tag. This must be empty for void tags
368 * like <br>. Array elements are concatenated.
369 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
370 * @throws {Error} If invalid tag name, attribute name, or attribute value is
371 * provided.
372 * @throws {goog.asserts.AssertionError} If content for void tag is provided.
373 */
374goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
375 if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
376 throw Error('Invalid tag name <' + tagName + '>.');
377 }
378 if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
379 throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
380 }
381 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
382 tagName, opt_attributes, opt_content);
383};
384
385
386/**
387 * Creates a SafeHtml representing an iframe tag.
388 *
389 * By default the sandbox attribute is set to an empty value, which is the most
390 * secure option, as it confers the iframe the least privileges. If this
391 * is too restrictive then granting individual privileges is the preferable
392 * option. Unsetting the attribute entirely is the least secure option and
393 * should never be done unless it's stricly necessary.
394 *
395 * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src
396 * attribute. If null or undefined src will not be set.
397 * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
398 * If null or undefined srcdoc will not be set.
399 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
400 * opt_attributes Mapping from attribute names to their values. Only
401 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
402 * undefined causes the attribute to be omitted.
403 * @param {!goog.html.SafeHtml.TextOrHtml_|
404 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
405 * HTML-escape and put inside the tag. Array elements are concatenated.
406 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
407 * @throws {Error} If invalid tag name, attribute name, or attribute value is
408 * provided. If opt_attributes contains the src or srcdoc attributes.
409 */
410goog.html.SafeHtml.createIframe = function(
411 opt_src, opt_srcdoc, opt_attributes, opt_content) {
412 var fixedAttributes = {};
413 fixedAttributes['src'] = opt_src || null;
414 fixedAttributes['srcdoc'] = opt_srcdoc || null;
415 var defaultAttributes = {'sandbox': ''};
416 var attributes = goog.html.SafeHtml.combineAttributes(
417 fixedAttributes, defaultAttributes, opt_attributes);
418 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
419 'iframe', attributes, opt_content);
420};
421
422
423/**
424 * Creates a SafeHtml representing a style tag. The type attribute is set
425 * to "text/css".
426 * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
427 * styleSheet Content to put inside the tag. Array elements are
428 * concatenated.
429 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
430 * opt_attributes Mapping from attribute names to their values. Only
431 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
432 * undefined causes the attribute to be omitted.
433 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
434 * @throws {Error} If invalid attribute name or attribute value is provided. If
435 * opt_attributes contains the type attribute.
436 */
437goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {
438 var fixedAttributes = {'type': 'text/css'};
439 var defaultAttributes = {};
440 var attributes = goog.html.SafeHtml.combineAttributes(
441 fixedAttributes, defaultAttributes, opt_attributes);
442
443 var content = '';
444 styleSheet = goog.array.concat(styleSheet);
445 for (var i = 0; i < styleSheet.length; i++) {
446 content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);
447 }
448 // Convert to SafeHtml so that it's not HTML-escaped.
449 var htmlContent = goog.html.SafeHtml
450 .createSafeHtmlSecurityPrivateDoNotAccessOrElse(
451 content, goog.i18n.bidi.Dir.NEUTRAL);
452 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
453 'style', attributes, htmlContent);
454};
455
456
457/**
458 * @param {string} tagName The tag name.
459 * @param {string} name The attribute name.
460 * @param {!goog.html.SafeHtml.AttributeValue_} value The attribute value.
461 * @return {string} A "name=value" string.
462 * @throws {Error} If attribute value is unsafe for the given tag and attribute.
463 * @private
464 */
465goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
466 // If it's goog.string.Const, allow any valid attribute name.
467 if (value instanceof goog.string.Const) {
468 value = goog.string.Const.unwrap(value);
469 } else if (name.toLowerCase() == 'style') {
470 value = goog.html.SafeHtml.getStyleValue_(value);
471 } else if (/^on/i.test(name)) {
472 // TODO(jakubvrana): Disallow more attributes with a special meaning.
473 throw Error('Attribute "' + name +
474 '" requires goog.string.Const value, "' + value + '" given.');
475 // URL attributes handled differently accroding to tag.
476 } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
477 if (value instanceof goog.html.TrustedResourceUrl) {
478 value = goog.html.TrustedResourceUrl.unwrap(value);
479 } else if (value instanceof goog.html.SafeUrl) {
480 value = goog.html.SafeUrl.unwrap(value);
481 } else {
482 // TODO(user): Allow strings and sanitize them automatically,
483 // so that it's consistent with accepting a map directly for "style".
484 throw Error('Attribute "' + name + '" on tag "' + tagName +
485 '" requires goog.html.SafeUrl or goog.string.Const value, "' +
486 value + '" given.');
487 }
488 }
489
490 // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
491 // HTML-escaping.
492 if (value.implementsGoogStringTypedString) {
493 // Ok to call getTypedStringValue() since there's no reliance on the type
494 // contract for security here.
495 value = value.getTypedStringValue();
496 }
497
498 goog.asserts.assert(goog.isString(value) || goog.isNumber(value),
499 'String or number value expected, got ' +
500 (typeof value) + ' with value: ' + value);
501 return name + '="' + goog.string.htmlEscape(String(value)) + '"';
502};
503
504
505/**
506 * Gets value allowed in "style" attribute.
507 * @param {goog.html.SafeHtml.AttributeValue_} value It could be SafeStyle or a
508 * map which will be passed to goog.html.SafeStyle.create.
509 * @return {string} Unwrapped value.
510 * @throws {Error} If string value is given.
511 * @private
512 */
513goog.html.SafeHtml.getStyleValue_ = function(value) {
514 if (!goog.isObject(value)) {
515 throw Error('The "style" attribute requires goog.html.SafeStyle or map ' +
516 'of style properties, ' + (typeof value) + ' given: ' + value);
517 }
518 if (!(value instanceof goog.html.SafeStyle)) {
519 // Process the property bag into a style object.
520 value = goog.html.SafeStyle.create(value);
521 }
522 return goog.html.SafeStyle.unwrap(value);
523};
524
525
526/**
527 * Creates a SafeHtml content with known directionality consisting of a tag with
528 * optional attributes and optional content.
529 * @param {!goog.i18n.bidi.Dir} dir Directionality.
530 * @param {string} tagName
531 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
532 * @param {!goog.html.SafeHtml.TextOrHtml_|
533 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
534 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
535 */
536goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes,
537 opt_content) {
538 var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
539 html.dir_ = dir;
540 return html;
541};
542
543
544/**
545 * Creates a new SafeHtml object by concatenating values.
546 * @param {...(!goog.html.SafeHtml.TextOrHtml_|
547 * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
548 * @return {!goog.html.SafeHtml}
549 */
550goog.html.SafeHtml.concat = function(var_args) {
551 var dir = goog.i18n.bidi.Dir.NEUTRAL;
552 var content = '';
553
554 /**
555 * @param {!goog.html.SafeHtml.TextOrHtml_|
556 * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
557 */
558 var addArgument = function(argument) {
559 if (goog.isArray(argument)) {
560 goog.array.forEach(argument, addArgument);
561 } else {
562 var html = goog.html.SafeHtml.htmlEscape(argument);
563 content += goog.html.SafeHtml.unwrap(html);
564 var htmlDir = html.getDirection();
565 if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
566 dir = htmlDir;
567 } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
568 dir = null;
569 }
570 }
571 };
572
573 goog.array.forEach(arguments, addArgument);
574 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
575 content, dir);
576};
577
578
579/**
580 * Creates a new SafeHtml object with known directionality by concatenating the
581 * values.
582 * @param {!goog.i18n.bidi.Dir} dir Directionality.
583 * @param {...(!goog.html.SafeHtml.TextOrHtml_|
584 * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array
585 * arguments would be processed recursively.
586 * @return {!goog.html.SafeHtml}
587 */
588goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
589 var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
590 html.dir_ = dir;
591 return html;
592};
593
594
595/**
596 * Type marker for the SafeHtml type, used to implement additional run-time
597 * type checking.
598 * @const
599 * @private
600 */
601goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
602
603
604/**
605 * Package-internal utility method to create SafeHtml instances.
606 *
607 * @param {string} html The string to initialize the SafeHtml object with.
608 * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
609 * constructed, or null if unknown.
610 * @return {!goog.html.SafeHtml} The initialized SafeHtml object.
611 * @package
612 */
613goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
614 html, dir) {
615 return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
616 html, dir);
617};
618
619
620/**
621 * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
622 * method exists only so that the compiler can dead code eliminate static
623 * fields (like EMPTY) when they're not accessed.
624 * @param {string} html
625 * @param {?goog.i18n.bidi.Dir} dir
626 * @return {!goog.html.SafeHtml}
627 * @private
628 */
629goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
630 html, dir) {
631 this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
632 this.dir_ = dir;
633 return this;
634};
635
636
637/**
638 * Like create() but does not restrict which tags can be constructed.
639 *
640 * @param {string} tagName Tag name. Set or validated by caller.
641 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
642 * @param {(!goog.html.SafeHtml.TextOrHtml_|
643 * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
644 * @return {!goog.html.SafeHtml}
645 * @throws {Error} If invalid or unsafe attribute name or value is provided.
646 * @throws {goog.asserts.AssertionError} If content for void tag is provided.
647 * @package
648 */
649goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse =
650 function(tagName, opt_attributes, opt_content) {
651 var dir = null;
652 var result = '<' + tagName;
653
654 if (opt_attributes) {
655 for (var name in opt_attributes) {
656 if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
657 throw Error('Invalid attribute name "' + name + '".');
658 }
659 var value = opt_attributes[name];
660 if (!goog.isDefAndNotNull(value)) {
661 continue;
662 }
663 result += ' ' +
664 goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
665 }
666 }
667
668 var content = opt_content;
669 if (!goog.isDefAndNotNull(content)) {
670 content = [];
671 } else if (!goog.isArray(content)) {
672 content = [content];
673 }
674
675 if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
676 goog.asserts.assert(!content.length,
677 'Void tag <' + tagName + '> does not allow content.');
678 result += '>';
679 } else {
680 var html = goog.html.SafeHtml.concat(content);
681 result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
682 dir = html.getDirection();
683 }
684
685 var dirAttribute = opt_attributes && opt_attributes['dir'];
686 if (dirAttribute) {
687 if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
688 // If the tag has the "dir" attribute specified then its direction is
689 // neutral because it can be safely used in any context.
690 dir = goog.i18n.bidi.Dir.NEUTRAL;
691 } else {
692 dir = null;
693 }
694 }
695
696 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
697 result, dir);
698};
699
700
701/**
702 * @param {!Object<string, string>} fixedAttributes
703 * @param {!Object<string, string>} defaultAttributes
704 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
705 * opt_attributes Optional attributes passed to create*().
706 * @return {!Object<string, goog.html.SafeHtml.AttributeValue_>}
707 * @throws {Error} If opt_attributes contains an attribute with the same name
708 * as an attribute in fixedAttributes.
709 * @package
710 */
711goog.html.SafeHtml.combineAttributes = function(
712 fixedAttributes, defaultAttributes, opt_attributes) {
713 var combinedAttributes = {};
714 var name;
715
716 for (name in fixedAttributes) {
717 goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
718 combinedAttributes[name] = fixedAttributes[name];
719 }
720 for (name in defaultAttributes) {
721 goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
722 combinedAttributes[name] = defaultAttributes[name];
723 }
724
725 for (name in opt_attributes) {
726 var nameLower = name.toLowerCase();
727 if (nameLower in fixedAttributes) {
728 throw Error('Cannot override "' + nameLower + '" attribute, got "' +
729 name + '" with value "' + opt_attributes[name] + '"');
730 }
731 if (nameLower in defaultAttributes) {
732 delete combinedAttributes[nameLower];
733 }
734 combinedAttributes[name] = opt_attributes[name];
735 }
736
737 return combinedAttributes;
738};
739
740
741/**
742 * A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
743 * @const {!goog.html.SafeHtml}
744 */
745goog.html.SafeHtml.DOCTYPE_HTML =
746 goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
747 '<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
748
749
750/**
751 * A SafeHtml instance corresponding to the empty string.
752 * @const {!goog.html.SafeHtml}
753 */
754goog.html.SafeHtml.EMPTY =
755 goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
756 '', goog.i18n.bidi.Dir.NEUTRAL);