Skip to content

Commit e0410fb

Browse files
committed
AG-30395 Add new scriptlet — 'trusted-replace-argument'. #405
Squashed commit of the following: commit a320f97 Merge: 102f601 f10b5b9 Author: Adam Wróblewski <[email protected]> Date: Fri Aug 1 10:04:39 2025 +0200 Merge branch 'master' into feature/AG-30395 commit 102f601 Merge: 1e1ea03 1c85645 Author: Adam Wróblewski <[email protected]> Date: Mon Jul 28 15:58:43 2025 +0200 Merge branch 'master' into feature/AG-30395 commit 1e1ea03 Author: Adam Wróblewski <[email protected]> Date: Thu Jul 17 10:49:06 2025 +0200 Add tests for edge cases Handle empty and invalid regex in extractRegexAndReplacement commit 63b221a Author: Adam Wróblewski <[email protected]> Date: Fri Jul 11 15:17:40 2025 +0200 Update documentation commit c74c178 Author: Adam Wróblewski <[email protected]> Date: Fri Jul 11 10:58:34 2025 +0200 Update documentation commit b12c29b Author: Adam Wróblewski <[email protected]> Date: Fri Jul 11 10:42:40 2025 +0200 Update `createFormattedMessage` JSDoc commit 36a1e50 Author: Adam Wróblewski <[email protected]> Date: Fri Jul 11 10:35:35 2025 +0200 Update documentation commit 65afb3e Author: Adam Wróblewski <[email protected]> Date: Wed Jul 9 13:02:20 2025 +0200 Update changelog commit a2e70a8 Merge: 9188274 bfcf681 Author: Adam Wróblewski <[email protected]> Date: Wed Jul 9 12:58:12 2025 +0200 Merge branch 'master' into feature/AG-30395 commit 9188274 Author: Adam Wróblewski <[email protected]> Date: Wed Jul 9 12:56:01 2025 +0200 Add support for `json:` in `argumentValue` commit a7cbc89 Author: Adam Wróblewski <[email protected]> Date: Tue Jul 8 14:23:06 2025 +0200 Move function to helpers and add tests commit 0431aec Author: Adam Wróblewski <[email protected]> Date: Fri Jul 4 14:05:16 2025 +0200 Add trusted-replace-argument scriptlet
1 parent f10b5b9 commit e0410fb

File tree

8 files changed

+1011
-1
lines changed

8 files changed

+1011
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
1212

1313
## [Unreleased]
1414

15+
### Added
16+
17+
- `trusted-replace-argument` scriptlet [#405].
18+
1519
### Fixed
1620

1721
- Incorrectly escaped quotes in `trusted-replace-node-text` scriptlet [#517].
1822
- `TrustedScriptURL` in `prevent-element-src-loading` scriptlet [#514].
1923

2024
[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v2.2.8...HEAD
25+
[#405]: https://github.com/AdguardTeam/Scriptlets/issues/405
2126
[#514]: https://github.com/AdguardTeam/Scriptlets/issues/514
2227
[#517]: https://github.com/AdguardTeam/Scriptlets/issues/517
2328

src/helpers/string-utils.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,92 @@ export function inferValue(value: string): unknown {
470470

471471
throw new TypeError(errorMessage);
472472
}
473+
474+
/**
475+
* Parses a string in the format 'replace:/regex/replacement/' and extracts the regex and replacement parts.
476+
*
477+
* @param str - The argument value string to parse, expected to start with 'replace:' and be in the
478+
* format 'replace:/regex/replacement/'.
479+
* To perform a global replacement, append the 'g' flag at the end, e.g. 'replace:/foo/bar/g'.
480+
* The 'g' flag will be included in the resulting RegExp.
481+
* @returns An object with the RegExp or string for the regex part and the replacement string,
482+
* or undefined if the format is invalid.
483+
*/
484+
export const extractRegexAndReplacement = (
485+
str: string,
486+
): { regexPart: RegExp | string; replacementPart: string } | undefined => {
487+
if (!str) {
488+
return undefined;
489+
}
490+
491+
const SLASH = '/';
492+
const REPLACE_MARKER = 'replace:';
493+
494+
let regexWithReplacement = str.slice(REPLACE_MARKER.length);
495+
let regexFlags = '';
496+
497+
// Support for /g flag at the end
498+
if (regexWithReplacement.endsWith('/g')) {
499+
regexWithReplacement = regexWithReplacement.slice(0, -1);
500+
regexFlags = 'g';
501+
}
502+
503+
// Must start and end with a slash
504+
if (!regexWithReplacement.startsWith('/') || !regexWithReplacement.endsWith('/')) {
505+
return undefined;
506+
}
507+
508+
// Remove the leading and trailing slashes
509+
const content = regexWithReplacement.slice(1, -1);
510+
511+
// Find the delimiter slash that separates regex from replacement
512+
// We need to find the first unescaped slash
513+
let delimiterIndex = -1;
514+
for (let i = 0; i < content.length; i += 1) {
515+
if (content[i] === '/') {
516+
// Check if this slash is escaped by looking at preceding backslashes
517+
let slashIsEscaped = false;
518+
let backslashIndex = i - 1;
519+
while (backslashIndex >= 0 && content[backslashIndex] === '\\') {
520+
slashIsEscaped = !slashIsEscaped;
521+
backslashIndex -= 1;
522+
}
523+
// If not escaped, this is our delimiter
524+
if (!slashIsEscaped) {
525+
delimiterIndex = i;
526+
break;
527+
}
528+
}
529+
}
530+
531+
if (delimiterIndex === -1) {
532+
return undefined;
533+
}
534+
535+
// Add slashes at the beginning and end of the regex part
536+
// and add the flag if it was found
537+
const regex = `${SLASH}${content.slice(0, delimiterIndex)}${SLASH}${regexFlags}`;
538+
const replacement = content.slice(delimiterIndex + 1);
539+
540+
// If the regex part is empty, return undefined
541+
if (!regex || regex === '//') {
542+
return undefined;
543+
}
544+
545+
let replaceRegexValue;
546+
try {
547+
replaceRegexValue = toRegExp(regex);
548+
} catch (error) {
549+
return undefined;
550+
}
551+
if (!replaceRegexValue) {
552+
return undefined;
553+
}
554+
555+
const objReplacementWithRegex = {
556+
regexPart: replaceRegexValue,
557+
replacementPart: replacement,
558+
};
559+
560+
return objReplacementWithRegex;
561+
};

src/scriptlets/scriptlets-list.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export { jsonPruneXhrResponse } from './json-prune-xhr-response';
7474
export { trustedDispatchEvent } from './trusted-dispatch-event';
7575
export { trustedReplaceOutboundText } from './trusted-replace-outbound-text';
7676
export { preventCanvas } from './prevent-canvas';
77+
export { trustedReplaceArgument } from './trusted-replace-argument';
7778
// redirects as scriptlets
7879
// https://github.com/AdguardTeam/Scriptlets/issues/300
7980
export { AmazonApstag } from './amazon-apstag';

src/scriptlets/scriptlets-names-list.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export { jsonPruneXhrResponseNames } from './json-prune-xhr-response';
7474
export { trustedDispatchEventNames } from './trusted-dispatch-event';
7575
export { trustedReplaceOutboundTextNames } from './trusted-replace-outbound-text';
7676
export { preventCanvasNames } from './prevent-canvas';
77+
export { trustedReplaceArgumentNames } from './trusted-replace-argument';
7778
// redirects as scriptlets
7879
// https://github.com/AdguardTeam/Scriptlets/issues/300
7980
export { AmazonApstagNames } from './amazon-apstag';

0 commit comments

Comments
 (0)