From 5ebdbf3e24393560156fdcd931e31f901471f7f3 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 9 Jul 2023 08:03:47 -0400 Subject: [PATCH] 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. --- src/js/static-filtering-parser.js | 8 ++++++ src/js/static-net-filtering.js | 42 +++++++++++++++++++++---------- src/js/traffic.js | 34 +++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index c7035767e..34356d093 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -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_OBJECT = 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_POPUNDER = 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 ], /* synonym */ [ 'object-subrequest', NODE_TYPE_NET_OPTION_NAME_OBJECT ], [ 'other', NODE_TYPE_NET_OPTION_NAME_OTHER ], + [ 'permissions', NODE_TYPE_NET_OPTION_NAME_PERMISSIONS ], [ 'ping', NODE_TYPE_NET_OPTION_NAME_PING ], /* synonym */ [ 'beacon', NODE_TYPE_NET_OPTION_NAME_PING ], [ 'popunder', NODE_TYPE_NET_OPTION_NAME_POPUNDER ], @@ -1293,6 +1295,11 @@ export class AstFilterParser { case NODE_TYPE_NET_OPTION_NAME_MATCHCASE: realBad = this.isRegexPattern() === false; 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_WEBSOCKET: realBad = hasValue; @@ -1349,6 +1356,7 @@ export class AstFilterParser { realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; break; case NODE_TYPE_NET_OPTION_NAME_CSP: + case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; break; case NODE_TYPE_NET_OPTION_NAME_INLINEFONT: diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index bd912f607..5add2e634 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -182,12 +182,14 @@ const MODIFIER_TYPE_REDIRECT = 1; const MODIFIER_TYPE_REDIRECTRULE = 2; const MODIFIER_TYPE_REMOVEPARAM = 3; const MODIFIER_TYPE_CSP = 4; +const MODIFIER_TYPE_PERMISSIONS = 5; const modifierTypeFromName = new Map([ [ 'redirect', MODIFIER_TYPE_REDIRECT ], [ 'redirect-rule', MODIFIER_TYPE_REDIRECTRULE ], [ 'removeparam', MODIFIER_TYPE_REMOVEPARAM ], [ 'csp', MODIFIER_TYPE_CSP ], + [ 'permissions', MODIFIER_TYPE_PERMISSIONS ], ]); const modifierNameFromType = new Map([ @@ -195,6 +197,7 @@ const modifierNameFromType = new Map([ [ MODIFIER_TYPE_REDIRECTRULE, 'redirect-rule' ], [ MODIFIER_TYPE_REMOVEPARAM, 'removeparam' ], [ MODIFIER_TYPE_CSP, 'csp' ], + [ MODIFIER_TYPE_PERMISSIONS, 'permissions' ], ]); //const typeValueFromCatBits = catBits => (catBits >>> TypeBitsOffset) & 0b11111; @@ -3169,6 +3172,7 @@ class FilterCompiler { ]); this.modifierIdToNormalizedId = new Map([ [ 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_REDIRECTRULE, MODIFIER_TYPE_REDIRECTRULE ], [ sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM, MODIFIER_TYPE_REMOVEPARAM ], @@ -3438,6 +3442,12 @@ class FilterCompiler { this.processMethodOption(parser.getNetOptionValue(id)); this.optionUnitBits |= this.METHOD_BIT; 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: { const actualId = this.action === AllowAction ? 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_HEADER: 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_REDIRECTRULE: case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: @@ -3622,8 +3633,12 @@ class FilterCompiler { this.optionUnitBits |= this.NOT_TYPE_BIT; } - // CSP directives implicitly apply only to document/subdocument. - if ( this.modifyType === MODIFIER_TYPE_CSP ) { + // CSP/permissions options implicitly apply only to + // document/subdocument. + if ( + this.modifyType === MODIFIER_TYPE_CSP || + this.modifyType === MODIFIER_TYPE_PERMISSIONS + ) { if ( this.typeBits === 0 ) { this.processTypeOption(sfp.NODE_TYPE_NET_OPTION_NAME_DOC, false); this.processTypeOption(sfp.NODE_TYPE_NET_OPTION_NAME_FRAME, false); @@ -4013,17 +4028,18 @@ class FilterCompiler { } } -FilterCompiler.prototype.FROM_BIT = 0b00000000001; -FilterCompiler.prototype.TO_BIT = 0b00000000010; -FilterCompiler.prototype.DENYALLOW_BIT = 0b00000000100; -FilterCompiler.prototype.HEADER_BIT = 0b00000001000; -FilterCompiler.prototype.STRICT_PARTY_BIT = 0b00000010000; -FilterCompiler.prototype.CSP_BIT = 0b00000100000; -FilterCompiler.prototype.REMOVEPARAM_BIT = 0b00001000000; -FilterCompiler.prototype.REDIRECT_BIT = 0b00010000000; -FilterCompiler.prototype.NOT_TYPE_BIT = 0b00100000000; -FilterCompiler.prototype.IMPORTANT_BIT = 0b01000000000; -FilterCompiler.prototype.METHOD_BIT = 0b10000000000; +FilterCompiler.prototype.FROM_BIT = 0b000000000001; +FilterCompiler.prototype.TO_BIT = 0b000000000010; +FilterCompiler.prototype.DENYALLOW_BIT = 0b000000000100; +FilterCompiler.prototype.HEADER_BIT = 0b000000001000; +FilterCompiler.prototype.STRICT_PARTY_BIT = 0b000000010000; +FilterCompiler.prototype.CSP_BIT = 0b000000100000; +FilterCompiler.prototype.REMOVEPARAM_BIT = 0b000001000000; +FilterCompiler.prototype.REDIRECT_BIT = 0b000010000000; +FilterCompiler.prototype.NOT_TYPE_BIT = 0b000100000000; +FilterCompiler.prototype.IMPORTANT_BIT = 0b001000000000; +FilterCompiler.prototype.METHOD_BIT = 0b010000000000; +FilterCompiler.prototype.PERMISSIONS_BIT = 0b100000000000; FilterCompiler.prototype.FILTER_OK = 0; FilterCompiler.prototype.FILTER_INVALID = 1; diff --git a/src/js/traffic.js b/src/js/traffic.js index cd9067a8e..3677cb69d 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -557,6 +557,10 @@ const onHeadersReceived = function(details) { modifiedHeaders = true; } + if ( injectPP(fctxt, pageStore, responseHeaders) === true ) { + modifiedHeaders = true; + } + // https://bugzilla.mozilla.org/show_bug.cgi?id=1376932 // Prevent document from being cached by the browser if we modified it, // 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 // "Block elements by size". // https://github.com/gorhill/uBlock/issues/1390#issuecomment-187310719