diff --git a/index.js b/index.js index 8ddc89b..49abfb4 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,11 @@ var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g; // stays in script data, so only these need neutralizing. The bare-prefix form // matters because the matching `>` could be supplied by a different // serialized value later in the output, so escaping stops there without -// waiting for a closing `>`. A trailing backslash is not a delimiter here +// waiting for a closing `>`. For the same reason the bare form also matches +// at the very end of the string: each function body is escaped on its own, so +// a body ending in `\/\u2028\u2029]/g; // longer contain a second `<`, every `]*>|<\/script(?=[\t\n\f\r \/>])/gi; +var SCRIPT_CLOSE_REGEXP = /<\/script[^<>]*>|<\/script(?=$|[\t\n\f\r \/>])/gi; var RESERVED_SYMBOLS = ['*', 'async']; diff --git a/test/unit/serialize.js b/test/unit/serialize.js index 3d4de00..8949260 100644 --- a/test/unit/serialize.js +++ b/test/unit/serialize.js @@ -664,6 +664,30 @@ describe('serialize( obj )', function () { strictEqual(serialized.includes('\\u003C\\u002Fscript\\u003E'), true); }); + it('should escape a script close tag at the very end of a function body (PSECBUGS-118415)', function () { + // Each body is escaped on its own, so one ending in a bare + // ``. + var f = function () {}; + f.toString = function () { return 'function f(){ return 1 }// '; }; + var serialized = serialize({ a: { f: f }, g: g }, { space: 2 }); + + strictEqual(CLOSES_SCRIPT.test(serialized), false); + // Inside the line comment, so it unicode-escapes. + strictEqual(serialized.includes('//\\u003C\\u002Fscript\n'), true); + }); + + it('should neutralize a script close tag ending a function body in code position', function () { + var fn = function () {}; + fn.toString = function () { return 'function f(x){ return x }