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