Skip to content

Commit

Permalink
Add ability to quote static network option values
Browse files Browse the repository at this point in the history
For the sake of convenience for filter list maintainers, this commit
add ability to quote static network option values, so as to avoid the
need to escape commas when parser ambiguity arises.

The quotes can be `"`, `'`, or backticks.

Example, the following filter requires escaping commas:

  example.com$xhr,replace=/"loremIpsum.*?([A-Z]"\}|"\}{2\,4})\}\]\,//,1p

Can be now rewritten with no need to escape when using quotes:

  example.com$xhr,replace='/"loremIpsum.*?([A-Z]"\}|"\}{2,4})\}\],//',1p
  • Loading branch information
gorhill committed Sep 8, 2024
1 parent 1b464f7 commit 2011569
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 37 deletions.
1 change: 1 addition & 0 deletions src/js/codemirror/ubo-static-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ const uBOStaticFilteringMode = (( ) => {
mode.lastNetOptionType = nodeType;
return 'def';
case sfp.NODE_TYPE_NET_OPTION_ASSIGN:
case sfp.NODE_TYPE_NET_OPTION_QUOTE:
return 'def';
case sfp.NODE_TYPE_NET_OPTION_VALUE:
if ( mode.astWalker.canGoDown() ) { break; }
Expand Down
105 changes: 70 additions & 35 deletions src/js/static-filtering-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ 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_WEBSOCKET = iota++;
export const NODE_TYPE_NET_OPTION_ASSIGN = iota++;
export const NODE_TYPE_NET_OPTION_QUOTE = iota++;
export const NODE_TYPE_NET_OPTION_VALUE = iota++;
export const NODE_TYPE_OPTION_VALUE_DOMAIN_LIST = iota++;
export const NODE_TYPE_OPTION_VALUE_DOMAIN_RAW = iota++;
Expand Down Expand Up @@ -896,7 +897,9 @@ export class AstFilterParser {
this.reGoodRegexToken = /[^\x01%0-9A-Za-z][%0-9A-Za-z]{7,}|[^\x01%0-9A-Za-z][%0-9A-Za-z]{1,6}[^\x01%0-9A-Za-z]/;
this.reBadCSP = /(?:^|[;,])\s*report-(?:to|uri)\b/i;
this.reBadPP = /(?:^|[;,])\s*report-to\b/i;
this.reNetOption = /^(~?)([13a-z_-]+)(=?)/;
this.reNoopOption = /^_+$/;
this.netOptionValueParser = new ArgListParser(',');
this.scriptletArgListParser = new ArgListParser(',');
}

Expand Down Expand Up @@ -1959,16 +1962,17 @@ export class AstFilterParser {
const head = this.allocHeadNode();
let prev = head, next = 0;
let optionBeg = 0, optionEnd = 0;
let emptyOption = false, badComma = false;
while ( optionBeg !== optionsEnd ) {
optionEnd = this.endOfNetOption(s, optionBeg);
next = this.allocTypedNode(
NODE_TYPE_NET_OPTION_RAW,
parentBeg + optionBeg,
parentBeg + optionEnd
parentBeg + optionsEnd // open ended
);
emptyOption = optionEnd === optionBeg;
this.linkDown(next, this.parseNetOption(next));
const { node: down, len: optionLen } = this.parseNetOption(next);
// set next's end to down's end
optionEnd += optionLen;
this.nodes[next+NODE_END_INDEX] = parentBeg + optionEnd;
this.linkDown(next, down);
prev = this.linkRight(prev, next);
if ( optionEnd === optionsEnd ) { break; }
optionBeg = optionEnd + 1;
Expand All @@ -1977,44 +1981,46 @@ export class AstFilterParser {
parentBeg + optionEnd,
parentBeg + optionBeg
);
badComma = optionBeg === optionsEnd;
prev = this.linkRight(prev, next);
if ( emptyOption || badComma ) {
if ( optionLen === 0 || optionBeg === optionsEnd ) {
this.addNodeFlags(next, NODE_FLAG_ERROR);
this.addFlags(AST_FLAG_HAS_ERROR);
}
prev = this.linkRight(prev, next);
optionEnd = optionBeg;
}
this.linkRight(prev,
this.allocSentinelNode(NODE_TYPE_NET_OPTION_SENTINEL, parentEnd)
);
return this.throwHeadNode(head);
}

endOfNetOption(s, beg) {
const match = this.reNetOptionComma.exec(s.slice(beg));
return match !== null ? beg + match.index : s.length;
}

parseNetOption(parent) {
const parentBeg = this.nodes[parent+NODE_BEG_INDEX];
const s = this.getNodeString(parent);
const optionEnd = s.length;
const match = this.reNetOption.exec(s) || [];
if ( match.length === 0 ) {
this.addNodeFlags(parent, NODE_FLAG_ERROR);
this.addFlags(AST_FLAG_HAS_ERROR);
this.astError = AST_ERROR_OPTION_UNKNOWN;
return { node: 0, len: s.length };
}
const head = this.allocHeadNode();
let prev = head, next = 0;
let nameBeg = 0;
if ( s.charCodeAt(0) === 0x7E ) {
const matchEnd = match && match[0].length || 0;
const negated = match[1] === '~';
if ( negated ) {
this.addNodeFlags(parent, NODE_FLAG_IS_NEGATED);
next = this.allocTypedNode(
NODE_TYPE_NET_OPTION_NAME_NOT,
parentBeg,
parentBeg+1
);
prev = this.linkRight(prev, next);
nameBeg += 1;
}
const equalPos = s.indexOf('=');
const nameEnd = equalPos !== -1 ? equalPos : s.length;
const name = s.slice(nameBeg, nameEnd);
const nameBeg = negated ? 1 : 0;
const assigned = match[3] === '=';
const nameEnd = matchEnd - (assigned ? 1 : 0);
const name = match[2] || '';
let nodeOptionType = nodeTypeFromOptionName.get(name);
if ( nodeOptionType === undefined ) {
nodeOptionType = this.reNoopOption.test(name)
Expand All @@ -2037,27 +2043,43 @@ export class AstFilterParser {
this.addNodeToRegister(nodeOptionType, parent);
}
prev = this.linkRight(prev, next);
if ( equalPos === -1 ) {
return this.throwHeadNode(head);
if ( assigned === false ) {
return { node: this.throwHeadNode(head), len: matchEnd };
}
const valueBeg = equalPos + 1;
next = this.allocTypedNode(
NODE_TYPE_NET_OPTION_ASSIGN,
parentBeg + equalPos,
parentBeg + valueBeg
parentBeg + matchEnd - 1,
parentBeg + matchEnd
);
prev = this.linkRight(prev, next);
if ( (equalPos+1) === optionEnd ) {
this.addNodeFlags(parent, NODE_FLAG_ERROR);
this.addFlags(AST_FLAG_HAS_ERROR);
return this.throwHeadNode(head);
}
this.addNodeFlags(parent, NODE_FLAG_OPTION_HAS_VALUE);
const details = this.netOptionValueParser.nextArg(s, matchEnd);
if ( details.quoteBeg !== details.argBeg ) {
next = this.allocTypedNode(
NODE_TYPE_NET_OPTION_QUOTE,
parentBeg + details.quoteBeg,
parentBeg + details.argBeg
);
prev = this.linkRight(prev, next);
} else {
const argEnd = this.endOfNetOption(s, matchEnd);
if ( argEnd !== details.argEnd ) {
details.argEnd = details.quoteEnd = argEnd;
}
}
next = this.allocTypedNode(
NODE_TYPE_NET_OPTION_VALUE,
parentBeg + valueBeg,
parentBeg + optionEnd
parentBeg + details.argBeg,
parentBeg + details.argEnd
);
if ( details.argBeg === details.argEnd ) {
this.addNodeFlags(parent, NODE_FLAG_ERROR);
this.addFlags(AST_FLAG_HAS_ERROR);
this.astError = AST_ERROR_OPTION_BADVALUE;
} else if ( details.transform ) {
const arg = s.slice(details.argBeg, details.argEnd);
this.setNodeTransform(next, this.netOptionValueParser.normalizeArg(arg));
}
switch ( nodeOptionType ) {
case NODE_TYPE_NET_OPTION_NAME_DENYALLOW:
this.linkDown(next, this.parseDomainList(next, '|'), 0b00000);
Expand All @@ -2069,8 +2091,21 @@ export class AstFilterParser {
default:
break;
}
this.linkRight(prev, next);
return this.throwHeadNode(head);
prev = this.linkRight(prev, next);
if ( details.quoteEnd !== details.argEnd ) {
next = this.allocTypedNode(
NODE_TYPE_NET_OPTION_QUOTE,
parentBeg + details.argEnd,
parentBeg + details.quoteEnd
);
this.linkRight(prev, next);
}
return { node: this.throwHeadNode(head), len: details.quoteEnd };
}

endOfNetOption(s, beg) {
const match = this.reNetOptionComma.exec(s.slice(beg));
return match !== null ? beg + match.index : s.length;
}

getNetOptionValue(type) {
Expand Down Expand Up @@ -3086,8 +3121,8 @@ export const netOptionTokenDescriptors = new Map([
/* synonym */ [ 'rewrite', { mustAssign: true } ],
[ 'redirect-rule', { mustAssign: true } ],
[ 'removeparam', { } ],
[ 'replace', { mustAssign: true } ],
/* synonym */ [ 'queryprune', { } ],
[ 'replace', { mustAssign: true } ],
[ 'script', { canNegate: true } ],
[ 'shide', { } ],
/* synonym */ [ 'specifichide', { } ],
Expand Down
12 changes: 10 additions & 2 deletions src/js/static-net-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,14 @@ class LogData {
isPureHostname() {
return this.tokenHash === DOT_TOKEN_HASH;
}

static requote(s) {
if ( /^(["'`]).+\1$|,/.test(s) === false ) { return s; }
if ( s.includes("'") === false ) { return `'${s}'`; }
if ( s.includes('"') === false ) { return `"${s}"`; }
if ( s.includes('`') === false ) { return `\`${s}\``; }
return `'${s.replace(/'/g, "\\'")}'`;
}
}

/******************************************************************************/
Expand Down Expand Up @@ -2128,7 +2136,7 @@ class FilterModifier {
let opt = modifierNameFromType.get(filterData[idata+2]);
const refs = filterRefs[filterData[idata+3]];
if ( refs.value !== '' ) {
opt += `=${refs.value}`;
opt += `=${LogData.requote(refs.value)}`;
}
details.options.push(opt);
}
Expand Down Expand Up @@ -2947,7 +2955,7 @@ class FilterOnHeaders {
const headerOpt = filterRefs[irefs].headerOpt;
let opt = 'header';
if ( headerOpt !== '' ) {
opt += `=${headerOpt}`;
opt += `=${LogData.requote(headerOpt)}`;
}
details.options.push(opt);
}
Expand Down

0 comments on commit 2011569

Please sign in to comment.