closure 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +12 -12
  3. data/closure-compiler/README.md +503 -0
  4. data/closure-compiler/compiler.jar +0 -0
  5. data/closure-templates/SoyToJsSrcCompiler.jar +0 -0
  6. data/closure-templates/soyutils.js +1593 -469
  7. data/closure-templates/soyutils_usegoog.js +1105 -223
  8. data/docs/SCRIPT.md +5 -5
  9. data/docs/closure/Closure/BeanShell.html +63 -54
  10. data/docs/closure/Closure/Compiler/Compilation.html +124 -107
  11. data/docs/closure/Closure/Compiler/Error.html +28 -21
  12. data/docs/closure/Closure/Compiler.html +81 -76
  13. data/docs/closure/Closure/FileResponse.html +113 -98
  14. data/docs/closure/Closure/Goog.html +264 -253
  15. data/docs/closure/Closure/Middleware.html +66 -55
  16. data/docs/closure/Closure/Script/NotFound.html +28 -21
  17. data/docs/closure/Closure/Script/RenderStackOverflow.html +28 -21
  18. data/docs/closure/Closure/Script.html +212 -203
  19. data/docs/closure/Closure/Server.html +100 -90
  20. data/docs/closure/Closure/ShowExceptions.html +63 -52
  21. data/docs/closure/Closure/Sources.html +254 -246
  22. data/docs/closure/Closure/Templates/Error.html +28 -21
  23. data/docs/closure/Closure/Templates.html +88 -80
  24. data/docs/closure/Closure.html +181 -163
  25. data/docs/closure/_index.html +42 -38
  26. data/docs/closure/class_list.html +19 -8
  27. data/docs/closure/css/full_list.css +4 -2
  28. data/docs/closure/css/style.css +68 -51
  29. data/docs/closure/file.LICENSE.html +24 -217
  30. data/docs/closure/file.README.html +54 -47
  31. data/docs/closure/file_list.html +20 -9
  32. data/docs/closure/frames.html +18 -5
  33. data/docs/closure/index.html +54 -47
  34. data/docs/closure/js/app.js +60 -46
  35. data/docs/closure/js/full_list.js +24 -10
  36. data/docs/closure/js/jquery.js +4 -16
  37. data/docs/closure/method_list.html +74 -175
  38. data/docs/closure/top-level-namespace.html +29 -20
  39. data/lib/closure/compiler.rb +32 -42
  40. data/lib/closure/goog.rb +12 -12
  41. data/lib/closure/server.rb +6 -6
  42. data/lib/closure/show_exceptions.rb +15 -12
  43. data/lib/closure/version.rb +1 -1
  44. data/scripts/git.erb +183 -0
  45. data/scripts/hello/compiler.js.erb +2 -2
  46. data/scripts/hello/hello.js +1 -1
  47. data/scripts/hello/index.erb +6 -0
  48. data/scripts/hello/legume.js +12 -7
  49. data/scripts/index.erb +15 -13
  50. data/scripts/modules/compiler.js.erb +3 -3
  51. data/scripts/modules/compiler_build.js +3 -3
  52. data/scripts/modules/compiler_build.map +13 -12159
  53. data/scripts/modules/compiler_build_api.js +1 -1
  54. data/scripts/modules/compiler_build_app.js +74 -71
  55. data/scripts/modules/compiler_build_settings.js +2 -2
  56. data/scripts/modules/index.erb +5 -3
  57. data/scripts/modules/settings.js +1 -1
  58. data/scripts/svn.erb +11 -11
  59. data/scripts/welcome.erb +7 -6
  60. metadata +65 -81
  61. data/closure-compiler/README +0 -292
  62. data/scripts/hello/compiler_build.js +0 -5
  63. data/scripts/hello/compiler_build.map +0 -748
  64. data/scripts/hello/compiler_debug.js +0 -119
  65. data/scripts/modules/compiler_debug.js +0 -6
  66. data/scripts/modules/compiler_debug_api.js +0 -11
  67. data/scripts/modules/compiler_debug_app.js +0 -2414
  68. data/scripts/modules/compiler_debug_settings.js +0 -39
  69. data/scripts/rails/index.erb +0 -46
  70. data/scripts/rails/rails_ujs.js +0 -96
@@ -30,9 +30,10 @@
30
30
  * by Soy-generated JS code. Please do not use these functions directly from
31
31
  * your hand-writen code. Their names all start with '$$'.
32
32
  *
33
+ * @author Garrett Boyer
33
34
  * @author Mike Samuel
34
35
  * @author Kai Huang
35
- * @author Aharon Lenin
36
+ * @author Aharon Lanin
36
37
  */
37
38
 
38
39
  goog.provide('soy');
@@ -41,8 +42,10 @@ goog.provide('soy.esc');
41
42
  goog.provide('soydata');
42
43
  goog.provide('soydata.SanitizedHtml');
43
44
  goog.provide('soydata.SanitizedHtmlAttribute');
45
+ goog.provide('soydata.SanitizedJs');
44
46
  goog.provide('soydata.SanitizedJsStrChars');
45
47
  goog.provide('soydata.SanitizedUri');
48
+ goog.provide('soydata.VERY_UNSAFE');
46
49
 
47
50
  goog.require('goog.asserts');
48
51
  goog.require('goog.dom.DomHelper');
@@ -50,6 +53,7 @@ goog.require('goog.format');
50
53
  goog.require('goog.i18n.BidiFormatter');
51
54
  goog.require('goog.i18n.bidi');
52
55
  goog.require('goog.soy');
56
+ goog.require('goog.soy.data.SanitizedContentKind');
53
57
  goog.require('goog.string');
54
58
  goog.require('goog.string.StringBuffer');
55
59
 
@@ -60,7 +64,7 @@ goog.require('goog.string.StringBuffer');
60
64
 
61
65
  /**
62
66
  * Utility class to facilitate much faster string concatenation in IE,
63
- * using Array.join() rather than the '+' operator. For other browsers
67
+ * using Array.join() rather than the '+' operator. For other browsers
64
68
  * we simply use the '+' operator.
65
69
  *
66
70
  * @param {Object} var_args Initial items to append,
@@ -77,131 +81,441 @@ soy.StringBuilder = goog.string.StringBuffer;
77
81
 
78
82
  /**
79
83
  * A type of textual content.
80
- * @enum {number}
84
+ *
85
+ * This is an enum of type Object so that these values are unforgeable.
86
+ *
87
+ * @enum {!Object}
81
88
  */
82
- soydata.SanitizedContentKind = {
89
+ soydata.SanitizedContentKind = goog.soy.data.SanitizedContentKind;
83
90
 
84
- /**
85
- * A snippet of HTML that does not start or end inside a tag, comment, entity,
86
- * or DOCTYPE; and that does not contain any executable code
87
- * (JS, {@code <object>}s, etc.) from a different trust domain.
88
- */
89
- HTML: 0,
90
91
 
91
- /**
92
- * A sequence of code units that can appear between quotes (either kind) in a
93
- * JS program without causing a parse error, and without causing any side
94
- * effects.
95
- * <p>
96
- * The content should not contain unescaped quotes, newlines, or anything else
97
- * that would cause parsing to fail or to cause a JS parser to finish the
98
- * string its parsing inside the content.
99
- * <p>
100
- * The content must also not end inside an escape sequence ; no partial octal
101
- * escape sequences or odd number of '{@code \}'s at the end.
102
- */
103
- JS_STR_CHARS: 1,
92
+ /**
93
+ * Checks whether a given value is of a given content kind.
94
+ *
95
+ * @param {*} value The value to be examined.
96
+ * @param {soydata.SanitizedContentKind} contentKind The desired content
97
+ * kind.
98
+ * @return {boolean} Whether the given value is of the given kind.
99
+ * @private
100
+ */
101
+ soydata.isContentKind = function(value, contentKind) {
102
+ // TODO(user): This function should really include the assert on
103
+ // value.constructor that is currently sprinkled at most of the call sites.
104
+ // Unfortunately, that would require a (debug-mode-only) switch statement.
105
+ // TODO(user): Perhaps we should get rid of the contentKind property
106
+ // altogether and only at the constructor.
107
+ return value != null && value.contentKind === contentKind;
108
+ };
104
109
 
105
- /** A properly encoded portion of a URI. */
106
- URI: 2,
107
110
 
108
- /** An attribute name and value such as {@code dir="ltr"}. */
109
- HTML_ATTRIBUTE: 3
111
+ /**
112
+ * Returns a given value's contentDir property, constrained to a
113
+ * goog.i18n.bidi.Dir value or null. Returns null if the value is null,
114
+ * undefined, a primitive or does not have a contentDir property, or the
115
+ * property's value is not 1 (for LTR), -1 (for RTL), or 0 (for neutral).
116
+ *
117
+ * @param {*} value The value whose contentDir property, if any, is to
118
+ * be returned.
119
+ * @return {?goog.i18n.bidi.Dir} The contentDir property.
120
+ */
121
+ soydata.getContentDir = function(value) {
122
+ if (value != null) {
123
+ switch (value.contentDir) {
124
+ case goog.i18n.bidi.Dir.LTR:
125
+ return goog.i18n.bidi.Dir.LTR;
126
+ case goog.i18n.bidi.Dir.RTL:
127
+ return goog.i18n.bidi.Dir.RTL;
128
+ case goog.i18n.bidi.Dir.NEUTRAL:
129
+ return goog.i18n.bidi.Dir.NEUTRAL;
130
+ }
131
+ }
132
+ return null;
110
133
  };
111
134
 
112
135
 
113
136
  /**
114
- * A string-like object that carries a content-type.
115
- * @param {string} content
137
+ * Content of type {@link soydata.SanitizedContentKind.HTML}.
138
+ *
139
+ * The content is a string of HTML that can safely be embedded in a PCDATA
140
+ * context in your app. If you would be surprised to find that an HTML
141
+ * sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs) and
142
+ * you wouldn't write a template that produces {@code s} on security or privacy
143
+ * grounds, then don't pass {@code s} here. The default content direction is
144
+ * unknown, i.e. to be estimated when necessary.
145
+ *
116
146
  * @constructor
117
- * @private
147
+ * @extends {goog.soy.data.SanitizedContent}
118
148
  */
119
- soydata.SanitizedContent = function(content) {
120
- /**
121
- * The textual content.
122
- * @type {string}
123
- */
124
- this.content = content;
149
+ soydata.SanitizedHtml = function() {
150
+ goog.soy.data.SanitizedContent.call(this); // Throws an exception.
125
151
  };
126
-
127
- /** @type {soydata.SanitizedContentKind} */
128
- soydata.SanitizedContent.prototype.contentKind;
152
+ goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedContent);
129
153
 
130
154
  /** @override */
131
- soydata.SanitizedContent.prototype.toString = function() {
132
- return this.content;
155
+ soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML;
156
+
157
+ /**
158
+ * Returns a SanitizedHtml object for a particular value. The content direction
159
+ * is preserved.
160
+ *
161
+ * This HTML-escapes the value unless it is already SanitizedHtml.
162
+ *
163
+ * @param {*} value The value to convert. If it is already a SanitizedHtml
164
+ * object, it is left alone.
165
+ * @return {!soydata.SanitizedHtml} A SanitizedHtml object derived from the
166
+ * stringified value. It is escaped unless the input is SanitizedHtml.
167
+ */
168
+ soydata.SanitizedHtml.from = function(value) {
169
+ // The check is soydata.isContentKind() inlined for performance.
170
+ if (value != null &&
171
+ value.contentKind === soydata.SanitizedContentKind.HTML) {
172
+ goog.asserts.assert(value.constructor === soydata.SanitizedHtml);
173
+ return /** @type {!soydata.SanitizedHtml} */ (value);
174
+ }
175
+ return soydata.VERY_UNSAFE.ordainSanitizedHtml(
176
+ soy.esc.$$escapeHtmlHelper(String(value)), soydata.getContentDir(value));
133
177
  };
134
178
 
135
179
 
136
180
  /**
137
- * Content of type {@link soydata.SanitizedContentKind.HTML}.
138
- * @param {string} content A string of HTML that can safely be embedded in
139
- * a PCDATA context in your app. If you would be surprised to find that an
140
- * HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs)
141
- * and you wouldn't write a template that produces {@code s} on security or
142
- * privacy grounds, then don't pass {@code s} here.
181
+ * Content of type {@link soydata.SanitizedContentKind.JS}.
182
+ *
183
+ * The content is Javascript source that when evaluated does not execute any
184
+ * attacker-controlled scripts. The content direction is LTR.
185
+ *
143
186
  * @constructor
144
- * @extends {soydata.SanitizedContent}
187
+ * @extends {goog.soy.data.SanitizedContent}
145
188
  */
146
- soydata.SanitizedHtml = function(content) {
147
- soydata.SanitizedContent.call(this, content);
189
+ soydata.SanitizedJs = function() {
190
+ goog.soy.data.SanitizedContent.call(this); // Throws an exception.
148
191
  };
149
- goog.inherits(soydata.SanitizedHtml, soydata.SanitizedContent);
192
+ goog.inherits(soydata.SanitizedJs, goog.soy.data.SanitizedContent);
150
193
 
151
194
  /** @override */
152
- soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML;
195
+ soydata.SanitizedJs.prototype.contentKind =
196
+ soydata.SanitizedContentKind.JS;
197
+
198
+ /** @override */
199
+ soydata.SanitizedJs.prototype.contentDir = goog.i18n.bidi.Dir.LTR;
153
200
 
154
201
 
155
202
  /**
156
203
  * Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}.
157
- * @param {string} content A string of JS that when evaled, produces a
158
- * value that does not depend on any sensitive data and has no side effects
159
- * <b>OR</b> a string of JS that does not reference any variables or have
160
- * any side effects not known statically to the app authors.
204
+ *
205
+ * The content can be safely inserted as part of a single- or double-quoted
206
+ * string without terminating the string. The default content direction is
207
+ * unknown, i.e. to be estimated when necessary.
208
+ *
161
209
  * @constructor
162
- * @extends {soydata.SanitizedContent}
210
+ * @extends {goog.soy.data.SanitizedContent}
163
211
  */
164
- soydata.SanitizedJsStrChars = function(content) {
165
- soydata.SanitizedContent.call(this, content);
212
+ soydata.SanitizedJsStrChars = function() {
213
+ goog.soy.data.SanitizedContent.call(this); // Throws an exception.
166
214
  };
167
- goog.inherits(soydata.SanitizedJsStrChars, soydata.SanitizedContent);
215
+ goog.inherits(soydata.SanitizedJsStrChars, goog.soy.data.SanitizedContent);
168
216
 
169
217
  /** @override */
170
218
  soydata.SanitizedJsStrChars.prototype.contentKind =
171
219
  soydata.SanitizedContentKind.JS_STR_CHARS;
172
220
 
173
-
174
221
  /**
175
222
  * Content of type {@link soydata.SanitizedContentKind.URI}.
176
- * @param {string} content A chunk of URI that the caller knows is safe to
177
- * emit in a template.
223
+ *
224
+ * The content is a URI chunk that the caller knows is safe to emit in a
225
+ * template. The content direction is LTR.
226
+ *
178
227
  * @constructor
179
- * @extends {soydata.SanitizedContent}
228
+ * @extends {goog.soy.data.SanitizedContent}
180
229
  */
181
- soydata.SanitizedUri = function(content) {
182
- soydata.SanitizedContent.call(this, content);
230
+ soydata.SanitizedUri = function() {
231
+ goog.soy.data.SanitizedContent.call(this); // Throws an exception.
183
232
  };
184
- goog.inherits(soydata.SanitizedUri, soydata.SanitizedContent);
233
+ goog.inherits(soydata.SanitizedUri, goog.soy.data.SanitizedContent);
185
234
 
186
235
  /** @override */
187
236
  soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI;
188
237
 
238
+ /** @override */
239
+ soydata.SanitizedUri.prototype.contentDir = goog.i18n.bidi.Dir.LTR;
240
+
189
241
 
190
242
  /**
191
- * Content of type {@link soydata.SanitizedContentKind.HTML_ATTRIBUTE}.
192
- * @param {string} content An attribute name and value, such as
193
- * {@code dir="ltr"}.
243
+ * Content of type {@link soydata.SanitizedContentKind.ATTRIBUTES}.
244
+ *
245
+ * The content should be safely embeddable within an open tag, such as a
246
+ * key="value" pair. The content direction is LTR.
247
+ *
194
248
  * @constructor
195
- * @extends {soydata.SanitizedContent}
249
+ * @extends {goog.soy.data.SanitizedContent}
196
250
  */
197
- soydata.SanitizedHtmlAttribute = function(content) {
198
- soydata.SanitizedContent.call(this, content);
251
+ soydata.SanitizedHtmlAttribute = function() {
252
+ goog.soy.data.SanitizedContent.call(this); // Throws an exception.
199
253
  };
200
- goog.inherits(soydata.SanitizedHtmlAttribute, soydata.SanitizedContent);
254
+ goog.inherits(soydata.SanitizedHtmlAttribute, goog.soy.data.SanitizedContent);
201
255
 
202
256
  /** @override */
203
257
  soydata.SanitizedHtmlAttribute.prototype.contentKind =
204
- soydata.SanitizedContentKind.HTML_ATTRIBUTE;
258
+ soydata.SanitizedContentKind.ATTRIBUTES;
259
+
260
+ /** @override */
261
+ soydata.SanitizedHtmlAttribute.prototype.contentDir = goog.i18n.bidi.Dir.LTR;
262
+
263
+
264
+ /**
265
+ * Content of type {@link soydata.SanitizedContentKind.CSS}.
266
+ *
267
+ * The content is non-attacker-exploitable CSS, such as {@code color:#c3d9ff}.
268
+ * The content direction is LTR.
269
+ *
270
+ * @constructor
271
+ * @extends {goog.soy.data.SanitizedContent}
272
+ */
273
+ soydata.SanitizedCss = function() {
274
+ goog.soy.data.SanitizedContent.call(this); // Throws an exception.
275
+ };
276
+ goog.inherits(soydata.SanitizedCss, goog.soy.data.SanitizedContent);
277
+
278
+ /** @override */
279
+ soydata.SanitizedCss.prototype.contentKind =
280
+ soydata.SanitizedContentKind.CSS;
281
+
282
+ /** @override */
283
+ soydata.SanitizedCss.prototype.contentDir = goog.i18n.bidi.Dir.LTR;
284
+
285
+
286
+ /**
287
+ * Unsanitized plain text string.
288
+ *
289
+ * While all strings are effectively safe to use as a plain text, there are no
290
+ * guarantees about safety in any other context such as HTML. This is
291
+ * sometimes used to mark that should never be used unescaped.
292
+ *
293
+ * @param {*} content Plain text with no guarantees.
294
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if
295
+ * unknown and thus to be estimated when necessary. Default: null.
296
+ * @constructor
297
+ * @extends {goog.soy.data.SanitizedContent}
298
+ */
299
+ soydata.UnsanitizedText = function(content, opt_contentDir) {
300
+ /** @override */
301
+ this.content = String(content);
302
+ this.contentDir = opt_contentDir != null ? opt_contentDir : null;
303
+ };
304
+ goog.inherits(soydata.UnsanitizedText, goog.soy.data.SanitizedContent);
305
+
306
+ /** @override */
307
+ soydata.UnsanitizedText.prototype.contentKind =
308
+ soydata.SanitizedContentKind.TEXT;
309
+
310
+
311
+ /**
312
+ * Empty string, used as a type in Soy templates.
313
+ * @enum {string}
314
+ * @private
315
+ */
316
+ soydata.$$EMPTY_STRING_ = {
317
+ VALUE: ''
318
+ };
319
+
320
+
321
+ /**
322
+ * Creates a factory for SanitizedContent types.
323
+ *
324
+ * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can
325
+ * instantiate Sanitized* classes, without making the Sanitized* constructors
326
+ * publicly usable. Requiring all construction to use the VERY_UNSAFE names
327
+ * helps callers and their reviewers easily tell that creating SanitizedContent
328
+ * is not always safe and calls for careful review.
329
+ *
330
+ * @param {function(new: T)} ctor A constructor.
331
+ * @return {!function(*, ?goog.i18n.bidi.Dir=): T} A factory that takes
332
+ * content and an optional content direction and returns a new instance. If
333
+ * the content direction is undefined, ctor.prototype.contentDir is used.
334
+ * @template T
335
+ * @private
336
+ */
337
+ soydata.$$makeSanitizedContentFactory_ = function(ctor) {
338
+ /** @type {function(new: goog.soy.data.SanitizedContent)} */
339
+ function InstantiableCtor() {}
340
+ InstantiableCtor.prototype = ctor.prototype;
341
+ /**
342
+ * Creates a ctor-type SanitizedContent instance.
343
+ *
344
+ * @param {*} content The content to put in the instance.
345
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction. If
346
+ * undefined, ctor.prototype.contentDir is used.
347
+ * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually
348
+ * of type T above (ctor's type, a descendant of SanitizedContent), but
349
+ * there is no way to express that here.
350
+ */
351
+ function sanitizedContentFactory(content, opt_contentDir) {
352
+ var result = new InstantiableCtor();
353
+ result.content = String(content);
354
+ if (opt_contentDir !== undefined) {
355
+ result.contentDir = opt_contentDir;
356
+ }
357
+ return result;
358
+ }
359
+ return sanitizedContentFactory;
360
+ };
361
+
362
+
363
+ /**
364
+ * Creates a factory for SanitizedContent types that should always have their
365
+ * default directionality.
366
+ *
367
+ * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can
368
+ * instantiate Sanitized* classes, without making the Sanitized* constructors
369
+ * publicly usable. Requiring all construction to use the VERY_UNSAFE names
370
+ * helps callers and their reviewers easily tell that creating SanitizedContent
371
+ * is not always safe and calls for careful review.
372
+ *
373
+ * @param {function(new: T, string)} ctor A constructor.
374
+ * @return {!function(*): T} A factory that takes content and returns a new
375
+ * instance (with default directionality, i.e. ctor.prototype.contentDir).
376
+ * @template T
377
+ * @private
378
+ */
379
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_ = function(ctor) {
380
+ /** @type {function(new: goog.soy.data.SanitizedContent)} */
381
+ function InstantiableCtor() {}
382
+ InstantiableCtor.prototype = ctor.prototype;
383
+ /**
384
+ * Creates a ctor-type SanitizedContent instance.
385
+ *
386
+ * @param {*} content The content to put in the instance.
387
+ * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually
388
+ * of type T above (ctor's type, a descendant of SanitizedContent), but
389
+ * there is no way to express that here.
390
+ */
391
+ function sanitizedContentFactory(content) {
392
+ var result = new InstantiableCtor();
393
+ result.content = String(content);
394
+ return result;
395
+ }
396
+ return sanitizedContentFactory;
397
+ };
398
+
399
+
400
+ // -----------------------------------------------------------------------------
401
+ // Sanitized content ordainers. Please use these with extreme caution (with the
402
+ // exception of markUnsanitizedText). A good recommendation is to limit usage
403
+ // of these to just a handful of files in your source tree where usages can be
404
+ // carefully audited.
405
+
406
+
407
+ /**
408
+ * Protects a string from being used in an noAutoescaped context.
409
+ *
410
+ * This is useful for content where there is significant risk of accidental
411
+ * unescaped usage in a Soy template. A great case is for user-controlled
412
+ * data that has historically been a source of vulernabilities.
413
+ *
414
+ * @param {*} content Text to protect.
415
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if
416
+ * unknown and thus to be estimated when necessary. Default: null.
417
+ * @return {!soydata.UnsanitizedText} A wrapper that is rejected by the
418
+ * Soy noAutoescape print directive.
419
+ */
420
+ soydata.markUnsanitizedText = function(content, opt_contentDir) {
421
+ return new soydata.UnsanitizedText(content, opt_contentDir);
422
+ };
423
+
424
+
425
+ /**
426
+ * Takes a leap of faith that the provided content is "safe" HTML.
427
+ *
428
+ * @param {*} content A string of HTML that can safely be embedded in
429
+ * a PCDATA context in your app. If you would be surprised to find that an
430
+ * HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs)
431
+ * and you wouldn't write a template that produces {@code s} on security or
432
+ * privacy grounds, then don't pass {@code s} here.
433
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if
434
+ * unknown and thus to be estimated when necessary. Default: null.
435
+ * @return {!soydata.SanitizedHtml} Sanitized content wrapper that
436
+ * indicates to Soy not to escape when printed as HTML.
437
+ */
438
+ soydata.VERY_UNSAFE.ordainSanitizedHtml =
439
+ soydata.$$makeSanitizedContentFactory_(soydata.SanitizedHtml);
440
+
441
+
442
+ /**
443
+ * Takes a leap of faith that the provided content is "safe" (non-attacker-
444
+ * controlled, XSS-free) Javascript.
445
+ *
446
+ * @param {*} content Javascript source that when evaluated does not
447
+ * execute any attacker-controlled scripts.
448
+ * @return {!soydata.SanitizedJs} Sanitized content wrapper that indicates to
449
+ * Soy not to escape when printed as Javascript source.
450
+ */
451
+ soydata.VERY_UNSAFE.ordainSanitizedJs =
452
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_(
453
+ soydata.SanitizedJs);
454
+
455
+
456
+ // TODO: This function is probably necessary, either externally or internally
457
+ // as an implementation detail. Generally, plain text will always work here,
458
+ // as there's no harm to unescaping the string and then re-escaping when
459
+ // finally printed.
460
+ /**
461
+ * Takes a leap of faith that the provided content can be safely embedded in
462
+ * a Javascript string without re-esacping.
463
+ *
464
+ * @param {*} content Content that can be safely inserted as part of a
465
+ * single- or double-quoted string without terminating the string.
466
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if
467
+ * unknown and thus to be estimated when necessary. Default: null.
468
+ * @return {!soydata.SanitizedJsStrChars} Sanitized content wrapper that
469
+ * indicates to Soy not to escape when printed in a JS string.
470
+ */
471
+ soydata.VERY_UNSAFE.ordainSanitizedJsStrChars =
472
+ soydata.$$makeSanitizedContentFactory_(soydata.SanitizedJsStrChars);
473
+
474
+
475
+ /**
476
+ * Takes a leap of faith that the provided content is "safe" to use as a URI
477
+ * in a Soy template.
478
+ *
479
+ * This creates a Soy SanitizedContent object which indicates to Soy there is
480
+ * no need to escape it when printed as a URI (e.g. in an href or src
481
+ * attribute), such as if it's already been encoded or if it's a Javascript:
482
+ * URI.
483
+ *
484
+ * @param {*} content A chunk of URI that the caller knows is safe to
485
+ * emit in a template.
486
+ * @return {!soydata.SanitizedUri} Sanitized content wrapper that indicates to
487
+ * Soy not to escape or filter when printed in URI context.
488
+ */
489
+ soydata.VERY_UNSAFE.ordainSanitizedUri =
490
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_(
491
+ soydata.SanitizedUri);
492
+
493
+
494
+ /**
495
+ * Takes a leap of faith that the provided content is "safe" to use as an
496
+ * HTML attribute.
497
+ *
498
+ * @param {*} content An attribute name and value, such as
499
+ * {@code dir="ltr"}.
500
+ * @return {!soydata.SanitizedHtmlAttribute} Sanitized content wrapper that
501
+ * indicates to Soy not to escape when printed as an HTML attribute.
502
+ */
503
+ soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute =
504
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_(
505
+ soydata.SanitizedHtmlAttribute);
506
+
507
+
508
+ /**
509
+ * Takes a leap of faith that the provided content is "safe" to use as CSS
510
+ * in a style attribute or block.
511
+ *
512
+ * @param {*} content CSS, such as {@code color:#c3d9ff}.
513
+ * @return {!soydata.SanitizedCss} Sanitized CSS wrapper that indicates to
514
+ * Soy there is no need to escape or filter when printed in CSS context.
515
+ */
516
+ soydata.VERY_UNSAFE.ordainSanitizedCss =
517
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_(
518
+ soydata.SanitizedCss);
205
519
 
206
520
 
207
521
  // -----------------------------------------------------------------------------
@@ -217,9 +531,11 @@ soydata.SanitizedHtmlAttribute.prototype.contentKind =
217
531
  * NOTE: New code should consider using goog.soy.renderElement instead.
218
532
  *
219
533
  * @param {Element} element The element whose content we are rendering.
220
- * @param {Function} template The Soy template defining the element's content.
221
- * @param {Object=} opt_templateData The data for the template.
534
+ * @param {null|function(ARG_TYPES, null=, Object.<string, *>=):*} template
535
+ * The Soy template defining the element's content.
536
+ * @param {ARG_TYPES} opt_templateData The data for the template.
222
537
  * @param {Object=} opt_injectedData The injected data for the template.
538
+ * @template ARG_TYPES
223
539
  */
224
540
  soy.renderElement = goog.soy.renderElement;
225
541
 
@@ -234,12 +550,14 @@ soy.renderElement = goog.soy.renderElement;
234
550
  * NOTE: New code should consider using goog.soy.renderAsFragment
235
551
  * instead (note that the arguments are different).
236
552
  *
237
- * @param {Function} template The Soy template defining the element's content.
238
- * @param {Object=} opt_templateData The data for the template.
553
+ * @param {null|function(ARG_TYPES, null=, Object.<string, *>=):*} template
554
+ * The Soy template defining the element's content.
555
+ * @param {ARG_TYPES} opt_templateData The data for the template.
239
556
  * @param {Document=} opt_document The document used to create DOM nodes. If not
240
557
  * specified, global document object is used.
241
558
  * @param {Object=} opt_injectedData The injected data for the template.
242
559
  * @return {!Node} The resulting node or document fragment.
560
+ * @template ARG_TYPES
243
561
  */
244
562
  soy.renderAsFragment = function(
245
563
  template, opt_templateData, opt_document, opt_injectedData) {
@@ -257,13 +575,15 @@ soy.renderAsFragment = function(
257
575
  * NOTE: New code should consider using goog.soy.renderAsElement
258
576
  * instead (note that the arguments are different).
259
577
  *
260
- * @param {Function} template The Soy template defining the element's content.
261
- * @param {Object=} opt_templateData The data for the template.
578
+ * @param {null|function(ARG_TYPES, null=, Object.<string, *>=):*} template
579
+ * The Soy template defining the element's content.
580
+ * @param {ARG_TYPES} opt_templateData The data for the template.
262
581
  * @param {Document=} opt_document The document used to create DOM nodes. If not
263
582
  * specified, global document object is used.
264
583
  * @param {Object=} opt_injectedData The injected data for the template.
265
584
  * @return {!Element} Rendered template contents, wrapped in a parent DIV
266
585
  * element if necessary.
586
+ * @template ARG_TYPES
267
587
  */
268
588
  soy.renderAsElement = function(
269
589
  template, opt_templateData, opt_document, opt_injectedData) {
@@ -278,39 +598,61 @@ soy.renderAsElement = function(
278
598
 
279
599
 
280
600
  /**
281
- * Builds an augmented data object to be passed when a template calls another,
282
- * and needs to pass both original data and additional params. The returned
283
- * object will contain both the original data and the additional params. If the
284
- * same key appears in both, then the value from the additional params will be
285
- * visible, while the value from the original data will be hidden. The original
286
- * data object will be used, but not modified.
601
+ * Whether the locale is right-to-left.
602
+ *
603
+ * @type {boolean}
604
+ */
605
+ soy.$$IS_LOCALE_RTL = goog.i18n.bidi.IS_RTL;
606
+
607
+
608
+ /**
609
+ * Builds an augmented map. The returned map will contain mappings from both
610
+ * the base map and the additional map. If the same key appears in both, then
611
+ * the value from the additional map will be visible, while the value from the
612
+ * base map will be hidden. The base map will be used, but not modified.
287
613
  *
288
- * @param {!Object} origData The original data to pass.
289
- * @param {Object} additionalParams The additional params to pass.
290
- * @return {Object} An augmented data object containing both the original data
291
- * and the additional params.
614
+ * @param {!Object} baseMap The original map to augment.
615
+ * @param {!Object} additionalMap A map containing the additional mappings.
616
+ * @return {!Object} An augmented map containing both the original and
617
+ * additional mappings.
292
618
  */
293
- soy.$$augmentData = function(origData, additionalParams) {
619
+ soy.$$augmentMap = function(baseMap, additionalMap) {
294
620
 
295
- // Create a new object whose '__proto__' field is set to origData.
621
+ // Create a new map whose '__proto__' field is set to baseMap.
296
622
  /** @constructor */
297
623
  function TempCtor() {}
298
- TempCtor.prototype = origData;
299
- var newData = new TempCtor();
624
+ TempCtor.prototype = baseMap;
625
+ var augmentedMap = new TempCtor();
300
626
 
301
- // Add the additional params to the new object.
302
- for (var key in additionalParams) {
303
- newData[key] = additionalParams[key];
627
+ // Add the additional mappings to the new map.
628
+ for (var key in additionalMap) {
629
+ augmentedMap[key] = additionalMap[key];
304
630
  }
305
631
 
306
- return newData;
632
+ return augmentedMap;
633
+ };
634
+
635
+
636
+ /**
637
+ * Checks that the given map key is a string.
638
+ * @param {*} key Key to check.
639
+ * @return {string} The given key.
640
+ */
641
+ soy.$$checkMapKey = function(key) {
642
+ // TODO: Support map literal with nonstring key.
643
+ if ((typeof key) != 'string') {
644
+ throw Error(
645
+ 'Map literal\'s key expression must evaluate to string' +
646
+ ' (encountered type "' + (typeof key) + '").');
647
+ }
648
+ return key;
307
649
  };
308
650
 
309
651
 
310
652
  /**
311
653
  * Gets the keys in a map as an array. There are no guarantees on the order.
312
654
  * @param {Object} map The map to get the keys of.
313
- * @return {Array.<string>} The array of keys in the given map.
655
+ * @return {!Array.<string>} The array of keys in the given map.
314
656
  */
315
657
  soy.$$getMapKeys = function(map) {
316
658
  var mapKeys = [];
@@ -339,13 +681,13 @@ soy.$$getMapKeys = function(map) {
339
681
  *
340
682
  * @consistentIdGenerator
341
683
  */
342
- soy.$$getDelegateId = function(delTemplateName) {
684
+ soy.$$getDelTemplateId = function(delTemplateName) {
343
685
  return delTemplateName;
344
686
  };
345
687
 
346
688
 
347
689
  /**
348
- * Map from registered delegate template id/name to the priority of the
690
+ * Map from registered delegate template key to the priority of the
349
691
  * implementation.
350
692
  * @type {Object}
351
693
  * @private
@@ -353,7 +695,7 @@ soy.$$getDelegateId = function(delTemplateName) {
353
695
  soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {};
354
696
 
355
697
  /**
356
- * Map from registered delegate template id/name to the implementation function.
698
+ * Map from registered delegate template key to the implementation function.
357
699
  * @type {Object}
358
700
  * @private
359
701
  */
@@ -361,17 +703,21 @@ soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {};
361
703
 
362
704
 
363
705
  /**
364
- * Registers a delegate implementation. If the same delegate template id/name
365
- * has been registered previously, then priority values are compared and only
366
- * the higher priority implementation is stored (if priorities are equal, an
367
- * error is thrown).
706
+ * Registers a delegate implementation. If the same delegate template key (id
707
+ * and variant) has been registered previously, then priority values are
708
+ * compared and only the higher priority implementation is stored (if
709
+ * priorities are equal, an error is thrown).
368
710
  *
369
- * @param {string} delTemplateId The delegate template id/name to register.
711
+ * @param {string} delTemplateId The delegate template id.
712
+ * @param {string} delTemplateVariant The delegate template variant (can be
713
+ * empty string).
370
714
  * @param {number} delPriority The implementation's priority value.
371
715
  * @param {Function} delFn The implementation function.
372
716
  */
373
- soy.$$registerDelegateFn = function(delTemplateId, delPriority, delFn) {
374
- var mapKey = 'key_' + delTemplateId;
717
+ soy.$$registerDelegateFn = function(
718
+ delTemplateId, delTemplateVariant, delPriority, delFn) {
719
+
720
+ var mapKey = 'key_' + delTemplateId + ':' + delTemplateVariant;
375
721
  var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey];
376
722
  if (currPriority === undefined || delPriority > currPriority) {
377
723
  // Registering new or higher-priority function: replace registry entry.
@@ -380,8 +726,8 @@ soy.$$registerDelegateFn = function(delTemplateId, delPriority, delFn) {
380
726
  } else if (delPriority == currPriority) {
381
727
  // Registering same-priority function: error.
382
728
  throw Error(
383
- 'Encountered two active delegates with same priority (id/name "' +
384
- delTemplateId + '").');
729
+ 'Encountered two active delegates with the same priority ("' +
730
+ delTemplateId + ':' + delTemplateVariant + '").');
385
731
  } else {
386
732
  // Registering lower-priority function: do nothing.
387
733
  }
@@ -390,16 +736,38 @@ soy.$$registerDelegateFn = function(delTemplateId, delPriority, delFn) {
390
736
 
391
737
  /**
392
738
  * Retrieves the (highest-priority) implementation that has been registered for
393
- * a given delegate template id/name. If no implementation has been registered
394
- * for the id/name, then returns an implementation that is equivalent to an
395
- * empty template (i.e. rendered output would be empty string).
739
+ * a given delegate template key (id and variant). If no implementation has
740
+ * been registered for the key, then the fallback is the same id with empty
741
+ * variant. If the fallback is also not registered, and allowsEmptyDefault is
742
+ * true, then returns an implementation that is equivalent to an empty template
743
+ * (i.e. rendered output would be empty string).
396
744
  *
397
- * @param {string} delTemplateId The delegate template id/name to get.
745
+ * @param {string} delTemplateId The delegate template id.
746
+ * @param {string} delTemplateVariant The delegate template variant (can be
747
+ * empty string).
748
+ * @param {boolean} allowsEmptyDefault Whether to default to the empty template
749
+ * function if there's no active implementation.
398
750
  * @return {Function} The retrieved implementation function.
399
751
  */
400
- soy.$$getDelegateFn = function(delTemplateId) {
401
- var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId];
402
- return delFn ? delFn : soy.$$EMPTY_TEMPLATE_FN_;
752
+ soy.$$getDelegateFn = function(
753
+ delTemplateId, delTemplateVariant, allowsEmptyDefault) {
754
+
755
+ var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_[
756
+ 'key_' + delTemplateId + ':' + delTemplateVariant];
757
+ if (! delFn && delTemplateVariant != '') {
758
+ // Fallback to empty variant.
759
+ delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':'];
760
+ }
761
+
762
+ if (delFn) {
763
+ return delFn;
764
+ } else if (allowsEmptyDefault) {
765
+ return soy.$$EMPTY_TEMPLATE_FN_;
766
+ } else {
767
+ throw Error(
768
+ 'Found no active impl for delegate call to "' + delTemplateId + ':' +
769
+ delTemplateVariant + '" (and not allowemptydefault="true").');
770
+ }
403
771
  };
404
772
 
405
773
 
@@ -418,26 +786,222 @@ soy.$$EMPTY_TEMPLATE_FN_ = function(opt_data, opt_sb, opt_ijData) {
418
786
  };
419
787
 
420
788
 
789
+ // -----------------------------------------------------------------------------
790
+ // Internal sanitized content wrappers.
791
+
792
+
793
+ /**
794
+ * Creates a SanitizedContent factory for SanitizedContent types for internal
795
+ * Soy let and param blocks.
796
+ *
797
+ * This is a hack within Soy so that SanitizedContent objects created via let
798
+ * and param blocks will truth-test as false if they are empty string.
799
+ * Tricking the Javascript runtime to treat empty SanitizedContent as falsey is
800
+ * not possible, and changing the Soy compiler to wrap every boolean statement
801
+ * for just this purpose is impractical. Instead, we just avoid wrapping empty
802
+ * string as SanitizedContent, since it's a no-op for empty strings anyways.
803
+ *
804
+ * @param {function(new: T)} ctor A constructor.
805
+ * @return {!function(*, ?goog.i18n.bidi.Dir=): (T|soydata.$$EMPTY_STRING_)}
806
+ * A factory that takes content and an optional content direction and
807
+ * returns a new instance, or an empty string. If the content direction is
808
+ * undefined, ctor.prototype.contentDir is used.
809
+ * @template T
810
+ * @private
811
+ */
812
+ soydata.$$makeSanitizedContentFactoryForInternalBlocks_ = function(ctor) {
813
+ /** @type {function(new: goog.soy.data.SanitizedContent)} */
814
+ function InstantiableCtor() {}
815
+ InstantiableCtor.prototype = ctor.prototype;
816
+ /**
817
+ * Creates a ctor-type SanitizedContent instance.
818
+ *
819
+ * @param {*} content The content to put in the instance.
820
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction. If
821
+ * undefined, ctor.prototype.contentDir is used.
822
+ * @return {!goog.soy.data.SanitizedContent|soydata.$$EMPTY_STRING_} The new
823
+ * instance, or an empty string. A new instance is actually of type T
824
+ * above (ctor's type, a descendant of SanitizedContent), but there's no
825
+ * way to express that here.
826
+ * a descendant of SanitizedContent), but there's no way to express that here.
827
+ */
828
+ function sanitizedContentFactory(content, opt_contentDir) {
829
+ var contentString = String(content);
830
+ if (!contentString) {
831
+ return soydata.$$EMPTY_STRING_.VALUE;
832
+ }
833
+ var result = new InstantiableCtor();
834
+ result.content = String(content);
835
+ if (opt_contentDir !== undefined) {
836
+ result.contentDir = opt_contentDir;
837
+ }
838
+ return result;
839
+ }
840
+ return sanitizedContentFactory;
841
+ };
842
+
843
+
844
+ /**
845
+ * Creates a SanitizedContent factory for SanitizedContent types that should
846
+ * always have their default directionality for internal Soy let and param
847
+ * blocks.
848
+ *
849
+ * This is a hack within Soy so that SanitizedContent objects created via let
850
+ * and param blocks will truth-test as false if they are empty string.
851
+ * Tricking the Javascript runtime to treat empty SanitizedContent as falsey is
852
+ * not possible, and changing the Soy compiler to wrap every boolean statement
853
+ * for just this purpose is impractical. Instead, we just avoid wrapping empty
854
+ * string as SanitizedContent, since it's a no-op for empty strings anyways.
855
+ *
856
+ * @param {function(new: T)} ctor A constructor.
857
+ * @return {!function(*): (T|soydata.$$EMPTY_STRING_)} A
858
+ * factory that takes content and returns a
859
+ * new instance (with default directionality, i.e.
860
+ * ctor.prototype.contentDir), or an empty string.
861
+ * @template T
862
+ * @private
863
+ */
864
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_ =
865
+ function(ctor) {
866
+ /** @type {function(new: goog.soy.data.SanitizedContent)} */
867
+ function InstantiableCtor() {}
868
+ InstantiableCtor.prototype = ctor.prototype;
869
+ /**
870
+ * Creates a ctor-type SanitizedContent instance.
871
+ *
872
+ * @param {*} content The content to put in the instance.
873
+ * @return {!goog.soy.data.SanitizedContent|soydata.$$EMPTY_STRING_} The new
874
+ * instance, or an empty string. A new instance is actually of type T
875
+ * above (ctor's type, a descendant of SanitizedContent), but there's no
876
+ * way to express that here.
877
+ * a descendant of SanitizedContent), but there's no way to express that here.
878
+ */
879
+ function sanitizedContentFactory(content) {
880
+ var contentString = String(content);
881
+ if (!contentString) {
882
+ return soydata.$$EMPTY_STRING_.VALUE;
883
+ }
884
+ var result = new InstantiableCtor();
885
+ result.content = String(content);
886
+ return result;
887
+ }
888
+ return sanitizedContentFactory;
889
+ };
890
+
891
+
892
+ /**
893
+ * Creates kind="text" block contents (internal use only).
894
+ *
895
+ * @param {*} content Text.
896
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if
897
+ * unknown and thus to be estimated when necessary. Default: null.
898
+ * @return {!soydata.UnsanitizedText|soydata.$$EMPTY_STRING_} Wrapped result.
899
+ */
900
+ soydata.$$markUnsanitizedTextForInternalBlocks = function(
901
+ content, opt_contentDir) {
902
+ var contentString = String(content);
903
+ if (!contentString) {
904
+ return soydata.$$EMPTY_STRING_.VALUE;
905
+ }
906
+ return new soydata.UnsanitizedText(contentString, opt_contentDir);
907
+ };
908
+
909
+
910
+ /**
911
+ * Creates kind="html" block contents (internal use only).
912
+ *
913
+ * @param {*} content Text.
914
+ * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if
915
+ * unknown and thus to be estimated when necessary. Default: null.
916
+ * @return {!soydata.SanitizedHtml|soydata.$$EMPTY_STRING_} Wrapped result.
917
+ */
918
+ soydata.VERY_UNSAFE.$$ordainSanitizedHtmlForInternalBlocks =
919
+ soydata.$$makeSanitizedContentFactoryForInternalBlocks_(
920
+ soydata.SanitizedHtml);
921
+
922
+
923
+ /**
924
+ * Creates kind="js" block contents (internal use only).
925
+ *
926
+ * @param {*} content Text.
927
+ * @return {!soydata.SanitizedJs|soydata.$$EMPTY_STRING_} Wrapped result.
928
+ */
929
+ soydata.VERY_UNSAFE.$$ordainSanitizedJsForInternalBlocks =
930
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_(
931
+ soydata.SanitizedJs);
932
+
933
+
934
+ /**
935
+ * Creates kind="uri" block contents (internal use only).
936
+ *
937
+ * @param {*} content Text.
938
+ * @return {soydata.SanitizedUri|soydata.$$EMPTY_STRING_} Wrapped result.
939
+ */
940
+ soydata.VERY_UNSAFE.$$ordainSanitizedUriForInternalBlocks =
941
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_(
942
+ soydata.SanitizedUri);
943
+
944
+
945
+ /**
946
+ * Creates kind="attributes" block contents (internal use only).
947
+ *
948
+ * @param {*} content Text.
949
+ * @return {soydata.SanitizedHtmlAttribute|soydata.$$EMPTY_STRING_} Wrapped
950
+ * result.
951
+ */
952
+ soydata.VERY_UNSAFE.$$ordainSanitizedAttributesForInternalBlocks =
953
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_(
954
+ soydata.SanitizedHtmlAttribute);
955
+
956
+
957
+ /**
958
+ * Creates kind="css" block contents (internal use only).
959
+ *
960
+ * @param {*} content Text.
961
+ * @return {soydata.SanitizedCss|soydata.$$EMPTY_STRING_} Wrapped result.
962
+ */
963
+ soydata.VERY_UNSAFE.$$ordainSanitizedCssForInternalBlocks =
964
+ soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_(
965
+ soydata.SanitizedCss);
966
+
967
+
421
968
  // -----------------------------------------------------------------------------
422
969
  // Escape/filter/normalize.
423
970
 
424
971
 
425
972
  /**
426
- * Escapes HTML special characters in a string. Escapes double quote '"' in
427
- * addition to '&', '<', and '>' so that a string can be included in an HTML
428
- * tag attribute value within double quotes.
429
- * Will emit known safe HTML as-is.
973
+ * Returns a SanitizedHtml object for a particular value. The content direction
974
+ * is preserved.
430
975
  *
431
- * @param {*} value The string-like value to be escaped. May not be a string,
432
- * but the value will be coerced to a string.
433
- * @return {string} An escaped version of value.
976
+ * This HTML-escapes the value unless it is already SanitizedHtml. Escapes
977
+ * double quote '"' in addition to '&', '<', and '>' so that a string can be
978
+ * included in an HTML tag attribute value within double quotes.
979
+ *
980
+ * @param {*} value The value to convert. If it is already a SanitizedHtml
981
+ * object, it is left alone.
982
+ * @return {!soydata.SanitizedHtml} An escaped version of value.
434
983
  */
435
984
  soy.$$escapeHtml = function(value) {
436
- if (typeof value === 'object' && value &&
437
- value.contentKind === soydata.SanitizedContentKind.HTML) {
438
- return value.content;
985
+ return soydata.SanitizedHtml.from(value);
986
+ };
987
+
988
+
989
+ /**
990
+ * Strips unsafe tags to convert a string of untrusted HTML into HTML that
991
+ * is safe to embed. The content direction is preserved.
992
+ *
993
+ * @param {*} value The string-like value to be escaped. May not be a string,
994
+ * but the value will be coerced to a string.
995
+ * @return {!soydata.SanitizedHtml} A sanitized and normalized version of value.
996
+ */
997
+ soy.$$cleanHtml = function(value) {
998
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.HTML)) {
999
+ goog.asserts.assert(value.constructor === soydata.SanitizedHtml);
1000
+ return /** @type {!soydata.SanitizedHtml} */ (value);
439
1001
  }
440
- return soy.esc.$$escapeHtmlHelper(value);
1002
+ return soydata.VERY_UNSAFE.ordainSanitizedHtml(
1003
+ soy.$$stripHtmlTags(value, soy.esc.$$SAFE_TAG_WHITELIST_),
1004
+ soydata.getContentDir(value));
441
1005
  };
442
1006
 
443
1007
 
@@ -446,7 +1010,7 @@ soy.$$escapeHtml = function(value) {
446
1010
  * RCDATA.
447
1011
  * <p>
448
1012
  * Escapes HTML special characters so that the value will not prematurely end
449
- * the body of a tag like {@code <textarea>} or {@code <title>}. RCDATA tags
1013
+ * the body of a tag like {@code <textarea>} or {@code <title>}. RCDATA tags
450
1014
  * cannot contain other HTML entities, so it is not strictly necessary to escape
451
1015
  * HTML special characters except when part of that text looks like an HTML
452
1016
  * entity or like a close tag : {@code </textarea>}.
@@ -455,13 +1019,13 @@ soy.$$escapeHtml = function(value) {
455
1019
  * contain an innocuous {@code </textarea>} don't prematurely end an RCDATA
456
1020
  * element.
457
1021
  *
458
- * @param {*} value The string-like value to be escaped. May not be a string,
1022
+ * @param {*} value The string-like value to be escaped. May not be a string,
459
1023
  * but the value will be coerced to a string.
460
1024
  * @return {string} An escaped version of value.
461
1025
  */
462
1026
  soy.$$escapeHtmlRcdata = function(value) {
463
- if (typeof value === 'object' && value &&
464
- value.contentKind === soydata.SanitizedContentKind.HTML) {
1027
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.HTML)) {
1028
+ goog.asserts.assert(value.constructor === soydata.SanitizedHtml);
465
1029
  return soy.esc.$$normalizeHtmlHelper(value.content);
466
1030
  }
467
1031
  return soy.esc.$$escapeHtmlHelper(value);
@@ -469,29 +1033,133 @@ soy.$$escapeHtmlRcdata = function(value) {
469
1033
 
470
1034
 
471
1035
  /**
472
- * Removes HTML tags from a string of known safe HTML so it can be used as an
473
- * attribute value.
1036
+ * Matches any/only HTML5 void elements' start tags.
1037
+ * See http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
1038
+ * @type {RegExp}
1039
+ * @private
1040
+ */
1041
+ soy.$$HTML5_VOID_ELEMENTS_ = new RegExp(
1042
+ '^<(?:area|base|br|col|command|embed|hr|img|input' +
1043
+ '|keygen|link|meta|param|source|track|wbr)\\b');
1044
+
1045
+
1046
+ /**
1047
+ * Removes HTML tags from a string of known safe HTML.
1048
+ * If opt_tagWhitelist is not specified or is empty, then
1049
+ * the result can be used as an attribute value.
474
1050
  *
475
- * @param {*} value The HTML to be escaped. May not be a string, but the
1051
+ * @param {*} value The HTML to be escaped. May not be a string, but the
476
1052
  * value will be coerced to a string.
477
- * @return {string} A representation of value without tags, HTML comments, or
478
- * other content.
1053
+ * @param {Object.<string, number>=} opt_tagWhitelist Has an own property whose
1054
+ * name is a lower-case tag name and whose value is {@code 1} for
1055
+ * each element that is allowed in the output.
1056
+ * @return {string} A representation of value without disallowed tags,
1057
+ * HTML comments, or other non-text content.
1058
+ */
1059
+ soy.$$stripHtmlTags = function(value, opt_tagWhitelist) {
1060
+ if (!opt_tagWhitelist) {
1061
+ // If we have no white-list, then use a fast track which elides all tags.
1062
+ return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '')
1063
+ // This is just paranoia since callers should normalize the result
1064
+ // anyway, but if they didn't, it would be necessary to ensure that
1065
+ // after the first replace non-tag uses of < do not recombine into
1066
+ // tags as in "<<foo>script>alert(1337)</<foo>script>".
1067
+ .replace(soy.esc.$$LT_REGEX_, '&lt;');
1068
+ }
1069
+
1070
+ // Escapes '[' so that we can use [123] below to mark places where tags
1071
+ // have been removed.
1072
+ var html = String(value).replace(/\[/g, '&#91;');
1073
+
1074
+ // Consider all uses of '<' and replace whitelisted tags with markers like
1075
+ // [1] which are indices into a list of approved tag names.
1076
+ // Replace all other uses of < and > with entities.
1077
+ var tags = [];
1078
+ html = html.replace(
1079
+ soy.esc.$$HTML_TAG_REGEX_,
1080
+ function(tok, tagName) {
1081
+ if (tagName) {
1082
+ tagName = tagName.toLowerCase();
1083
+ if (opt_tagWhitelist.hasOwnProperty(tagName) &&
1084
+ opt_tagWhitelist[tagName]) {
1085
+ var start = tok.charAt(1) === '/' ? '</' : '<';
1086
+ var index = tags.length;
1087
+ tags[index] = start + tagName + '>';
1088
+ return '[' + index + ']';
1089
+ }
1090
+ }
1091
+ return '';
1092
+ });
1093
+
1094
+ // Escape HTML special characters. Now there are no '<' in html that could
1095
+ // start a tag.
1096
+ html = soy.esc.$$normalizeHtmlHelper(html);
1097
+
1098
+ var finalCloseTags = soy.$$balanceTags_(tags);
1099
+
1100
+ // Now html contains no tags or less-than characters that could become
1101
+ // part of a tag via a replacement operation and tags only contains
1102
+ // approved tags.
1103
+ // Reinsert the white-listed tags.
1104
+ html = html.replace(
1105
+ /\[(\d+)\]/g, function(_, index) { return tags[index]; });
1106
+
1107
+ // Close any still open tags.
1108
+ // This prevents unclosed formatting elements like <ol> and <table> from
1109
+ // breaking the layout of containing HTML.
1110
+ return html + finalCloseTags;
1111
+ };
1112
+
1113
+
1114
+ /**
1115
+ * Throw out any close tags that don't correspond to start tags.
1116
+ * If {@code <table>} is used for formatting, embedded HTML shouldn't be able
1117
+ * to use a mismatched {@code </table>} to break page layout.
1118
+ *
1119
+ * @param {Array.<string>} tags an array of tags that will be modified in place
1120
+ * include tags, the empty string, or concatenations of empty tags.
1121
+ * @return {string} zero or more closed tags that close all elements that are
1122
+ * opened in tags but not closed.
1123
+ * @private
479
1124
  */
480
- soy.$$stripHtmlTags = function(value) {
481
- return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '');
1125
+ soy.$$balanceTags_ = function(tags) {
1126
+ var open = [];
1127
+ for (var i = 0, n = tags.length; i < n; ++i) {
1128
+ var tag = tags[i];
1129
+ if (tag.charAt(1) === '/') {
1130
+ var openTagIndex = open.length - 1;
1131
+ // NOTE: This is essentially lastIndexOf, but it's not supported in IE.
1132
+ while (openTagIndex >= 0 && open[openTagIndex] != tag) {
1133
+ openTagIndex--;
1134
+ }
1135
+ if (openTagIndex < 0) {
1136
+ tags[i] = ''; // Drop close tag.
1137
+ } else {
1138
+ tags[i] = open.slice(openTagIndex).reverse().join('');
1139
+ open.length = openTagIndex;
1140
+ }
1141
+ } else if (!soy.$$HTML5_VOID_ELEMENTS_.test(tag)) {
1142
+ open.push('</' + tag.substring(1));
1143
+ }
1144
+ }
1145
+ return open.reverse().join('');
482
1146
  };
483
1147
 
484
1148
 
485
1149
  /**
486
1150
  * Escapes HTML special characters in an HTML attribute value.
487
1151
  *
488
- * @param {*} value The HTML to be escaped. May not be a string, but the
1152
+ * @param {*} value The HTML to be escaped. May not be a string, but the
489
1153
  * value will be coerced to a string.
490
1154
  * @return {string} An escaped version of value.
491
1155
  */
492
1156
  soy.$$escapeHtmlAttribute = function(value) {
493
- if (typeof value === 'object' && value &&
494
- value.contentKind === soydata.SanitizedContentKind.HTML) {
1157
+ // NOTE: We don't accept ATTRIBUTES here because ATTRIBUTES is actually not
1158
+ // the attribute value context, but instead k/v pairs.
1159
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.HTML)) {
1160
+ // NOTE: After removing tags, we also escape quotes ("normalize") so that
1161
+ // the HTML can be embedded in attribute context.
1162
+ goog.asserts.assert(value.constructor === soydata.SanitizedHtml);
495
1163
  return soy.esc.$$normalizeHtmlHelper(soy.$$stripHtmlTags(value.content));
496
1164
  }
497
1165
  return soy.esc.$$escapeHtmlHelper(value);
@@ -502,13 +1170,13 @@ soy.$$escapeHtmlAttribute = function(value) {
502
1170
  * Escapes HTML special characters in a string including space and other
503
1171
  * characters that can end an unquoted HTML attribute value.
504
1172
  *
505
- * @param {*} value The HTML to be escaped. May not be a string, but the
1173
+ * @param {*} value The HTML to be escaped. May not be a string, but the
506
1174
  * value will be coerced to a string.
507
1175
  * @return {string} An escaped version of value.
508
1176
  */
509
1177
  soy.$$escapeHtmlAttributeNospace = function(value) {
510
- if (typeof value === 'object' && value &&
511
- value.contentKind === soydata.SanitizedContentKind.HTML) {
1178
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.HTML)) {
1179
+ goog.asserts.assert(value.constructor === soydata.SanitizedHtml);
512
1180
  return soy.esc.$$normalizeHtmlNospaceHelper(
513
1181
  soy.$$stripHtmlTags(value.content));
514
1182
  }
@@ -519,29 +1187,46 @@ soy.$$escapeHtmlAttributeNospace = function(value) {
519
1187
  /**
520
1188
  * Filters out strings that cannot be a substring of a valid HTML attribute.
521
1189
  *
522
- * @param {*} value The value to escape. May not be a string, but the value
1190
+ * Note the input is expected to be key=value pairs.
1191
+ *
1192
+ * @param {*} value The value to escape. May not be a string, but the value
523
1193
  * will be coerced to a string.
524
1194
  * @return {string} A valid HTML attribute name part or name/value pair.
525
1195
  * {@code "zSoyz"} if the input is invalid.
526
1196
  */
527
- soy.$$filterHtmlAttribute = function(value) {
528
- if (typeof value === 'object' && value &&
529
- value.contentKind === soydata.SanitizedContentKind.HTML_ATTRIBUTE) {
530
- return value.content.replace(/=([^"']*)$/, '="$1"');
1197
+ soy.$$filterHtmlAttributes = function(value) {
1198
+ // NOTE: Explicitly no support for SanitizedContentKind.HTML, since that is
1199
+ // meaningless in this context, which is generally *between* html attributes.
1200
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.ATTRIBUTES)) {
1201
+ goog.asserts.assert(value.constructor === soydata.SanitizedHtmlAttribute);
1202
+ // Add a space at the end to ensure this won't get merged into following
1203
+ // attributes, unless the interpretation is unambiguous (ending with quotes
1204
+ // or a space).
1205
+ return value.content.replace(/([^"'\s])$/, '$1 ');
531
1206
  }
532
- return soy.esc.$$filterHtmlAttributeHelper(value);
1207
+ // TODO: Dynamically inserting attributes that aren't marked as trusted is
1208
+ // probably unnecessary. Any filtering done here will either be inadequate
1209
+ // for security or not flexible enough. Having clients use kind="attributes"
1210
+ // in parameters seems like a wiser idea.
1211
+ return soy.esc.$$filterHtmlAttributesHelper(value);
533
1212
  };
534
1213
 
535
1214
 
536
1215
  /**
537
1216
  * Filters out strings that cannot be a substring of a valid HTML element name.
538
1217
  *
539
- * @param {*} value The value to escape. May not be a string, but the value
1218
+ * @param {*} value The value to escape. May not be a string, but the value
540
1219
  * will be coerced to a string.
541
1220
  * @return {string} A valid HTML element name part.
542
1221
  * {@code "zSoyz"} if the input is invalid.
543
1222
  */
544
1223
  soy.$$filterHtmlElementName = function(value) {
1224
+ // NOTE: We don't accept any SanitizedContent here. HTML indicates valid
1225
+ // PCDATA, not tag names. A sloppy developer shouldn't be able to cause an
1226
+ // exploit:
1227
+ // ... {let userInput}script src=http://evil.com/evil.js{/let} ...
1228
+ // ... {param tagName kind="html"}{$userInput}{/param} ...
1229
+ // ... <{$tagName}>Hello World</{$tagName}>
545
1230
  return soy.esc.$$filterHtmlElementNameHelper(value);
546
1231
  };
547
1232
 
@@ -550,7 +1235,7 @@ soy.$$filterHtmlElementName = function(value) {
550
1235
  * Escapes characters in the value to make it valid content for a JS string
551
1236
  * literal.
552
1237
  *
553
- * @param {*} value The value to escape. May not be a string, but the value
1238
+ * @param {*} value The value to escape. May not be a string, but the value
554
1239
  * will be coerced to a string.
555
1240
  * @return {string} An escaped version of value.
556
1241
  * @deprecated
@@ -564,13 +1249,15 @@ soy.$$escapeJs = function(value) {
564
1249
  * Escapes characters in the value to make it valid content for a JS string
565
1250
  * literal.
566
1251
  *
567
- * @param {*} value The value to escape. May not be a string, but the value
1252
+ * @param {*} value The value to escape. May not be a string, but the value
568
1253
  * will be coerced to a string.
569
1254
  * @return {string} An escaped version of value.
570
1255
  */
571
1256
  soy.$$escapeJsString = function(value) {
572
- if (typeof value === 'object' &&
573
- value.contentKind === soydata.SanitizedContentKind.JS_STR_CHARS) {
1257
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.JS_STR_CHARS)) {
1258
+ // TODO: It might still be worthwhile to normalize it to remove
1259
+ // unescaped quotes, null, etc: replace(/(?:^|[^\])['"]/g, '\\$
1260
+ goog.asserts.assert(value.constructor === soydata.SanitizedJsStrChars);
574
1261
  return value.content;
575
1262
  }
576
1263
  return soy.esc.$$escapeJsStringHelper(value);
@@ -580,7 +1267,7 @@ soy.$$escapeJsString = function(value) {
580
1267
  /**
581
1268
  * Encodes a value as a JavaScript literal.
582
1269
  *
583
- * @param {*} value The value to escape. May not be a string, but the value
1270
+ * @param {*} value The value to escape. May not be a string, but the value
584
1271
  * will be coerced to a string.
585
1272
  * @return {string} A JavaScript code representation of the input.
586
1273
  */
@@ -595,6 +1282,10 @@ soy.$$escapeJsValue = function(value) {
595
1282
  // distinct undefined value.
596
1283
  return ' null ';
597
1284
  }
1285
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.JS)) {
1286
+ goog.asserts.assert(value.constructor === soydata.SanitizedJs);
1287
+ return value.content;
1288
+ }
598
1289
  switch (typeof value) {
599
1290
  case 'boolean': case 'number':
600
1291
  return ' ' + value + ' ';
@@ -608,7 +1299,7 @@ soy.$$escapeJsValue = function(value) {
608
1299
  * Escapes characters in the string to make it valid content for a JS regular
609
1300
  * expression literal.
610
1301
  *
611
- * @param {*} value The value to escape. May not be a string, but the value
1302
+ * @param {*} value The value to escape. May not be a string, but the value
612
1303
  * will be coerced to a string.
613
1304
  * @return {string} An escaped version of value.
614
1305
  */
@@ -642,13 +1333,13 @@ soy.$$pctEncode_ = function(ch) {
642
1333
  /**
643
1334
  * Escapes a string so that it can be safely included in a URI.
644
1335
  *
645
- * @param {*} value The value to escape. May not be a string, but the value
1336
+ * @param {*} value The value to escape. May not be a string, but the value
646
1337
  * will be coerced to a string.
647
1338
  * @return {string} An escaped version of value.
648
1339
  */
649
1340
  soy.$$escapeUri = function(value) {
650
- if (typeof value === 'object' &&
651
- value.contentKind === soydata.SanitizedContentKind.URI) {
1341
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.URI)) {
1342
+ goog.asserts.assert(value.constructor === soydata.SanitizedUri);
652
1343
  return soy.$$normalizeUri(value);
653
1344
  }
654
1345
  // Apostophes and parentheses are not matched by encodeURIComponent.
@@ -667,7 +1358,7 @@ soy.$$escapeUri = function(value) {
667
1358
  /**
668
1359
  * Removes rough edges from a URI by escaping any raw HTML/JS string delimiters.
669
1360
  *
670
- * @param {*} value The value to escape. May not be a string, but the value
1361
+ * @param {*} value The value to escape. May not be a string, but the value
671
1362
  * will be coerced to a string.
672
1363
  * @return {string} An escaped version of value.
673
1364
  */
@@ -680,19 +1371,37 @@ soy.$$normalizeUri = function(value) {
680
1371
  * Vets a URI's protocol and removes rough edges from a URI by escaping
681
1372
  * any raw HTML/JS string delimiters.
682
1373
  *
683
- * @param {*} value The value to escape. May not be a string, but the value
1374
+ * @param {*} value The value to escape. May not be a string, but the value
684
1375
  * will be coerced to a string.
685
1376
  * @return {string} An escaped version of value.
686
1377
  */
687
1378
  soy.$$filterNormalizeUri = function(value) {
1379
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.URI)) {
1380
+ goog.asserts.assert(value.constructor === soydata.SanitizedUri);
1381
+ return soy.$$normalizeUri(value);
1382
+ }
688
1383
  return soy.esc.$$filterNormalizeUriHelper(value);
689
1384
  };
690
1385
 
691
1386
 
1387
+ /**
1388
+ * Allows only data-protocol image URI's.
1389
+ *
1390
+ * @param {*} value The value to process. May not be a string, but the value
1391
+ * will be coerced to a string.
1392
+ * @return {!soydata.SanitizedUri} An escaped version of value.
1393
+ */
1394
+ soy.$$filterImageDataUri = function(value) {
1395
+ // NOTE: Even if it's a SanitizedUri, we will still filter it.
1396
+ return soydata.VERY_UNSAFE.ordainSanitizedUri(
1397
+ soy.esc.$$filterImageDataUriHelper(value));
1398
+ };
1399
+
1400
+
692
1401
  /**
693
1402
  * Escapes a string so it can safely be included inside a quoted CSS string.
694
1403
  *
695
- * @param {*} value The value to escape. May not be a string, but the value
1404
+ * @param {*} value The value to escape. May not be a string, but the value
696
1405
  * will be coerced to a string.
697
1406
  * @return {string} An escaped version of value.
698
1407
  */
@@ -704,11 +1413,15 @@ soy.$$escapeCssString = function(value) {
704
1413
  /**
705
1414
  * Encodes a value as a CSS identifier part, keyword, or quantity.
706
1415
  *
707
- * @param {*} value The value to escape. May not be a string, but the value
1416
+ * @param {*} value The value to escape. May not be a string, but the value
708
1417
  * will be coerced to a string.
709
1418
  * @return {string} A safe CSS identifier part, keyword, or quanitity.
710
1419
  */
711
1420
  soy.$$filterCssValue = function(value) {
1421
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.CSS)) {
1422
+ goog.asserts.assert(value.constructor === soydata.SanitizedCss);
1423
+ return value.content;
1424
+ }
712
1425
  // Uses == to intentionally match null and undefined for Java compatibility.
713
1426
  if (value == null) {
714
1427
  return '';
@@ -717,17 +1430,47 @@ soy.$$filterCssValue = function(value) {
717
1430
  };
718
1431
 
719
1432
 
1433
+ /**
1434
+ * Sanity-checks noAutoescape input for explicitly tainted content.
1435
+ *
1436
+ * SanitizedContentKind.TEXT is used to explicitly mark input that was never
1437
+ * meant to be used unescaped.
1438
+ *
1439
+ * @param {*} value The value to filter.
1440
+ * @return {*} The value, that we dearly hope will not cause an attack.
1441
+ */
1442
+ soy.$$filterNoAutoescape = function(value) {
1443
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.TEXT)) {
1444
+ // Fail in development mode.
1445
+ goog.asserts.fail(
1446
+ 'Tainted SanitizedContentKind.TEXT for |noAutoescape: `%s`',
1447
+ [value.content]);
1448
+ // Return innocuous data in production.
1449
+ return 'zSoyz';
1450
+ }
1451
+
1452
+ return value;
1453
+ };
1454
+
1455
+
720
1456
  // -----------------------------------------------------------------------------
721
1457
  // Basic directives/functions.
722
1458
 
723
1459
 
724
1460
  /**
725
1461
  * Converts \r\n, \r, and \n to <br>s
726
- * @param {*} str The string in which to convert newlines.
727
- * @return {string} A copy of {@code str} with converted newlines.
728
- */
729
- soy.$$changeNewlineToBr = function(str) {
730
- return goog.string.newLineToBr(String(str), false);
1462
+ * @param {*} value The string in which to convert newlines.
1463
+ * @return {string|!soydata.SanitizedHtml} A copy of {@code value} with
1464
+ * converted newlines. If {@code value} is SanitizedHtml, the return value
1465
+ * is also SanitizedHtml, of the same known directionality.
1466
+ */
1467
+ soy.$$changeNewlineToBr = function(value) {
1468
+ var result = goog.string.newLineToBr(String(value), false);
1469
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.HTML)) {
1470
+ return soydata.VERY_UNSAFE.ordainSanitizedHtml(
1471
+ result, soydata.getContentDir(value));
1472
+ }
1473
+ return result;
731
1474
  };
732
1475
 
733
1476
 
@@ -737,14 +1480,22 @@ soy.$$changeNewlineToBr = function(str) {
737
1480
  * HTML tags or entities. Entites count towards the character count; HTML tags
738
1481
  * do not.
739
1482
  *
740
- * @param {*} str The HTML string to insert word breaks into. Can be other
1483
+ * @param {*} value The HTML string to insert word breaks into. Can be other
741
1484
  * types, but the value will be coerced to a string.
742
1485
  * @param {number} maxCharsBetweenWordBreaks Maximum number of non-space
743
1486
  * characters to allow before adding a word break.
744
- * @return {string} The string including word breaks.
745
- */
746
- soy.$$insertWordBreaks = function(str, maxCharsBetweenWordBreaks) {
747
- return goog.format.insertWordBreaks(String(str), maxCharsBetweenWordBreaks);
1487
+ * @return {string|!soydata.SanitizedHtml} The string including word
1488
+ * breaks. If {@code value} is SanitizedHtml, the return value
1489
+ * is also SanitizedHtml, of the same known directionality.
1490
+ */
1491
+ soy.$$insertWordBreaks = function(value, maxCharsBetweenWordBreaks) {
1492
+ var result = goog.format.insertWordBreaks(
1493
+ String(value), maxCharsBetweenWordBreaks);
1494
+ if (soydata.isContentKind(value, soydata.SanitizedContentKind.HTML)) {
1495
+ return soydata.VERY_UNSAFE.ordainSanitizedHtml(
1496
+ result, soydata.getContentDir(value));
1497
+ }
1498
+ return result;
748
1499
  };
749
1500
 
750
1501
 
@@ -832,7 +1583,7 @@ soy.$$bidiFormatterCache_ = {};
832
1583
  * Returns cached bidi formatter for bidiGlobalDir, or creates a new one.
833
1584
  * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
834
1585
  * if rtl, 0 if unknown.
835
- * @return {goog.i18n.BidiFormatter} A formatter for bidiGlobalDir.
1586
+ * @return {!goog.i18n.BidiFormatter} A formatter for bidiGlobalDir.
836
1587
  * @private
837
1588
  */
838
1589
  soy.$$getBidiFormatterInstance_ = function(bidiGlobalDir) {
@@ -846,37 +1597,53 @@ soy.$$getBidiFormatterInstance_ = function(bidiGlobalDir) {
846
1597
  * Estimate the overall directionality of text. If opt_isHtml, makes sure to
847
1598
  * ignore the LTR nature of the mark-up and escapes in text, making the logic
848
1599
  * suitable for HTML and HTML-escaped text.
849
- * @param {string} text The text whose directionality is to be estimated.
1600
+ * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of
1601
+ * estimating the directionality.
1602
+ *
1603
+ * @param {*} text The content whose directionality is to be estimated.
850
1604
  * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
851
1605
  * Default: false.
852
1606
  * @return {number} 1 if text is LTR, -1 if it is RTL, and 0 if it is neutral.
853
1607
  */
854
1608
  soy.$$bidiTextDir = function(text, opt_isHtml) {
855
- if (!text) {
856
- return 0;
1609
+ var contentDir = soydata.getContentDir(text);
1610
+ if (contentDir != null) {
1611
+ return contentDir;
857
1612
  }
858
- return goog.i18n.bidi.detectRtlDirectionality(text, opt_isHtml) ? -1 : 1;
1613
+ var isHtml = opt_isHtml ||
1614
+ soydata.isContentKind(text, soydata.SanitizedContentKind.HTML);
1615
+ return goog.i18n.bidi.estimateDirection(text + '', isHtml);
859
1616
  };
860
1617
 
861
1618
 
862
1619
  /**
863
- * Returns "dir=ltr" or "dir=rtl", depending on text's estimated
1620
+ * Returns 'dir="ltr"' or 'dir="rtl"', depending on text's estimated
864
1621
  * directionality, if it is not the same as bidiGlobalDir.
865
1622
  * Otherwise, returns the empty string.
866
1623
  * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
867
1624
  * in text, making the logic suitable for HTML and HTML-escaped text.
1625
+ * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of
1626
+ * estimating the directionality.
1627
+ *
868
1628
  * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
869
1629
  * if rtl, 0 if unknown.
870
- * @param {string} text The text whose directionality is to be estimated.
1630
+ * @param {*} text The content whose directionality is to be estimated.
871
1631
  * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
872
1632
  * Default: false.
873
- * @return {soydata.SanitizedHtmlAttribute} "dir=rtl" for RTL text in non-RTL
874
- * context; "dir=ltr" for LTR text in non-LTR context;
1633
+ * @return {!soydata.SanitizedHtmlAttribute} 'dir="rtl"' for RTL text in non-RTL
1634
+ * context; 'dir="ltr"' for LTR text in non-LTR context;
875
1635
  * else, the empty string.
876
1636
  */
877
1637
  soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
878
- return new soydata.SanitizedHtmlAttribute(
879
- soy.$$getBidiFormatterInstance_(bidiGlobalDir).dirAttr(text, opt_isHtml));
1638
+ var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
1639
+ var contentDir = soydata.getContentDir(text);
1640
+ if (contentDir == null) {
1641
+ var isHtml = opt_isHtml ||
1642
+ soydata.isContentKind(text, soydata.SanitizedContentKind.HTML);
1643
+ contentDir = goog.i18n.bidi.estimateDirection(text + '', isHtml);
1644
+ }
1645
+ return soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute(
1646
+ formatter.knownDirAttr(contentDir));
880
1647
  };
881
1648
 
882
1649
 
@@ -886,9 +1653,12 @@ soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
886
1653
  * bidiGlobalDir. Otherwise returns the empty string.
887
1654
  * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
888
1655
  * in text, making the logic suitable for HTML and HTML-escaped text.
1656
+ * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of
1657
+ * estimating the directionality.
1658
+ *
889
1659
  * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
890
1660
  * if rtl, 0 if unknown.
891
- * @param {string} text The text whose directionality is to be estimated.
1661
+ * @param {*} text The content whose directionality is to be estimated.
892
1662
  * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
893
1663
  * Default: false.
894
1664
  * @return {string} A Unicode bidi mark matching bidiGlobalDir, or the empty
@@ -897,44 +1667,112 @@ soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
897
1667
  */
898
1668
  soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) {
899
1669
  var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
900
- return formatter.markAfter(text, opt_isHtml);
1670
+ var isHtml = opt_isHtml ||
1671
+ soydata.isContentKind(text, soydata.SanitizedContentKind.HTML);
1672
+ return formatter.markAfterKnownDir(soydata.getContentDir(text), text + '',
1673
+ isHtml);
901
1674
  };
902
1675
 
903
1676
 
904
1677
  /**
905
- * Returns str wrapped in a <span dir=ltr|rtl> according to its directionality -
906
- * but only if that is neither neutral nor the same as the global context.
907
- * Otherwise, returns str unchanged.
908
- * Always treats str as HTML/HTML-escaped, i.e. ignores mark-up and escapes when
909
- * estimating str's directionality.
1678
+ * Returns text wrapped in a <span dir="ltr|rtl"> according to its
1679
+ * directionality - but only if that is neither neutral nor the same as the
1680
+ * global context. Otherwise, returns text unchanged.
1681
+ * Always treats text as HTML/HTML-escaped, i.e. ignores mark-up and escapes
1682
+ * when estimating text's directionality.
1683
+ * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of
1684
+ * estimating the directionality.
1685
+ *
910
1686
  * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
911
1687
  * if rtl, 0 if unknown.
912
- * @param {*} str The string to be wrapped. Can be other types, but the value
1688
+ * @param {*} text The string to be wrapped. Can be other types, but the value
913
1689
  * will be coerced to a string.
914
- * @return {string} The wrapped string.
1690
+ * @return {!goog.soy.data.SanitizedContent|string} The wrapped text.
915
1691
  */
916
- soy.$$bidiSpanWrap = function(bidiGlobalDir, str) {
1692
+ soy.$$bidiSpanWrap = function(bidiGlobalDir, text) {
917
1693
  var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
918
- return formatter.spanWrap(str + '', true);
1694
+
1695
+ // We always treat the value as HTML, because span-wrapping is only useful
1696
+ // when its output will be treated as HTML (without escaping), and because
1697
+ // |bidiSpanWrap is not itself specified to do HTML escaping in Soy. (Both
1698
+ // explicit and automatic HTML escaping, if any, is done before calling
1699
+ // |bidiSpanWrap because the BidiSpanWrapDirective Java class implements
1700
+ // SanitizedContentOperator, but this does not mean that the input has to be
1701
+ // HTML SanitizedContent. In legacy usage, a string that is not
1702
+ // SanitizedContent is often printed in an autoescape="false" template or by
1703
+ // a print with a |noAutoescape, in which case our input is just SoyData.) If
1704
+ // the output will be treated as HTML, the input had better be safe
1705
+ // HTML/HTML-escaped (even if it isn't HTML SanitizedData), or we have an XSS
1706
+ // opportunity and a much bigger problem than bidi garbling.
1707
+ var wrappedText = formatter.spanWrapWithKnownDir(
1708
+ soydata.getContentDir(text), text + '', true /* opt_isHtml */);
1709
+
1710
+ // Like other directives whose Java class implements SanitizedContentOperator,
1711
+ // |bidiSpanWrap is called after the escaping (if any) has already been done,
1712
+ // and thus there is no need for it to produce actual SanitizedContent.
1713
+ return wrappedText;
919
1714
  };
920
1715
 
921
1716
 
922
1717
  /**
923
- * Returns str wrapped in Unicode BiDi formatting characters according to its
1718
+ * Returns text wrapped in Unicode BiDi formatting characters according to its
924
1719
  * directionality, i.e. either LRE or RLE at the beginning and PDF at the end -
925
- * but only if str's directionality is neither neutral nor the same as the
926
- * global context. Otherwise, returns str unchanged.
927
- * Always treats str as HTML/HTML-escaped, i.e. ignores mark-up and escapes when
928
- * estimating str's directionality.
1720
+ * but only if text's directionality is neither neutral nor the same as the
1721
+ * global context. Otherwise, returns text unchanged.
1722
+ * Only treats soydata.SanitizedHtml as HTML/HTML-escaped, i.e. ignores mark-up
1723
+ * and escapes when estimating text's directionality.
1724
+ * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of
1725
+ * estimating the directionality.
1726
+ *
929
1727
  * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
930
1728
  * if rtl, 0 if unknown.
931
- * @param {*} str The string to be wrapped. Can be other types, but the value
1729
+ * @param {*} text The string to be wrapped. Can be other types, but the value
932
1730
  * will be coerced to a string.
933
- * @return {string} The wrapped string.
1731
+ * @return {!goog.soy.data.SanitizedContent|string} The wrapped string.
934
1732
  */
935
- soy.$$bidiUnicodeWrap = function(bidiGlobalDir, str) {
1733
+ soy.$$bidiUnicodeWrap = function(bidiGlobalDir, text) {
936
1734
  var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
937
- return formatter.unicodeWrap(str + '', true);
1735
+
1736
+ // We treat the value as HTML if and only if it says it's HTML, even though in
1737
+ // legacy usage, we sometimes have an HTML string (not SanitizedContent) that
1738
+ // is passed to an autoescape="false" template or a {print $foo|noAutoescape},
1739
+ // with the output going into an HTML context without escaping. We simply have
1740
+ // no way of knowing if this is what is happening when we get
1741
+ // non-SanitizedContent input, and most of the time it isn't.
1742
+ var isHtml = soydata.isContentKind(text, soydata.SanitizedContentKind.HTML);
1743
+ var wrappedText = formatter.unicodeWrapWithKnownDir(
1744
+ soydata.getContentDir(text), text + '', isHtml);
1745
+
1746
+ // Bidi-wrapping a value converts it to the context directionality. Since it
1747
+ // does not cost us anything, we will indicate this known direction in the
1748
+ // output SanitizedContent, even though the intended consumer of that
1749
+ // information - a bidi wrapping directive - has already been run.
1750
+ var wrappedTextDir = formatter.getContextDir();
1751
+
1752
+ // Unicode-wrapping UnsanitizedText gives UnsanitizedText.
1753
+ // Unicode-wrapping safe HTML or JS string data gives valid, safe HTML or JS
1754
+ // string data.
1755
+ // ATTENTION: Do these need to be ...ForInternalBlocks()?
1756
+ if (soydata.isContentKind(text, soydata.SanitizedContentKind.TEXT)) {
1757
+ return new soydata.UnsanitizedText(wrappedText, wrappedTextDir);
1758
+ }
1759
+ if (isHtml) {
1760
+ return soydata.VERY_UNSAFE.ordainSanitizedHtml(wrappedText, wrappedTextDir);
1761
+ }
1762
+ if (soydata.isContentKind(text, soydata.SanitizedContentKind.JS_STR_CHARS)) {
1763
+ return soydata.VERY_UNSAFE.ordainSanitizedJsStrChars(
1764
+ wrappedText, wrappedTextDir);
1765
+ }
1766
+
1767
+ // Unicode-wrapping does not conform to the syntax of the other types of
1768
+ // content. For lack of anything better to do, we we do not declare a content
1769
+ // kind at all by falling through to the non-SanitizedContent case below.
1770
+ // TODO(user): Consider throwing a runtime error on receipt of
1771
+ // SanitizedContent other than TEXT, HTML, or JS_STR_CHARS.
1772
+
1773
+ // The input was not SanitizedContent, so our output isn't SanitizedContent
1774
+ // either.
1775
+ return wrappedText;
938
1776
  };
939
1777
 
940
1778
 
@@ -944,6 +1782,10 @@ soy.$$bidiUnicodeWrap = function(bidiGlobalDir, str) {
944
1782
 
945
1783
 
946
1784
 
1785
+
1786
+
1787
+
1788
+
947
1789
  // START GENERATED CODE FOR ESCAPERS.
948
1790
 
949
1791
  /**
@@ -954,7 +1796,7 @@ soy.esc.$$escapeUriHelper = function(v) {
954
1796
  };
955
1797
 
956
1798
  /**
957
- * Maps charcters to the escaped versions for the named escape directives.
1799
+ * Maps characters to the escaped versions for the named escape directives.
958
1800
  * @type {Object.<string, string>}
959
1801
  * @private
960
1802
  */
@@ -982,7 +1824,7 @@ soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSP
982
1824
  };
983
1825
 
984
1826
  /**
985
- * A function that can be used with String.replace..
1827
+ * A function that can be used with String.replace.
986
1828
  * @param {string} ch A single character matched by a compatible matcher.
987
1829
  * @return {string} A token in the output language.
988
1830
  * @private
@@ -992,7 +1834,7 @@ soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPAC
992
1834
  };
993
1835
 
994
1836
  /**
995
- * Maps charcters to the escaped versions for the named escape directives.
1837
+ * Maps characters to the escaped versions for the named escape directives.
996
1838
  * @type {Object.<string, string>}
997
1839
  * @private
998
1840
  */
@@ -1034,7 +1876,7 @@ soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = {
1034
1876
  };
1035
1877
 
1036
1878
  /**
1037
- * A function that can be used with String.replace..
1879
+ * A function that can be used with String.replace.
1038
1880
  * @param {string} ch A single character matched by a compatible matcher.
1039
1881
  * @return {string} A token in the output language.
1040
1882
  * @private
@@ -1044,7 +1886,7 @@ soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = function(ch) {
1044
1886
  };
1045
1887
 
1046
1888
  /**
1047
- * Maps charcters to the escaped versions for the named escape directives.
1889
+ * Maps characters to the escaped versions for the named escape directives.
1048
1890
  * @type {Object.<string, string>}
1049
1891
  * @private
1050
1892
  */
@@ -1079,7 +1921,7 @@ soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_ = {
1079
1921
  };
1080
1922
 
1081
1923
  /**
1082
- * A function that can be used with String.replace..
1924
+ * A function that can be used with String.replace.
1083
1925
  * @param {string} ch A single character matched by a compatible matcher.
1084
1926
  * @return {string} A token in the output language.
1085
1927
  * @private
@@ -1089,7 +1931,7 @@ soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_ = function(ch) {
1089
1931
  };
1090
1932
 
1091
1933
  /**
1092
- * Maps charcters to the escaped versions for the named escape directives.
1934
+ * Maps characters to the escaped versions for the named escape directives.
1093
1935
  * @type {Object.<string, string>}
1094
1936
  * @private
1095
1937
  */
@@ -1162,7 +2004,7 @@ soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = {
1162
2004
  };
1163
2005
 
1164
2006
  /**
1165
- * A function that can be used with String.replace..
2007
+ * A function that can be used with String.replace.
1166
2008
  * @param {string} ch A single character matched by a compatible matcher.
1167
2009
  * @return {string} A token in the output language.
1168
2010
  * @private
@@ -1246,7 +2088,14 @@ soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_ = /^(?:(?:https?|mailto):|[^&:\/?#]*(
1246
2088
  * @type RegExp
1247
2089
  * @private
1248
2090
  */
1249
- soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTE_ = /^(?!style|on|action|archive|background|cite|classid|codebase|data|dsync|href|longdesc|src|usemap)(?:[a-z0-9_$:-]*)$/i;
2091
+ soy.esc.$$FILTER_FOR_FILTER_IMAGE_DATA_URI_ = /^data:image\/(?:bmp|gif|jpe?g|png|tiff|webp);base64,[a-z0-9+\/]+=*$/i;
2092
+
2093
+ /**
2094
+ * A pattern that vets values produced by the named directives.
2095
+ * @type RegExp
2096
+ * @private
2097
+ */
2098
+ soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_ = /^(?!style|on|action|archive|background|cite|classid|codebase|data|dsync|href|longdesc|src|usemap)(?:[a-z0-9_$:-]*)$/i;
1250
2099
 
1251
2100
  /**
1252
2101
  * A pattern that vets values produced by the named directives.
@@ -1374,7 +2223,7 @@ soy.esc.$$filterNormalizeUriHelper = function(value) {
1374
2223
  var str = String(value);
1375
2224
  if (!soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_.test(str)) {
1376
2225
  goog.asserts.fail('Bad value `%s` for |filterNormalizeUri', [str]);
1377
- return 'zSoyz';
2226
+ return '#zSoyz';
1378
2227
  }
1379
2228
  return str.replace(
1380
2229
  soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_,
@@ -1382,14 +2231,28 @@ soy.esc.$$filterNormalizeUriHelper = function(value) {
1382
2231
  };
1383
2232
 
1384
2233
  /**
1385
- * A helper for the Soy directive |filterHtmlAttribute
2234
+ * A helper for the Soy directive |filterImageDataUri
1386
2235
  * @param {*} value Can be of any type but will be coerced to a string.
1387
2236
  * @return {string} The escaped text.
1388
2237
  */
1389
- soy.esc.$$filterHtmlAttributeHelper = function(value) {
2238
+ soy.esc.$$filterImageDataUriHelper = function(value) {
1390
2239
  var str = String(value);
1391
- if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTE_.test(str)) {
1392
- goog.asserts.fail('Bad value `%s` for |filterHtmlAttribute', [str]);
2240
+ if (!soy.esc.$$FILTER_FOR_FILTER_IMAGE_DATA_URI_.test(str)) {
2241
+ goog.asserts.fail('Bad value `%s` for |filterImageDataUri', [str]);
2242
+ return 'data:image/gif;base64,zSoyz';
2243
+ }
2244
+ return str;
2245
+ };
2246
+
2247
+ /**
2248
+ * A helper for the Soy directive |filterHtmlAttributes
2249
+ * @param {*} value Can be of any type but will be coerced to a string.
2250
+ * @return {string} The escaped text.
2251
+ */
2252
+ soy.esc.$$filterHtmlAttributesHelper = function(value) {
2253
+ var str = String(value);
2254
+ if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_.test(str)) {
2255
+ goog.asserts.fail('Bad value `%s` for |filterHtmlAttributes', [str]);
1393
2256
  return 'zSoyz';
1394
2257
  }
1395
2258
  return str;
@@ -1411,10 +2274,29 @@ soy.esc.$$filterHtmlElementNameHelper = function(value) {
1411
2274
 
1412
2275
  /**
1413
2276
  * Matches all tags, HTML comments, and DOCTYPEs in tag soup HTML.
2277
+ * By removing these, and replacing any '<' or '>' characters with
2278
+ * entities we guarantee that the result can be embedded into a
2279
+ * an attribute without introducing a tag boundary.
2280
+ *
2281
+ * @type {RegExp}
2282
+ * @private
2283
+ */
2284
+ soy.esc.$$HTML_TAG_REGEX_ = /<(?:!|\/?([a-zA-Z][a-zA-Z0-9:\-]*))(?:[^>'"]|"[^"]*"|'[^']*')*>/g;
2285
+
2286
+ /**
2287
+ * Matches all occurrences of '<'.
1414
2288
  *
1415
2289
  * @type {RegExp}
1416
2290
  * @private
1417
2291
  */
1418
- soy.esc.$$HTML_TAG_REGEX_ = /<(?:!|\/?[a-zA-Z])(?:[^>'"]|"[^"]*"|'[^']*')*>/g;
2292
+ soy.esc.$$LT_REGEX_ = /</g;
2293
+
2294
+ /**
2295
+ * Maps lower-case names of innocuous tags to 1.
2296
+ *
2297
+ * @type {Object.<string,number>}
2298
+ * @private
2299
+ */
2300
+ soy.esc.$$SAFE_TAG_WHITELIST_ = {'b': 1, 'br': 1, 'em': 1, 'i': 1, 's': 1, 'sub': 1, 'sup': 1, 'u': 1};
1419
2301
 
1420
2302
  // END GENERATED CODE