Skip to content

Commit

Permalink
Merge branch 'master' into fix/update-browserslist-db-001
Browse files Browse the repository at this point in the history
  • Loading branch information
slavaleleka committed Aug 8, 2024
2 parents 80651f1 + d8d25ec commit 5eac065
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 14 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
<!-- TODO: change `@added unknown` tag due to the actual version -->
<!-- during new scriptlets or redirects releasing -->

## [Unreleased]

### Added

- support for matching line number in `abort-on-stack-trace` scriptlet
when `inlineScript` or `injectedScript` option is used [#439]

[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v1.11.16...HEAD
[#439]: https://github.com/AdguardTeam/Scriptlets/issues/439

## [v1.11.16] - 2024-08-01

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adguard/scriptlets",
"version": "1.11.16",
"version": "1.11.17",
"description": "AdGuard's JavaScript library of Scriptlets and Redirect resources",
"scripts": {
"build": "babel-node -x .js,.ts scripts/build.js",
Expand Down
40 changes: 27 additions & 13 deletions src/helpers/script-source-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { toRegExp } from './string-utils';

/**
* Determines if type of script is inline or injected
* and when it's one of them then return true, otherwise false
Expand Down Expand Up @@ -30,27 +32,32 @@ export const shouldAbortInlineOrInjectedScript = (stackMatch: string, stackTrace
const stackSteps = stackTrace.split('\n').slice(2).map((line) => line.trim());
const stackLines = stackSteps.map((line) => {
let stack;
// Get stack trace URL
// Get stack trace values
// in Firefox stack trace looks like this: advanceTaskQueue@http://127.0.0.1:8080/scriptlets/tests/dist/qunit.js:1834:20
// in Chrome like this: at Assert.throws (http://127.0.0.1:8080/scriptlets/tests/dist/qunit.js:3178:16)
// so, first group "(.*?@)" is required for Firefox, second group contains URL
const getStackTraceURL = /(.*?@)?(\S+)(:\d+):\d+\)?$/.exec(line);
if (getStackTraceURL) {
let stackURL = getStackTraceURL[2];
// so, first group "(.*?@)" is required for Firefox, second group contains URL,
// third group contains line number, fourth group contains column number
const getStackTraceValues = /(.*?@)?(\S+)(:\d+)(:\d+)\)?$/.exec(line);
if (getStackTraceValues) {
let stackURL = getStackTraceValues[2];
const stackLine = getStackTraceValues[3];
const stackCol = getStackTraceValues[4];
if (stackURL?.startsWith('(')) {
stackURL = stackURL.slice(1);
}
if (stackURL?.startsWith(INJECTED_SCRIPT_MARKER)) {
stackURL = INJECTED_SCRIPT_STRING;
let stackFunction = getStackTraceURL[1] !== undefined
? getStackTraceURL[1].slice(0, -1)
: line.slice(0, getStackTraceURL.index).trim();
let stackFunction = getStackTraceValues[1] !== undefined
? getStackTraceValues[1].slice(0, -1)
: line.slice(0, getStackTraceValues.index).trim();
if (stackFunction?.startsWith('at')) {
stackFunction = stackFunction.slice(2).trim();
}
stack = `${stackFunction} ${stackURL}`.trim();
stack = `${stackFunction} ${stackURL}${stackLine}${stackCol}`.trim();
} else if (stackURL === documentURL) {
stack = `${INLINE_SCRIPT_STRING}${stackLine}${stackCol}`.trim();
} else {
stack = stackURL;
stack = `${stackURL}${stackLine}${stackCol}`.trim();
}
} else {
stack = line;
Expand All @@ -59,11 +66,18 @@ export const shouldAbortInlineOrInjectedScript = (stackMatch: string, stackTrace
});
if (stackLines) {
for (let index = 0; index < stackLines.length; index += 1) {
if (isInlineScript(stackMatch) && documentURL === stackLines[index]) {
if (
isInlineScript(stackMatch)
&& stackLines[index].startsWith(INLINE_SCRIPT_STRING)
&& stackLines[index].match(toRegExp(stackMatch))
) {
return true;
}
if (isInjectedScript(stackMatch)
&& stackLines[index].startsWith(INJECTED_SCRIPT_STRING)) {
if (
isInjectedScript(stackMatch)
&& stackLines[index].startsWith(INJECTED_SCRIPT_STRING)
&& stackLines[index].match(toRegExp(stackMatch))
) {
return true;
}
}
Expand Down
105 changes: 105 additions & 0 deletions tests/scriptlets/abort-on-stack-trace.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,71 @@ test('abort Math.random, injected script', (assert) => {
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('abort Math.ceil, injected script with line number', (assert) => {
const property = 'Math.ceil';
const stackMatch = 'injectedScript:1';
const scriptletArgs = [property, stackMatch];
runScriptlet(name, scriptletArgs);

window.testPassed = false;
const scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
// set window.testPassed to true if script is aborted
// eslint-disable-next-line max-len
scriptElement.innerText = 'try { Math.ceil(2.1); } catch(error) { window.testPassed = true; console.log("Script aborted:", error); }';
document.body.appendChild(scriptElement);
scriptElement.parentNode.removeChild(scriptElement);

assert.strictEqual(window.testPassed, true, 'testPassed set to true, script has been aborted');
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('abort Math.floor, injected script with line number regexp', (assert) => {
const property = 'Math.floor';
const stackMatch = '/injectedScript:\\d:\\d/';
const scriptletArgs = [property, stackMatch];
runScriptlet(name, scriptletArgs);

window.testPassed = false;
const scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
// set window.testPassed to true if script is aborted
// eslint-disable-next-line max-len
scriptElement.innerText = 'try { Math.floor(1.1); } catch(error) { window.testPassed = true; console.log("Script aborted:", error); }';
document.body.appendChild(scriptElement);
scriptElement.parentNode.removeChild(scriptElement);

assert.strictEqual(window.testPassed, true, 'testPassed set to true, script has been aborted');
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('abort Math.pow, injected script with line number regexp, two scripts abort only first', (assert) => {
const property = 'Math.pow';
const stackMatch = '/injectedScript:\\d:1/';
const scriptletArgs = [property, stackMatch];
runScriptlet(name, scriptletArgs);

window.testPassed = false;
const scriptElement1 = document.createElement('script');
scriptElement1.type = 'text/javascript';
// set window.testPassed to true if script is aborted
// eslint-disable-next-line max-len
scriptElement1.innerText = 'try { Math.pow(2, 2); } catch(error) { window.testPassed = true; console.log("Script aborted:", error); }';
document.body.appendChild(scriptElement1);
scriptElement1.parentNode.removeChild(scriptElement1);

const scriptElement2 = document.createElement('script');
scriptElement2.type = 'text/javascript';
// This script should not be aborted, so set window.testPassed to false if script is aborted
// eslint-disable-next-line max-len
scriptElement2.innerText = 'try { (()=>{ const test1 = 1; const test2 = 2; const test3 = 3; const test4 = Math.pow(2, 2); })() } catch(error) { window.testPassed = false; }';
document.body.appendChild(scriptElement2);
scriptElement2.parentNode.removeChild(scriptElement2);

assert.strictEqual(window.testPassed, true, 'testPassed set to true, only first script has been aborted');
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('abort String.fromCharCode, inline script', (assert) => {
const property = 'String.fromCharCode';
const stackMatch = 'inlineScript';
Expand All @@ -330,6 +395,46 @@ test('abort String.fromCharCode, inline script', (assert) => {
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('abort String.fromCodePoint, inline script line number regexp', (assert) => {
const property = 'String.fromCodePoint';
const stackMatch = '/inlineScript:\\d/';
const scriptletArgs = [property, stackMatch];
runScriptlet(name, scriptletArgs);
assert.throws(
() => String.fromCodePoint(65),
/ReferenceError/,
'Reference error thrown when trying to access property String.fromCodePoint',
);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('abort JSON.parse, inline script line number regexp, two scripts abort only second', (assert) => {
const property = 'JSON.parse';
const stackMatch = '/inlineScript:33/';
const scriptletArgs = [property, stackMatch];
runScriptlet(name, scriptletArgs);

let obj = {};

// This should not be aborted
try {
obj = JSON.parse('{"test":true}');
} catch (error) {
/* empty */
}

assert.throws(
() => {
const objString = '{}';
JSON.parse(objString);
},
/ReferenceError/,
'Reference error thrown when trying to access property JSON.parse',
);
assert.strictEqual(obj.test, true, 'obj.test is true');
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('do NOT abort Math.round, test for injected script', (assert) => {
const property = 'Math.round';
const stackMatch = 'injectedScript';
Expand Down

0 comments on commit 5eac065

Please sign in to comment.