safeurl.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 SafeUrl type and its builders.
  16. *
  17. * TODO(xtof): Link to document stating type contract.
  18. */
  19. goog.provide('goog.html.SafeUrl');
  20. goog.require('goog.asserts');
  21. goog.require('goog.fs.url');
  22. goog.require('goog.html.TrustedResourceUrl');
  23. goog.require('goog.i18n.bidi.Dir');
  24. goog.require('goog.i18n.bidi.DirectionalString');
  25. goog.require('goog.string');
  26. goog.require('goog.string.Const');
  27. goog.require('goog.string.TypedString');
  28. /**
  29. * A string that is safe to use in URL context in DOM APIs and HTML documents.
  30. *
  31. * A SafeUrl is a string-like object that carries the security type contract
  32. * that its value as a string will not cause untrusted script execution
  33. * when evaluated as a hyperlink URL in a browser.
  34. *
  35. * Values of this type are guaranteed to be safe to use in URL/hyperlink
  36. * contexts, such as assignment to URL-valued DOM properties, in the sense that
  37. * the use will not result in a Cross-Site-Scripting vulnerability. Similarly,
  38. * SafeUrls can be interpolated into the URL context of an HTML template (e.g.,
  39. * inside a href attribute). However, appropriate HTML-escaping must still be
  40. * applied.
  41. *
  42. * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's
  43. * contract does not guarantee that instances are safe to interpolate into HTML
  44. * without appropriate escaping.
  45. *
  46. * Note also that this type's contract does not imply any guarantees regarding
  47. * the resource the URL refers to. In particular, SafeUrls are <b>not</b>
  48. * safe to use in a context where the referred-to resource is interpreted as
  49. * trusted code, e.g., as the src of a script tag.
  50. *
  51. * Instances of this type must be created via the factory methods
  52. * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}),
  53. * etc and not by invoking its constructor. The constructor intentionally
  54. * takes no parameters and the type is immutable; hence only a default instance
  55. * corresponding to the empty string can be obtained via constructor invocation.
  56. *
  57. * @see goog.html.SafeUrl#fromConstant
  58. * @see goog.html.SafeUrl#from
  59. * @see goog.html.SafeUrl#sanitize
  60. * @constructor
  61. * @final
  62. * @struct
  63. * @implements {goog.i18n.bidi.DirectionalString}
  64. * @implements {goog.string.TypedString}
  65. */
  66. goog.html.SafeUrl = function() {
  67. /**
  68. * The contained value of this SafeUrl. The field has a purposely ugly
  69. * name to make (non-compiled) code that attempts to directly access this
  70. * field stand out.
  71. * @private {string}
  72. */
  73. this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
  74. /**
  75. * A type marker used to implement additional run-time type checking.
  76. * @see goog.html.SafeUrl#unwrap
  77. * @const {!Object}
  78. * @private
  79. */
  80. this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
  81. goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
  82. };
  83. /**
  84. * The innocuous string generated by goog.html.SafeUrl.sanitize when passed
  85. * an unsafe URL.
  86. *
  87. * about:invalid is registered in
  88. * http://www.w3.org/TR/css3-values/#about-invalid.
  89. * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to
  90. * contain a fragment, which is not to be considered when determining if an
  91. * about URL is well-known.
  92. *
  93. * Using about:invalid seems preferable to using a fixed data URL, since
  94. * browsers might choose to not report CSP violations on it, as legitimate
  95. * CSS function calls to attr() can result in this URL being produced. It is
  96. * also a standard URL which matches exactly the semantics we need:
  97. * "The about:invalid URI references a non-existent document with a generic
  98. * error condition. It can be used when a URI is necessary, but the default
  99. * value shouldn't be resolveable as any type of document".
  100. *
  101. * @const {string}
  102. */
  103. goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
  104. /**
  105. * @override
  106. * @const
  107. */
  108. goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
  109. /**
  110. * Returns this SafeUrl's value a string.
  111. *
  112. * IMPORTANT: In code where it is security relevant that an object's type is
  113. * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this
  114. * method. If in doubt, assume that it's security relevant. In particular, note
  115. * that goog.html functions which return a goog.html type do not guarantee that
  116. * the returned instance is of the right type. For example:
  117. *
  118. * <pre>
  119. * var fakeSafeHtml = new String('fake');
  120. * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
  121. * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
  122. * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
  123. * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
  124. * // goog.html.SafeHtml.
  125. * </pre>
  126. *
  127. * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
  128. * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
  129. * be appropriately escaped before embedding in a HTML document. Note that the
  130. * required escaping is context-sensitive (e.g. a different escaping is
  131. * required for embedding a URL in a style property within a style
  132. * attribute, as opposed to embedding in a href attribute).
  133. *
  134. * @see goog.html.SafeUrl#unwrap
  135. * @override
  136. */
  137. goog.html.SafeUrl.prototype.getTypedStringValue = function() {
  138. return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
  139. };
  140. /**
  141. * @override
  142. * @const
  143. */
  144. goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
  145. /**
  146. * Returns this URLs directionality, which is always {@code LTR}.
  147. * @override
  148. */
  149. goog.html.SafeUrl.prototype.getDirection = function() {
  150. return goog.i18n.bidi.Dir.LTR;
  151. };
  152. if (goog.DEBUG) {
  153. /**
  154. * Returns a debug string-representation of this value.
  155. *
  156. * To obtain the actual string value wrapped in a SafeUrl, use
  157. * {@code goog.html.SafeUrl.unwrap}.
  158. *
  159. * @see goog.html.SafeUrl#unwrap
  160. * @override
  161. */
  162. goog.html.SafeUrl.prototype.toString = function() {
  163. return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
  164. '}';
  165. };
  166. }
  167. /**
  168. * Performs a runtime check that the provided object is indeed a SafeUrl
  169. * object, and returns its value.
  170. *
  171. * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
  172. * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
  173. * be appropriately escaped before embedding in a HTML document. Note that the
  174. * required escaping is context-sensitive (e.g. a different escaping is
  175. * required for embedding a URL in a style property within a style
  176. * attribute, as opposed to embedding in a href attribute).
  177. *
  178. * @param {!goog.html.SafeUrl} safeUrl The object to extract from.
  179. * @return {string} The SafeUrl object's contained string, unless the run-time
  180. * type check fails. In that case, {@code unwrap} returns an innocuous
  181. * string, or, if assertions are enabled, throws
  182. * {@code goog.asserts.AssertionError}.
  183. */
  184. goog.html.SafeUrl.unwrap = function(safeUrl) {
  185. // Perform additional Run-time type-checking to ensure that safeUrl is indeed
  186. // an instance of the expected type. This provides some additional protection
  187. // against security bugs due to application code that disables type checks.
  188. // Specifically, the following checks are performed:
  189. // 1. The object is an instance of the expected type.
  190. // 2. The object is not an instance of a subclass.
  191. // 3. The object carries a type marker for the expected type. "Faking" an
  192. // object requires a reference to the type marker, which has names intended
  193. // to stand out in code reviews.
  194. if (safeUrl instanceof goog.html.SafeUrl &&
  195. safeUrl.constructor === goog.html.SafeUrl &&
  196. safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
  197. goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
  198. return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
  199. } else {
  200. goog.asserts.fail('expected object of type SafeUrl, got \'' +
  201. safeUrl + '\' of type ' + goog.typeOf(safeUrl));
  202. return 'type_error:SafeUrl';
  203. }
  204. };
  205. /**
  206. * Creates a SafeUrl object from a compile-time constant string.
  207. *
  208. * Compile-time constant strings are inherently program-controlled and hence
  209. * trusted.
  210. *
  211. * @param {!goog.string.Const} url A compile-time-constant string from which to
  212. * create a SafeUrl.
  213. * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}.
  214. */
  215. goog.html.SafeUrl.fromConstant = function(url) {
  216. return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
  217. goog.string.Const.unwrap(url));
  218. };
  219. /**
  220. * A pattern that matches Blob or data types that can have SafeUrls created
  221. * from URL.createObjectURL(blob) or via a data: URI. Only matches image and
  222. * video types, currently.
  223. * @const
  224. * @private
  225. */
  226. goog.html.SAFE_MIME_TYPE_PATTERN_ =
  227. /^(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm))$/i;
  228. /**
  229. * Creates a SafeUrl wrapping a blob URL for the given {@code blob}.
  230. *
  231. * The blob URL is created with {@code URL.createObjectURL}. If the MIME type
  232. * for {@code blob} is not of a known safe image or video MIME type, then the
  233. * SafeUrl will wrap {@link #INNOCUOUS_STRING}.
  234. *
  235. * @see http://www.w3.org/TR/FileAPI/#url
  236. * @param {!Blob} blob
  237. * @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped
  238. * as a SafeUrl.
  239. */
  240. goog.html.SafeUrl.fromBlob = function(blob) {
  241. var url = goog.html.SAFE_MIME_TYPE_PATTERN_.test(blob.type) ?
  242. goog.fs.url.createObjectUrl(blob) :
  243. goog.html.SafeUrl.INNOCUOUS_STRING;
  244. return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
  245. };
  246. /**
  247. * Matches a base-64 data URL, with the first match group being the MIME type.
  248. * @const
  249. * @private
  250. */
  251. goog.html.DATA_URL_PATTERN_ = /^data:([^;,]*);base64,[a-z0-9+\/]+=*$/i;
  252. /**
  253. * Creates a SafeUrl wrapping a data: URL, after validating it matches a
  254. * known-safe image or video MIME type.
  255. *
  256. * @param {string} dataUrl A valid base64 data URL with one of the whitelisted
  257. * image or video MIME types.
  258. * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
  259. * wrapped as a SafeUrl if it does not pass.
  260. */
  261. goog.html.SafeUrl.fromDataUrl = function(dataUrl) {
  262. // There's a slight risk here that a browser sniffs the content type if it
  263. // doesn't know the MIME type and executes HTML within the data: URL. For this
  264. // to cause XSS it would also have to execute the HTML in the same origin
  265. // of the page with the link. It seems unlikely that both of these will
  266. // happen, particularly in not really old IEs.
  267. var match = dataUrl.match(goog.html.DATA_URL_PATTERN_);
  268. var valid = match && goog.html.SAFE_MIME_TYPE_PATTERN_.test(match[1]);
  269. return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
  270. valid ? dataUrl : goog.html.SafeUrl.INNOCUOUS_STRING);
  271. };
  272. /**
  273. * Creates a SafeUrl wrapping a tel: URL.
  274. *
  275. * @param {string} telUrl A tel URL.
  276. * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
  277. * wrapped as a SafeUrl if it does not pass.
  278. */
  279. goog.html.SafeUrl.fromTelUrl = function(telUrl) {
  280. // There's a risk that a tel: URL could immediately place a call once
  281. // clicked, without requiring user confirmation. For that reason it is
  282. // handled in this separate function.
  283. if (!goog.string.caseInsensitiveStartsWith(telUrl, 'tel:')) {
  284. telUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
  285. }
  286. return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
  287. telUrl);
  288. };
  289. /**
  290. * Creates a SafeUrl from TrustedResourceUrl. This is safe because
  291. * TrustedResourceUrl is more tightly restricted than SafeUrl.
  292. *
  293. * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl
  294. * @return {!goog.html.SafeUrl}
  295. */
  296. goog.html.SafeUrl.fromTrustedResourceUrl = function(trustedResourceUrl) {
  297. return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
  298. goog.html.TrustedResourceUrl.unwrap(trustedResourceUrl));
  299. };
  300. /**
  301. * A pattern that recognizes a commonly useful subset of URLs that satisfy
  302. * the SafeUrl contract.
  303. *
  304. * This regular expression matches a subset of URLs that will not cause script
  305. * execution if used in URL context within a HTML document. Specifically, this
  306. * regular expression matches if (comment from here on and regex copied from
  307. * Soy's EscapingConventions):
  308. * (1) Either a protocol in a whitelist (http, https, mailto or ftp).
  309. * (2) or no protocol. A protocol must be followed by a colon. The below
  310. * allows that by allowing colons only after one of the characters [/?#].
  311. * A colon after a hash (#) must be in the fragment.
  312. * Otherwise, a colon after a (?) must be in a query.
  313. * Otherwise, a colon after a single solidus (/) must be in a path.
  314. * Otherwise, a colon after a double solidus (//) must be in the authority
  315. * (before port).
  316. *
  317. * @private
  318. * @const {!RegExp}
  319. */
  320. goog.html.SAFE_URL_PATTERN_ =
  321. /^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i;
  322. /**
  323. * Creates a SafeUrl object from {@code url}. If {@code url} is a
  324. * goog.html.SafeUrl then it is simply returned. Otherwise the input string is
  325. * validated to match a pattern of commonly used safe URLs.
  326. *
  327. * {@code url} may be a URL with the http, https, mailto or ftp scheme,
  328. * or a relative URL (i.e., a URL without a scheme; specifically, a
  329. * scheme-relative, absolute-path-relative, or path-relative URL).
  330. *
  331. * @see http://url.spec.whatwg.org/#concept-relative-url
  332. * @param {string|!goog.string.TypedString} url The URL to validate.
  333. * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
  334. */
  335. goog.html.SafeUrl.sanitize = function(url) {
  336. if (url instanceof goog.html.SafeUrl) {
  337. return url;
  338. } else if (url.implementsGoogStringTypedString) {
  339. url = url.getTypedStringValue();
  340. } else {
  341. url = String(url);
  342. }
  343. if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
  344. url = goog.html.SafeUrl.INNOCUOUS_STRING;
  345. }
  346. return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
  347. };
  348. /**
  349. * Type marker for the SafeUrl type, used to implement additional run-time
  350. * type checking.
  351. * @const {!Object}
  352. * @private
  353. */
  354. goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
  355. /**
  356. * Package-internal utility method to create SafeUrl instances.
  357. *
  358. * @param {string} url The string to initialize the SafeUrl object with.
  359. * @return {!goog.html.SafeUrl} The initialized SafeUrl object.
  360. * @package
  361. */
  362. goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function(
  363. url) {
  364. var safeUrl = new goog.html.SafeUrl();
  365. safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url;
  366. return safeUrl;
  367. };
  368. /**
  369. * A SafeUrl corresponding to the special about:blank url.
  370. * @const {!goog.html.SafeUrl}
  371. */
  372. goog.html.SafeUrl.ABOUT_BLANK =
  373. goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
  374. 'about:blank');