Skip to content

Commit

Permalink
Merge branch 'release/v1.7' into fix/AG-16980
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-atr committed Nov 16, 2022
2 parents 3cce20b + ef618b3 commit cb86ba7
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 14 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ AdGuard's Scriptlets and Redirect resources library which provides extended capa
* [Syntax](#scriptlet-syntax)
* [Available scriptlets](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#scriptlets)
* [Scriptlets compatibility table](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/compatibility-table.md#scriptlets)
* [Trusted scriptlets](#trusted-scriptlets)
* [Restriction](#trusted-scriptlets-restriction)
* [Available trusted scriptlets](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#trusted-scriptlets)
* [Redirect resources](#redirect-resources)
* [Syntax](#redirect-syntax)
* [Available redirect resources](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-redirects.md#redirect-resources)
Expand Down Expand Up @@ -49,12 +52,29 @@ example.org#%#//scriptlet('abort-on-property-read', 'alert')
example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]')
```

This rule applies the `abort-on-property-read` scriptlet on all pages of `example.org` and its subdomains, and passes one orgument to it (`alert`).
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`).

* **[Scriptlets list](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#scriptlets)**
* **[Scriptlets compatibility table](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/compatibility-table.md#scriptlets)**


### <a id="trusted-scriptlets"></a> Trusted scriptlets

Trusted scriptlets are scriptlets with extended functionality. Their names are prefixed with `trusted-`, e.g `trusted-click-element`, to be easily distinguished from common scriptlets.

#### <a id="trusted-scriptlets-restriction"></a> Restriction

Trusted scriptlets application must be restricted due to dangerous nature of their capabilities.
Allowed sources of trusted scriptlets are:
* filters created by AdGuard Team,
* custom filters which were installed as `trusted`,
* user rules.

> Trusted scriptlets has no compatibility table as they are not compatible with any other blocker.
**[Trusted scriptlets list](https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#trusted-scriptlets)**


## Redirect resources

AdGuard is able to redirect web requests to a local "resource".
Expand Down
44 changes: 40 additions & 4 deletions scripts/build-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ const { getDataFromFiles } = require('./helpers');
const {
WIKI_DIR_PATH,
scriptletsFilenames,
trustedScriptletsFilenames,
redirectsFilenames,
SCRIPTLETS_SRC_RELATIVE_DIR_PATH,
REDIRECTS_SRC_RELATIVE_DIR_PATH,
SCRIPTLET_TYPE,
TRUSTED_SCRIPTLET_TYPE,
REDIRECT_TYPE,
} = require('./constants');

const STATIC_REDIRECTS_FILENAME = 'static-redirects.yml';
Expand All @@ -28,10 +32,16 @@ const blockingRedirectsPath = path.resolve(
);

const ABOUT_SCRIPTLETS_FILENAME = 'about-scriptlets.md';
const ABOUT_TRUSTED_SCRIPTLETS_FILENAME = 'about-trusted-scriptlets.md';
const ABOUT_REDIRECTS_FILENAME = 'about-redirects.md';

const aboutScriptletsPath = path.resolve(__dirname, WIKI_DIR_PATH, ABOUT_SCRIPTLETS_FILENAME);
const aboutRedirectsPath = path.resolve(__dirname, WIKI_DIR_PATH, ABOUT_REDIRECTS_FILENAME);
const aboutTrustedScriptletsPath = path.resolve(
__dirname,
WIKI_DIR_PATH,
ABOUT_TRUSTED_SCRIPTLETS_FILENAME,
);

/**
* Collects required comments from files and
Expand All @@ -42,17 +52,31 @@ const manageDataFromFiles = () => {
scriptletsFilenames,
SCRIPTLETS_SRC_RELATIVE_DIR_PATH,
);

const dataFromTrustedScriptletsFiles = getDataFromFiles(
trustedScriptletsFilenames,
SCRIPTLETS_SRC_RELATIVE_DIR_PATH,
);

const dataFromRedirectsFiles = getDataFromFiles(
redirectsFilenames,
REDIRECTS_SRC_RELATIVE_DIR_PATH,
);

const fullData = dataFromScriptletsFiles.concat(dataFromRedirectsFiles).flat(Infinity);
const fullData = dataFromScriptletsFiles
.concat(dataFromTrustedScriptletsFiles)
.concat(dataFromRedirectsFiles)
.flat(Infinity);

const scriptletsData = fullData.filter(({ type }) => type === 'scriptlet');
const redirectsData = fullData.filter(({ type }) => type === 'redirect');
const scriptletsData = fullData.filter(({ type }) => type === SCRIPTLET_TYPE);
const trustedScriptletsData = fullData.filter(({ type }) => type === TRUSTED_SCRIPTLET_TYPE);
const redirectsData = fullData.filter(({ type }) => type === REDIRECT_TYPE);

return { scriptletsData, redirectsData };
return {
scriptletsData,
trustedScriptletsData,
redirectsData,
};
};

/**
Expand Down Expand Up @@ -171,6 +195,9 @@ const buildWikiAboutPages = () => {
try {
const filesData = manageDataFromFiles();
const scriptletsMarkdownData = getMarkdownData(filesData.scriptletsData);

const trustedScriptletsMarkdownData = getMarkdownData(filesData.trustedScriptletsData);

const redirectsMarkdownData = getMarkdownData(filesData.redirectsData);
const staticRedirectsMarkdownData = getMarkdownDataForStaticRedirects();
const blockingRedirectsMarkdownData = getMarkdownDataForBlockingRedirects();
Expand All @@ -183,6 +210,15 @@ ${scriptletsMarkdownData.body}`;
scriptletsPageContent,
);

// eslint-disable-next-line max-len
const trustedScriptletsPageContent = `## <a id="trusted-scriptlets"></a> Available Trusted Scriptlets
${trustedScriptletsMarkdownData.list}* * *
${trustedScriptletsMarkdownData.body}`;
fs.writeFileSync(
path.resolve(__dirname, aboutTrustedScriptletsPath),
trustedScriptletsPageContent,
);

/* eslint-disable max-len */
const redirectsPageContent = `## <a id="redirect-resources"></a> Available Redirect resources
${staticRedirectsMarkdownData.list}${redirectsMarkdownData.list}${blockingRedirectsMarkdownData.list}* * *
Expand Down
12 changes: 11 additions & 1 deletion scripts/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const SRC_REDIRECTS_SUB_DIR = 'redirects';
const SCRIPTLETS_SRC_RELATIVE_DIR_PATH = `${SRC_RELATIVE_DIR}/${SRC_SCRIPTLETS_SUB_DIR}`;
const REDIRECTS_SRC_RELATIVE_DIR_PATH = `${SRC_RELATIVE_DIR}/${SRC_REDIRECTS_SUB_DIR}`;

const TRUSTED_SCRIPTLETS_PREFIX = 'trusted-';

// files which are not scriptlets in the source directory
const NON_SCRIPTLETS_FILES = [
'index.js',
Expand All @@ -30,8 +32,15 @@ const NON_SCRIPTLETS_FILES = [
'scriptlets-wrapper.js',
'scriptlets-umd-wrapper.js',
];

const isUtilityFileName = (filename) => NON_SCRIPTLETS_FILES.includes(filename);
const isTrustedScriptletsFilename = (filename) => filename.startsWith(TRUSTED_SCRIPTLETS_PREFIX);

const scriptletsFilenames = getFilesList(SCRIPTLETS_SRC_RELATIVE_DIR_PATH)
.filter((el) => !NON_SCRIPTLETS_FILES.includes(el));
.filter((el) => !isUtilityFileName(el) && !isTrustedScriptletsFilename(el));

const trustedScriptletsFilenames = getFilesList(SCRIPTLETS_SRC_RELATIVE_DIR_PATH)
.filter((el) => isTrustedScriptletsFilename(el));

// files which are not redirects in the source directory
const NON_REDIRECTS_FILES = [
Expand All @@ -49,5 +58,6 @@ module.exports = {
SCRIPTLETS_SRC_RELATIVE_DIR_PATH,
REDIRECTS_SRC_RELATIVE_DIR_PATH,
scriptletsFilenames,
trustedScriptletsFilenames,
redirectsFilenames,
};
14 changes: 11 additions & 3 deletions scripts/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ const path = require('path');
const fs = require('fs-extra');
const dox = require('dox');

const SCRIPTLET_TYPE = 'scriptlet';
const TRUSTED_SCRIPTLET_TYPE = 'trustedScriptlet';
const REDIRECT_TYPE = 'redirect';

/**
* Asynchronously writes data to a file, replacing the file if it already exists.
*
Expand Down Expand Up @@ -33,7 +37,7 @@ const getFilesList = (relativeDirPath) => {

/**
* Returns parsed tags data which we use to describe the sources:
* - `@scriptlet`/`@redirect` to describe the type and name of source;
* - `@scriptlet`/`trustedScriptlet`/`@redirect` to describe the type and name of source;
* - `@description` actual description for scriptlet or redirect.
* required comments from file.
* In one file might be comments describing scriptlet and redirect as well.
Expand All @@ -54,8 +58,9 @@ const getDescribingCommentTags = (filePath) => {
return false;
}
const [base] = tags;
return base?.type === 'scriptlet'
|| base?.type === 'redirect';
return base?.type === SCRIPTLET_TYPE
|| base?.type === TRUSTED_SCRIPTLET_TYPE
|| base?.type === REDIRECT_TYPE;
});

if (describingComment.length === 0) {
Expand Down Expand Up @@ -122,4 +127,7 @@ module.exports = {
writeFile,
getFilesList,
getDataFromFiles,
SCRIPTLET_TYPE,
TRUSTED_SCRIPTLET_TYPE,
REDIRECT_TYPE,
};
2 changes: 1 addition & 1 deletion src/scriptlets/trusted-click-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

/* eslint-disable max-len */
/**
* @scriptlet trusted-click-element
* @trustedScriptlet trusted-click-element
*
* @description
* Clicks selected elements in a strict sequence, ordered by selectors passed, and waiting for them to render in the DOM first.
Expand Down
2 changes: 1 addition & 1 deletion src/scriptlets/trusted-replace-fetch-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {

/* eslint-disable max-len */
/**
* @scriptlet trusted-replace-fetch-response
* @trustedScriptlet trusted-replace-fetch-response
*
* @description
* Replaces response text content of `fetch` requests if **all** given parameters match.
Expand Down
2 changes: 1 addition & 1 deletion src/scriptlets/trusted-replace-xhr-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {

/* eslint-disable max-len */
/**
* @scriptlet trusted-replace-xhr-response
* @trustedScriptlet trusted-replace-xhr-response
*
* @description
* Replaces response content of `xhr` requests if **all** given parameters match.
Expand Down
2 changes: 1 addition & 1 deletion src/scriptlets/trusted-set-cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {

/* eslint-disable max-len */
/**
* @scriptlet trusted-set-cookie
* @trustedScriptlet trusted-set-cookie
*
* @description
* Sets a cookie with arbitrary name and value, with optional path
Expand Down
2 changes: 1 addition & 1 deletion src/scriptlets/trusted-set-local-storage-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

/* eslint-disable max-len */
/**
* @scriptlet trusted-set-local-storage-item
* @trustedScriptlet trusted-set-local-storage-item
*
* @description
* Adds item with arbitrary key and value to localStorage object, or updates the value of the key if it already exists.
Expand Down
14 changes: 14 additions & 0 deletions tests/scriptlets/abort-current-inline-script.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ test('works with chained properties', (assert) => {
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('Works with an empty object in chain', (assert) => {
const scriptletArgs = ['window.aaa.bbb'];

window.onerror = onError(assert);
window.aaa = {};

runScriptlet(name, scriptletArgs);

window.aaa.bbb = 1;
addAndRemoveInlineScript('window.aaa.bbb = 4');

assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('should not work if chained properties are undefined', (assert) => {
window.onerror = onError(assert);
const chainProperty = 'a.b.c';
Expand Down
15 changes: 15 additions & 0 deletions tests/scriptlets/abort-on-property-read.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,18 @@ test('dot notation deferred defenition', (assert) => {
);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('Works with an empty object in chain', (assert) => {
window.aaa = {};
const scriptletArgs = [CHAIN_PROPERTY];
runScriptlet(name, scriptletArgs);

window.aaa.bbb = 'value';

assert.throws(
() => window.aaa.bbb,
/ReferenceError/,
`should throw Reference error when try to access property ${CHAIN_PROPERTY}`,
);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});
18 changes: 18 additions & 0 deletions tests/scriptlets/abort-on-property-write.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,21 @@ test('dot notation deferred defenition', (assert) => {
);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('Works with an empty object in chain', (assert) => {
const CHAIN_PROPERTY = 'window.aaa.bbb';
const scriptletArgs = [CHAIN_PROPERTY];
window.aaa = {};
runScriptlet(name, scriptletArgs);

window.aaa.bbb = 'value';

assert.throws(
() => {
window.aaa.bbb = 'new value';
},
/ReferenceError/,
`should throw Reference error when try to access property ${CHAIN_PROPERTY}`,
);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});
22 changes: 22 additions & 0 deletions tests/scriptlets/abort-on-stack-trace.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,28 @@ test('simple, matches stack', (assert) => {
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('simple, matches stack with an empty object in chain', (assert) => {
const PROPERTY = 'window.aaa.bbb';
window.aaa = {};
const scriptletArgs = [PROPERTY];
runScriptlet(name, scriptletArgs);

window.aaa.bbb = 'value';

assert.throws(
() => window.aaa.bbb,
/ReferenceError/,
`Reference error thrown when trying to access property ${PROPERTY}`,
);
assert.throws(
// eslint-disable-next-line no-return-assign
() => window.aaa.bbb = 'new value',
/ReferenceError/,
`Reference error thrown when trying to reassign property ${PROPERTY}`,
);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('simple, does NOT match stack', (assert) => {
window[PROPERTY] = 'value';
const noStackMatch = 'no_match.js';
Expand Down
14 changes: 14 additions & 0 deletions tests/scriptlets/debug-current-inline-script.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ test('works', (assert) => {
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('works with an empty object in chain', (assert) => {
window.onerror = onError(assert);
const property = 'window.aaa.bbb';
const scriptletArgs = [property];

window.aaa = {};
runScriptlet(name, scriptletArgs);
window.aaa.bbb = 'value';

addAndRemoveInlineScript('window.aaa.bbb;');

assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('works with chained properties', (assert) => {
window.onerror = onError(assert);
const chainProperty = 'aaa.bbb.ccc';
Expand Down
11 changes: 11 additions & 0 deletions tests/scriptlets/debug-on-property-read.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,14 @@ test('dot notation deferred defenition', (assert) => {
console.log(window.aaa.bbb);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('works with an empty object in chain', (assert) => {
const scriptletArgs = [CHAIN_PROPERTY];

window.aaa = {};
runScriptlet(name, scriptletArgs);
window.aaa.bbb = 'value';

console.log(window.aaa.bbb);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});
11 changes: 11 additions & 0 deletions tests/scriptlets/debug-on-property-write.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,14 @@ test('dot notation deferred defenition', (assert) => {
window.aaa.bbb = 'new value';
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('works with an empty object in chain', (assert) => {
const scriptletArgs = [CHAIN_PROPERTY];

window.aaa = {};
runScriptlet(name, scriptletArgs);
window.aaa.bbb = 'value';

window.aaa.bbb = 'new value';
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});
Loading

0 comments on commit cb86ba7

Please sign in to comment.