Skip to content

Commit

Permalink
New static network filter option ipaddress=
Browse files Browse the repository at this point in the history
The purpose is to block according to the ip address of a network
request. In the current implementation, the filter option can only
be enforced at onHeadersReceived time.

The new filter option cannot be enforced in Chromium-based browsers
since the ip address of network requests is available only at
onResponseStarted time, which is not blocking.

The value assigned to `ipaddress` can either be a plain string which
must match exactly a given ip address, or a regex which will be
matched against the ip address.

The `ipaddress` option can only be enforced when the extension
framework does provide a valid ip address in a onHeadersReceived
listener. For instance, cached resources do not have a valid ip
address and thus can't be a match to `ipaddress` option.

Example:

  *$script,ipaddress=93.184.215.14
  • Loading branch information
gorhill committed Sep 9, 2024
1 parent 2011569 commit c6dedd2
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 100 deletions.
1 change: 1 addition & 0 deletions src/js/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ const µBlock = { // jshint ignore:line
this.setMethod(details.method);
this.setURL(details.url);
this.aliasURL = details.aliasURL || undefined;
this.ipaddress = details.ip || undefined;
this.redirectURL = undefined;
this.filter = undefined;
if ( this.itype !== this.SUB_FRAME ) {
Expand Down
109 changes: 68 additions & 41 deletions src/js/filtering-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/

'use strict';

/******************************************************************************/

import {
hostnameFromURI,
domainFromHostname,
hostnameFromURI,
originFromURI,
} from './uri-utils.js';

Expand Down Expand Up @@ -140,6 +136,7 @@ export const FilteringContext = class {
this.stype = undefined;
this.url = undefined;
this.aliasURL = undefined;
this.ipaddress = undefined;
this.hostname = undefined;
this.domain = undefined;
this.docId = -1;
Expand Down Expand Up @@ -418,42 +415,72 @@ export const FilteringContext = class {
static getMethodName(a) {
return methodBitToStrMap.get(a) || '';
}
};

/******************************************************************************/

FilteringContext.prototype.BEACON = FilteringContext.BEACON = BEACON;
FilteringContext.prototype.CSP_REPORT = FilteringContext.CSP_REPORT = CSP_REPORT;
FilteringContext.prototype.FONT = FilteringContext.FONT = FONT;
FilteringContext.prototype.IMAGE = FilteringContext.IMAGE = IMAGE;
FilteringContext.prototype.IMAGESET = FilteringContext.IMAGESET = IMAGESET;
FilteringContext.prototype.MAIN_FRAME = FilteringContext.MAIN_FRAME = MAIN_FRAME;
FilteringContext.prototype.MEDIA = FilteringContext.MEDIA = MEDIA;
FilteringContext.prototype.OBJECT = FilteringContext.OBJECT = OBJECT;
FilteringContext.prototype.OBJECT_SUBREQUEST = FilteringContext.OBJECT_SUBREQUEST = OBJECT_SUBREQUEST;
FilteringContext.prototype.PING = FilteringContext.PING = PING;
FilteringContext.prototype.SCRIPT = FilteringContext.SCRIPT = SCRIPT;
FilteringContext.prototype.STYLESHEET = FilteringContext.STYLESHEET = STYLESHEET;
FilteringContext.prototype.SUB_FRAME = FilteringContext.SUB_FRAME = SUB_FRAME;
FilteringContext.prototype.WEBSOCKET = FilteringContext.WEBSOCKET = WEBSOCKET;
FilteringContext.prototype.XMLHTTPREQUEST = FilteringContext.XMLHTTPREQUEST = XMLHTTPREQUEST;
FilteringContext.prototype.INLINE_FONT = FilteringContext.INLINE_FONT = INLINE_FONT;
FilteringContext.prototype.INLINE_SCRIPT = FilteringContext.INLINE_SCRIPT = INLINE_SCRIPT;
FilteringContext.prototype.OTHER = FilteringContext.OTHER = OTHER;
FilteringContext.prototype.FRAME_ANY = FilteringContext.FRAME_ANY = FRAME_ANY;
FilteringContext.prototype.FONT_ANY = FilteringContext.FONT_ANY = FONT_ANY;
FilteringContext.prototype.INLINE_ANY = FilteringContext.INLINE_ANY = INLINE_ANY;
FilteringContext.prototype.PING_ANY = FilteringContext.PING_ANY = PING_ANY;
FilteringContext.prototype.SCRIPT_ANY = FilteringContext.SCRIPT_ANY = SCRIPT_ANY;

FilteringContext.prototype.METHOD_NONE = FilteringContext.METHOD_NONE = METHOD_NONE;
FilteringContext.prototype.METHOD_CONNECT = FilteringContext.METHOD_CONNECT = METHOD_CONNECT;
FilteringContext.prototype.METHOD_DELETE = FilteringContext.METHOD_DELETE = METHOD_DELETE;
FilteringContext.prototype.METHOD_GET = FilteringContext.METHOD_GET = METHOD_GET;
FilteringContext.prototype.METHOD_HEAD = FilteringContext.METHOD_HEAD = METHOD_HEAD;
FilteringContext.prototype.METHOD_OPTIONS = FilteringContext.METHOD_OPTIONS = METHOD_OPTIONS;
FilteringContext.prototype.METHOD_PATCH = FilteringContext.METHOD_PATCH = METHOD_PATCH;
FilteringContext.prototype.METHOD_POST = FilteringContext.METHOD_POST = METHOD_POST;
FilteringContext.prototype.METHOD_PUT = FilteringContext.METHOD_PUT = METHOD_PUT;
BEACON = BEACON;
CSP_REPORT = CSP_REPORT;
FONT = FONT;
IMAGE = IMAGE;
IMAGESET = IMAGESET;
MAIN_FRAME = MAIN_FRAME;
MEDIA = MEDIA;
OBJECT = OBJECT;
OBJECT_SUBREQUEST = OBJECT_SUBREQUEST;
PING = PING;
SCRIPT = SCRIPT;
STYLESHEET = STYLESHEET;
SUB_FRAME = SUB_FRAME;
WEBSOCKET = WEBSOCKET;
XMLHTTPREQUEST = XMLHTTPREQUEST;
INLINE_FONT = INLINE_FONT;
INLINE_SCRIPT = INLINE_SCRIPT;
OTHER = OTHER;
FRAME_ANY = FRAME_ANY;
FONT_ANY = FONT_ANY;
INLINE_ANY = INLINE_ANY;
PING_ANY = PING_ANY;
SCRIPT_ANY = SCRIPT_ANY;
METHOD_NONE = METHOD_NONE;
METHOD_CONNECT = METHOD_CONNECT;
METHOD_DELETE = METHOD_DELETE;
METHOD_GET = METHOD_GET;
METHOD_HEAD = METHOD_HEAD;
METHOD_OPTIONS = METHOD_OPTIONS;
METHOD_PATCH = METHOD_PATCH;
METHOD_POST = METHOD_POST;
METHOD_PUT = METHOD_PUT;

static BEACON = BEACON;
static CSP_REPORT = CSP_REPORT;
static FONT = FONT;
static IMAGE = IMAGE;
static IMAGESET = IMAGESET;
static MAIN_FRAME = MAIN_FRAME;
static MEDIA = MEDIA;
static OBJECT = OBJECT;
static OBJECT_SUBREQUEST = OBJECT_SUBREQUEST;
static PING = PING;
static SCRIPT = SCRIPT;
static STYLESHEET = STYLESHEET;
static SUB_FRAME = SUB_FRAME;
static WEBSOCKET = WEBSOCKET;
static XMLHTTPREQUEST = XMLHTTPREQUEST;
static INLINE_FONT = INLINE_FONT;
static INLINE_SCRIPT = INLINE_SCRIPT;
static OTHER = OTHER;
static FRAME_ANY = FRAME_ANY;
static FONT_ANY = FONT_ANY;
static INLINE_ANY = INLINE_ANY;
static PING_ANY = PING_ANY;
static SCRIPT_ANY = SCRIPT_ANY;
static METHOD_NONE = METHOD_NONE;
static METHOD_CONNECT = METHOD_CONNECT;
static METHOD_DELETE = METHOD_DELETE;
static METHOD_GET = METHOD_GET;
static METHOD_HEAD = METHOD_HEAD;
static METHOD_OPTIONS = METHOD_OPTIONS;
static METHOD_PATCH = METHOD_PATCH;
static METHOD_POST = METHOD_POST;
static METHOD_PUT = METHOD_PUT;
};

/******************************************************************************/
19 changes: 17 additions & 2 deletions src/js/static-filtering-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const NODE_TYPE_NET_OPTION_NAME_IMAGE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_IMPORTANT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_INLINEFONT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT = iota++;
export const NODE_TYPE_NET_OPTION_NAME_IPADDRESS = iota++;
export const NODE_TYPE_NET_OPTION_NAME_MATCHCASE = iota++;
export const NODE_TYPE_NET_OPTION_NAME_MEDIA = iota++;
export const NODE_TYPE_NET_OPTION_NAME_METHOD = iota++;
Expand Down Expand Up @@ -249,6 +250,7 @@ export const nodeTypeFromOptionName = new Map([
[ 'important', NODE_TYPE_NET_OPTION_NAME_IMPORTANT ],
[ 'inline-font', NODE_TYPE_NET_OPTION_NAME_INLINEFONT ],
[ 'inline-script', NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT ],
[ 'ipaddress', NODE_TYPE_NET_OPTION_NAME_IPADDRESS ],
[ 'match-case', NODE_TYPE_NET_OPTION_NAME_MATCHCASE ],
[ 'media', NODE_TYPE_NET_OPTION_NAME_MEDIA ],
[ 'method', NODE_TYPE_NET_OPTION_NAME_METHOD ],
Expand Down Expand Up @@ -1401,6 +1403,14 @@ export class AstFilterParser {
modifierType = type;
unredirectableTypeCount += 1;
break;
case NODE_TYPE_NET_OPTION_NAME_IPADDRESS: {
const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_IPADDRESS);
if ( /^\/.+\/$/.test(value) ) {
try { void new RegExp(value); }
catch(_) { realBad = true; }
}
break;
}
case NODE_TYPE_NET_OPTION_NAME_MATCHCASE:
realBad = this.isRegexPattern() === false;
break;
Expand Down Expand Up @@ -3104,6 +3114,7 @@ export const netOptionTokenDescriptors = new Map([
[ 'important', { blockOnly: true } ],
[ 'inline-font', { canNegate: true } ],
[ 'inline-script', { canNegate: true } ],
[ 'ipaddress', { mustAssign: true } ],
[ 'match-case', { } ],
[ 'media', { canNegate: true } ],
[ 'method', { mustAssign: true } ],
Expand Down Expand Up @@ -4324,6 +4335,7 @@ export const utils = (( ) => {
[ 'env_safari', 'safari' ],
[ 'cap_html_filtering', 'html_filtering' ],
[ 'cap_user_stylesheet', 'user_stylesheet' ],
[ 'cap_ipaddress', 'ipaddress' ],
[ 'false', 'false' ],
// Hoping ABP-only list maintainers can at least make use of it to
// help non-ABP content blockers better deal with filters benefiting
Expand Down Expand Up @@ -4358,8 +4370,11 @@ export const utils = (( ) => {
static evaluateExprToken(token, env = []) {
const not = token.charCodeAt(0) === 0x21 /* ! */;
if ( not ) { token = token.slice(1); }
const state = preparserTokens.get(token);
if ( state === undefined ) { return; }
let state = preparserTokens.get(token);
if ( state === undefined ) {
if ( token.startsWith('cap_') === false ) { return; }
state = 'false';
}
return state === 'false' && not || env.includes(state) !== not;
}

Expand Down
Loading

0 comments on commit c6dedd2

Please sign in to comment.