Skip to content

Commit

Permalink
Merge branch 'master' into feature/AG-23175
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamWr committed Feb 20, 2024
2 parents db038c2 + b273341 commit d77d01f
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 126 deletions.
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic

### Added

- `href-sanitizer` scriptlet [#327](https://github.com/AdguardTeam/Scriptlets/issues/327)
- `href-sanitizer` scriptlet [#327]

### Changed

- Validation of scriptlet rules with no name and args for multiple scriptlet exception rules [#377]

[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v1.10.1...HEAD
[#377]: https://github.com/AdguardTeam/Scriptlets/issues/377
[#327]: https://github.com/AdguardTeam/Scriptlets/issues/327

## [v1.10.1] - 2024-02-12

Expand Down Expand Up @@ -345,7 +353,6 @@ prevent inline `onerror` and match `link` tag [#276](https://github.com/AdguardT
- `metrika-yandex-tag` [#254](https://github.com/AdguardTeam/Scriptlets/issues/254)
- `googlesyndication-adsbygoogle` [#252](https://github.com/AdguardTeam/Scriptlets/issues/252)

[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v1.10.1...HEAD
[v1.10.1]: https://github.com/AdguardTeam/Scriptlets/compare/v1.9.105...v1.10.1
[v1.9.105]: https://github.com/AdguardTeam/Scriptlets/compare/v1.9.101...v1.9.105
[v1.9.101]: https://github.com/AdguardTeam/Scriptlets/compare/v1.9.96...v1.9.101
Expand Down
64 changes: 53 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ AdGuard's Scriptlets and Redirect resources library which provides extended capa

- [Scriptlets](#scriptlets)
- [Syntax](#scriptlet-syntax)
- [Blocking rules](#scriptlet-syntax--blocking)
- [Exception rules](#scriptlet-syntax--exceptions)
- [Available scriptlets](./wiki/about-scriptlets.md#scriptlets)
- [Scriptlets compatibility table](./wiki/compatibility-table.md#scriptlets)
- [Trusted scriptlets](#trusted-scriptlets)
Expand Down Expand Up @@ -65,12 +67,15 @@ Please note, that in order to achieve cross-blocker compatibility, we also suppo

### <a name="scriptlet-syntax"></a> Syntax

#### <a name="scriptlet-syntax--blocking"></a> Blocking rules

```text
rule = [domains] "#%#//scriptlet(" scriptletName arguments ")"
[domains]#%#//scriptlet(name[, arguments])
```

- `scriptletName` (mandatory) is a name of the scriptlet from AdGuard's scriptlets library
- `arguments` (optional) a list of `String` arguments (no other types of arguments are supported)
- `domains` — optional, a list of domains where the rule should be applied;
- `name` — required, a name of the scriptlet from AdGuard's scriptlets library;
- `arguments` — optional, a list of `String` arguments (no other types of arguments are supported).

> **Remarks**
>
Expand All @@ -86,20 +91,57 @@ rule = [domains] "#%#//scriptlet(" scriptletName arguments ")"
> - `'prop['nested']'`
> - `"prop["nested"]"`
>
> - You can use either single or double quotes for the scriptlet name and arguments.
> - Scriptlet `name` and each of the `arguments` should be wrapped in quotes.
> You can use either single or double quotes for the scriptlet name and arguments.
> Single quote is recommended but not for cases when its usage makes readability worse,
> e.g. `".css('display','block');"` is more preferred then `'.css(\'display\',\'block\');'`.
#### <a name="scriptlet-syntax--exceptions"></a> Exception rules

#### Example

```adblock
example.org#%#//scriptlet('abort-on-property-read', 'alert')
example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]')
```text
[domains]#@%#//scriptlet([name])
```

This rule applies the `abort-on-property-read` scriptlet on all pages of `example.org` and its subdomains,
and passes one argument to it (`alert`).
- `domains` — optional, a list of domains where the rule should be applied;
- `name` — optional, a name of the scriptlet to except from the applying.
If not set, all scriptlets will not be applied.

#### Examples

1. Apply the `abort-on-property-read` scriptlet on all pages of `example.org` and its subdomains,
and passes one argument to it (`alert`):

```adblock
example.org#%#//scriptlet('abort-on-property-read', 'alert')
```

1. Remove the `branding` class from all `div[class^="inner"]` elements
on all pages of `example.org` and its subdomains:

```adblock
example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]')
```

1. Apply `set-constant` and `set-cookie` on any webpage,
but because of specific scriptlet exception rule
only `set-constant` scriptlet will be applied on `example.org` and its subdomains:

```adblock
#%#//scriptlet('set-constant', 'adList', 'emptyArr')
#%#//scriptlet('set-cookie', 'accepted', 'true')
example.org#@%#//scriptlet('set-cookie')
```

1. Apply `adjust-setInterval` on any webpage,
and `set-local-storage-item` on all pages of `example.com` and its subdomains,
but there is also multiple scriptlet exception rule,
so no scriptlet rules will be applied on `example.com` and its subdomains:

```adblock
#%#//scriptlet('adjust-setInterval', 'count', '*', '0.001')
example.com#%#//scriptlet('set-local-storage-item', 'ALLOW_COOKIES', 'false')
example.com#@%#//scriptlet()
```

- **[Scriptlets list](./wiki/about-scriptlets.md#scriptlets)**
- **[Scriptlets compatibility table](./wiki/compatibility-table.md#scriptlets)**
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.10.1",
"version": "1.10.2",
"description": "AdGuard's JavaScript library of Scriptlets and Redirect resources",
"scripts": {
"build": "babel-node -x .js,.ts scripts/build.js",
Expand Down
56 changes: 42 additions & 14 deletions src/helpers/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type AdgScriptletObject = {
/**
* AdGuard scriptlet rule
*/
const ADGUARD_SCRIPTLET_MASK_REG = /#@?%#\/\/scriptlet\(.+\)/;
const ADGUARD_SCRIPTLET_MASK_REG = /#@?%#\/\/scriptlet\(.*\)/;
// eslint-disable-next-line no-template-curly-in-string
const ADGUARD_SCRIPTLET_TEMPLATE = '${domains}#%#//scriptlet(${args})';
// eslint-disable-next-line no-template-curly-in-string
Expand Down Expand Up @@ -221,9 +221,24 @@ export const convertUboScriptletToAdg = (rule: string): string[] => {
}
const argsStr = getStringInBraces(rule);
let parsedArgs = splitArgs(argsStr);
const scriptletName = parsedArgs[0].includes(UBO_SCRIPTLET_JS_ENDING)
? `ubo-${parsedArgs[0]}`
: `ubo-${parsedArgs[0]}${UBO_SCRIPTLET_JS_ENDING}`;
let scriptletName = '';
const possibleName = parsedArgs[0];
if (!possibleName) {
scriptletName = '';
const adgRule = replacePlaceholders(
template,
{ domains, args: scriptletName },
);
// empty string can be a valid scriptlet name in scriptlet exception rule
// https://github.com/AdguardTeam/Scriptlets/issues/377
return [adgRule];
}

if (possibleName.includes(UBO_SCRIPTLET_JS_ENDING)) {
scriptletName = `ubo-${parsedArgs[0]}`;
} else {
scriptletName = `ubo-${parsedArgs[0]}${UBO_SCRIPTLET_JS_ENDING}`;
}

if (REMOVE_ATTR_ALIASES.includes(scriptletName) || REMOVE_CLASS_ALIASES.includes(scriptletName)) {
parsedArgs = validateRemoveAttrClassArgs(parsedArgs);
Expand All @@ -247,6 +262,7 @@ export const convertUboScriptletToAdg = (rule: string): string[] => {
})
.map((arg) => wrapInSingleQuotes(arg))
.join(`${COMMA_SEPARATOR} `);

const adgRule = replacePlaceholders(
template,
{ domains, args },
Expand Down Expand Up @@ -387,6 +403,24 @@ export const convertAdgScriptletToUbo = (rule: string): string | undefined => {
if (validator.isAdgScriptletRule(rule)) {
const { name: parsedName, args: parsedParams } = parseRule(rule);

const matchResult = rule.match(ADGUARD_SCRIPTLET_MASK_REG);
const mask = Array.isArray(matchResult) ? matchResult[0] : null;
let template;
if (mask?.includes('@')) {
template = UBO_SCRIPTLET_EXCEPTION_TEMPLATE;
} else {
template = UBO_SCRIPTLET_TEMPLATE;
}
const domains = getBeforeRegExp(rule, ADGUARD_SCRIPTLET_MASK_REG);

if (!parsedName) {
const uboRule = replacePlaceholders(
template,
{ domains, args: '' },
);
return uboRule;
}

let preparedParams;
if (parsedName === ADG_SET_CONSTANT_NAME
// https://github.com/AdguardTeam/FiltersCompiler/issues/102
Expand Down Expand Up @@ -437,15 +471,6 @@ export const convertAdgScriptletToUbo = (rule: string): string | undefined => {
.find((alias) => alias.includes(UBO_ALIAS_NAME_MARKER));

if (uboAlias) {
const matchResult = rule.match(ADGUARD_SCRIPTLET_MASK_REG);
const mask = Array.isArray(matchResult) ? matchResult[0] : null;
let template;
if (mask?.includes('@')) {
template = UBO_SCRIPTLET_EXCEPTION_TEMPLATE;
} else {
template = UBO_SCRIPTLET_TEMPLATE;
}
const domains = getBeforeRegExp(rule, ADGUARD_SCRIPTLET_MASK_REG);
const uboName = uboAlias
.replace(UBO_ALIAS_NAME_MARKER, '')
// '.js' in the Ubo scriptlet name can be omitted
Expand Down Expand Up @@ -476,6 +501,9 @@ export const convertAdgScriptletToUbo = (rule: string): string | undefined => {
* @returns Scriptlet name or null.
*/
const getAdgScriptletName = (rule: string): string | null => {
if (rule.includes(`${ADG_SCRIPTLET_MASK}()`)) {
return '';
}
// get substring after '#//scriptlet('
let buffer = substringAfter(rule, `${ADG_SCRIPTLET_MASK}(`);
if (!buffer) {
Expand Down Expand Up @@ -524,7 +552,7 @@ export const isValidScriptletRule = (ruleText: string): boolean => {
// if at least one of them is not valid - whole `ruleText` is not valid too
const isValid = rulesArray.every((rule) => {
const name = getAdgScriptletName(rule);
return name && validator.isValidScriptletName(name);
return validator.isValidScriptletName(name);
});

return isValid;
Expand Down
18 changes: 16 additions & 2 deletions src/helpers/parse-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ interface TransitionHelper {
}

type ParsedRule = {
name: string;
/**
* An empty string "" is used for unnamed scriptlets, e.g., "#@%#//scriptlet()". It is utilized for allowlisting
* scriptlets.
* Null is used in cases where the name could not be parsed.
*/
name: string | null;
args: string[];
};

Expand Down Expand Up @@ -111,6 +116,15 @@ const substringAfter = (str: string, separator: string) => {
*/
export const parseRule = (ruleText: string): ParsedRule => {
ruleText = substringAfter(ruleText, ADG_SCRIPTLET_MASK);

// in the case of allowlist scriptlet, the rule name is empty string
if (ruleText === '()') {
return {
name: '',
args: [],
};
}

/**
* Transition function: the current index position in start, end or between params
*
Expand Down Expand Up @@ -200,7 +214,7 @@ export const parseRule = (ruleText: string): ParsedRule => {

const args = saver.getAll();
return {
name: args[0],
name: args[0] === '' ? null : args[0],
args: args.slice(1),
};
};
16 changes: 9 additions & 7 deletions src/helpers/string-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,18 +336,20 @@ export const objectToString = (obj: ArbitraryObject): string => {
* @returns type's string representation
*/
export const convertTypeToString = (value: unknown): string => {
let output;
if (typeof value === 'undefined') {
return 'undefined';
}
if (typeof value === 'object') {
output = 'undefined';
} else if (typeof value === 'object') {
if (value === null) {
return 'null';
output = 'null';
} else {
output = objectToString(value as Record<string, unknown>);
}

return objectToString(value as ArbitraryObject);
} else {
output = String(value);
}

return (value as Pick<Object, 'toString'>).toString();
return output;
};

/**
Expand Down
10 changes: 8 additions & 2 deletions src/helpers/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,15 @@ const scriptletNameValidationCache = new Map();
* Uses cache for better performance.
*
* @param name Scriptlet name.
* @returns true if scriptlet name is a valid one.
* @returns True if scriptlet name is a valid one or an empty string,
* otherwise false.
*/
const isValidScriptletName = (name: string): boolean => {
const isValidScriptletName = (name: string | null): boolean => {
// empty name is used for allowlist scriptlets. e.g.
// - '#@%#//scriptlet()'
if (name === '') {
return true;
}
if (!name) {
return false;
}
Expand Down
Loading

0 comments on commit d77d01f

Please sign in to comment.