# lib/goog/html/safestylesheet.js

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 | /**
16 | * @fileoverview The SafeStyleSheet type and its builders.
17 | *
18 | * TODO(xtof): Link to document stating type contract.
19 | */
20 |
21 | goog.provide('goog.html.SafeStyleSheet');
22 |
23 | goog.require('goog.array');
24 | goog.require('goog.asserts');
25 | goog.require('goog.string');
26 | goog.require('goog.string.Const');
27 | goog.require('goog.string.TypedString');
28 |
29 |
30 |
31 | /**
32 | * A string-like object which represents a CSS style sheet and that carries the
33 | * security type contract that its value, as a string, will not cause untrusted
34 | * script execution (XSS) when evaluated as CSS in a browser.
35 | *
36 | * Instances of this type must be created via the factory method
37 | * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its
38 | * constructor. The constructor intentionally takes no parameters and the type
39 | * is immutable; hence only a default instance corresponding to the empty string
40 | * can be obtained via constructor invocation.
41 | *
42 | * A SafeStyleSheet's string representation can safely be interpolated as the
43 | * content of a style element within HTML. The SafeStyleSheet string should
44 | * not be escaped before interpolation.
45 | *
46 | * Values of this type must be composable, i.e. for any two values
47 | * {@code styleSheet1} and {@code styleSheet2} of this type,
48 | * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) +
49 | * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that
50 | * satisfies the SafeStyleSheet type constraint. This requirement implies that
51 | * for any value {@code styleSheet} of this type,
52 | * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in
53 | * "beginning of rule" context.
54 |
55 | * A SafeStyleSheet can be constructed via security-reviewed unchecked
56 | * conversions. In this case producers of SafeStyleSheet must ensure themselves
57 | * that the SafeStyleSheet does not contain unsafe script. Note in particular
58 | * that {@code &lt;} is dangerous, even when inside CSS strings, and so should
59 | * always be forbidden or CSS-escaped in user controlled input. For example, if
60 | * {@code &lt;/style&gt;&lt;script&gt;evil&lt;/script&gt;"} were interpolated
61 | * inside a CSS string, it would break out of the context of the original
62 | * style element and {@code evil} would execute. Also note that within an HTML
63 | * style (raw text) element, HTML character references, such as
64 | * {@code &amp;lt;}, are not allowed. See
65 | * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
66 | * (similar considerations apply to the style element).
67 | *
68 | * @see goog.html.SafeStyleSheet#fromConstant
69 | * @constructor
70 | * @final
71 | * @struct
72 | * @implements {goog.string.TypedString}
73 | */
74 | goog.html.SafeStyleSheet = function() {
75 | /**
76 | * The contained value of this SafeStyleSheet. The field has a purposely
77 | * ugly name to make (non-compiled) code that attempts to directly access this
78 | * field stand out.
79 | * @private {string}
80 | */
81 | this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = '';
82 |
83 | /**
84 | * A type marker used to implement additional run-time type checking.
85 | * @see goog.html.SafeStyleSheet#unwrap
86 | * @const
87 | * @private
88 | */
89 | this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
90 | goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
91 | };
92 |
93 |
94 | /**
95 | * @override
96 | * @const
97 | */
98 | goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
99 |
100 |
101 | /**
102 | * Type marker for the SafeStyleSheet type, used to implement additional
103 | * run-time type checking.
104 | * @const {!Object}
105 | * @private
106 | */
107 | goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
108 |
109 |
110 | /**
111 | * Creates a new SafeStyleSheet object by concatenating values.
112 | * @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)}
113 | * var_args Values to concatenate.
114 | * @return {!goog.html.SafeStyleSheet}
115 | */
116 | goog.html.SafeStyleSheet.concat = function(var_args) {
117 | var result = '';
118 |
119 | /**
120 | * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
121 | * argument
122 | */
123 | var addArgument = function(argument) {
124 | if (goog.isArray(argument)) {
125 | goog.array.forEach(argument, addArgument);
126 | } else {
127 | result += goog.html.SafeStyleSheet.unwrap(argument);
128 | }
129 | };
130 |
131 | goog.array.forEach(arguments, addArgument);
132 | return goog.html.SafeStyleSheet
133 | .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
134 | };
135 |
136 |
137 | /**
138 | * Creates a SafeStyleSheet object from a compile-time constant string.
139 | *
140 | * {@code styleSheet} must not have any &lt; characters in it, so that
141 | * the syntactic structure of the surrounding HTML is not affected.
142 | *
143 | * @param {!goog.string.Const} styleSheet A compile-time-constant string from
144 | * which to create a SafeStyleSheet.
145 | * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to
146 | * {@code styleSheet}.
147 | */
148 | goog.html.SafeStyleSheet.fromConstant = function(styleSheet) {
149 | var styleSheetString = goog.string.Const.unwrap(styleSheet);
150 | if (styleSheetString.length === 0) {
151 | return goog.html.SafeStyleSheet.EMPTY;
152 | }
153 | // > is a valid character in CSS selectors and there's no strict need to
154 | // block it if we already block <.
155 | goog.asserts.assert(!goog.string.contains(styleSheetString, '<'),
156 | "Forbidden '<' character in style sheet string: " + styleSheetString);
157 | return goog.html.SafeStyleSheet.
158 | createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString);
159 | };
160 |
161 |
162 | /**
163 | * Returns this SafeStyleSheet's value as a string.
164 | *
165 | * IMPORTANT: In code where it is security relevant that an object's type is
166 | * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap}
167 | * instead of this method. If in doubt, assume that it's security relevant. In
168 | * particular, note that goog.html functions which return a goog.html type do
169 | * not guarantee the returned instance is of the right type. For example:
170 | *
171 | * <pre>
172 | * var fakeSafeHtml = new String('fake');
173 | * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
174 | * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
175 | * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
176 | * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
177 | * // instanceof goog.html.SafeHtml.
178 | * </pre>
179 | *
180 | * @see goog.html.SafeStyleSheet#unwrap
181 | * @override
182 | */
183 | goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() {
184 | return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
185 | };
186 |
187 |
188 | if (goog.DEBUG) {
189 | /**
190 | * Returns a debug string-representation of this value.
191 | *
192 | * To obtain the actual string value wrapped in a SafeStyleSheet, use
193 | * {@code goog.html.SafeStyleSheet.unwrap}.
194 | *
195 | * @see goog.html.SafeStyleSheet#unwrap
196 | * @override
197 | */
198 | goog.html.SafeStyleSheet.prototype.toString = function() {
199 | return 'SafeStyleSheet{' +
200 | this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}';
201 | };
202 | }
203 |
204 |
205 | /**
206 | * Performs a runtime check that the provided object is indeed a
207 | * SafeStyleSheet object, and returns its value.
208 | *
209 | * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from.
210 | * @return {string} The safeStyleSheet object's contained string, unless
211 | * the run-time type check fails. In that case, {@code unwrap} returns an
212 | * innocuous string, or, if assertions are enabled, throws
213 | * {@code goog.asserts.AssertionError}.
214 | */
215 | goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) {
216 | // Perform additional Run-time type-checking to ensure that
217 | // safeStyleSheet is indeed an instance of the expected type. This
218 | // provides some additional protection against security bugs due to
219 | // application code that disables type checks.
220 | // Specifically, the following checks are performed:
221 | // 1. The object is an instance of the expected type.
222 | // 2. The object is not an instance of a subclass.
223 | // 3. The object carries a type marker for the expected type. "Faking" an
224 | // object requires a reference to the type marker, which has names intended
225 | // to stand out in code reviews.
226 | if (safeStyleSheet instanceof goog.html.SafeStyleSheet &&
227 | safeStyleSheet.constructor === goog.html.SafeStyleSheet &&
228 | safeStyleSheet.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
229 | goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
230 | return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
231 | } else {
232 | goog.asserts.fail(
233 | "expected object of type SafeStyleSheet, got '" + safeStyleSheet +
234 | "'");
235 | return 'type_error:SafeStyleSheet';
236 | }
237 | };
238 |
239 |
240 | /**
241 | * Package-internal utility method to create SafeStyleSheet instances.
242 | *
243 | * @param {string} styleSheet The string to initialize the SafeStyleSheet
244 | * object with.
245 | * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object.
246 | * @package
247 | */
248 | goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse =
249 | function(styleSheet) {
250 | return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_(
251 | styleSheet);
252 | };
253 |
254 |
255 | /**
256 | * Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This
257 | * method exists only so that the compiler can dead code eliminate static
258 | * fields (like EMPTY) when they're not accessed.
259 | * @param {string} styleSheet
260 | * @return {!goog.html.SafeStyleSheet}
261 | * @private
262 | */
263 | goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ =
264 | function(styleSheet) {
265 | this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet;
266 | return this;
267 | };
268 |
269 |
270 | /**
271 | * A SafeStyleSheet instance corresponding to the empty string.
272 | * @const {!goog.html.SafeStyleSheet}
273 | */
274 | goog.html.SafeStyleSheet.EMPTY =
275 | goog.html.SafeStyleSheet.
276 | createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');