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