New static network filter option `urlskip=`

Related issue:
https://github.com/uBlockOrigin/uBlock-issues/issues/3206

The main purpose is to bypass URLs designed to track whether a user
visited a specific URL, typically used in click-tracking links.

The `urlskip=` option ...

- ... is valid only when used in a trusted filter list
- ... is enforced only on top documents
- ... is enforced on both blocked and non-blocked documents
- ... is a modifier, i.e. it cannot be used along with other
      modifier options in a single filter

The syntax is `urlskip=[steps]`, where steps is a space-separated
list of extraction directives detailing what action to perform on
the current URL.

The only supported directive in this first commit is `?name`,
which purpose is to extract the value of a named URL parameter
and use the result as the new URL. Example:

  ||example.com/path/to/tracker$urlskip=?url

The above filter will cause navigation to

  https://example.com/path/to/tracker?url=https://example.org/

to automatically bypass navigation to `example.com` and navigate
directly to

  https://example.org/

It is possible to recursively extract URL parameters by using
more than one directive, example:

  ||example.com/path/to/tracker$urlskip=?url ?to

More extraction capabilities may be added in the future.
This commit is contained in:
Raymond Hill 2024-09-15 09:17:19 -04:00
parent 4b285c0593
commit 266ec4894b
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
6 changed files with 216 additions and 128 deletions

View File

@ -163,6 +163,9 @@ export const FilteringContext = class {
this.stype = a; this.stype = a;
} }
isRootDocument() {
return (this.itype & MAIN_FRAME) !== 0;
}
isDocument() { isDocument() {
return (this.itype & FRAME_ANY) !== 0; return (this.itype & FRAME_ANY) !== 0;
} }

View File

@ -331,7 +331,7 @@ const processLoggerEntries = function(response) {
parsed.type === 'main_frame' && parsed.type === 'main_frame' &&
parsed.aliased === false && ( parsed.aliased === false && (
parsed.filter === undefined || parsed.filter === undefined ||
parsed.filter.modifier !== true parsed.filter.modifier !== true && parsed.filter.source !== 'redirect'
) )
) { ) {
const separator = createLogSeparator(parsed, unboxed.url); const separator = createLogSeparator(parsed, unboxed.url);

View File

@ -933,19 +933,14 @@ const PageStore = class {
} }
redirectNonBlockedRequest(fctxt) { redirectNonBlockedRequest(fctxt) {
const transformDirectives = staticNetFilteringEngine.transformRequest(fctxt); const directives = [];
const pruneDirectives = fctxt.redirectURL === undefined && staticNetFilteringEngine.transformRequest(fctxt, directives);
staticNetFilteringEngine.hasQuery(fctxt) && if ( staticNetFilteringEngine.hasQuery(fctxt) ) {
staticNetFilteringEngine.filterQuery(fctxt) || staticNetFilteringEngine.filterQuery(fctxt, directives);
undefined; }
if ( transformDirectives === undefined && pruneDirectives === undefined ) { return; } if ( directives.length === 0 ) { return; }
if ( logger.enabled !== true ) { return; } if ( logger.enabled !== true ) { return; }
if ( transformDirectives !== undefined ) { fctxt.pushFilters(directives.map(a => a.logData()));
fctxt.pushFilters(transformDirectives.map(a => a.logData()));
}
if ( pruneDirectives !== undefined ) {
fctxt.pushFilters(pruneDirectives.map(a => a.logData()));
}
if ( fctxt.redirectURL === undefined ) { return; } if ( fctxt.redirectURL === undefined ) { return; }
fctxt.pushFilter({ fctxt.pushFilter({
source: 'redirect', source: 'redirect',
@ -953,6 +948,19 @@ const PageStore = class {
}); });
} }
skipMainDocument(fctxt) {
const directives = staticNetFilteringEngine.urlSkip(fctxt);
if ( directives === undefined ) { return; }
if ( logger.enabled !== true ) { return; }
fctxt.pushFilters(directives.map(a => a.logData()));
if ( fctxt.redirectURL !== undefined ) {
fctxt.pushFilter({
source: 'redirect',
raw: fctxt.redirectURL
});
}
}
filterCSPReport(fctxt) { filterCSPReport(fctxt) {
if ( if (
sessionSwitches.evaluateZ( sessionSwitches.evaluateZ(

View File

@ -191,6 +191,7 @@ export const NODE_TYPE_NET_OPTION_NAME_REPLACE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_SCRIPT = iota++; export const NODE_TYPE_NET_OPTION_NAME_SCRIPT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_SHIDE = iota++; export const NODE_TYPE_NET_OPTION_NAME_SHIDE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_TO = iota++; export const NODE_TYPE_NET_OPTION_NAME_TO = iota++;
export const NODE_TYPE_NET_OPTION_NAME_URLSKIP = iota++;
export const NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM = iota++; export const NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM = iota++;
export const NODE_TYPE_NET_OPTION_NAME_XHR = iota++; export const NODE_TYPE_NET_OPTION_NAME_XHR = iota++;
export const NODE_TYPE_NET_OPTION_NAME_WEBRTC = iota++; export const NODE_TYPE_NET_OPTION_NAME_WEBRTC = iota++;
@ -274,6 +275,7 @@ export const nodeTypeFromOptionName = new Map([
[ 'shide', NODE_TYPE_NET_OPTION_NAME_SHIDE ], [ 'shide', NODE_TYPE_NET_OPTION_NAME_SHIDE ],
/* synonym */ [ 'specifichide', NODE_TYPE_NET_OPTION_NAME_SHIDE ], /* synonym */ [ 'specifichide', NODE_TYPE_NET_OPTION_NAME_SHIDE ],
[ 'to', NODE_TYPE_NET_OPTION_NAME_TO ], [ 'to', NODE_TYPE_NET_OPTION_NAME_TO ],
[ 'urlskip', NODE_TYPE_NET_OPTION_NAME_URLSKIP ],
[ 'uritransform', NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM ], [ 'uritransform', NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM ],
[ 'xhr', NODE_TYPE_NET_OPTION_NAME_XHR ], [ 'xhr', NODE_TYPE_NET_OPTION_NAME_XHR ],
/* synonym */ [ 'xmlhttprequest', NODE_TYPE_NET_OPTION_NAME_XHR ], /* synonym */ [ 'xmlhttprequest', NODE_TYPE_NET_OPTION_NAME_XHR ],
@ -1441,6 +1443,7 @@ export class AstFilterParser {
case NODE_TYPE_NET_OPTION_NAME_REDIRECT: case NODE_TYPE_NET_OPTION_NAME_REDIRECT:
case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
case NODE_TYPE_NET_OPTION_NAME_REPLACE: case NODE_TYPE_NET_OPTION_NAME_REPLACE:
case NODE_TYPE_NET_OPTION_NAME_URLSKIP:
case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM:
realBad = isNegated || (isException || hasValue) === false || realBad = isNegated || (isException || hasValue) === false ||
modifierType !== 0; modifierType !== 0;
@ -1519,6 +1522,21 @@ export class AstFilterParser {
} }
break; break;
} }
case NODE_TYPE_NET_OPTION_NAME_URLSKIP: {
realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount;
if ( realBad ) { break; }
if ( requiresTrustedSource() ) {
this.astError = AST_ERROR_UNTRUSTED_SOURCE;
realBad = true;
break;
}
const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_URLSKIP);
if ( value.startsWith('?') === false || value.length < 2 ) {
this.astError = AST_ERROR_OPTION_BADVALUE;
realBad = true;
}
break;
}
case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: { case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: {
realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount;
if ( realBad ) { break; } if ( realBad ) { break; }
@ -3139,6 +3157,7 @@ export const netOptionTokenDescriptors = new Map([
[ 'shide', { } ], [ 'shide', { } ],
/* synonym */ [ 'specifichide', { } ], /* synonym */ [ 'specifichide', { } ],
[ 'to', { mustAssign: true } ], [ 'to', { mustAssign: true } ],
[ 'urlskip', { mustAssign: true } ],
[ 'uritransform', { mustAssign: true } ], [ 'uritransform', { mustAssign: true } ],
[ 'xhr', { canNegate: true } ], [ 'xhr', { canNegate: true } ],
/* synonym */ [ 'xmlhttprequest', { canNegate: true } ], /* synonym */ [ 'xmlhttprequest', { canNegate: true } ],

View File

@ -43,97 +43,99 @@ const keyvalStore = typeof vAPI !== 'undefined'
/******************************************************************************/ /******************************************************************************/
// 0fedcba9876543210 // 10fedcba9876543210
// ||||||| | || | // |||||||| | || |
// ||||||| | || | // |||||||| | || |
// ||||||| | || | // |||||||| | || |
// ||||||| | || | // |||||||| | || |
// ||||||| | || +---- bit 0- 1: block=0, allow=1, block important=2 // |||||||| | || +---- bit 0- 1: block=0, allow=1, block important=2
// ||||||| | |+------ bit 2: unused // |||||||| | |+------ bit 2: unused
// ||||||| | +------- bit 3- 4: party [0-3] // |||||||| | +------- bit 3- 4: party [0-3]
// ||||||| +--------- bit 5- 9: type [0-31] // |||||||| +--------- bit 5- 9: type [0-31]
// ||||||+-------------- bit 10: headers-based filters // |||||||+-------------- bit 10: headers-based filters
// |||||+--------------- bit 11: redirect filters // ||||||+--------------- bit 11: redirect filters
// ||||+---------------- bit 12: removeparam filters // |||||+---------------- bit 12: removeparam filters
// |||+----------------- bit 13: csp filters // ||||+----------------- bit 13: csp filters
// ||+------------------ bit 14: permissions filters // |||+------------------ bit 14: permissions filters
// |+------------------- bit 15: uritransform filters // ||+------------------- bit 15: uritransform filters
// +-------------------- bit 16: replace filters // |+-------------------- bit 16: replace filters
// TODO: bit 11-16 can be converted into 3-bit value, as these options are not // +--------------------- bit 17: urlskip filters
// TODO: bit 11-17 could be converted into 3-bit value, as these options are not
// meant to be combined. // meant to be combined.
const RealmBitsMask = 0b00000000111; const BLOCK_REALM = 0b0000_0000_0000_0000_0000;
const ActionBitsMask = 0b00000000011; const ALLOW_REALM = 0b0000_0000_0000_0000_0001;
const TypeBitsMask = 0b01111100000; const IMPORTANT_REALM = 0b0000_0000_0000_0000_0010;
const TypeBitsOffset = 5; const BLOCKALLOW_REALM = BLOCK_REALM | ALLOW_REALM | IMPORTANT_REALM;
const BLOCK_REALM = 0b00000000000000000;
const ALLOW_REALM = 0b00000000000000001;
const IMPORTANT_REALM = 0b00000000000000010;
const BLOCKIMPORTANT_REALM = BLOCK_REALM | IMPORTANT_REALM; const BLOCKIMPORTANT_REALM = BLOCK_REALM | IMPORTANT_REALM;
const ANYPARTY_REALM = 0b00000000000000000; const ANYPARTY_REALM = 0b0000_0000_0000_0000_0000;
const FIRSTPARTY_REALM = 0b00000000000001000; const FIRSTPARTY_REALM = 0b0000_0000_0000_0000_1000;
const THIRDPARTY_REALM = 0b00000000000010000; const THIRDPARTY_REALM = 0b0000_0000_0000_0001_0000;
const ALLPARTIES_REALM = FIRSTPARTY_REALM | THIRDPARTY_REALM; const ALLPARTIES_REALM = FIRSTPARTY_REALM | THIRDPARTY_REALM;
const HEADERS_REALM = 0b00000010000000000; const TYPE_REALM = 0b0000_0000_0011_1110_0000;
const REDIRECT_REALM = 0b00000100000000000; const HEADERS_REALM = 0b0000_0000_0100_0000_0000;
const REMOVEPARAM_REALM = 0b00001000000000000; const REDIRECT_REALM = 0b0000_0000_1000_0000_0000;
const CSP_REALM = 0b00010000000000000; const REMOVEPARAM_REALM = 0b0000_0001_0000_0000_0000;
const PERMISSIONS_REALM = 0b00100000000000000; const CSP_REALM = 0b0000_0010_0000_0000_0000;
const URLTRANSFORM_REALM = 0b01000000000000000; const PERMISSIONS_REALM = 0b0000_0100_0000_0000_0000;
const REPLACE_REALM = 0b10000000000000000; const URLTRANSFORM_REALM = 0b0000_1000_0000_0000_0000;
const REPLACE_REALM = 0b0001_0000_0000_0000_0000;
const URLSKIP_REALM = 0b0010_0000_0000_0000_0000;
const MODIFY_REALMS = REDIRECT_REALM | CSP_REALM | const MODIFY_REALMS = REDIRECT_REALM | CSP_REALM |
REMOVEPARAM_REALM | PERMISSIONS_REALM | REMOVEPARAM_REALM | PERMISSIONS_REALM |
URLTRANSFORM_REALM | REPLACE_REALM; URLTRANSFORM_REALM | REPLACE_REALM |
URLSKIP_REALM;
const TYPE_REALM_OFFSET = 5;
const typeNameToTypeValue = { const typeNameToTypeValue = {
'no_type': 0 << TypeBitsOffset, 'no_type': 0 << TYPE_REALM_OFFSET,
'stylesheet': 1 << TypeBitsOffset, 'stylesheet': 1 << TYPE_REALM_OFFSET,
'image': 2 << TypeBitsOffset, 'image': 2 << TYPE_REALM_OFFSET,
'object': 3 << TypeBitsOffset, 'object': 3 << TYPE_REALM_OFFSET,
'object_subrequest': 3 << TypeBitsOffset, 'object_subrequest': 3 << TYPE_REALM_OFFSET,
'script': 4 << TypeBitsOffset, 'script': 4 << TYPE_REALM_OFFSET,
'fetch': 5 << TypeBitsOffset, 'fetch': 5 << TYPE_REALM_OFFSET,
'xmlhttprequest': 5 << TypeBitsOffset, 'xmlhttprequest': 5 << TYPE_REALM_OFFSET,
'sub_frame': 6 << TypeBitsOffset, 'sub_frame': 6 << TYPE_REALM_OFFSET,
'font': 7 << TypeBitsOffset, 'font': 7 << TYPE_REALM_OFFSET,
'media': 8 << TypeBitsOffset, 'media': 8 << TYPE_REALM_OFFSET,
'websocket': 9 << TypeBitsOffset, 'websocket': 9 << TYPE_REALM_OFFSET,
'beacon': 10 << TypeBitsOffset, 'beacon': 10 << TYPE_REALM_OFFSET,
'ping': 10 << TypeBitsOffset, 'ping': 10 << TYPE_REALM_OFFSET,
'other': 11 << TypeBitsOffset, 'other': 11 << TYPE_REALM_OFFSET,
'popup': 12 << TypeBitsOffset, // start of behavioral filtering 'popup': 12 << TYPE_REALM_OFFSET, // start of behavioral filtering
'popunder': 13 << TypeBitsOffset, 'popunder': 13 << TYPE_REALM_OFFSET,
'main_frame': 14 << TypeBitsOffset, // start of 1p behavioral filtering 'main_frame': 14 << TYPE_REALM_OFFSET, // start of 1p behavioral filtering
'generichide': 15 << TypeBitsOffset, 'generichide': 15 << TYPE_REALM_OFFSET,
'specifichide': 16 << TypeBitsOffset, 'specifichide': 16 << TYPE_REALM_OFFSET,
'inline-font': 17 << TypeBitsOffset, 'inline-font': 17 << TYPE_REALM_OFFSET,
'inline-script': 18 << TypeBitsOffset, 'inline-script': 18 << TYPE_REALM_OFFSET,
'cname': 19 << TypeBitsOffset, 'cname': 19 << TYPE_REALM_OFFSET,
'webrtc': 20 << TypeBitsOffset, 'webrtc': 20 << TYPE_REALM_OFFSET,
'unsupported': 21 << TypeBitsOffset, 'unsupported': 21 << TYPE_REALM_OFFSET,
}; };
const otherTypeBitValue = typeNameToTypeValue.other; const otherTypeBitValue = typeNameToTypeValue.other;
const bitFromType = type => const bitFromType = type =>
1 << ((typeNameToTypeValue[type] >>> TypeBitsOffset) - 1); 1 << ((typeNameToTypeValue[type] >>> TYPE_REALM_OFFSET) - 1);
// All network request types to bitmap // All network request types to bitmap
// bring origin to 0 (from TypeBitsOffset -- see typeNameToTypeValue) // bring origin to 0 (from TYPE_REALM_OFFSET -- see typeNameToTypeValue)
// left-shift 1 by the above-calculated value // left-shift 1 by the above-calculated value
// subtract 1 to set all type bits // subtract 1 to set all type bits
const allNetworkTypesBits = const allNetworkTypesBits =
(1 << (otherTypeBitValue >>> TypeBitsOffset)) - 1; (1 << (otherTypeBitValue >>> TYPE_REALM_OFFSET)) - 1;
const allTypesBits = const allTypesBits =
allNetworkTypesBits | allNetworkTypesBits |
1 << (typeNameToTypeValue['popup'] >>> TypeBitsOffset) - 1 | 1 << (typeNameToTypeValue['popup'] >>> TYPE_REALM_OFFSET) - 1 |
1 << (typeNameToTypeValue['main_frame'] >>> TypeBitsOffset) - 1 | 1 << (typeNameToTypeValue['main_frame'] >>> TYPE_REALM_OFFSET) - 1 |
1 << (typeNameToTypeValue['inline-font'] >>> TypeBitsOffset) - 1 | 1 << (typeNameToTypeValue['inline-font'] >>> TYPE_REALM_OFFSET) - 1 |
1 << (typeNameToTypeValue['inline-script'] >>> TypeBitsOffset) - 1; 1 << (typeNameToTypeValue['inline-script'] >>> TYPE_REALM_OFFSET) - 1;
const unsupportedTypeBit = const unsupportedTypeBit =
1 << (typeNameToTypeValue['unsupported'] >>> TypeBitsOffset) - 1; 1 << (typeNameToTypeValue['unsupported'] >>> TYPE_REALM_OFFSET) - 1;
const typeValueToTypeName = [ const typeValueToTypeName = [
'', '',
@ -186,6 +188,7 @@ const MODIFIER_TYPE_CSP = 4;
const MODIFIER_TYPE_PERMISSIONS = 5; const MODIFIER_TYPE_PERMISSIONS = 5;
const MODIFIER_TYPE_URLTRANSFORM = 6; const MODIFIER_TYPE_URLTRANSFORM = 6;
const MODIFIER_TYPE_REPLACE = 7; const MODIFIER_TYPE_REPLACE = 7;
const MODIFIER_TYPE_URLSKIP = 8;
const modifierBitsFromType = new Map([ const modifierBitsFromType = new Map([
[ MODIFIER_TYPE_REDIRECT, REDIRECT_REALM ], [ MODIFIER_TYPE_REDIRECT, REDIRECT_REALM ],
@ -195,6 +198,7 @@ const modifierBitsFromType = new Map([
[ MODIFIER_TYPE_PERMISSIONS, PERMISSIONS_REALM ], [ MODIFIER_TYPE_PERMISSIONS, PERMISSIONS_REALM ],
[ MODIFIER_TYPE_URLTRANSFORM, URLTRANSFORM_REALM ], [ MODIFIER_TYPE_URLTRANSFORM, URLTRANSFORM_REALM ],
[ MODIFIER_TYPE_REPLACE, REPLACE_REALM ], [ MODIFIER_TYPE_REPLACE, REPLACE_REALM ],
[ MODIFIER_TYPE_URLSKIP, URLSKIP_REALM ],
]); ]);
const modifierTypeFromName = new Map([ const modifierTypeFromName = new Map([
@ -205,6 +209,7 @@ const modifierTypeFromName = new Map([
[ 'permissions', MODIFIER_TYPE_PERMISSIONS ], [ 'permissions', MODIFIER_TYPE_PERMISSIONS ],
[ 'uritransform', MODIFIER_TYPE_URLTRANSFORM ], [ 'uritransform', MODIFIER_TYPE_URLTRANSFORM ],
[ 'replace', MODIFIER_TYPE_REPLACE ], [ 'replace', MODIFIER_TYPE_REPLACE ],
[ 'urlskip', MODIFIER_TYPE_URLSKIP ],
]); ]);
const modifierNameFromType = new Map([ const modifierNameFromType = new Map([
@ -215,22 +220,23 @@ const modifierNameFromType = new Map([
[ MODIFIER_TYPE_PERMISSIONS, 'permissions' ], [ MODIFIER_TYPE_PERMISSIONS, 'permissions' ],
[ MODIFIER_TYPE_URLTRANSFORM, 'uritransform' ], [ MODIFIER_TYPE_URLTRANSFORM, 'uritransform' ],
[ MODIFIER_TYPE_REPLACE, 'replace' ], [ MODIFIER_TYPE_REPLACE, 'replace' ],
[ MODIFIER_TYPE_URLSKIP, 'urlskip' ],
]); ]);
//const typeValueFromCatBits = catBits => (catBits >>> TypeBitsOffset) & 0b11111; //const typeValueFromCatBits = catBits => (catBits >>> TYPE_REALM_OFFSET) & 0b11111;
const MAX_TOKEN_LENGTH = 7; const MAX_TOKEN_LENGTH = 7;
// Four upper bits of token hash are reserved for built-in predefined // Four upper bits of token hash are reserved for built-in predefined
// token hashes, which should never end up being used when tokenizing // token hashes, which should never end up being used when tokenizing
// any arbitrary string. // any arbitrary string.
const NO_TOKEN_HASH = 0x50000000; const NO_TOKEN_HASH = 0x5000_0000;
const DOT_TOKEN_HASH = 0x10000000; const DOT_TOKEN_HASH = 0x1000_0000;
const ANY_TOKEN_HASH = 0x20000000; const ANY_TOKEN_HASH = 0x2000_0000;
const ANY_HTTPS_TOKEN_HASH = 0x30000000; const ANY_HTTPS_TOKEN_HASH = 0x3000_0000;
const ANY_HTTP_TOKEN_HASH = 0x40000000; const ANY_HTTP_TOKEN_HASH = 0x4000_0000;
const EMPTY_TOKEN_HASH = 0xF0000000; const EMPTY_TOKEN_HASH = 0xF000_0000;
const INVALID_TOKEN_HASH = 0xFFFFFFFF; const INVALID_TOKEN_HASH = 0xFFFF_FFFF;
/******************************************************************************/ /******************************************************************************/
@ -374,9 +380,9 @@ class LogData {
} else if ( (categoryBits & FIRSTPARTY_REALM) !== 0 ) { } else if ( (categoryBits & FIRSTPARTY_REALM) !== 0 ) {
logData.options.unshift('1p'); logData.options.unshift('1p');
} }
const type = categoryBits & TypeBitsMask; const type = categoryBits & TYPE_REALM;
if ( type !== 0 ) { if ( type !== 0 ) {
logData.options.unshift(typeValueToTypeName[type >>> TypeBitsOffset]); logData.options.unshift(typeValueToTypeName[type >>> TYPE_REALM_OFFSET]);
} }
let raw = logData.pattern.join(''); let raw = logData.pattern.join('');
if ( if (
@ -2163,7 +2169,7 @@ class FilterModifierResult {
this.refs = filterRefs[filterData[imodifierunit+3]]; this.refs = filterRefs[filterData[imodifierunit+3]];
this.ireportedunit = env.iunit; this.ireportedunit = env.iunit;
this.th = env.th; this.th = env.th;
this.bits = (env.bits & ~RealmBitsMask) | filterData[imodifierunit+1]; this.bits = (env.bits & ~BLOCKALLOW_REALM) | filterData[imodifierunit+1];
} }
get result() { get result() {
@ -3276,6 +3282,7 @@ class FilterCompiler {
[ sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM, MODIFIER_TYPE_REMOVEPARAM ], [ sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM, MODIFIER_TYPE_REMOVEPARAM ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM, MODIFIER_TYPE_URLTRANSFORM ], [ sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM, MODIFIER_TYPE_URLTRANSFORM ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE, MODIFIER_TYPE_REPLACE ], [ sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE, MODIFIER_TYPE_REPLACE ],
[ sfp.NODE_TYPE_NET_OPTION_NAME_URLSKIP, MODIFIER_TYPE_URLSKIP ],
]); ]);
// These top 100 "bad tokens" are collated using the "miss" histogram // These top 100 "bad tokens" are collated using the "miss" histogram
// from tokenHistograms(). The "score" is their occurrence among the // from tokenHistograms(). The "score" is their occurrence among the
@ -3548,6 +3555,7 @@ class FilterCompiler {
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:
case sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE: case sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE:
case sfp.NODE_TYPE_NET_OPTION_NAME_URLSKIP:
case sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: case sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM:
if ( this.processModifierOption(id, parser.getNetOptionValue(id)) === false ) { if ( this.processModifierOption(id, parser.getNetOptionValue(id)) === false ) {
return false; return false;
@ -3667,6 +3675,7 @@ class FilterCompiler {
case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM:
case sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE: case sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE:
case sfp.NODE_TYPE_NET_OPTION_NAME_TO: case sfp.NODE_TYPE_NET_OPTION_NAME_TO:
case sfp.NODE_TYPE_NET_OPTION_NAME_URLSKIP:
case sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: case sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM:
if ( this.processOptionWithValue(parser, type) === false ) { if ( this.processOptionWithValue(parser, type) === false ) {
return this.FILTER_INVALID; return this.FILTER_INVALID;
@ -4054,7 +4063,7 @@ class FilterCompiler {
// IMPORTANT: the modifier unit MUST always appear first in a sequence // IMPORTANT: the modifier unit MUST always appear first in a sequence
if ( this.modifyType !== undefined ) { if ( this.modifyType !== undefined ) {
units.unshift(FilterModifier.compile(this)); units.unshift(FilterModifier.compile(this));
this.action = (this.action & ~ActionBitsMask) | this.action = (this.action & ~BLOCKALLOW_REALM) |
modifierBitsFromType.get(this.modifyType); modifierBitsFromType.get(this.modifyType);
} }
@ -4123,7 +4132,7 @@ class FilterCompiler {
do { do {
if ( typeBits & 1 ) { if ( typeBits & 1 ) {
writer.push([ writer.push([
catBits | (bitOffset << TypeBitsOffset), catBits | (bitOffset << TYPE_REALM_OFFSET),
this.tokenHash, this.tokenHash,
fdata fdata
]); ]);
@ -4286,7 +4295,7 @@ StaticNetFilteringEngine.prototype.freeze = function() {
// the block-important realm should be checked when and only when // the block-important realm should be checked when and only when
// there is a matched exception filter, which important filters are // there is a matched exception filter, which important filters are
// meant to override. // meant to override.
if ( (bits & ActionBitsMask) === BLOCKIMPORTANT_REALM ) { if ( (bits & BLOCKALLOW_REALM) === BLOCKIMPORTANT_REALM ) {
this.addFilterUnit( this.addFilterUnit(
bits & ~IMPORTANT_REALM, bits & ~IMPORTANT_REALM,
tokenHash, tokenHash,
@ -4446,6 +4455,7 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
[ PERMISSIONS_REALM, { type: 'permissions', priority: 0 } ], [ PERMISSIONS_REALM, { type: 'permissions', priority: 0 } ],
[ URLTRANSFORM_REALM, { type: 'uritransform', priority: 0 } ], [ URLTRANSFORM_REALM, { type: 'uritransform', priority: 0 } ],
[ HEADERS_REALM, { type: 'block', priority: 0 } ], [ HEADERS_REALM, { type: 'block', priority: 0 } ],
[ URLSKIP_REALM, { type: 'urlskip', priority: 0 } ],
]); ]);
const partyness = new Map([ const partyness = new Map([
[ ANYPARTY_REALM, '' ], [ ANYPARTY_REALM, '' ],
@ -4860,7 +4870,7 @@ StaticNetFilteringEngine.prototype.matchAndFetchModifiers = function(
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
$requestMethodBit = fctxt.method || 0; $requestMethodBit = fctxt.method || 0;
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $requestTypeValue = (typeBits & TYPE_REALM) >>> TYPE_REALM_OFFSET;
$requestAddress = fctxt.getIPAddress(); $requestAddress = fctxt.getIPAddress();
const modifierType = modifierTypeFromName.get(modifierName); const modifierType = modifierTypeFromName.get(modifierName);
@ -4955,7 +4965,7 @@ StaticNetFilteringEngine.prototype.matchAndFetchModifiers = function(
const toRemove = new Map(); const toRemove = new Map();
for ( const result of results ) { for ( const result of results ) {
const actionBits = result.bits & ActionBitsMask; const actionBits = result.bits & BLOCKALLOW_REALM;
const modifyValue = result.value; const modifyValue = result.value;
if ( actionBits === BLOCKIMPORTANT_REALM ) { if ( actionBits === BLOCKIMPORTANT_REALM ) {
toAddImportant.set(modifyValue, result); toAddImportant.set(modifyValue, result);
@ -5158,7 +5168,7 @@ StaticNetFilteringEngine.prototype.matchRequestReverse = function(type, url) {
$requestURL = urlTokenizer.setURL(url); $requestURL = urlTokenizer.setURL(url);
$requestURLRaw = url; $requestURLRaw = url;
$requestMethodBit = 0; $requestMethodBit = 0;
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $requestTypeValue = (typeBits & TYPE_REALM) >>> TYPE_REALM_OFFSET;
$requestAddress = ''; $requestAddress = '';
$isBlockImportant = false; $isBlockImportant = false;
this.$filterUnit = 0; this.$filterUnit = 0;
@ -5227,7 +5237,7 @@ StaticNetFilteringEngine.prototype.matchRequest = function(fctxt, modifiers = 0)
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
$requestMethodBit = fctxt.method || 0; $requestMethodBit = fctxt.method || 0;
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $requestTypeValue = (typeBits & TYPE_REALM) >>> TYPE_REALM_OFFSET;
$requestAddress = fctxt.getIPAddress(); $requestAddress = fctxt.getIPAddress();
$isBlockImportant = false; $isBlockImportant = false;
@ -5263,7 +5273,7 @@ StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) {
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
$requestMethodBit = fctxt.method || 0; $requestMethodBit = fctxt.method || 0;
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $requestTypeValue = (typeBits & TYPE_REALM) >>> TYPE_REALM_OFFSET;
$requestAddress = fctxt.getIPAddress(); $requestAddress = fctxt.getIPAddress();
$httpHeaders.init(headers); $httpHeaders.init(headers);
@ -5309,11 +5319,35 @@ StaticNetFilteringEngine.prototype.redirectRequest = function(redirectEngine, fc
return directives; return directives;
}; };
StaticNetFilteringEngine.prototype.transformRequest = function(fctxt) { function parseRedirectRequestValue(directive) {
if ( directive.cache === null ) {
directive.cache = sfp.parseRedirectValue(directive.value);
}
return directive.cache;
}
function compareRedirectRequests(redirectEngine, a, b) {
const { token: atok, priority: aint, bits: abits } =
parseRedirectRequestValue(a);
if ( redirectEngine.hasToken(atok) === false ) { return -1; }
const { token: btok, priority: bint, bits: bbits } =
parseRedirectRequestValue(b);
if ( redirectEngine.hasToken(btok) === false ) { return 1; }
if ( abits !== bbits ) {
if ( (abits & IMPORTANT_REALM) !== 0 ) { return 1; }
if ( (bbits & IMPORTANT_REALM) !== 0 ) { return -1; }
if ( (abits & ALLOW_REALM) !== 0 ) { return -1; }
if ( (bbits & ALLOW_REALM) !== 0 ) { return 1; }
}
return aint - bint;
}
/******************************************************************************/
StaticNetFilteringEngine.prototype.transformRequest = function(fctxt, out = []) {
const directives = this.matchAndFetchModifiers(fctxt, 'uritransform'); const directives = this.matchAndFetchModifiers(fctxt, 'uritransform');
if ( directives === undefined ) { return; } if ( directives === undefined ) { return; }
const redirectURL = new URL(fctxt.url); const redirectURL = new URL(fctxt.url);
const out = [];
for ( const directive of directives ) { for ( const directive of directives ) {
if ( (directive.bits & ALLOW_REALM) !== 0 ) { if ( (directive.bits & ALLOW_REALM) !== 0 ) {
out.push(directive); out.push(directive);
@ -5345,27 +5379,47 @@ StaticNetFilteringEngine.prototype.transformRequest = function(fctxt) {
return out; return out;
}; };
function parseRedirectRequestValue(directive) { /******************************************************************************/
if ( directive.cache === null ) {
directive.cache = sfp.parseRedirectValue(directive.value);
}
return directive.cache;
}
function compareRedirectRequests(redirectEngine, a, b) { StaticNetFilteringEngine.prototype.urlSkip = function(fctxt, out = []) {
const { token: atok, priority: aint, bits: abits } = if ( fctxt.redirectURL !== undefined ) { return; }
parseRedirectRequestValue(a); const directives = this.matchAndFetchModifiers(fctxt, 'urlskip');
if ( redirectEngine.hasToken(atok) === false ) { return -1; } if ( directives === undefined ) { return; }
const { token: btok, priority: bint, bits: bbits } = for ( const directive of directives ) {
parseRedirectRequestValue(b); if ( (directive.bits & ALLOW_REALM) !== 0 ) {
if ( redirectEngine.hasToken(btok) === false ) { return 1; } out.push(directive);
if ( abits !== bbits ) { continue;
if ( (abits & IMPORTANT_REALM) !== 0 ) { return 1; } }
if ( (bbits & IMPORTANT_REALM) !== 0 ) { return -1; } const urlin = fctxt.url;
if ( (abits & ALLOW_REALM) !== 0 ) { return -1; } const value = directive.value;
if ( (bbits & ALLOW_REALM) !== 0 ) { return 1; } const steps = value.includes(' ') && value.split(/ +/) || [ value ];
const urlout = urlSkip(urlin, steps);
if ( urlout === undefined ) { continue; }
if ( urlout === urlin ) { continue; }
fctxt.redirectURL = urlout;
out.push(directive);
break;
}
if ( out.length === 0 ) { return; }
return out;
};
function urlSkip(urlin, steps) {
try {
let urlout;
for ( const step of steps ) {
if ( step.startsWith('?') === false ) { return; }
urlout = (new URL(urlin)).searchParams.get(step.slice(1));
if ( urlout === null ) { return; }
if ( urlout.includes(' ') ) {
urlout = urlout.replace(/ /g, '%20');
}
urlin = urlout;
}
void new URL(urlout);
return urlout;
} catch(x) {
} }
return aint - bint;
} }
/******************************************************************************/ /******************************************************************************/
@ -5373,7 +5427,8 @@ function compareRedirectRequests(redirectEngine, a, b) {
// https://github.com/uBlockOrigin/uBlock-issues/issues/1626 // https://github.com/uBlockOrigin/uBlock-issues/issues/1626
// Do not redirect when the number of query parameters does not change. // Do not redirect when the number of query parameters does not change.
StaticNetFilteringEngine.prototype.filterQuery = function(fctxt) { StaticNetFilteringEngine.prototype.filterQuery = function(fctxt, out = []) {
if ( fctxt.redirectURL !== undefined ) { return; }
const directives = this.matchAndFetchModifiers(fctxt, 'removeparam'); const directives = this.matchAndFetchModifiers(fctxt, 'removeparam');
if ( directives === undefined ) { return; } if ( directives === undefined ) { return; }
const url = fctxt.url; const url = fctxt.url;
@ -5396,7 +5451,6 @@ StaticNetFilteringEngine.prototype.filterQuery = function(fctxt) {
} }
} }
const inParamCount = params.size; const inParamCount = params.size;
const out = [];
for ( const directive of directives ) { for ( const directive of directives ) {
if ( params.size === 0 ) { break; } if ( params.size === 0 ) { break; }
const isException = (directive.bits & ALLOW_REALM) !== 0; const isException = (directive.bits & ALLOW_REALM) !== 0;
@ -5664,6 +5718,7 @@ StaticNetFilteringEngine.prototype.dump = function() {
[ PERMISSIONS_REALM, 'permissions' ], [ PERMISSIONS_REALM, 'permissions' ],
[ URLTRANSFORM_REALM, 'uritransform' ], [ URLTRANSFORM_REALM, 'uritransform' ],
[ REPLACE_REALM, 'replace' ], [ REPLACE_REALM, 'replace' ],
[ URLSKIP_REALM, 'urlskip' ],
]); ]);
const partyness = new Map([ const partyness = new Map([
[ ANYPARTY_REALM, 'any-party' ], [ ANYPARTY_REALM, 'any-party' ],

View File

@ -188,17 +188,20 @@ const onBeforeRootFrameRequest = function(fctxt) {
} }
if ( logger.enabled ) { if ( logger.enabled ) {
fctxt.setFilter(logData); fctxt.setRealm('network').setFilter(logData);
} }
// https://github.com/uBlockOrigin/uBlock-issues/issues/760 // https://github.com/uBlockOrigin/uBlock-issues/issues/760
// Redirect non-blocked request? // Redirect non-blocked request?
if ( result !== 1 && trusted === false && pageStore !== null ) { if ( trusted === false && pageStore !== null ) {
if ( result !== 1 ) {
pageStore.redirectNonBlockedRequest(fctxt); pageStore.redirectNonBlockedRequest(fctxt);
} }
pageStore.skipMainDocument(fctxt);
}
if ( logger.enabled ) { if ( logger.enabled ) {
fctxt.setRealm('network').toLogger(); fctxt.toLogger();
} }
// Redirected // Redirected