Add static network filter option: permissions

Related discussion:
https://github.com/uBlockOrigin/uBlock-issues/discussions/2714

Reference:
https://adguard.com/kb/general/ad-filtering/create-own-filters/#permissions-modifier

Example:

    ||example.org^$permissions=browsing-topics=()

Difference with AdGuard's syntax: use `|` to separate permissions
policy directives instead of `\,` -- uBO will replace instances
of `|` with `, `:

    *$permissions=oversized-images=()|unsized-media=()

Eventually uBO will support AdGuard's syntax of using escaped
commas, but not for this first iteration.
This commit is contained in:
Raymond Hill 2023-07-09 08:03:47 -04:00
parent 8d09c562ab
commit 5ebdbf3e24
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
3 changed files with 71 additions and 13 deletions

View file

@ -178,6 +178,7 @@ export const NODE_TYPE_NET_OPTION_NAME_MP4 = iota++;
export const NODE_TYPE_NET_OPTION_NAME_NOOP = iota++; export const NODE_TYPE_NET_OPTION_NAME_NOOP = iota++;
export const NODE_TYPE_NET_OPTION_NAME_OBJECT = iota++; export const NODE_TYPE_NET_OPTION_NAME_OBJECT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_OTHER = iota++; export const NODE_TYPE_NET_OPTION_NAME_OTHER = iota++;
export const NODE_TYPE_NET_OPTION_NAME_PERMISSIONS = iota++;
export const NODE_TYPE_NET_OPTION_NAME_PING = iota++; export const NODE_TYPE_NET_OPTION_NAME_PING = iota++;
export const NODE_TYPE_NET_OPTION_NAME_POPUNDER = iota++; export const NODE_TYPE_NET_OPTION_NAME_POPUNDER = iota++;
export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++; export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++;
@ -252,6 +253,7 @@ export const nodeTypeFromOptionName = new Map([
[ 'object', NODE_TYPE_NET_OPTION_NAME_OBJECT ], [ 'object', NODE_TYPE_NET_OPTION_NAME_OBJECT ],
/* synonym */ [ 'object-subrequest', NODE_TYPE_NET_OPTION_NAME_OBJECT ], /* synonym */ [ 'object-subrequest', NODE_TYPE_NET_OPTION_NAME_OBJECT ],
[ 'other', NODE_TYPE_NET_OPTION_NAME_OTHER ], [ 'other', NODE_TYPE_NET_OPTION_NAME_OTHER ],
[ 'permissions', NODE_TYPE_NET_OPTION_NAME_PERMISSIONS ],
[ 'ping', NODE_TYPE_NET_OPTION_NAME_PING ], [ 'ping', NODE_TYPE_NET_OPTION_NAME_PING ],
/* synonym */ [ 'beacon', NODE_TYPE_NET_OPTION_NAME_PING ], /* synonym */ [ 'beacon', NODE_TYPE_NET_OPTION_NAME_PING ],
[ 'popunder', NODE_TYPE_NET_OPTION_NAME_POPUNDER ], [ 'popunder', NODE_TYPE_NET_OPTION_NAME_POPUNDER ],
@ -1293,6 +1295,11 @@ export class AstFilterParser {
case NODE_TYPE_NET_OPTION_NAME_MATCHCASE: case NODE_TYPE_NET_OPTION_NAME_MATCHCASE:
realBad = this.isRegexPattern() === false; realBad = this.isRegexPattern() === false;
break; break;
case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
realBad = modifierType !== 0 || (hasValue || isException) === false;
if ( realBad ) { break; }
modifierType = type;
break;
case NODE_TYPE_NET_OPTION_NAME_PING: case NODE_TYPE_NET_OPTION_NAME_PING:
case NODE_TYPE_NET_OPTION_NAME_WEBSOCKET: case NODE_TYPE_NET_OPTION_NAME_WEBSOCKET:
realBad = hasValue; realBad = hasValue;
@ -1349,6 +1356,7 @@ export class AstFilterParser {
realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount;
break; break;
case NODE_TYPE_NET_OPTION_NAME_CSP: case NODE_TYPE_NET_OPTION_NAME_CSP:
case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount;
break; break;
case NODE_TYPE_NET_OPTION_NAME_INLINEFONT: case NODE_TYPE_NET_OPTION_NAME_INLINEFONT:

View file

@ -182,12 +182,14 @@ const MODIFIER_TYPE_REDIRECT = 1;
const MODIFIER_TYPE_REDIRECTRULE = 2; const MODIFIER_TYPE_REDIRECTRULE = 2;
const MODIFIER_TYPE_REMOVEPARAM = 3; const MODIFIER_TYPE_REMOVEPARAM = 3;
const MODIFIER_TYPE_CSP = 4; const MODIFIER_TYPE_CSP = 4;
const MODIFIER_TYPE_PERMISSIONS = 5;
const modifierTypeFromName = new Map([ const modifierTypeFromName = new Map([
[ 'redirect', MODIFIER_TYPE_REDIRECT ], [ 'redirect', MODIFIER_TYPE_REDIRECT ],
[ 'redirect-rule', MODIFIER_TYPE_REDIRECTRULE ], [ 'redirect-rule', MODIFIER_TYPE_REDIRECTRULE ],
[ 'removeparam', MODIFIER_TYPE_REMOVEPARAM ], [ 'removeparam', MODIFIER_TYPE_REMOVEPARAM ],
[ 'csp', MODIFIER_TYPE_CSP ], [ 'csp', MODIFIER_TYPE_CSP ],
[ 'permissions', MODIFIER_TYPE_PERMISSIONS ],
]); ]);
const modifierNameFromType = new Map([ const modifierNameFromType = new Map([
@ -195,6 +197,7 @@ const modifierNameFromType = new Map([
[ MODIFIER_TYPE_REDIRECTRULE, 'redirect-rule' ], [ MODIFIER_TYPE_REDIRECTRULE, 'redirect-rule' ],
[ MODIFIER_TYPE_REMOVEPARAM, 'removeparam' ], [ MODIFIER_TYPE_REMOVEPARAM, 'removeparam' ],
[ MODIFIER_TYPE_CSP, 'csp' ], [ MODIFIER_TYPE_CSP, 'csp' ],
[ MODIFIER_TYPE_PERMISSIONS, 'permissions' ],
]); ]);
//const typeValueFromCatBits = catBits => (catBits >>> TypeBitsOffset) & 0b11111; //const typeValueFromCatBits = catBits => (catBits >>> TypeBitsOffset) & 0b11111;
@ -3169,6 +3172,7 @@ class FilterCompiler {
]); ]);
this.modifierIdToNormalizedId = new Map([ this.modifierIdToNormalizedId = new Map([
[ sfp.NODE_TYPE_NET_OPTION_NAME_CSP, MODIFIER_TYPE_CSP ], [ sfp.NODE_TYPE_NET_OPTION_NAME_CSP, MODIFIER_TYPE_CSP ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_PERMISSIONS, MODIFIER_TYPE_PERMISSIONS ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT, MODIFIER_TYPE_REDIRECT ], [ sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT, MODIFIER_TYPE_REDIRECT ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE, MODIFIER_TYPE_REDIRECTRULE ], [ sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE, MODIFIER_TYPE_REDIRECTRULE ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM, MODIFIER_TYPE_REMOVEPARAM ], [ sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM, MODIFIER_TYPE_REMOVEPARAM ],
@ -3438,6 +3442,12 @@ class FilterCompiler {
this.processMethodOption(parser.getNetOptionValue(id)); this.processMethodOption(parser.getNetOptionValue(id));
this.optionUnitBits |= this.METHOD_BIT; this.optionUnitBits |= this.METHOD_BIT;
break; break;
case sfp.NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
if ( this.processModifierOption(id, parser.getNetOptionValue(id)) === false ) {
return false;
}
this.optionUnitBits |= this.PERMISSIONS_BIT;
break;
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: { case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: {
const actualId = this.action === AllowAction const actualId = this.action === AllowAction
? sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE ? sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE
@ -3554,6 +3564,7 @@ class FilterCompiler {
case sfp.NODE_TYPE_NET_OPTION_NAME_FROM: case sfp.NODE_TYPE_NET_OPTION_NAME_FROM:
case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER:
case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD: case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD:
case sfp.NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT:
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM:
@ -3622,8 +3633,12 @@ class FilterCompiler {
this.optionUnitBits |= this.NOT_TYPE_BIT; this.optionUnitBits |= this.NOT_TYPE_BIT;
} }
// CSP directives implicitly apply only to document/subdocument. // CSP/permissions options implicitly apply only to
if ( this.modifyType === MODIFIER_TYPE_CSP ) { // document/subdocument.
if (
this.modifyType === MODIFIER_TYPE_CSP ||
this.modifyType === MODIFIER_TYPE_PERMISSIONS
) {
if ( this.typeBits === 0 ) { if ( this.typeBits === 0 ) {
this.processTypeOption(sfp.NODE_TYPE_NET_OPTION_NAME_DOC, false); this.processTypeOption(sfp.NODE_TYPE_NET_OPTION_NAME_DOC, false);
this.processTypeOption(sfp.NODE_TYPE_NET_OPTION_NAME_FRAME, false); this.processTypeOption(sfp.NODE_TYPE_NET_OPTION_NAME_FRAME, false);
@ -4013,17 +4028,18 @@ class FilterCompiler {
} }
} }
FilterCompiler.prototype.FROM_BIT = 0b00000000001; FilterCompiler.prototype.FROM_BIT = 0b000000000001;
FilterCompiler.prototype.TO_BIT = 0b00000000010; FilterCompiler.prototype.TO_BIT = 0b000000000010;
FilterCompiler.prototype.DENYALLOW_BIT = 0b00000000100; FilterCompiler.prototype.DENYALLOW_BIT = 0b000000000100;
FilterCompiler.prototype.HEADER_BIT = 0b00000001000; FilterCompiler.prototype.HEADER_BIT = 0b000000001000;
FilterCompiler.prototype.STRICT_PARTY_BIT = 0b00000010000; FilterCompiler.prototype.STRICT_PARTY_BIT = 0b000000010000;
FilterCompiler.prototype.CSP_BIT = 0b00000100000; FilterCompiler.prototype.CSP_BIT = 0b000000100000;
FilterCompiler.prototype.REMOVEPARAM_BIT = 0b00001000000; FilterCompiler.prototype.REMOVEPARAM_BIT = 0b000001000000;
FilterCompiler.prototype.REDIRECT_BIT = 0b00010000000; FilterCompiler.prototype.REDIRECT_BIT = 0b000010000000;
FilterCompiler.prototype.NOT_TYPE_BIT = 0b00100000000; FilterCompiler.prototype.NOT_TYPE_BIT = 0b000100000000;
FilterCompiler.prototype.IMPORTANT_BIT = 0b01000000000; FilterCompiler.prototype.IMPORTANT_BIT = 0b001000000000;
FilterCompiler.prototype.METHOD_BIT = 0b10000000000; FilterCompiler.prototype.METHOD_BIT = 0b010000000000;
FilterCompiler.prototype.PERMISSIONS_BIT = 0b100000000000;
FilterCompiler.prototype.FILTER_OK = 0; FilterCompiler.prototype.FILTER_OK = 0;
FilterCompiler.prototype.FILTER_INVALID = 1; FilterCompiler.prototype.FILTER_INVALID = 1;

View file

@ -557,6 +557,10 @@ const onHeadersReceived = function(details) {
modifiedHeaders = true; modifiedHeaders = true;
} }
if ( injectPP(fctxt, pageStore, responseHeaders) === true ) {
modifiedHeaders = true;
}
// https://bugzilla.mozilla.org/show_bug.cgi?id=1376932 // https://bugzilla.mozilla.org/show_bug.cgi?id=1376932
// Prevent document from being cached by the browser if we modified it, // Prevent document from being cached by the browser if we modified it,
// either through HTML filtering and/or modified response headers. // either through HTML filtering and/or modified response headers.
@ -1004,6 +1008,36 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
/******************************************************************************/ /******************************************************************************/
const injectPP = function(fctxt, pageStore, responseHeaders) {
const permissions = [];
const directives = staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'permissions');
if ( directives !== undefined ) {
for ( const directive of directives ) {
if ( directive.result !== 1 ) { continue; }
permissions.push(directive.value.replace('|', ', '));
}
}
if ( logger.enabled && directives !== undefined ) {
fctxt.setRealm('network')
.pushFilters(directives.map(a => a.logData()))
.toLogger();
}
if ( permissions.length === 0 ) { return; }
µb.updateToolbarIcon(fctxt.tabId, 0x02);
responseHeaders.push({
name: 'permissions-policy',
value: permissions.join(', ')
});
return true;
};
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1163 // https://github.com/gorhill/uBlock/issues/1163
// "Block elements by size". // "Block elements by size".
// https://github.com/gorhill/uBlock/issues/1390#issuecomment-187310719 // https://github.com/gorhill/uBlock/issues/1390#issuecomment-187310719