Skip to content

Commit

Permalink
Fix: replace cutAfterJSON with cutAfterJS (#1126)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
TimeForANinja committed Aug 26, 2022
1 parent eb811de commit a299081
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 26 deletions.
2 changes: 1 addition & 1 deletion lib/info.js
Expand Up @@ -265,7 +265,7 @@ const findJSON = (source, varName, body, left, right, prependJSON) => {
if (!jsonStr) {
throw Error(`Could not find ${varName} in ${source}`);
}
return parseJSON(source, varName, utils.cutAfterJSON(`${prependJSON}${jsonStr}`));
return parseJSON(source, varName, utils.cutAfterJS(`${prependJSON}${jsonStr}`));
};


Expand Down
6 changes: 3 additions & 3 deletions lib/sig.js
Expand Up @@ -39,7 +39,7 @@ exports.extractFunctions = body => {
const ndx = body.indexOf(functionStart);
if (ndx < 0) return '';
const subBody = body.slice(ndx + functionStart.length - 1);
return `var ${functionName}=${utils.cutAfterJSON(subBody)}`;
return `var ${functionName}=${utils.cutAfterJS(subBody)}`;
};
const extractDecipher = () => {
const functionName = utils.between(body, `a.set("alr","yes");c&&(c=`, `(decodeURIC`);
Expand All @@ -48,7 +48,7 @@ exports.extractFunctions = body => {
const ndx = body.indexOf(functionStart);
if (ndx >= 0) {
const subBody = body.slice(ndx + functionStart.length);
let functionBody = `var ${functionStart}${utils.cutAfterJSON(subBody)}`;
let functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)}`;
functionBody = `${extractManipulations(functionBody)};${functionBody};${functionName}(sig);`;
functions.push(functionBody);
}
Expand All @@ -62,7 +62,7 @@ exports.extractFunctions = body => {
const ndx = body.indexOf(functionStart);
if (ndx >= 0) {
const subBody = body.slice(ndx + functionStart.length);
const functionBody = `var ${functionStart}${utils.cutAfterJSON(subBody)};${functionName}(ncode);`;
const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
functions.push(functionBody);
}
}
Expand Down
48 changes: 39 additions & 9 deletions lib/utils.js
Expand Up @@ -48,14 +48,29 @@ exports.parseAbbreviatedNumber = string => {
return null;
};

/**
* Escape sequences for cutAfterJS
* @param {string} start the character string the escape sequence
* @param {string} end the character string to stop the escape seequence
* @param {undefined|Regex} startPrefix a regex to check against the preceding 10 characters
*/
const ESCAPING_SEQUENZES = [
// Strings
{ start: '"', end: '"' },
{ start: "'", end: "'" },
{ start: '`', end: '`' },
// RegeEx
{ start: '/', end: '/', startPrefix: /(^|[[{:;,])\s+$/ },
];

/**
* Match begin and end braces of input JSON, return only json
* Match begin and end braces of input JS, return only JS
*
* @param {string} mixedJson
* @returns {string}
*/
exports.cutAfterJSON = mixedJson => {
exports.cutAfterJS = mixedJson => {
// Define the general open and closing tag
let open, close;
if (mixedJson[0] === '[') {
open = '[';
Expand All @@ -69,8 +84,8 @@ exports.cutAfterJSON = mixedJson => {
throw new Error(`Can't cut unsupported JSON (need to begin with [ or { ) but got: ${mixedJson[0]}`);
}

// States if the loop is currently in a string
let isString = false;
// States if the loop is currently inside an escaped js object
let isEscapedObject = null;

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

let i;
// Go through all characters from the start
for (i = 0; i < mixedJson.length; i++) {
// Toggle the isString boolean when leaving/entering string
if (mixedJson[i] === '"' && !isEscaped) {
isString = !isString;
// End of current escaped object
if (!isEscaped && isEscapedObject !== null && mixedJson[i] === isEscapedObject.end) {
isEscapedObject = null;
continue;
// Might be the start of a new escaped object
} else if (!isEscaped && isEscapedObject === null) {
for (const escaped of ESCAPING_SEQUENZES) {
if (mixedJson[i] !== escaped.start) continue;
// Test startPrefix against last 10 characters
if (!escaped.startPrefix || mixedJson.substring(i - 10, i).match(escaped.startPrefix)) {
isEscapedObject = escaped;
break;
}
}
// Continue if we found a new escaped object
if (isEscapedObject !== null) {
continue;
}
}

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

if (isString) continue;
if (isEscapedObject !== null) continue;

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

Expand Down
55 changes: 42 additions & 13 deletions test/utils-test.js
Expand Up @@ -32,57 +32,86 @@ describe('utils.between()', () => {
});


describe('utils.cutAfterJSON()', () => {
describe('utils.cutAfterJS()', () => {
it('Works with simple JSON', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": 1, "b": 1}'), '{"a": 1, "b": 1}');
assert.strictEqual(utils.cutAfterJS('{"a": 1, "b": 1}'), '{"a": 1, "b": 1}');
});
it('Cut extra characters after JSON', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": 1, "b": 1}abcd'), '{"a": 1, "b": 1}');
assert.strictEqual(utils.cutAfterJS('{"a": 1, "b": 1}abcd'), '{"a": 1, "b": 1}');
});
it('Tolerant to double-quoted string constants', () => {
assert.strictEqual(utils.cutAfterJS('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
});
it('Tolerant to single-quoted string constants', () => {
assert.strictEqual(utils.cutAfterJS(`{"a": '}1', "b": 1}abcd`), `{"a": '}1', "b": 1}`);
});
it('Tolerant to complex single-quoted string constants', () => {
const str = "[-1816574795, '\",;/[;', function asdf() { a = 2/3; return a;}]";
assert.strictEqual(utils.cutAfterJS(`${str}abcd`), str);
});
it('Tolerant to back-tick-quoted string constants', () => {
assert.strictEqual(utils.cutAfterJS('{"a": `}1`, "b": 1}abcd'), '{"a": `}1`, "b": 1}');
});
it('Tolerant to string constants', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
assert.strictEqual(utils.cutAfterJS('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
});
it('Tolerant to string with escaped quoting', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": "\\"}1", "b": 1}abcd'), '{"a": "\\"}1", "b": 1}');
assert.strictEqual(utils.cutAfterJS('{"a": "\\"}1", "b": 1}abcd'), '{"a": "\\"}1", "b": 1}');
});
it('works with nested', () => {
it('Tolerant to string with regexes', () => {
assert.strictEqual(
utils.cutAfterJSON('{"a": "\\"1", "b": 1, "c": {"test": 1}}abcd'),
utils.cutAfterJS('{"a": "\\"}1", "b": 1, "c": /[0-9]}}\\/}/}abcd'),
'{"a": "\\"}1", "b": 1, "c": /[0-9]}}\\/}/}',
);
});
it('does not fail for division followed by a regex', () => {
assert.strictEqual(
utils.cutAfterJS('{"a": "\\"}1", "b": 1, "c": [4/6, /[0-9]}}\\/}/]}abcd', true),
'{"a": "\\"}1", "b": 1, "c": [4/6, /[0-9]}}\\/}/]}',
);
});
it('works with nested objects', () => {
assert.strictEqual(
utils.cutAfterJS('{"a": "\\"1", "b": 1, "c": {"test": 1}}abcd'),
'{"a": "\\"1", "b": 1, "c": {"test": 1}}',
);
});
it('works with try/catch', () => {
let testStr = '{"a": "\\"1", "b": 1, "c": () => { try { /* do sth */ } catch (e) { a = [2+3] }; return 5}}';
assert.strictEqual(utils.cutAfterJS(`${testStr}abcd`), testStr);
});
it('Works with utf', () => {
assert.strictEqual(
utils.cutAfterJSON('{"a": "\\"фыва", "b": 1, "c": {"test": 1}}abcd'),
utils.cutAfterJS('{"a": "\\"фыва", "b": 1, "c": {"test": 1}}abcd'),
'{"a": "\\"фыва", "b": 1, "c": {"test": 1}}',
);
});
it('Works with \\\\ in string', () => {
assert.strictEqual(
utils.cutAfterJSON('{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}abcd'),
utils.cutAfterJS('{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}abcd'),
'{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}',
);
});
it('Works with \\\\ towards the end of a string', () => {
assert.strictEqual(
utils.cutAfterJSON('{"text": "\\\\"};'),
utils.cutAfterJS('{"text": "\\\\"};'),
'{"text": "\\\\"}',
);
});
it('Works with [ as start', () => {
assert.strictEqual(
utils.cutAfterJSON('[{"a": 1}, {"b": 2}]abcd'),
utils.cutAfterJS('[{"a": 1}, {"b": 2}]abcd'),
'[{"a": 1}, {"b": 2}]',
);
});
it('Returns an error when not beginning with [ or {', () => {
assert.throws(() => {
utils.cutAfterJSON('abcd]}');
utils.cutAfterJS('abcd]}');
}, /Can't cut unsupported JSON \(need to begin with \[ or { \) but got: ./);
});
it('Returns an error when missing closing bracket', () => {
assert.throws(() => {
utils.cutAfterJSON('{"a": 1,{ "b": 1}');
utils.cutAfterJS('{"a": 1,{ "b": 1}');
}, /Can't cut unsupported JSON \(no matching closing bracket found\)/);
});
});
Expand Down

0 comments on commit a299081

Please sign in to comment.