safescript.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright 2014 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 SafeScript type and its builders.
  16. *
  17. * TODO(xtof): Link to document stating type contract.
  18. */
  19. goog.provide('goog.html.SafeScript');
  20. goog.require('goog.asserts');
  21. goog.require('goog.string.Const');
  22. goog.require('goog.string.TypedString');
  23. /**
  24. * A string-like object which represents JavaScript code and that carries the
  25. * security type contract that its value, as a string, will not cause execution
  26. * of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
  27. * in a browser.
  28. *
  29. * Instances of this type must be created via the factory method
  30. * {@code goog.html.SafeScript.fromConstant} and not by invoking its
  31. * constructor. The constructor intentionally takes no parameters and the type
  32. * is immutable; hence only a default instance corresponding to the empty string
  33. * can be obtained via constructor invocation.
  34. *
  35. * A SafeScript's string representation can safely be interpolated as the
  36. * content of a script element within HTML. The SafeScript string should not be
  37. * escaped before interpolation.
  38. *
  39. * Note that the SafeScript might contain text that is attacker-controlled but
  40. * that text should have been interpolated with appropriate escaping,
  41. * sanitization and/or validation into the right location in the script, such
  42. * that it is highly constrained in its effect (for example, it had to match a
  43. * set of whitelisted words).
  44. *
  45. * A SafeScript can be constructed via security-reviewed unchecked
  46. * conversions. In this case producers of SafeScript must ensure themselves that
  47. * the SafeScript does not contain unsafe script. Note in particular that
  48. * {@code <} is dangerous, even when inside JavaScript strings, and so should
  49. * always be forbidden or JavaScript escaped in user controlled input. For
  50. * example, if {@code </script><script>evil</script>"} were
  51. * interpolated inside a JavaScript string, it would break out of the context
  52. * of the original script element and {@code evil} would execute. Also note
  53. * that within an HTML script (raw text) element, HTML character references,
  54. * such as "<" are not allowed. See
  55. * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
  56. *
  57. * @see goog.html.SafeScript#fromConstant
  58. * @constructor
  59. * @final
  60. * @struct
  61. * @implements {goog.string.TypedString}
  62. */
  63. goog.html.SafeScript = function() {
  64. /**
  65. * The contained value of this SafeScript. The field has a purposely
  66. * ugly name to make (non-compiled) code that attempts to directly access this
  67. * field stand out.
  68. * @private {string}
  69. */
  70. this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = '';
  71. /**
  72. * A type marker used to implement additional run-time type checking.
  73. * @see goog.html.SafeScript#unwrap
  74. * @const {!Object}
  75. * @private
  76. */
  77. this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
  78. goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
  79. };
  80. /**
  81. * @override
  82. * @const
  83. */
  84. goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
  85. /**
  86. * Type marker for the SafeScript type, used to implement additional
  87. * run-time type checking.
  88. * @const {!Object}
  89. * @private
  90. */
  91. goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
  92. /**
  93. * Creates a SafeScript object from a compile-time constant string.
  94. *
  95. * @param {!goog.string.Const} script A compile-time-constant string from which
  96. * to create a SafeScript.
  97. * @return {!goog.html.SafeScript} A SafeScript object initialized to
  98. * {@code script}.
  99. */
  100. goog.html.SafeScript.fromConstant = function(script) {
  101. var scriptString = goog.string.Const.unwrap(script);
  102. if (scriptString.length === 0) {
  103. return goog.html.SafeScript.EMPTY;
  104. }
  105. return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
  106. scriptString);
  107. };
  108. /**
  109. * Returns this SafeScript's value as a string.
  110. *
  111. * IMPORTANT: In code where it is security relevant that an object's type is
  112. * indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of
  113. * this method. If in doubt, assume that it's security relevant. In particular,
  114. * note that goog.html functions which return a goog.html type do not guarantee
  115. * the returned instance is of the right type. For example:
  116. *
  117. * <pre>
  118. * var fakeSafeHtml = new String('fake');
  119. * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
  120. * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
  121. * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
  122. * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
  123. * // instanceof goog.html.SafeHtml.
  124. * </pre>
  125. *
  126. * @see goog.html.SafeScript#unwrap
  127. * @override
  128. */
  129. goog.html.SafeScript.prototype.getTypedStringValue = function() {
  130. return this.privateDoNotAccessOrElseSafeScriptWrappedValue_;
  131. };
  132. if (goog.DEBUG) {
  133. /**
  134. * Returns a debug string-representation of this value.
  135. *
  136. * To obtain the actual string value wrapped in a SafeScript, use
  137. * {@code goog.html.SafeScript.unwrap}.
  138. *
  139. * @see goog.html.SafeScript#unwrap
  140. * @override
  141. */
  142. goog.html.SafeScript.prototype.toString = function() {
  143. return 'SafeScript{' +
  144. this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}';
  145. };
  146. }
  147. /**
  148. * Performs a runtime check that the provided object is indeed a
  149. * SafeScript object, and returns its value.
  150. *
  151. * @param {!goog.html.SafeScript} safeScript The object to extract from.
  152. * @return {string} The safeScript object's contained string, unless
  153. * the run-time type check fails. In that case, {@code unwrap} returns an
  154. * innocuous string, or, if assertions are enabled, throws
  155. * {@code goog.asserts.AssertionError}.
  156. */
  157. goog.html.SafeScript.unwrap = function(safeScript) {
  158. // Perform additional Run-time type-checking to ensure that
  159. // safeScript is indeed an instance of the expected type. This
  160. // provides some additional protection against security bugs due to
  161. // application code that disables type checks.
  162. // Specifically, the following checks are performed:
  163. // 1. The object is an instance of the expected type.
  164. // 2. The object is not an instance of a subclass.
  165. // 3. The object carries a type marker for the expected type. "Faking" an
  166. // object requires a reference to the type marker, which has names intended
  167. // to stand out in code reviews.
  168. if (safeScript instanceof goog.html.SafeScript &&
  169. safeScript.constructor === goog.html.SafeScript &&
  170. safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
  171. goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
  172. return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
  173. } else {
  174. goog.asserts.fail('expected object of type SafeScript, got \'' +
  175. safeScript + '\' of type ' + goog.typeOf(safeScript));
  176. return 'type_error:SafeScript';
  177. }
  178. };
  179. /**
  180. * Package-internal utility method to create SafeScript instances.
  181. *
  182. * @param {string} script The string to initialize the SafeScript object with.
  183. * @return {!goog.html.SafeScript} The initialized SafeScript object.
  184. * @package
  185. */
  186. goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse =
  187. function(script) {
  188. return new goog.html.SafeScript().initSecurityPrivateDoNotAccessOrElse_(
  189. script);
  190. };
  191. /**
  192. * Called from createSafeScriptSecurityPrivateDoNotAccessOrElse(). This
  193. * method exists only so that the compiler can dead code eliminate static
  194. * fields (like EMPTY) when they're not accessed.
  195. * @param {string} script
  196. * @return {!goog.html.SafeScript}
  197. * @private
  198. */
  199. goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
  200. script) {
  201. this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
  202. return this;
  203. };
  204. /**
  205. * A SafeScript instance corresponding to the empty string.
  206. * @const {!goog.html.SafeScript}
  207. */
  208. goog.html.SafeScript.EMPTY =
  209. goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');