closure 1.4.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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