safehtml.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  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. * @fileoverview The SafeHtml type and its builders.
  16. *
  17. * TODO(xtof): Link to document stating type contract.
  18. */
  19. goog.provide('goog.html.SafeHtml');
  20. goog.require('goog.array');
  21. goog.require('goog.asserts');
  22. goog.require('goog.dom.TagName');
  23. goog.require('goog.dom.tags');
  24. goog.require('goog.html.SafeScript');
  25. goog.require('goog.html.SafeStyle');
  26. goog.require('goog.html.SafeStyleSheet');
  27. goog.require('goog.html.SafeUrl');
  28. goog.require('goog.html.TrustedResourceUrl');
  29. goog.require('goog.i18n.bidi.Dir');
  30. goog.require('goog.i18n.bidi.DirectionalString');
  31. goog.require('goog.labs.userAgent.browser');
  32. goog.require('goog.object');
  33. goog.require('goog.string');
  34. goog.require('goog.string.Const');
  35. goog.require('goog.string.TypedString');
  36. /**
  37. * A string that is safe to use in HTML context in DOM APIs and HTML documents.
  38. *
  39. * A SafeHtml is a string-like object that carries the security type contract
  40. * that its value as a string will not cause untrusted script execution when
  41. * evaluated as HTML in a browser.
  42. *
  43. * Values of this type are guaranteed to be safe to use in HTML contexts,
  44. * such as, assignment to the innerHTML DOM property, or interpolation into
  45. * a HTML template in HTML PC_DATA context, in the sense that the use will not
  46. * result in a Cross-Site-Scripting vulnerability.
  47. *
  48. * Instances of this type must be created via the factory methods
  49. * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),
  50. * etc and not by invoking its constructor. The constructor intentionally
  51. * takes no parameters and the type is immutable; hence only a default instance
  52. * corresponding to the empty string can be obtained via constructor invocation.
  53. *
  54. * @see goog.html.SafeHtml#create
  55. * @see goog.html.SafeHtml#htmlEscape
  56. * @constructor
  57. * @final
  58. * @struct
  59. * @implements {goog.i18n.bidi.DirectionalString}
  60. * @implements {goog.string.TypedString}
  61. */
  62. goog.html.SafeHtml = function() {
  63. /**
  64. * The contained value of this SafeHtml. The field has a purposely ugly
  65. * name to make (non-compiled) code that attempts to directly access this
  66. * field stand out.
  67. * @private {string}
  68. */
  69. this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
  70. /**
  71. * A type marker used to implement additional run-time type checking.
  72. * @see goog.html.SafeHtml#unwrap
  73. * @const {!Object}
  74. * @private
  75. */
  76. this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
  77. goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
  78. /**
  79. * This SafeHtml's directionality, or null if unknown.
  80. * @private {?goog.i18n.bidi.Dir}
  81. */
  82. this.dir_ = null;
  83. };
  84. /**
  85. * @override
  86. * @const
  87. */
  88. goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
  89. /** @override */
  90. goog.html.SafeHtml.prototype.getDirection = function() {
  91. return this.dir_;
  92. };
  93. /**
  94. * @override
  95. * @const
  96. */
  97. goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
  98. /**
  99. * Returns this SafeHtml's value as string.
  100. *
  101. * IMPORTANT: In code where it is security relevant that an object's type is
  102. * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of
  103. * this method. If in doubt, assume that it's security relevant. In particular,
  104. * note that goog.html functions which return a goog.html type do not guarantee
  105. * that the returned instance is of the right type. For example:
  106. *
  107. * <pre>
  108. * var fakeSafeHtml = new String('fake');
  109. * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
  110. * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
  111. * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
  112. * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
  113. * // instanceof goog.html.SafeHtml.
  114. * </pre>
  115. *
  116. * @see goog.html.SafeHtml#unwrap
  117. * @override
  118. */
  119. goog.html.SafeHtml.prototype.getTypedStringValue = function() {
  120. return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
  121. };
  122. if (goog.DEBUG) {
  123. /**
  124. * Returns a debug string-representation of this value.
  125. *
  126. * To obtain the actual string value wrapped in a SafeHtml, use
  127. * {@code goog.html.SafeHtml.unwrap}.
  128. *
  129. * @see goog.html.SafeHtml#unwrap
  130. * @override
  131. */
  132. goog.html.SafeHtml.prototype.toString = function() {
  133. return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
  134. '}';
  135. };
  136. }
  137. /**
  138. * Performs a runtime check that the provided object is indeed a SafeHtml
  139. * object, and returns its value.
  140. * @param {!goog.html.SafeHtml} safeHtml The object to extract from.
  141. * @return {string} The SafeHtml object's contained string, unless the run-time
  142. * type check fails. In that case, {@code unwrap} returns an innocuous
  143. * string, or, if assertions are enabled, throws
  144. * {@code goog.asserts.AssertionError}.
  145. */
  146. goog.html.SafeHtml.unwrap = function(safeHtml) {
  147. // Perform additional run-time type-checking to ensure that safeHtml is indeed
  148. // an instance of the expected type. This provides some additional protection
  149. // against security bugs due to application code that disables type checks.
  150. // Specifically, the following checks are performed:
  151. // 1. The object is an instance of the expected type.
  152. // 2. The object is not an instance of a subclass.
  153. // 3. The object carries a type marker for the expected type. "Faking" an
  154. // object requires a reference to the type marker, which has names intended
  155. // to stand out in code reviews.
  156. if (safeHtml instanceof goog.html.SafeHtml &&
  157. safeHtml.constructor === goog.html.SafeHtml &&
  158. safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
  159. goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
  160. return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
  161. } else {
  162. goog.asserts.fail('expected object of type SafeHtml, got \'' +
  163. safeHtml + '\' of type ' + goog.typeOf(safeHtml));
  164. return 'type_error:SafeHtml';
  165. }
  166. };
  167. /**
  168. * Shorthand for union of types that can sensibly be converted to strings
  169. * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).
  170. * @private
  171. * @typedef {string|number|boolean|!goog.string.TypedString|
  172. * !goog.i18n.bidi.DirectionalString}
  173. */
  174. goog.html.SafeHtml.TextOrHtml_;
  175. /**
  176. * Returns HTML-escaped text as a SafeHtml object.
  177. *
  178. * If text is of a type that implements
  179. * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new
  180. * {@code SafeHtml} object is set to {@code text}'s directionality, if known.
  181. * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,
  182. * {@code null}).
  183. *
  184. * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
  185. * the parameter is of type SafeHtml it is returned directly (no escaping
  186. * is done).
  187. * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
  188. */
  189. goog.html.SafeHtml.htmlEscape = function(textOrHtml) {
  190. if (textOrHtml instanceof goog.html.SafeHtml) {
  191. return textOrHtml;
  192. }
  193. var dir = null;
  194. if (textOrHtml.implementsGoogI18nBidiDirectionalString) {
  195. dir = textOrHtml.getDirection();
  196. }
  197. var textAsString;
  198. if (textOrHtml.implementsGoogStringTypedString) {
  199. textAsString = textOrHtml.getTypedStringValue();
  200. } else {
  201. textAsString = String(textOrHtml);
  202. }
  203. return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  204. goog.string.htmlEscape(textAsString), dir);
  205. };
  206. /**
  207. * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
  208. * &lt;br&gt;.
  209. * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
  210. * the parameter is of type SafeHtml it is returned directly (no escaping
  211. * is done).
  212. * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
  213. */
  214. goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {
  215. if (textOrHtml instanceof goog.html.SafeHtml) {
  216. return textOrHtml;
  217. }
  218. var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
  219. return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  220. goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),
  221. html.getDirection());
  222. };
  223. /**
  224. * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
  225. * &lt;br&gt; and escaping whitespace to preserve spatial formatting. Character
  226. * entity #160 is used to make it safer for XML.
  227. * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
  228. * the parameter is of type SafeHtml it is returned directly (no escaping
  229. * is done).
  230. * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
  231. */
  232. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
  233. textOrHtml) {
  234. if (textOrHtml instanceof goog.html.SafeHtml) {
  235. return textOrHtml;
  236. }
  237. var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
  238. return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  239. goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),
  240. html.getDirection());
  241. };
  242. /**
  243. * Coerces an arbitrary object into a SafeHtml object.
  244. *
  245. * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same
  246. * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and
  247. * HTML-escaped. If {@code textOrHtml} is of a type that implements
  248. * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is
  249. * preserved.
  250. *
  251. * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to
  252. * coerce.
  253. * @return {!goog.html.SafeHtml} The resulting SafeHtml object.
  254. * @deprecated Use goog.html.SafeHtml.htmlEscape.
  255. */
  256. goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
  257. /**
  258. * @const
  259. * @private
  260. */
  261. goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
  262. /**
  263. * Set of attributes containing URL as defined at
  264. * http://www.w3.org/TR/html5/index.html#attributes-1.
  265. * @private @const {!Object<string,boolean>}
  266. */
  267. goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet(
  268. 'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster',
  269. 'src');
  270. /**
  271. * Tags which are unsupported via create(). They might be supported via a
  272. * tag-specific create method. These are tags which might require a
  273. * TrustedResourceUrl in one of their attributes or a restricted type for
  274. * their content.
  275. * @private @const {!Object<string,boolean>}
  276. */
  277. goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
  278. goog.dom.TagName.APPLET, goog.dom.TagName.BASE, goog.dom.TagName.EMBED,
  279. goog.dom.TagName.IFRAME, goog.dom.TagName.LINK, goog.dom.TagName.MATH,
  280. goog.dom.TagName.META, goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT,
  281. goog.dom.TagName.STYLE, goog.dom.TagName.SVG, goog.dom.TagName.TEMPLATE);
  282. /**
  283. * @typedef {string|number|goog.string.TypedString|
  284. * goog.html.SafeStyle.PropertyMap|undefined}
  285. */
  286. goog.html.SafeHtml.AttributeValue;
  287. /**
  288. * Creates a SafeHtml content consisting of a tag with optional attributes and
  289. * optional content.
  290. *
  291. * For convenience tag names and attribute names are accepted as regular
  292. * strings, instead of goog.string.Const. Nevertheless, you should not pass
  293. * user-controlled values to these parameters. Note that these parameters are
  294. * syntactically validated at runtime, and invalid values will result in
  295. * an exception.
  296. *
  297. * Example usage:
  298. *
  299. * goog.html.SafeHtml.create('br');
  300. * goog.html.SafeHtml.create('div', {'class': 'a'});
  301. * goog.html.SafeHtml.create('p', {}, 'a');
  302. * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));
  303. *
  304. * goog.html.SafeHtml.create('span', {
  305. * 'style': {'margin': '0'}
  306. * });
  307. *
  308. * To guarantee SafeHtml's type contract is upheld there are restrictions on
  309. * attribute values and tag names.
  310. *
  311. * - For attributes which contain script code (on*), a goog.string.Const is
  312. * required.
  313. * - For attributes which contain style (style), a goog.html.SafeStyle or a
  314. * goog.html.SafeStyle.PropertyMap is required.
  315. * - For attributes which are interpreted as URLs (e.g. src, href) a
  316. * goog.html.SafeUrl, goog.string.Const or string is required. If a string
  317. * is passed, it will be sanitized with SafeUrl.sanitize().
  318. * - For tags which can load code or set security relevant page metadata,
  319. * more specific goog.html.SafeHtml.create*() functions must be used. Tags
  320. * which are not supported by this function are applet, base, embed, iframe,
  321. * link, math, object, script, style, svg, and template.
  322. *
  323. * @param {!goog.dom.TagName|string} tagName The name of the tag. Only tag names
  324. * consisting of [a-zA-Z0-9-] are allowed. Tag names documented above are
  325. * disallowed.
  326. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  327. * Mapping from attribute names to their values. Only attribute names
  328. * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
  329. * the attribute to be omitted.
  330. * @param {!goog.html.SafeHtml.TextOrHtml_|
  331. * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
  332. * HTML-escape and put inside the tag. This must be empty for void tags
  333. * like <br>. Array elements are concatenated.
  334. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  335. * @throws {Error} If invalid tag name, attribute name, or attribute value is
  336. * provided.
  337. * @throws {goog.asserts.AssertionError} If content for void tag is provided.
  338. */
  339. goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
  340. goog.html.SafeHtml.verifyTagName(String(tagName));
  341. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  342. String(tagName), opt_attributes, opt_content);
  343. };
  344. /**
  345. * Verifies if the tag name is valid and if it doesn't change the context.
  346. * E.g. STRONG is fine but SCRIPT throws because it changes context. See
  347. * goog.html.SafeHtml.create for an explanation of allowed tags.
  348. * @param {string} tagName
  349. * @throws {Error} If invalid tag name is provided.
  350. * @package
  351. */
  352. goog.html.SafeHtml.verifyTagName = function(tagName) {
  353. if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
  354. throw Error('Invalid tag name <' + tagName + '>.');
  355. }
  356. if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
  357. throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
  358. }
  359. };
  360. /**
  361. * Creates a SafeHtml representing an iframe tag.
  362. *
  363. * This by default restricts the iframe as much as possible by setting the
  364. * sandbox attribute to the empty string. If the iframe requires less
  365. * restrictions, set the sandbox attribute as tight as possible, but do not rely
  366. * on the sandbox as a security feature because it is not supported by older
  367. * browsers. If a sandbox is essential to security (e.g. for third-party
  368. * frames), use createSandboxIframe which checks for browser support.
  369. *
  370. * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox
  371. *
  372. * @param {?goog.html.TrustedResourceUrl=} opt_src The value of the src
  373. * attribute. If null or undefined src will not be set.
  374. * @param {?goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
  375. * If null or undefined srcdoc will not be set.
  376. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  377. * Mapping from attribute names to their values. Only attribute names
  378. * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
  379. * the attribute to be omitted.
  380. * @param {!goog.html.SafeHtml.TextOrHtml_|
  381. * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
  382. * HTML-escape and put inside the tag. Array elements are concatenated.
  383. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  384. * @throws {Error} If invalid tag name, attribute name, or attribute value is
  385. * provided. If opt_attributes contains the src or srcdoc attributes.
  386. */
  387. goog.html.SafeHtml.createIframe = function(
  388. opt_src, opt_srcdoc, opt_attributes, opt_content) {
  389. if (opt_src) {
  390. // Check whether this is really TrustedResourceUrl.
  391. goog.html.TrustedResourceUrl.unwrap(opt_src);
  392. }
  393. var fixedAttributes = {};
  394. fixedAttributes['src'] = opt_src || null;
  395. fixedAttributes['srcdoc'] =
  396. opt_srcdoc && goog.html.SafeHtml.unwrap(opt_srcdoc);
  397. var defaultAttributes = {'sandbox': ''};
  398. var attributes = goog.html.SafeHtml.combineAttributes(
  399. fixedAttributes, defaultAttributes, opt_attributes);
  400. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  401. 'iframe', attributes, opt_content);
  402. };
  403. /**
  404. * Creates a SafeHtml representing a sandboxed iframe tag.
  405. *
  406. * The sandbox attribute is enforced in its most restrictive mode, an empty
  407. * string. Consequently, the security requirements for the src and srcdoc
  408. * attributes are relaxed compared to SafeHtml.createIframe. This function
  409. * will throw on browsers that do not support the sandbox attribute, as
  410. * determined by SafeHtml.canUseSandboxIframe.
  411. *
  412. * The SafeHtml returned by this function can trigger downloads with no
  413. * user interaction on Chrome (though only a few, further attempts are blocked).
  414. * Firefox and IE will block all downloads from the sandbox.
  415. *
  416. * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox
  417. * @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html
  418. *
  419. * @param {string|!goog.html.SafeUrl=} opt_src The value of the src
  420. * attribute. If null or undefined src will not be set.
  421. * @param {string=} opt_srcdoc The value of the srcdoc attribute.
  422. * If null or undefined srcdoc will not be set. Will not be sanitized.
  423. * @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  424. * Mapping from attribute names to their values. Only attribute names
  425. * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
  426. * the attribute to be omitted.
  427. * @param {!goog.html.SafeHtml.TextOrHtml_|
  428. * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
  429. * HTML-escape and put inside the tag. Array elements are concatenated.
  430. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  431. * @throws {Error} If invalid tag name, attribute name, or attribute value is
  432. * provided. If opt_attributes contains the src, srcdoc or sandbox
  433. * attributes. If browser does not support the sandbox attribute on iframe.
  434. */
  435. goog.html.SafeHtml.createSandboxIframe = function(
  436. opt_src, opt_srcdoc, opt_attributes, opt_content) {
  437. if (!goog.html.SafeHtml.canUseSandboxIframe()) {
  438. throw new Error('The browser does not support sandboxed iframes.');
  439. }
  440. var fixedAttributes = {};
  441. if (opt_src) {
  442. // Note that sanitize is a no-op on SafeUrl.
  443. fixedAttributes['src'] =
  444. goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(opt_src));
  445. } else {
  446. fixedAttributes['src'] = null;
  447. }
  448. fixedAttributes['srcdoc'] = opt_srcdoc || null;
  449. fixedAttributes['sandbox'] = '';
  450. var attributes =
  451. goog.html.SafeHtml.combineAttributes(fixedAttributes, {}, opt_attributes);
  452. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  453. 'iframe', attributes, opt_content);
  454. };
  455. /**
  456. * Checks if the user agent supports sandboxed iframes.
  457. * @return {boolean}
  458. */
  459. goog.html.SafeHtml.canUseSandboxIframe = function() {
  460. return goog.global['HTMLIFrameElement'] &&
  461. ('sandbox' in goog.global['HTMLIFrameElement'].prototype);
  462. };
  463. /**
  464. * Creates a SafeHtml representing a script tag with the src attribute.
  465. * @param {!goog.html.TrustedResourceUrl} src The value of the src
  466. * attribute.
  467. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=}
  468. * opt_attributes
  469. * Mapping from attribute names to their values. Only attribute names
  470. * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined
  471. * causes the attribute to be omitted.
  472. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  473. * @throws {Error} If invalid attribute name or value is provided. If
  474. * opt_attributes contains the src attribute.
  475. */
  476. goog.html.SafeHtml.createScriptSrc = function(src, opt_attributes) {
  477. // TODO(mlourenco): The charset attribute should probably be blocked. If
  478. // its value is attacker controlled, the script contains attacker controlled
  479. // sub-strings (even if properly escaped) and the server does not set charset
  480. // then XSS is likely possible.
  481. // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-charset
  482. // Check whether this is really TrustedResourceUrl.
  483. goog.html.TrustedResourceUrl.unwrap(src);
  484. var fixedAttributes = {'src': src};
  485. var defaultAttributes = {};
  486. var attributes = goog.html.SafeHtml.combineAttributes(
  487. fixedAttributes, defaultAttributes, opt_attributes);
  488. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  489. 'script', attributes);
  490. };
  491. /**
  492. * Creates a SafeHtml representing a script tag. Does not allow the language,
  493. * src, text or type attributes to be set.
  494. * @param {!goog.html.SafeScript|!Array<!goog.html.SafeScript>}
  495. * script Content to put inside the tag. Array elements are
  496. * concatenated.
  497. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  498. * Mapping from attribute names to their values. Only attribute names
  499. * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
  500. * the attribute to be omitted.
  501. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  502. * @throws {Error} If invalid attribute name or attribute value is provided. If
  503. * opt_attributes contains the language, src, text or type attribute.
  504. */
  505. goog.html.SafeHtml.createScript = function(script, opt_attributes) {
  506. for (var attr in opt_attributes) {
  507. var attrLower = attr.toLowerCase();
  508. if (attrLower == 'language' || attrLower == 'src' || attrLower == 'text' ||
  509. attrLower == 'type') {
  510. throw Error('Cannot set "' + attrLower + '" attribute');
  511. }
  512. }
  513. var content = '';
  514. script = goog.array.concat(script);
  515. for (var i = 0; i < script.length; i++) {
  516. content += goog.html.SafeScript.unwrap(script[i]);
  517. }
  518. // Convert to SafeHtml so that it's not HTML-escaped. This is safe because
  519. // as part of its contract, SafeScript should have no dangerous '<'.
  520. var htmlContent =
  521. goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  522. content, goog.i18n.bidi.Dir.NEUTRAL);
  523. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  524. 'script', opt_attributes, htmlContent);
  525. };
  526. /**
  527. * Creates a SafeHtml representing a style tag. The type attribute is set
  528. * to "text/css".
  529. * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
  530. * styleSheet Content to put inside the tag. Array elements are
  531. * concatenated.
  532. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  533. * Mapping from attribute names to their values. Only attribute names
  534. * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
  535. * the attribute to be omitted.
  536. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  537. * @throws {Error} If invalid attribute name or attribute value is provided. If
  538. * opt_attributes contains the type attribute.
  539. */
  540. goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {
  541. var fixedAttributes = {'type': 'text/css'};
  542. var defaultAttributes = {};
  543. var attributes = goog.html.SafeHtml.combineAttributes(
  544. fixedAttributes, defaultAttributes, opt_attributes);
  545. var content = '';
  546. styleSheet = goog.array.concat(styleSheet);
  547. for (var i = 0; i < styleSheet.length; i++) {
  548. content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);
  549. }
  550. // Convert to SafeHtml so that it's not HTML-escaped. This is safe because
  551. // as part of its contract, SafeStyleSheet should have no dangerous '<'.
  552. var htmlContent =
  553. goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  554. content, goog.i18n.bidi.Dir.NEUTRAL);
  555. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  556. 'style', attributes, htmlContent);
  557. };
  558. /**
  559. * Creates a SafeHtml representing a meta refresh tag.
  560. * @param {!goog.html.SafeUrl|string} url Where to redirect. If a string is
  561. * passed, it will be sanitized with SafeUrl.sanitize().
  562. * @param {number=} opt_secs Number of seconds until the page should be
  563. * reloaded. Will be set to 0 if unspecified.
  564. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  565. */
  566. goog.html.SafeHtml.createMetaRefresh = function(url, opt_secs) {
  567. // Note that sanitize is a no-op on SafeUrl.
  568. var unwrappedUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url));
  569. if (goog.labs.userAgent.browser.isIE() ||
  570. goog.labs.userAgent.browser.isEdge()) {
  571. // IE/EDGE can't parse the content attribute if the url contains a
  572. // semicolon. We can fix this by adding quotes around the url, but then we
  573. // can't parse quotes in the URL correctly. Also, it seems that IE/EDGE
  574. // did not unescape semicolons in these URLs at some point in the past. We
  575. // take a best-effort approach.
  576. //
  577. // If the URL has semicolons (which may happen in some cases, see
  578. // http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2
  579. // for instance), wrap it in single quotes to protect the semicolons.
  580. // If the URL has semicolons and single quotes, url-encode the single quotes
  581. // as well.
  582. //
  583. // This is imperfect. Notice that both ' and ; are reserved characters in
  584. // URIs, so this could do the wrong thing, but at least it will do the wrong
  585. // thing in only rare cases.
  586. if (goog.string.contains(unwrappedUrl, ';')) {
  587. unwrappedUrl = "'" + unwrappedUrl.replace(/'/g, '%27') + "'";
  588. }
  589. }
  590. var attributes = {
  591. 'http-equiv': 'refresh',
  592. 'content': (opt_secs || 0) + '; url=' + unwrappedUrl
  593. };
  594. // This function will handle the HTML escaping for attributes.
  595. return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
  596. 'meta', attributes);
  597. };
  598. /**
  599. * @param {string} tagName The tag name.
  600. * @param {string} name The attribute name.
  601. * @param {!goog.html.SafeHtml.AttributeValue} value The attribute value.
  602. * @return {string} A "name=value" string.
  603. * @throws {Error} If attribute value is unsafe for the given tag and attribute.
  604. * @private
  605. */
  606. goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
  607. // If it's goog.string.Const, allow any valid attribute name.
  608. if (value instanceof goog.string.Const) {
  609. value = goog.string.Const.unwrap(value);
  610. } else if (name.toLowerCase() == 'style') {
  611. value = goog.html.SafeHtml.getStyleValue_(value);
  612. } else if (/^on/i.test(name)) {
  613. // TODO(jakubvrana): Disallow more attributes with a special meaning.
  614. throw Error(
  615. 'Attribute "' + name + '" requires goog.string.Const value, "' + value +
  616. '" given.');
  617. // URL attributes handled differently according to tag.
  618. } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
  619. if (value instanceof goog.html.TrustedResourceUrl) {
  620. value = goog.html.TrustedResourceUrl.unwrap(value);
  621. } else if (value instanceof goog.html.SafeUrl) {
  622. value = goog.html.SafeUrl.unwrap(value);
  623. } else if (goog.isString(value)) {
  624. value = goog.html.SafeUrl.sanitize(value).getTypedStringValue();
  625. } else {
  626. throw Error(
  627. 'Attribute "' + name + '" on tag "' + tagName +
  628. '" requires goog.html.SafeUrl, goog.string.Const, or string,' +
  629. ' value "' + value + '" given.');
  630. }
  631. }
  632. // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
  633. // HTML-escaping.
  634. if (value.implementsGoogStringTypedString) {
  635. // Ok to call getTypedStringValue() since there's no reliance on the type
  636. // contract for security here.
  637. value = value.getTypedStringValue();
  638. }
  639. goog.asserts.assert(
  640. goog.isString(value) || goog.isNumber(value),
  641. 'String or number value expected, got ' + (typeof value) +
  642. ' with value: ' + value);
  643. return name + '="' + goog.string.htmlEscape(String(value)) + '"';
  644. };
  645. /**
  646. * Gets value allowed in "style" attribute.
  647. * @param {!goog.html.SafeHtml.AttributeValue} value It could be SafeStyle or a
  648. * map which will be passed to goog.html.SafeStyle.create.
  649. * @return {string} Unwrapped value.
  650. * @throws {Error} If string value is given.
  651. * @private
  652. */
  653. goog.html.SafeHtml.getStyleValue_ = function(value) {
  654. if (!goog.isObject(value)) {
  655. throw Error(
  656. 'The "style" attribute requires goog.html.SafeStyle or map ' +
  657. 'of style properties, ' + (typeof value) + ' given: ' + value);
  658. }
  659. if (!(value instanceof goog.html.SafeStyle)) {
  660. // Process the property bag into a style object.
  661. value = goog.html.SafeStyle.create(value);
  662. }
  663. return goog.html.SafeStyle.unwrap(value);
  664. };
  665. /**
  666. * Creates a SafeHtml content with known directionality consisting of a tag with
  667. * optional attributes and optional content.
  668. * @param {!goog.i18n.bidi.Dir} dir Directionality.
  669. * @param {string} tagName
  670. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  671. * @param {!goog.html.SafeHtml.TextOrHtml_|
  672. * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
  673. * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
  674. */
  675. goog.html.SafeHtml.createWithDir = function(
  676. dir, tagName, opt_attributes, opt_content) {
  677. var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
  678. html.dir_ = dir;
  679. return html;
  680. };
  681. /**
  682. * Creates a new SafeHtml object by concatenating values.
  683. * @param {...(!goog.html.SafeHtml.TextOrHtml_|
  684. * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
  685. * @return {!goog.html.SafeHtml}
  686. */
  687. goog.html.SafeHtml.concat = function(var_args) {
  688. var dir = goog.i18n.bidi.Dir.NEUTRAL;
  689. var content = '';
  690. /**
  691. * @param {!goog.html.SafeHtml.TextOrHtml_|
  692. * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
  693. */
  694. var addArgument = function(argument) {
  695. if (goog.isArray(argument)) {
  696. goog.array.forEach(argument, addArgument);
  697. } else {
  698. var html = goog.html.SafeHtml.htmlEscape(argument);
  699. content += goog.html.SafeHtml.unwrap(html);
  700. var htmlDir = html.getDirection();
  701. if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
  702. dir = htmlDir;
  703. } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
  704. dir = null;
  705. }
  706. }
  707. };
  708. goog.array.forEach(arguments, addArgument);
  709. return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  710. content, dir);
  711. };
  712. /**
  713. * Creates a new SafeHtml object with known directionality by concatenating the
  714. * values.
  715. * @param {!goog.i18n.bidi.Dir} dir Directionality.
  716. * @param {...(!goog.html.SafeHtml.TextOrHtml_|
  717. * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array
  718. * arguments would be processed recursively.
  719. * @return {!goog.html.SafeHtml}
  720. */
  721. goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
  722. var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
  723. html.dir_ = dir;
  724. return html;
  725. };
  726. /**
  727. * Type marker for the SafeHtml type, used to implement additional run-time
  728. * type checking.
  729. * @const {!Object}
  730. * @private
  731. */
  732. goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
  733. /**
  734. * Package-internal utility method to create SafeHtml instances.
  735. *
  736. * @param {string} html The string to initialize the SafeHtml object with.
  737. * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
  738. * constructed, or null if unknown.
  739. * @return {!goog.html.SafeHtml} The initialized SafeHtml object.
  740. * @package
  741. */
  742. goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
  743. html, dir) {
  744. return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
  745. html, dir);
  746. };
  747. /**
  748. * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
  749. * method exists only so that the compiler can dead code eliminate static
  750. * fields (like EMPTY) when they're not accessed.
  751. * @param {string} html
  752. * @param {?goog.i18n.bidi.Dir} dir
  753. * @return {!goog.html.SafeHtml}
  754. * @private
  755. */
  756. goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
  757. html, dir) {
  758. this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
  759. this.dir_ = dir;
  760. return this;
  761. };
  762. /**
  763. * Like create() but does not restrict which tags can be constructed.
  764. *
  765. * @param {string} tagName Tag name. Set or validated by caller.
  766. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  767. * @param {(!goog.html.SafeHtml.TextOrHtml_|
  768. * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
  769. * @return {!goog.html.SafeHtml}
  770. * @throws {Error} If invalid or unsafe attribute name or value is provided.
  771. * @throws {goog.asserts.AssertionError} If content for void tag is provided.
  772. * @package
  773. */
  774. goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = function(
  775. tagName, opt_attributes, opt_content) {
  776. var dir = null;
  777. var result = '<' + tagName;
  778. result += goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes);
  779. var content = opt_content;
  780. if (!goog.isDefAndNotNull(content)) {
  781. content = [];
  782. } else if (!goog.isArray(content)) {
  783. content = [content];
  784. }
  785. if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
  786. goog.asserts.assert(
  787. !content.length, 'Void tag <' + tagName + '> does not allow content.');
  788. result += '>';
  789. } else {
  790. var html = goog.html.SafeHtml.concat(content);
  791. result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
  792. dir = html.getDirection();
  793. }
  794. var dirAttribute = opt_attributes && opt_attributes['dir'];
  795. if (dirAttribute) {
  796. if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
  797. // If the tag has the "dir" attribute specified then its direction is
  798. // neutral because it can be safely used in any context.
  799. dir = goog.i18n.bidi.Dir.NEUTRAL;
  800. } else {
  801. dir = null;
  802. }
  803. }
  804. return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  805. result, dir);
  806. };
  807. /**
  808. * Creates a string with attributes to insert after tagName.
  809. * @param {string} tagName
  810. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  811. * @return {string} Returns an empty string if there are no attributes, returns
  812. * a string starting with a space otherwise.
  813. * @throws {Error} If attribute value is unsafe for the given tag and attribute.
  814. * @package
  815. */
  816. goog.html.SafeHtml.stringifyAttributes = function(tagName, opt_attributes) {
  817. var result = '';
  818. if (opt_attributes) {
  819. for (var name in opt_attributes) {
  820. if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
  821. throw Error('Invalid attribute name "' + name + '".');
  822. }
  823. var value = opt_attributes[name];
  824. if (!goog.isDefAndNotNull(value)) {
  825. continue;
  826. }
  827. result +=
  828. ' ' + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
  829. }
  830. }
  831. return result;
  832. };
  833. /**
  834. * @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>} fixedAttributes
  835. * @param {!Object<string, string>} defaultAttributes
  836. * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
  837. * Optional attributes passed to create*().
  838. * @return {!Object<string, ?goog.html.SafeHtml.AttributeValue>}
  839. * @throws {Error} If opt_attributes contains an attribute with the same name
  840. * as an attribute in fixedAttributes.
  841. * @package
  842. */
  843. goog.html.SafeHtml.combineAttributes = function(
  844. fixedAttributes, defaultAttributes, opt_attributes) {
  845. var combinedAttributes = {};
  846. var name;
  847. for (name in fixedAttributes) {
  848. goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
  849. combinedAttributes[name] = fixedAttributes[name];
  850. }
  851. for (name in defaultAttributes) {
  852. goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
  853. combinedAttributes[name] = defaultAttributes[name];
  854. }
  855. for (name in opt_attributes) {
  856. var nameLower = name.toLowerCase();
  857. if (nameLower in fixedAttributes) {
  858. throw Error(
  859. 'Cannot override "' + nameLower + '" attribute, got "' + name +
  860. '" with value "' + opt_attributes[name] + '"');
  861. }
  862. if (nameLower in defaultAttributes) {
  863. delete combinedAttributes[nameLower];
  864. }
  865. combinedAttributes[name] = opt_attributes[name];
  866. }
  867. return combinedAttributes;
  868. };
  869. /**
  870. * A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
  871. * @const {!goog.html.SafeHtml}
  872. */
  873. goog.html.SafeHtml.DOCTYPE_HTML =
  874. goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  875. '<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
  876. /**
  877. * A SafeHtml instance corresponding to the empty string.
  878. * @const {!goog.html.SafeHtml}
  879. */
  880. goog.html.SafeHtml.EMPTY =
  881. goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  882. '', goog.i18n.bidi.Dir.NEUTRAL);
  883. /**
  884. * A SafeHtml instance corresponding to the <br> tag.
  885. * @const {!goog.html.SafeHtml}
  886. */
  887. goog.html.SafeHtml.BR =
  888. goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
  889. '<br>', goog.i18n.bidi.Dir.NEUTRAL);