Compare commits

...

8 commits

Author SHA1 Message Date
Raymond Hill
fcc5113e54
New revision for stable release 2021-10-09 10:07:42 -04:00
Raymond Hill
c5d6aea28c
Update submodules 2021-10-09 10:07:10 -04:00
Raymond Hill
9aa216642c
Update twitch-videoad scriptlet
Related issue:
- https://github.com/uBlockOrigin/uAssets/issues/5184#issuecomment-938379331

Solution contributed by <https://github.com/pixeltris>:
- 6be4c53130
2021-10-09 10:05:43 -04:00
Raymond Hill
27bc54b2cf
new revision for stable release 2021-09-30 08:52:47 -04:00
Raymond Hill
9a208bd469
Update submodules 2021-09-30 08:52:09 -04:00
Raymond Hill
461c95c769
Fix handling of negated types in all filter option
Related feedback:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1730

Related commit:
- 8f98622374
2021-09-25 14:39:19 -04:00
Raymond Hill
01a3b3f717
Fix regression with important filter option
Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1732

The regression affect filter with the `important` option when
the following conditions were fulfilled:

- The filter pattern is pure hostname
- The filter has not one of the following options:
  - domain
  - denyallow
  - header
  - strict1p, strict3p
  - csp
  - removeparam
- There is a matching exception filter

Related commit:
- a2a8ef7e85

A related mocha test has been added in order to detect this
specific regression in the future through `make test`.
2021-09-24 12:49:41 -04:00
Raymond Hill
805336febd
Refactor implementation of ~[type] filter options
Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1730

A new filter unit, FilterNotType, is introduced to enforce
negated filter type options.

Before this commit, there was no actual negated types in the
static network filtering engine, as a negated type was internally
converted to non-negated types at compile time. As a result,
the logger would never output a matching filter with its original
negated type options.

This commit no longer causes an internal conversion to take place
at compile time, but explicitly enforce negated types at match time,
and as a result the logger will from now on output matching filter
with their original negated type options.
2021-09-24 12:49:23 -04:00
7 changed files with 141 additions and 51 deletions

View file

@ -1538,19 +1538,31 @@
})(); })();
// https://github.com/uBlockOrigin/uAssets/issues/5184
/// twitch-videoad.js /// twitch-videoad.js
// https://github.com/uBlockOrigin/uAssets/issues/5184
// https://github.com/pixeltris/TwitchAdSolutions/commit/6be4c5313035
(function() { (function() {
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
var realFetch = window.fetch; window.fetch = new Proxy(window.fetch, {
window.fetch = function(input) { apply: function(target, thisArg, args) {
if ( arguments.length >= 2 && typeof input === 'string' && input.includes('/access_token') ) { const [ url, init ] = args;
var url = new URL(arguments[0]); if (
url.searchParams.delete('platform'); typeof url === 'string' &&
arguments[0] = url.href; url.includes('gql') &&
init instanceof Object &&
init.headers instanceof Object
) {
const { headers } = init;
if ( typeof headers['X-Device-Id'] === 'string' ) {
headers['X-Device-Id'] = 'twitch-web-wall-mason';
}
if ( typeof headers['Device-ID'] === 'string' ) {
headers['Device-ID'] = 'twitch-web-wall-mason';
}
}
return Reflect.apply(target, thisArg, args);
} }
return realFetch.apply(this, arguments); });
};
})(); })();

2
dist/version vendored
View file

@ -1 +1 @@
1.38.0 1.38.4

View file

@ -1,6 +1,6 @@
{ {
"name": "@gorhill/ubo-core", "name": "@gorhill/ubo-core",
"version": "0.1.10", "version": "0.1.11",
"description": "To create a working instance of uBlock Origin's static network filtering engine", "description": "To create a working instance of uBlock Origin's static network filtering engine",
"type": "module", "type": "module",
"main": "index.js", "main": "index.js",

View file

@ -232,6 +232,24 @@ describe('SNFE', () => {
await engine.deserialize(serialized); await engine.deserialize(serialized);
}); });
}); });
describe('Filter matching', () => {
beforeEach(async () => {
engine = await module.StaticNetFilteringEngine.create();
});
it('should match block-important pure-hostname filter', async () => {
await engine.useLists([
{ name: 'test', raw: '@@||example.com^\n||example.com^$important' },
]);
engine.matchRequest({
originURL: 'https://www.example.com/',
type: 'main_frame',
url: 'https://www.example.com/',
});
});
});
}); });
} }
}); });

View file

@ -153,8 +153,8 @@ const µBlock = { // jshint ignore:line
// Read-only // Read-only
systemSettings: { systemSettings: {
compiledMagic: 38, // Increase when compiled format changes compiledMagic: 39, // Increase when compiled format changes
selfieMagic: 38, // Increase when selfie format changes selfieMagic: 39, // Increase when selfie format changes
}, },
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501

View file

@ -135,8 +135,6 @@ const allTypesBits =
1 << (typeNameToTypeValue['main_frame'] >>> TypeBitsOffset) - 1 | 1 << (typeNameToTypeValue['main_frame'] >>> TypeBitsOffset) - 1 |
1 << (typeNameToTypeValue['inline-font'] >>> TypeBitsOffset) - 1 | 1 << (typeNameToTypeValue['inline-font'] >>> TypeBitsOffset) - 1 |
1 << (typeNameToTypeValue['inline-script'] >>> TypeBitsOffset) - 1; 1 << (typeNameToTypeValue['inline-script'] >>> TypeBitsOffset) - 1;
const documentTypeBit =
1 << (typeNameToTypeValue['main_frame'] >>> TypeBitsOffset) - 1;
const unsupportedTypeBit = const unsupportedTypeBit =
1 << (typeNameToTypeValue['unsupported'] >>> TypeBitsOffset) - 1; 1 << (typeNameToTypeValue['unsupported'] >>> TypeBitsOffset) - 1;
@ -161,6 +159,8 @@ const typeValueToTypeName = [
'inline-font', 'inline-font',
'inline-script', 'inline-script',
'cname', 'cname',
'',
'',
'webrtc', 'webrtc',
'unsupported', 'unsupported',
]; ];
@ -186,6 +186,7 @@ const EMPTY_TOKEN_HASH = 0xF0000000;
// See the following as short-lived registers, used during evaluation. They are // See the following as short-lived registers, used during evaluation. They are
// valid until the next evaluation. // valid until the next evaluation.
let $requestTypeValue = 0;
let $requestURL = ''; let $requestURL = '';
let $requestURLRaw = ''; let $requestURLRaw = '';
let $requestHostname = ''; let $requestHostname = '';
@ -1271,6 +1272,57 @@ registerFilterClass(FilterRegex);
/******************************************************************************/ /******************************************************************************/
// stylesheet: 1 => bit 0
// image: 2 => bit 1
// object: 3 => bit 2
// script: 4 => bit 3
// ...
const FilterNotType = class {
constructor(notTypeBits) {
this.notTypeBits = notTypeBits;
}
match() {
return $requestTypeValue !== 0 &&
(this.notTypeBits & (1 << ($requestTypeValue - 1))) === 0;
}
logData(details) {
let bits = this.notTypeBits;
for ( let i = 1; bits !== 0 && i < typeValueToTypeName.length; i++ ) {
const bit = 1 << (i - 1);
if ( (bits & bit) === 0 ) { continue; }
bits &= ~bit;
details.options.push(`~${typeValueToTypeName[i]}`);
}
}
toSelfie() {
return [ this.fid, this.notTypeBits ];
}
static compile(details) {
return [ FilterNotType.fid, details.notTypeBits ];
}
static fromCompiled(args) {
return new FilterNotType(args[1]);
}
static fromSelfie(args) {
return new FilterNotType(args[1]);
}
static keyFromArgs(args) {
return `${args[1]}`;
}
};
registerFilterClass(FilterNotType);
/******************************************************************************/
// A helper class to parse `domain=` option. // A helper class to parse `domain=` option.
const DomainOptIterator = class { const DomainOptIterator = class {
@ -2908,8 +2960,10 @@ class FilterCompiler {
: allTypesBits; : allTypesBits;
if ( not ) { if ( not ) {
this.notTypeBits |= typeBit; this.notTypeBits |= typeBit;
this.typeBits &= ~typeBit;
} else { } else {
this.typeBits |= typeBit; this.typeBits |= typeBit;
this.notTypeBits &= ~typeBit;
} }
} }
@ -3013,6 +3067,7 @@ class FilterCompiler {
break; break;
case this.parser.OPTTokenImportant: case this.parser.OPTTokenImportant:
if ( this.action === AllowAction ) { return false; } if ( this.action === AllowAction ) { return false; }
this.optionUnitBits |= this.IMPORTANT_BIT;
this.action = BlockImportant; this.action = BlockImportant;
break; break;
// Used by Adguard: // Used by Adguard:
@ -3085,14 +3140,8 @@ class FilterCompiler {
// - no network type is present -- i.e. all network types are // - no network type is present -- i.e. all network types are
// implicitly toggled on // implicitly toggled on
if ( this.notTypeBits !== 0 ) { if ( this.notTypeBits !== 0 ) {
if (
(this.notTypeBits & allNetworkTypesBits) !== 0 ||
(this.typeBits & allNetworkTypesBits) === 0
) {
this.typeBits |= allNetworkTypesBits;
}
this.typeBits &= ~this.notTypeBits; this.typeBits &= ~this.notTypeBits;
if ( this.typeBits === 0 ) { return false; } this.optionUnitBits |= this.NOT_TYPE_BIT;
} }
// CSP directives implicitly apply only to document/subdocument. // CSP directives implicitly apply only to document/subdocument.
@ -3434,6 +3483,11 @@ class FilterCompiler {
units.push(FilterAnchorRight.compile()); units.push(FilterAnchorRight.compile());
} }
// Not types
if ( this.notTypeBits !== 0 ) {
units.push(FilterNotType.compile(this));
}
// Strict partiness // Strict partiness
if ( this.strictParty !== 0 ) { if ( this.strictParty !== 0 ) {
units.push(FilterStrictParty.compile(this)); units.push(FilterStrictParty.compile(this));
@ -3492,22 +3546,21 @@ class FilterCompiler {
compileToAtomicFilter(fdata, writer) { compileToAtomicFilter(fdata, writer) {
const catBits = this.action | this.party; const catBits = this.action | this.party;
let { typeBits } = this; let { notTypeBits, typeBits } = this;
// Typeless // Typeless
if ( typeBits === 0 ) { if ( typeBits === 0 ) {
writer.push([ catBits, this.tokenHash, fdata ]); writer.push([ catBits, this.tokenHash, fdata ]);
return; return;
} }
// If all network types are set, create a typeless filter // If all network types are set, create a typeless filter. Excluded
if ( // network types are tested at match time, se we act as if they are
(typeBits & allNetworkTypesBits) === allNetworkTypesBits && // set.
(this.notTypeBits & documentTypeBit) === 0 if ( ((typeBits | notTypeBits) & allNetworkTypesBits) === allNetworkTypesBits ) {
) {
writer.push([ catBits, this.tokenHash, fdata ]); writer.push([ catBits, this.tokenHash, fdata ]);
typeBits &= ~allNetworkTypesBits; typeBits &= ~allNetworkTypesBits;
if ( typeBits === 0 ) { return; }
} }
// One filter per specific types // One filter per specific types
let bitOffset = 1; let bitOffset = 1;
do { do {
@ -3524,13 +3577,15 @@ class FilterCompiler {
} }
} }
FilterCompiler.prototype.DOMAIN_BIT = 0b00000001; FilterCompiler.prototype.DOMAIN_BIT = 0b000000001;
FilterCompiler.prototype.DENYALLOW_BIT = 0b00000010; FilterCompiler.prototype.DENYALLOW_BIT = 0b000000010;
FilterCompiler.prototype.HEADER_BIT = 0b00000100; FilterCompiler.prototype.HEADER_BIT = 0b000000100;
FilterCompiler.prototype.STRICT_PARTY_BIT = 0b00001000; FilterCompiler.prototype.STRICT_PARTY_BIT = 0b000001000;
FilterCompiler.prototype.CSP_BIT = 0b00010000; FilterCompiler.prototype.CSP_BIT = 0b000010000;
FilterCompiler.prototype.QUERYPRUNE_BIT = 0b00100000; FilterCompiler.prototype.QUERYPRUNE_BIT = 0b000100000;
FilterCompiler.prototype.REDIRECT_BIT = 0b01000000; FilterCompiler.prototype.REDIRECT_BIT = 0b001000000;
FilterCompiler.prototype.NOT_TYPE_BIT = 0b010000000;
FilterCompiler.prototype.IMPORTANT_BIT = 0b100000000;
FilterCompiler.prototype.FILTER_OK = 0; FilterCompiler.prototype.FILTER_OK = 0;
FilterCompiler.prototype.FILTER_INVALID = 1; FilterCompiler.prototype.FILTER_INVALID = 1;
@ -3896,14 +3951,16 @@ FilterContainer.prototype.matchAndFetchModifiers = function(
fctxt, fctxt,
modifierType modifierType
) { ) {
const typeBits = typeNameToTypeValue[fctxt.type] || otherTypeBitValue;
$requestURL = urlTokenizer.setURL(fctxt.url); $requestURL = urlTokenizer.setURL(fctxt.url);
$requestURLRaw = fctxt.url; $requestURLRaw = fctxt.url;
$docHostname = fctxt.getDocHostname(); $docHostname = fctxt.getDocHostname();
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$docEntity.reset(); $docEntity.reset();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset;
const typeBits = typeNameToTypeValue[fctxt.type] || otherTypeBitValue;
const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty; const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty;
const catBits00 = ModifyAction; const catBits00 = ModifyAction;
@ -4192,6 +4249,7 @@ FilterContainer.prototype.matchRequestReverse = function(type, url) {
// Prime tokenizer: we get a normalized URL in return. // Prime tokenizer: we get a normalized URL in return.
$requestURL = urlTokenizer.setURL(url); $requestURL = urlTokenizer.setURL(url);
$requestURLRaw = url; $requestURLRaw = url;
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset;
this.$filterUnit = 0; this.$filterUnit = 0;
// These registers will be used by various filters // These registers will be used by various filters
@ -4234,17 +4292,17 @@ FilterContainer.prototype.matchRequestReverse = function(type, url) {
* @returns {integer} 0=no match, 1=block, 2=allow (exeption) * @returns {integer} 0=no match, 1=block, 2=allow (exeption)
*/ */
FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) { FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) {
let typeValue = typeNameToTypeValue[fctxt.type]; let typeBits = typeNameToTypeValue[fctxt.type];
if ( modifiers === 0 ) { if ( modifiers === 0 ) {
if ( typeValue === undefined ) { if ( typeBits === undefined ) {
typeValue = otherTypeBitValue; typeBits = otherTypeBitValue;
} else if ( typeValue === 0 || typeValue > otherTypeBitValue ) { } else if ( typeBits === 0 || typeBits > otherTypeBitValue ) {
modifiers |= 0b0001; modifiers |= 0b0001;
} }
} }
if ( (modifiers & 0b0001) !== 0 ) { if ( (modifiers & 0b0001) !== 0 ) {
if ( typeValue === undefined ) { return 0; } if ( typeBits === undefined ) { return 0; }
typeValue |= 0x80000000; typeBits |= 0x80000000;
} }
const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty; const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty;
@ -4259,13 +4317,14 @@ FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) {
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$docEntity.reset(); $docEntity.reset();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset;
// Evaluate block realm before allow realm, and allow realm before // Evaluate block realm before allow realm, and allow realm before
// block-important realm, i.e. by order of likelihood of a match. // block-important realm, i.e. by order of likelihood of a match.
const r = this.realmMatchString(BlockAction, typeValue, partyBits); const r = this.realmMatchString(BlockAction, typeBits, partyBits);
if ( r || (modifiers & 0b0010) !== 0 ) { if ( r || (modifiers & 0b0010) !== 0 ) {
if ( this.realmMatchString(AllowAction, typeValue, partyBits) ) { if ( this.realmMatchString(AllowAction, typeBits, partyBits) ) {
if ( this.realmMatchString(BlockImportant, typeValue, partyBits) ) { if ( this.realmMatchString(BlockImportant, typeBits, partyBits) ) {
return 1; return 1;
} }
return 2; return 2;
@ -4278,7 +4337,7 @@ FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.matchHeaders = function(fctxt, headers) { FilterContainer.prototype.matchHeaders = function(fctxt, headers) {
const typeValue = typeNameToTypeValue[fctxt.type] || otherTypeBitValue; const typeBits = typeNameToTypeValue[fctxt.type] || otherTypeBitValue;
const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty; const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty;
// Prime tokenizer: we get a normalized URL in return. // Prime tokenizer: we get a normalized URL in return.
@ -4291,13 +4350,14 @@ FilterContainer.prototype.matchHeaders = function(fctxt, headers) {
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$docEntity.reset(); $docEntity.reset();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
$requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset;
$httpHeaders.init(headers); $httpHeaders.init(headers);
let r = 0; let r = 0;
if ( this.realmMatchString(HEADERS | BlockImportant, typeValue, partyBits) ) { if ( this.realmMatchString(HEADERS | BlockImportant, typeBits, partyBits) ) {
r = 1; r = 1;
} else if ( this.realmMatchString(HEADERS | BlockAction, typeValue, partyBits) ) { } else if ( this.realmMatchString(HEADERS | BlockAction, typeBits, partyBits) ) {
r = this.realmMatchString(HEADERS | AllowAction, typeValue, partyBits) r = this.realmMatchString(HEADERS | AllowAction, typeBits, partyBits)
? 2 ? 2
: 1; : 1;
} }

@ -1 +1 @@
Subproject commit cfe4ae71348fa08bfd5018a20946d13df3be9813 Subproject commit 5c857ff1834fa44a482e0190e212d933bb41d28a