template.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. define(['./defaults', './underscore', './templateSettings'], function (defaults, underscore, templateSettings) {
  2. // When customizing `_.templateSettings`, if you don't want to define an
  3. // interpolation, evaluation or escaping regex, we need one that is
  4. // guaranteed not to match.
  5. var noMatch = /(.)^/;
  6. // Certain characters need to be escaped so that they can be put into a
  7. // string literal.
  8. var escapes = {
  9. "'": "'",
  10. '\\': '\\',
  11. '\r': 'r',
  12. '\n': 'n',
  13. '\u2028': 'u2028',
  14. '\u2029': 'u2029'
  15. };
  16. var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
  17. function escapeChar(match) {
  18. return '\\' + escapes[match];
  19. }
  20. // In order to prevent third-party code injection through
  21. // `_.templateSettings.variable`, we test it against the following regular
  22. // expression. It is intentionally a bit more liberal than just matching valid
  23. // identifiers, but still prevents possible loopholes through defaults or
  24. // destructuring assignment.
  25. var bareIdentifier = /^\s*(\w|\$)+\s*$/;
  26. // JavaScript micro-templating, similar to John Resig's implementation.
  27. // Underscore templating handles arbitrary delimiters, preserves whitespace,
  28. // and correctly escapes quotes within interpolated code.
  29. // NB: `oldSettings` only exists for backwards compatibility.
  30. function template(text, settings, oldSettings) {
  31. if (!settings && oldSettings) settings = oldSettings;
  32. settings = defaults({}, settings, underscore.templateSettings);
  33. // Combine delimiters into one regular expression via alternation.
  34. var matcher = RegExp([
  35. (settings.escape || noMatch).source,
  36. (settings.interpolate || noMatch).source,
  37. (settings.evaluate || noMatch).source
  38. ].join('|') + '|$', 'g');
  39. // Compile the template source, escaping string literals appropriately.
  40. var index = 0;
  41. var source = "__p+='";
  42. text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
  43. source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
  44. index = offset + match.length;
  45. if (escape) {
  46. source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
  47. } else if (interpolate) {
  48. source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
  49. } else if (evaluate) {
  50. source += "';\n" + evaluate + "\n__p+='";
  51. }
  52. // Adobe VMs need the match returned to produce the correct offset.
  53. return match;
  54. });
  55. source += "';\n";
  56. var argument = settings.variable;
  57. if (argument) {
  58. // Insure against third-party code injection. (CVE-2021-23358)
  59. if (!bareIdentifier.test(argument)) throw new Error(
  60. 'variable is not a bare identifier: ' + argument
  61. );
  62. } else {
  63. // If a variable is not specified, place data values in local scope.
  64. source = 'with(obj||{}){\n' + source + '}\n';
  65. argument = 'obj';
  66. }
  67. source = "var __t,__p='',__j=Array.prototype.join," +
  68. "print=function(){__p+=__j.call(arguments,'');};\n" +
  69. source + 'return __p;\n';
  70. var render;
  71. try {
  72. render = new Function(argument, '_', source);
  73. } catch (e) {
  74. e.source = source;
  75. throw e;
  76. }
  77. var template = function(data) {
  78. return render.call(this, data, underscore);
  79. };
  80. // Provide the compiled source as a convenience for precompilation.
  81. template.source = 'function(' + argument + '){\n' + source + '}';
  82. return template;
  83. }
  84. return template;
  85. });