Skip to content

Commit

Permalink
AG-21081 Improve 'trusted-replace-fetch-response'/'trusted-replace-xh…
Browse files Browse the repository at this point in the history
…r-response' — add ability to replace all matched content. #303

Squashed commit of the following:

commit ea24d90
Merge: 1d3046c 10e4c54
Author: Adam Wróblewski <[email protected]>
Date:   Fri Jul 7 13:16:11 2023 +0200

    Merge branch 'master' into fix/AG-21081

commit 1d3046c
Merge: 59be739 d6713c7
Author: Adam Wróblewski <[email protected]>
Date:   Fri Jul 7 13:06:30 2023 +0200

    Merge branch 'master' into fix/AG-21081

commit 59be739
Author: Slava Leleka <[email protected]>
Date:   Fri Jul 7 12:28:00 2023 +0300

    Update changelog

commit 71c74b5
Merge: e288834 c647f9a
Author: Adam Wróblewski <[email protected]>
Date:   Fri Jul 7 08:55:44 2023 +0200

    Merge branch 'master' into fix/AG-21081

commit e288834
Merge: 18cfaca 2b35bd5
Author: Adam Wróblewski <[email protected]>
Date:   Thu Jun 29 14:19:55 2023 +0200

    Merge branch 'master' into fix/AG-21081

commit 18cfaca
Merge: 9395fa5 c6b7eda
Author: Adam Wróblewski <[email protected]>
Date:   Thu Jun 29 12:27:45 2023 +0200

    Merge branch 'master' into fix/AG-21081

commit 9395fa5
Author: Adam Wróblewski <[email protected]>
Date:   Thu Jun 29 10:23:20 2023 +0200

    Rename hasRegExpFlags to getRegExpFlags
    Update JSDoc
    Get rid of unnecessary flagsStr

commit 8f02390
Author: Adam Wróblewski <[email protected]>
Date:   Wed Jun 28 11:03:32 2023 +0200

    Do not return boolean in hasRegExpFlags
    Use isValidRegExpFlag instead of match method

commit b26f700
Author: Adam Wróblewski <[email protected]>
Date:   Tue Jun 27 15:14:17 2023 +0200

    Update JSDoc
    Check only valid flags

commit 23c7526
Merge: c5114da 97e7764
Author: Adam Wróblewski <[email protected]>
Date:   Tue Jun 27 11:52:50 2023 +0200

    Merge branch 'master' into fix/AG-21081

commit c5114da
Author: Adam Wróblewski <[email protected]>
Date:   Tue Jun 27 10:08:04 2023 +0200

    Add JSDoc comments
    Update toRegExp description
    Rename regExpHasFlags to hasRegExpFlags
    Refactor the statement

commit 0e46a06
Author: Slava Leleka <[email protected]>
Date:   Mon Jun 26 11:53:50 2023 +0300

    Update changelog

commit 2364099
Author: Adam Wróblewski <[email protected]>
Date:   Mon Jun 26 09:09:09 2023 +0200

    Add ability to use regexp flags in toRegExp helper
  • Loading branch information
AdamWr committed Jul 7, 2023
1 parent 10e4c54 commit 9ca4a78
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- ability to use flags in regular expression scriptlet parameters
[#303](https://github.com/AdguardTeam/Scriptlets/issues/303)

### Fixed

- issue with printing unnecessary logs to the console in `log-addEventListener` scriptlet
Expand Down
56 changes: 52 additions & 4 deletions src/helpers/string-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,69 @@ export const replaceAll = (
export const escapeRegExp = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

/**
* Converts string to the regexp
* Converts string to the regexp,
* if string contains valid regexp flags it will be converted to regexp with flags
* TODO think about nested dependencies, but be careful with dependency loops
*
* @param input literal string or regexp pattern; defaults to '' (empty string)
* @returns regular expression; defaults to /.?/
* @throws Throw an error for invalid regex pattern
*/
export const toRegExp = (input: RawStrPattern = ''): RegExp => {
const DEFAULT_VALUE = '.?';
const FORWARD_SLASH = '/';
if (input === '') {
return new RegExp(DEFAULT_VALUE);
}
if (input[0] === FORWARD_SLASH && input[input.length - 1] === FORWARD_SLASH) {
return new RegExp(input.slice(1, -1));

const delimiterIndex = input.lastIndexOf(FORWARD_SLASH);
const flagsPart = input.substring(delimiterIndex + 1);
const regExpPart = input.substring(0, delimiterIndex + 1);

/**
* Checks whether the string is a valid regexp flag
*
* @param flag string
* @returns True if regexp flag is valid, otherwise false.
*/
const isValidRegExpFlag = (flag: string): boolean => {
if (!flag) {
return false;
}
try {
// eslint-disable-next-line no-new
new RegExp('', flag);
return true;
} catch (ex) {
return false;
}
};

/**
* Checks whether the text string contains valid regexp flags,
* and returns `flagsStr` if valid, otherwise empty string.
*
* @param regExpStr string
* @param flagsStr string
* @returns `flagsStr` if it is valid, otherwise empty string.
*/
const getRegExpFlags = (regExpStr: string, flagsStr: string): string => {
if (
regExpStr.startsWith(FORWARD_SLASH)
&& regExpStr.endsWith(FORWARD_SLASH)
// Not a correct regex if ends with '\\/'
&& !regExpStr.endsWith('\\/')
&& isValidRegExpFlag(flagsStr)
) {
return flagsStr;
}
return '';
};

const flags = getRegExpFlags(regExpPart, flagsPart);

if ((input.startsWith(FORWARD_SLASH) && input.endsWith(FORWARD_SLASH)) || flags) {
const regExpInput = flags ? regExpPart : input;
return new RegExp(regExpInput.slice(1, -1), flags);
}

const escaped = input
Expand Down
68 changes: 67 additions & 1 deletion tests/helpers/string-utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toRegExp, inferValue } from '../../src/helpers';

describe('Test inferValue', () => {
describe('Test string utils', () => {
describe('Test toRegExp for valid inputs', () => {
const DEFAULT_VALUE = '.?';
const defaultRegexp = new RegExp(DEFAULT_VALUE);
Expand Down Expand Up @@ -28,6 +28,72 @@ describe('Test inferValue', () => {
});
});

describe('Test toRegExp with flag', () => {
const DEFAULT_VALUE = '.?';
const defaultRegexp = new RegExp(DEFAULT_VALUE);

const testCases = [
{
actual: '/qwerty/g',
expected: /qwerty/g,
},
{
actual: '/[a-z]{1,9}/gm',
expected: /[a-z]{1,9}/gm,
},
{
actual: '',
expected: defaultRegexp,
},
{
actual: undefined,
expected: defaultRegexp,
},
];
test.each(testCases)('"$actual"', ({ actual, expected }) => {
expect(toRegExp(actual)).toStrictEqual(expected);
});
});

describe('Test toRegExp with not a valid flag', () => {
const DEFAULT_VALUE = '.?';
const defaultRegexp = new RegExp(DEFAULT_VALUE);

const testCases = [
{
actual: 'g',
expected: /g/,
},
{
actual: 'qwerty/g',
expected: /qwerty\/g/,
},
{
actual: '/asdf/gmtest',
expected: /\/asdf\/gmtest/,
},
{
actual: '/qwert/ggm',
expected: /\/qwert\/ggm/,
},
{
actual: '/test\\/g',
expected: /\/test\\\/g/,
},
{
actual: '',
expected: defaultRegexp,
},
{
actual: undefined,
expected: defaultRegexp,
},
];
test.each(testCases)('"$actual"', ({ actual, expected }) => {
expect(toRegExp(actual)).toStrictEqual(expected);
});
});

describe('Test toRegExp for invalid inputs', () => {
const invalidRegexpPatterns = [
'/\\/',
Expand Down
24 changes: 24 additions & 0 deletions tests/scriptlets/trusted-replace-fetch-response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ if (!isSupported) {
done();
});

test('Match all requests, replace by regex with flag', async (assert) => {
const INPUT_JSON_PATH = `${FETCH_OBJECTS_PATH}/test03.json`;
const TEST_METHOD = 'GET';
const init = {
method: TEST_METHOD,
};

const done = assert.async();

const PATTERN = '/inner/g';
const REPLACEMENT = 'qwerty';
runScriptlet(name, [PATTERN, REPLACEMENT]);

const response = await fetch(INPUT_JSON_PATH, init);
const actualJson = await response.json();

const textContent = JSON.stringify(actualJson);

assert.strictEqual(window.hit, 'FIRED', 'hit function fired');
assert.notOk(textContent.includes(PATTERN), 'Pattern is removed');
assert.ok(textContent.includes(REPLACEMENT), 'New content is set');
done();
});

test('Match all requests, replace multiline content', async (assert) => {
const INPUT_JSON_PATH = `${FETCH_OBJECTS_PATH}/empty.html`;
const TEST_METHOD = 'GET';
Expand Down
28 changes: 28 additions & 0 deletions tests/scriptlets/trusted-replace-xhr-response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,34 @@ if (isSupported) {
xhr.send();
});

test('Matched, regex pattern with flag', async (assert) => {
const METHOD = 'GET';
const URL = `${FETCH_OBJECTS_PATH}/test03.json`;

const PATTERN = /inner/g;
const REPLACEMENT = 'qwerty';
const MATCH_DATA = [`${PATTERN}`, REPLACEMENT, `${URL} method:${METHOD}`];

runScriptlet(name, MATCH_DATA);

const done = assert.async();

const xhr = new XMLHttpRequest();
xhr.open(METHOD, URL);
xhr.onload = () => {
assert.strictEqual(xhr.readyState, 4, 'Response done');
assert.ok(xhr.response.includes(REPLACEMENT) && !PATTERN.test(xhr.response), 'Response has been modified');
assert.ok(
xhr.responseText.includes(REPLACEMENT) && !PATTERN.test(xhr.responseText),
'Response text has been modified',
);

assert.strictEqual(window.hit, 'FIRED', 'hit function fired');
done();
};
xhr.send();
});

test('Matched, replaces multiline content', async (assert) => {
const METHOD = 'GET';
const URL = `${FETCH_OBJECTS_PATH}/empty.html`;
Expand Down

0 comments on commit 9ca4a78

Please sign in to comment.