Skip to content

Commit a299081

Browse files
Fix: replace cutAfterJSON with cutAfterJS (#1126)
* support regex in `utils#cutAfterJSON` * fix test and code * add support for single and backtick quoted strings to cutAfterJSON * yet another unit-test * update comments
1 parent eb811de commit a299081

File tree

4 files changed

+85
-26
lines changed

4 files changed

+85
-26
lines changed

lib/info.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ const findJSON = (source, varName, body, left, right, prependJSON) => {
265265
if (!jsonStr) {
266266
throw Error(`Could not find ${varName} in ${source}`);
267267
}
268-
return parseJSON(source, varName, utils.cutAfterJSON(`${prependJSON}${jsonStr}`));
268+
return parseJSON(source, varName, utils.cutAfterJS(`${prependJSON}${jsonStr}`));
269269
};
270270

271271

lib/sig.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ exports.extractFunctions = body => {
3939
const ndx = body.indexOf(functionStart);
4040
if (ndx < 0) return '';
4141
const subBody = body.slice(ndx + functionStart.length - 1);
42-
return `var ${functionName}=${utils.cutAfterJSON(subBody)}`;
42+
return `var ${functionName}=${utils.cutAfterJS(subBody)}`;
4343
};
4444
const extractDecipher = () => {
4545
const functionName = utils.between(body, `a.set("alr","yes");c&&(c=`, `(decodeURIC`);
@@ -48,7 +48,7 @@ exports.extractFunctions = body => {
4848
const ndx = body.indexOf(functionStart);
4949
if (ndx >= 0) {
5050
const subBody = body.slice(ndx + functionStart.length);
51-
let functionBody = `var ${functionStart}${utils.cutAfterJSON(subBody)}`;
51+
let functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)}`;
5252
functionBody = `${extractManipulations(functionBody)};${functionBody};${functionName}(sig);`;
5353
functions.push(functionBody);
5454
}
@@ -62,7 +62,7 @@ exports.extractFunctions = body => {
6262
const ndx = body.indexOf(functionStart);
6363
if (ndx >= 0) {
6464
const subBody = body.slice(ndx + functionStart.length);
65-
const functionBody = `var ${functionStart}${utils.cutAfterJSON(subBody)};${functionName}(ncode);`;
65+
const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
6666
functions.push(functionBody);
6767
}
6868
}

lib/utils.js

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,29 @@ exports.parseAbbreviatedNumber = string => {
4848
return null;
4949
};
5050

51+
/**
52+
* Escape sequences for cutAfterJS
53+
* @param {string} start the character string the escape sequence
54+
* @param {string} end the character string to stop the escape seequence
55+
* @param {undefined|Regex} startPrefix a regex to check against the preceding 10 characters
56+
*/
57+
const ESCAPING_SEQUENZES = [
58+
// Strings
59+
{ start: '"', end: '"' },
60+
{ start: "'", end: "'" },
61+
{ start: '`', end: '`' },
62+
// RegeEx
63+
{ start: '/', end: '/', startPrefix: /(^|[[{:;,])\s+$/ },
64+
];
5165

5266
/**
53-
* Match begin and end braces of input JSON, return only json
67+
* Match begin and end braces of input JS, return only JS
5468
*
5569
* @param {string} mixedJson
5670
* @returns {string}
5771
*/
58-
exports.cutAfterJSON = mixedJson => {
72+
exports.cutAfterJS = mixedJson => {
73+
// Define the general open and closing tag
5974
let open, close;
6075
if (mixedJson[0] === '[') {
6176
open = '[';
@@ -69,8 +84,8 @@ exports.cutAfterJSON = mixedJson => {
6984
throw new Error(`Can't cut unsupported JSON (need to begin with [ or { ) but got: ${mixedJson[0]}`);
7085
}
7186

72-
// States if the loop is currently in a string
73-
let isString = false;
87+
// States if the loop is currently inside an escaped js object
88+
let isEscapedObject = null;
7489

7590
// States if the current character is treated as escaped or not
7691
let isEscaped = false;
@@ -79,18 +94,33 @@ exports.cutAfterJSON = mixedJson => {
7994
let counter = 0;
8095

8196
let i;
97+
// Go through all characters from the start
8298
for (i = 0; i < mixedJson.length; i++) {
83-
// Toggle the isString boolean when leaving/entering string
84-
if (mixedJson[i] === '"' && !isEscaped) {
85-
isString = !isString;
99+
// End of current escaped object
100+
if (!isEscaped && isEscapedObject !== null && mixedJson[i] === isEscapedObject.end) {
101+
isEscapedObject = null;
86102
continue;
103+
// Might be the start of a new escaped object
104+
} else if (!isEscaped && isEscapedObject === null) {
105+
for (const escaped of ESCAPING_SEQUENZES) {
106+
if (mixedJson[i] !== escaped.start) continue;
107+
// Test startPrefix against last 10 characters
108+
if (!escaped.startPrefix || mixedJson.substring(i - 10, i).match(escaped.startPrefix)) {
109+
isEscapedObject = escaped;
110+
break;
111+
}
112+
}
113+
// Continue if we found a new escaped object
114+
if (isEscapedObject !== null) {
115+
continue;
116+
}
87117
}
88118

89119
// Toggle the isEscaped boolean for every backslash
90120
// Reset for every regular character
91121
isEscaped = mixedJson[i] === '\\' && !isEscaped;
92122

93-
if (isString) continue;
123+
if (isEscapedObject !== null) continue;
94124

95125
if (mixedJson[i] === open) {
96126
counter++;
@@ -101,7 +131,7 @@ exports.cutAfterJSON = mixedJson => {
101131
// All brackets have been closed, thus end of JSON is reached
102132
if (counter === 0) {
103133
// Return the cut JSON
104-
return mixedJson.substr(0, i + 1);
134+
return mixedJson.substring(0, i + 1);
105135
}
106136
}
107137

test/utils-test.js

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,57 +32,86 @@ describe('utils.between()', () => {
3232
});
3333

3434

35-
describe('utils.cutAfterJSON()', () => {
35+
describe('utils.cutAfterJS()', () => {
3636
it('Works with simple JSON', () => {
37-
assert.strictEqual(utils.cutAfterJSON('{"a": 1, "b": 1}'), '{"a": 1, "b": 1}');
37+
assert.strictEqual(utils.cutAfterJS('{"a": 1, "b": 1}'), '{"a": 1, "b": 1}');
3838
});
3939
it('Cut extra characters after JSON', () => {
40-
assert.strictEqual(utils.cutAfterJSON('{"a": 1, "b": 1}abcd'), '{"a": 1, "b": 1}');
40+
assert.strictEqual(utils.cutAfterJS('{"a": 1, "b": 1}abcd'), '{"a": 1, "b": 1}');
41+
});
42+
it('Tolerant to double-quoted string constants', () => {
43+
assert.strictEqual(utils.cutAfterJS('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
44+
});
45+
it('Tolerant to single-quoted string constants', () => {
46+
assert.strictEqual(utils.cutAfterJS(`{"a": '}1', "b": 1}abcd`), `{"a": '}1', "b": 1}`);
47+
});
48+
it('Tolerant to complex single-quoted string constants', () => {
49+
const str = "[-1816574795, '\",;/[;', function asdf() { a = 2/3; return a;}]";
50+
assert.strictEqual(utils.cutAfterJS(`${str}abcd`), str);
51+
});
52+
it('Tolerant to back-tick-quoted string constants', () => {
53+
assert.strictEqual(utils.cutAfterJS('{"a": `}1`, "b": 1}abcd'), '{"a": `}1`, "b": 1}');
4154
});
4255
it('Tolerant to string constants', () => {
43-
assert.strictEqual(utils.cutAfterJSON('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
56+
assert.strictEqual(utils.cutAfterJS('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
4457
});
4558
it('Tolerant to string with escaped quoting', () => {
46-
assert.strictEqual(utils.cutAfterJSON('{"a": "\\"}1", "b": 1}abcd'), '{"a": "\\"}1", "b": 1}');
59+
assert.strictEqual(utils.cutAfterJS('{"a": "\\"}1", "b": 1}abcd'), '{"a": "\\"}1", "b": 1}');
4760
});
48-
it('works with nested', () => {
61+
it('Tolerant to string with regexes', () => {
4962
assert.strictEqual(
50-
utils.cutAfterJSON('{"a": "\\"1", "b": 1, "c": {"test": 1}}abcd'),
63+
utils.cutAfterJS('{"a": "\\"}1", "b": 1, "c": /[0-9]}}\\/}/}abcd'),
64+
'{"a": "\\"}1", "b": 1, "c": /[0-9]}}\\/}/}',
65+
);
66+
});
67+
it('does not fail for division followed by a regex', () => {
68+
assert.strictEqual(
69+
utils.cutAfterJS('{"a": "\\"}1", "b": 1, "c": [4/6, /[0-9]}}\\/}/]}abcd', true),
70+
'{"a": "\\"}1", "b": 1, "c": [4/6, /[0-9]}}\\/}/]}',
71+
);
72+
});
73+
it('works with nested objects', () => {
74+
assert.strictEqual(
75+
utils.cutAfterJS('{"a": "\\"1", "b": 1, "c": {"test": 1}}abcd'),
5176
'{"a": "\\"1", "b": 1, "c": {"test": 1}}',
5277
);
5378
});
79+
it('works with try/catch', () => {
80+
let testStr = '{"a": "\\"1", "b": 1, "c": () => { try { /* do sth */ } catch (e) { a = [2+3] }; return 5}}';
81+
assert.strictEqual(utils.cutAfterJS(`${testStr}abcd`), testStr);
82+
});
5483
it('Works with utf', () => {
5584
assert.strictEqual(
56-
utils.cutAfterJSON('{"a": "\\"фыва", "b": 1, "c": {"test": 1}}abcd'),
85+
utils.cutAfterJS('{"a": "\\"фыва", "b": 1, "c": {"test": 1}}abcd'),
5786
'{"a": "\\"фыва", "b": 1, "c": {"test": 1}}',
5887
);
5988
});
6089
it('Works with \\\\ in string', () => {
6190
assert.strictEqual(
62-
utils.cutAfterJSON('{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}abcd'),
91+
utils.cutAfterJS('{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}abcd'),
6392
'{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}',
6493
);
6594
});
6695
it('Works with \\\\ towards the end of a string', () => {
6796
assert.strictEqual(
68-
utils.cutAfterJSON('{"text": "\\\\"};'),
97+
utils.cutAfterJS('{"text": "\\\\"};'),
6998
'{"text": "\\\\"}',
7099
);
71100
});
72101
it('Works with [ as start', () => {
73102
assert.strictEqual(
74-
utils.cutAfterJSON('[{"a": 1}, {"b": 2}]abcd'),
103+
utils.cutAfterJS('[{"a": 1}, {"b": 2}]abcd'),
75104
'[{"a": 1}, {"b": 2}]',
76105
);
77106
});
78107
it('Returns an error when not beginning with [ or {', () => {
79108
assert.throws(() => {
80-
utils.cutAfterJSON('abcd]}');
109+
utils.cutAfterJS('abcd]}');
81110
}, /Can't cut unsupported JSON \(need to begin with \[ or { \) but got: ./);
82111
});
83112
it('Returns an error when missing closing bracket', () => {
84113
assert.throws(() => {
85-
utils.cutAfterJSON('{"a": 1,{ "b": 1}');
114+
utils.cutAfterJS('{"a": 1,{ "b": 1}');
86115
}, /Can't cut unsupported JSON \(no matching closing bracket found\)/);
87116
});
88117
});

0 commit comments

Comments
 (0)