From b6981877ba8f9011292aee9556c4d4c08c1bfd2d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 18 Dec 2022 15:36:59 -0500 Subject: [PATCH] Add option to filter by HTTP method in static network filters Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/2117 Option: `method=` Value: a list of `|`-separated lowercased method names. Negated method names are allowed. These are valid methods: - connect - delete - get - head - options - patch - post - put As per DNR's own documentation: - https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#type-RequestMethod The logger shows the method used for every network request. It's possible to filter the logger output for most-common methods: `get`, `head`, `post`. --- .jshintrc | 1 + src/js/background.js | 44 +++++---- src/js/filtering-context.js | 73 +++++++++++++- src/js/logger-ui.js | 156 +++++++++++++++++------------- src/js/start.js | 2 +- src/js/static-filtering-parser.js | 7 +- src/js/static-net-filtering.js | 106 ++++++++++++++++++++ src/logger-ui.html | 5 +- 8 files changed, 305 insertions(+), 89 deletions(-) diff --git a/.jshintrc b/.jshintrc index 92bcdc78b..b1f443e4d 100644 --- a/.jshintrc +++ b/.jshintrc @@ -5,6 +5,7 @@ "esversion": 8, "globals": { "chrome": false, // global variable in Chromium, Chrome, Opera + "globalThis": false, "self": false, "vAPI": false, "URLSearchParams": false, diff --git a/src/js/background.js b/src/js/background.js index b91a2fb69..68b94457b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -176,8 +176,8 @@ const µBlock = { // jshint ignore:line // Read-only systemSettings: { - compiledMagic: 50, // Increase when compiled format changes - selfieMagic: 50, // Increase when selfie format changes + compiledMagic: 51, // Increase when compiled format changes + selfieMagic: 51, // Increase when selfie format changes }, // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 @@ -285,6 +285,7 @@ const µBlock = { // jshint ignore:line this.fromTabId(tabId); // Must be called AFTER tab context management this.realm = ''; this.id = details.requestId; + this.setMethod(details.method); this.setURL(details.url); this.aliasURL = details.aliasURL || undefined; if ( this.itype !== this.SUB_FRAME ) { @@ -337,24 +338,31 @@ const µBlock = { // jshint ignore:line } toLogger() { - this.tstamp = Date.now(); - if ( this.domain === undefined ) { - void this.getDomain(); - } - if ( this.docDomain === undefined ) { - void this.getDocDomain(); - } - if ( this.tabDomain === undefined ) { - void this.getTabDomain(); - } - const filters = this.filter; + const details = { + id: this.id, + tstamp: Date.now(), + realm: this.realm, + method: this.getMethodName(), + type: this.stype, + tabId: this.tabId, + tabDomain: this.getTabDomain(), + tabHostname: this.getTabHostname(), + docDomain: this.getDocDomain(), + docHostname: this.getDocHostname(), + domain: this.getDomain(), + hostname: this.getHostname(), + url: this.url, + aliasURL: this.aliasURL, + filter: undefined, + }; // Many filters may have been applied to the current context - if ( Array.isArray(filters) === false ) { - return logger.writeOne(this); + if ( Array.isArray(this.filter) === false ) { + details.filter = this.filter; + return logger.writeOne(details); } - for ( const filter of filters ) { - this.filter = filter; - logger.writeOne(this); + for ( const filter of this.filter ) { + details.filter = filter; + logger.writeOne(details); } } }; diff --git a/src/js/filtering-context.js b/src/js/filtering-context.js index 8d90d382d..bb3b7215d 100644 --- a/src/js/filtering-context.js +++ b/src/js/filtering-context.js @@ -84,6 +84,48 @@ const typeStrToIntMap = { 'other': OTHER, }; +const METHOD_NONE = 0; +const METHOD_CONNECT = 1 << 1; +const METHOD_DELETE = 1 << 2; +const METHOD_GET = 1 << 3; +const METHOD_HEAD = 1 << 4; +const METHOD_OPTIONS = 1 << 5; +const METHOD_PATCH = 1 << 6; +const METHOD_POST = 1 << 7; +const METHOD_PUT = 1 << 8; + +const methodStrToBitMap = { + '': METHOD_NONE, + 'connect': METHOD_CONNECT, + 'delete': METHOD_DELETE, + 'get': METHOD_GET, + 'head': METHOD_HEAD, + 'options': METHOD_OPTIONS, + 'patch': METHOD_PATCH, + 'post': METHOD_POST, + 'put': METHOD_PUT, + 'CONNECT': METHOD_CONNECT, + 'DELETE': METHOD_DELETE, + 'GET': METHOD_GET, + 'HEAD': METHOD_HEAD, + 'OPTIONS': METHOD_OPTIONS, + 'PATCH': METHOD_PATCH, + 'POST': METHOD_POST, + 'PUT': METHOD_PUT, +}; + +const methodBitToStrMap = new Map([ + [ METHOD_NONE, '' ], + [ METHOD_CONNECT, 'connect' ], + [ METHOD_DELETE, 'delete' ], + [ METHOD_GET, 'get' ], + [ METHOD_HEAD, 'head' ], + [ METHOD_OPTIONS, 'options' ], + [ METHOD_PATCH, 'patch' ], + [ METHOD_POST, 'post' ], + [ METHOD_PUT, 'put' ], +]); + /******************************************************************************/ const FilteringContext = class { @@ -94,7 +136,8 @@ const FilteringContext = class { this.tstamp = 0; this.realm = ''; this.id = undefined; - this.itype = 0; + this.method = 0; + this.itype = NO_TYPE; this.stype = undefined; this.url = undefined; this.aliasURL = undefined; @@ -133,6 +176,7 @@ const FilteringContext = class { fromFilteringContext(other) { this.realm = other.realm; this.type = other.type; + this.method = other.method; this.url = other.url; this.hostname = other.hostname; this.domain = other.domain; @@ -358,6 +402,23 @@ const FilteringContext = class { } return this; } + + setMethod(a) { + this.method = methodStrToBitMap[a] || 0; + return this; + } + + getMethodName() { + return FilteringContext.getMethodName(this.method); + } + + static getMethod(a) { + return methodStrToBitMap[a] || 0; + } + + static getMethodName(a) { + return methodBitToStrMap.get(a) || ''; + } }; /******************************************************************************/ @@ -386,6 +447,16 @@ 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; + /******************************************************************************/ export { FilteringContext }; diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index f97d86745..701dbf4a5 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -36,6 +36,16 @@ const logDate = new Date(); const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; const loggerEntries = []; +const COLUMN_TIMESTAMP = 0; +const COLUMN_FILTER = 1; +const COLUMN_MESSAGE = 1; +const COLUMN_RESULT = 2; +const COLUMN_INITIATOR = 3; +const COLUMN_PARTYNESS = 4; +const COLUMN_METHOD = 5; +const COLUMN_TYPE = 6; +const COLUMN_URL = 7; + let filteredLoggerEntries = []; let filteredLoggerEntryVoidedCount = 0; @@ -211,7 +221,6 @@ const LogEntry = function(details) { this[prop] = details[prop]; } } - this.type = details.stype || details.type; if ( details.aliasURL !== undefined ) { this.aliased = true; } @@ -233,6 +242,7 @@ LogEntry.prototype = { domain: '', filter: undefined, id: '', + method: '', realm: '', tabDomain: '', tabHostname: '', @@ -413,17 +423,20 @@ const parseLogEntry = function(details) { textContent.push(''); } - // Cell 5 + // Cell 5: method + textContent.push(entry.method || ''); + + // Cell 6 textContent.push( normalizeToStr(prettyRequestTypes[entry.type] || entry.type) ); - // Cell 6 + // Cell 7 textContent.push(normalizeToStr(details.url)); // Hidden cells -- useful for row-filtering purpose - // Cell 7 + // Cell 8 if ( entry.aliased ) { textContent.push(`aliasURL=${details.aliasURL}`); } @@ -534,49 +547,56 @@ const viewPort = (( ) => { : 0; }); const reservedWidth = - cellWidths[0] + cellWidths[2] + cellWidths[4] + cellWidths[5]; - cellWidths[6] = 0.5; - if ( cellWidths[1] === 0 && cellWidths[3] === 0 ) { - cellWidths[6] = 1; - } else if ( cellWidths[1] === 0 ) { - cellWidths[3] = 0.35; - cellWidths[6] = 0.65; - } else if ( cellWidths[3] === 0 ) { - cellWidths[1] = 0.35; - cellWidths[6] = 0.65; + cellWidths[COLUMN_TIMESTAMP] + + cellWidths[COLUMN_RESULT] + + cellWidths[COLUMN_PARTYNESS] + + cellWidths[COLUMN_METHOD] + + cellWidths[COLUMN_TYPE]; + cellWidths[COLUMN_URL] = 0.5; + if ( cellWidths[COLUMN_FILTER] === 0 && cellWidths[COLUMN_INITIATOR] === 0 ) { + cellWidths[COLUMN_URL] = 1; + } else if ( cellWidths[COLUMN_FILTER] === 0 ) { + cellWidths[COLUMN_INITIATOR] = 0.35; + cellWidths[COLUMN_URL] = 0.65; + } else if ( cellWidths[COLUMN_INITIATOR] === 0 ) { + cellWidths[COLUMN_FILTER] = 0.35; + cellWidths[COLUMN_URL] = 0.65; } else { - cellWidths[1] = 0.25; - cellWidths[3] = 0.25; - cellWidths[6] = 0.5; + cellWidths[COLUMN_FILTER] = 0.25; + cellWidths[COLUMN_INITIATOR] = 0.25; + cellWidths[COLUMN_URL] = 0.5; } const style = qs$('#vwRendererRuntimeStyles'); const cssRules = [ '#vwContent .logEntry {', ` height: ${newLineHeight}px;`, '}', - '#vwContent .logEntry > div > span:nth-of-type(1) {', - ` width: ${cellWidths[0]}px;`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_TIMESTAMP+1}) {`, + ` width: ${cellWidths[COLUMN_TIMESTAMP]}px;`, '}', - '#vwContent .logEntry > div > span:nth-of-type(2) {', - ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[1]});`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_FILTER+1}) {`, + ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[COLUMN_FILTER]});`, '}', - '#vwContent .logEntry > div.messageRealm > span:nth-of-type(2) {', - ` width: calc(100% - ${cellWidths[0]}px);`, + `#vwContent .logEntry > div.messageRealm > span:nth-of-type(${COLUMN_MESSAGE+1}) {`, + ` width: calc(100% - ${cellWidths[COLUMN_MESSAGE]}px);`, '}', - '#vwContent .logEntry > div > span:nth-of-type(3) {', - ` width: ${cellWidths[2]}px;`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_RESULT+1}) {`, + ` width: ${cellWidths[COLUMN_RESULT]}px;`, '}', - '#vwContent .logEntry > div > span:nth-of-type(4) {', - ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[3]});`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_INITIATOR+1}) {`, + ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[COLUMN_INITIATOR]});`, '}', - '#vwContent .logEntry > div > span:nth-of-type(5) {', - ` width: ${cellWidths[4]}px;`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_PARTYNESS+1}) {`, + ` width: ${cellWidths[COLUMN_PARTYNESS]}px;`, '}', - '#vwContent .logEntry > div > span:nth-of-type(6) {', - ` width: ${cellWidths[5]}px;`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_METHOD+1}) {`, + ` width: ${cellWidths[COLUMN_METHOD]}px;`, '}', - '#vwContent .logEntry > div > span:nth-of-type(7) {', - ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[6]});`, + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_TYPE+1}) {`, + ` width: ${cellWidths[COLUMN_TYPE]}px;`, + '}', + `#vwContent .logEntry > div > span:nth-of-type(${COLUMN_URL+1}) {`, + ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[COLUMN_URL]});`, '}', '', ]; @@ -651,8 +671,8 @@ const viewPort = (( ) => { } // Timestamp - span = div.children[0]; - span.textContent = cells[0]; + span = div.children[COLUMN_TIMESTAMP]; + span.textContent = cells[COLUMN_TIMESTAMP]; // Tab id if ( details.tabId !== undefined ) { @@ -666,8 +686,8 @@ const viewPort = (( ) => { if ( details.type !== undefined ) { dom.attr(div, 'data-type', details.type); } - span = div.children[1]; - span.textContent = cells[1]; + span = div.children[COLUMN_MESSAGE]; + span.textContent = cells[COLUMN_MESSAGE]; return div; } @@ -692,23 +712,23 @@ const viewPort = (( ) => { dom.attr(div, 'data-modifier', ''); } } - span = div.children[1]; - if ( renderFilterToSpan(span, cells[1]) === false ) { - span.textContent = cells[1]; + span = div.children[COLUMN_FILTER]; + if ( renderFilterToSpan(span, cells[COLUMN_FILTER]) === false ) { + span.textContent = cells[COLUMN_FILTER]; } // Event - if ( cells[2] === '--' ) { + if ( cells[COLUMN_RESULT] === '--' ) { dom.attr(div, 'data-status', '1'); - } else if ( cells[2] === '++' ) { + } else if ( cells[COLUMN_RESULT] === '++' ) { dom.attr(div, 'data-status', '2'); - } else if ( cells[2] === '**' ) { + } else if ( cells[COLUMN_RESULT] === '**' ) { dom.attr(div, 'data-status', '3'); - } else if ( cells[2] === '<<' ) { + } else if ( cells[COLUMN_RESULT] === '<<' ) { divcl.add('redirect'); } - span = div.children[2]; - span.textContent = cells[2]; + span = div.children[COLUMN_RESULT]; + span.textContent = cells[COLUMN_RESULT]; // Origins if ( details.tabHostname ) { @@ -717,12 +737,12 @@ const viewPort = (( ) => { if ( details.docHostname ) { dom.attr(div, 'data-dochn', details.docHostname); } - span = div.children[3]; - span.textContent = cells[3]; + span = div.children[COLUMN_INITIATOR]; + span.textContent = cells[COLUMN_INITIATOR]; // Partyness if ( - cells[4] !== '' && + cells[COLUMN_PARTYNESS] !== '' && details.realm === 'network' && details.domain !== undefined ) { @@ -733,12 +753,16 @@ const viewPort = (( ) => { text += ` \u21d2 ${details.domain}`; dom.attr(div, 'data-parties', text); } - span = div.children[4]; - span.textContent = cells[4]; + span = div.children[COLUMN_PARTYNESS]; + span.textContent = cells[COLUMN_PARTYNESS]; + + // Method + span = div.children[COLUMN_METHOD]; + span.textContent = cells[COLUMN_METHOD]; // Type - span = div.children[5]; - span.textContent = cells[5]; + span = div.children[COLUMN_TYPE]; + span.textContent = cells[COLUMN_TYPE]; // URL let re; @@ -747,7 +771,7 @@ const viewPort = (( ) => { } else if ( filteringType === 'dynamicUrl' ) { re = regexFromURLFilteringResult(filter.rule.join(' ')); } - nodeFromURL(div.children[6], cells[6], re); + nodeFromURL(div.children[COLUMN_URL], cells[COLUMN_URL], re); // Alias URL (CNAME, etc.) if ( cells.length > 7 ) { @@ -1468,7 +1492,7 @@ const reloadTab = function(ev) { }; const filterFromTargetRow = function() { - return dom.text(targetRow.children[1]); + return dom.text(targetRow.children[COLUMN_FILTER]); }; const aliasURLFromID = function(id) { @@ -1476,13 +1500,13 @@ const reloadTab = function(ev) { for ( const entry of loggerEntries ) { if ( entry.id !== id || entry.aliased ) { continue; } const fields = entry.textContent.split('\t'); - return fields[6] || ''; + return fields[COLUMN_URL] || ''; } return ''; }; const toSummaryPaneFilterNode = async function(receiver, filter) { - receiver.children[1].textContent = filter; + receiver.children[COLUMN_FILTER].textContent = filter; if ( dom.cl.has(targetRow, 'canLookup') === false ) { return; } const isException = reIsExceptionFilter.test(filter); let isExcepted = false; @@ -1498,7 +1522,7 @@ const reloadTab = function(ev) { }; const fillSummaryPaneFilterList = async function(rows) { - const rawFilter = targetRow.children[1].textContent; + const rawFilter = targetRow.children[COLUMN_FILTER].textContent; const nodeFromFilter = function(filter, lists) { const fragment = document.createDocumentFragment(); @@ -1561,7 +1585,7 @@ const reloadTab = function(ev) { } else if ( dom.cl.has(targetRow, 'extendedRealm') ) { const response = await messaging.send('loggerUI', { what: 'listsFromCosmeticFilter', - url: targetRow.children[6].textContent, + url: targetRow.children[COLUMN_URL].textContent, rawFilter: rawFilter, }); handleResponse(response); @@ -1619,19 +1643,19 @@ const reloadTab = function(ev) { // Partyness text = dom.attr(tr, 'data-parties') || ''; if ( text !== '' ) { - rows[5].children[1].textContent = `(${trch[4].textContent})\u2002${text}`; + rows[5].children[1].textContent = `(${trch[COLUMN_PARTYNESS].textContent})\u2002${text}`; } else { rows[5].style.display = 'none'; } // Type - text = trch[5].textContent; + text = trch[COLUMN_TYPE].textContent; if ( text !== '' ) { rows[6].children[1].textContent = text; } else { rows[6].style.display = 'none'; } // URL - const canonicalURL = trch[6].textContent; + const canonicalURL = trch[COLUMN_URL].textContent; if ( canonicalURL !== '' ) { const attr = dom.attr(tr, 'data-status') || ''; if ( attr !== '' ) { @@ -1640,7 +1664,7 @@ const reloadTab = function(ev) { dom.attr(rows[7], 'data-modifier', ''); } } - rows[7].children[1].appendChild(dom.clone(trch[6])); + rows[7].children[1].appendChild(dom.clone(trch[COLUMN_URL])); } else { rows[7].style.display = 'none'; } @@ -1846,8 +1870,8 @@ const reloadTab = function(ev) { if ( targetRow === null ) { return; } ev.stopPropagation(); targetTabId = tabIdFromAttribute(targetRow); - targetType = targetRow.children[5].textContent.trim() || ''; - targetURLs = createTargetURLs(targetRow.children[6].textContent); + targetType = targetRow.children[COLUMN_TYPE].textContent.trim() || ''; + targetURLs = createTargetURLs(targetRow.children[COLUMN_URL].textContent); targetPageHostname = dom.attr(targetRow, 'data-tabhn') || ''; targetFrameHostname = dom.attr(targetRow, 'data-dochn') || ''; @@ -2640,7 +2664,7 @@ const loggerSettings = (( ) => { maxEntryCount: 2000, // per-tab maxLoadCount: 20, // per-tab }, - columns: [ true, true, true, true, true, true, true, true ], + columns: [ true, true, true, true, true, true, true, true, true ], linesPerEntry: 4, }; diff --git a/src/js/start.js b/src/js/start.js index f5b086c64..e9b5024e1 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -511,7 +511,7 @@ browser.runtime.onUpdateAvailable.addListener(details => { if ( selfieIsValid ) { µb.supportStats.allReadyAfter += ' (selfie)'; } -ubolog(`All ready ${µb.supportStats.allReadyAfter} ms after launch`); +ubolog(`All ready ${µb.supportStats.allReadyAfter} after launch`); // <<<<< end of private scope })(); diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index aad7fea74..5692ad995 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -2377,7 +2377,8 @@ const OPTTokenShide = 37; const OPTTokenXhr = 38; const OPTTokenWebrtc = 39; const OPTTokenWebsocket = 40; -const OPTTokenCount = 41; +const OPTTokenMethod = 41; +const OPTTokenCount = 42; //const OPTPerOptionMask = 0x0000ff00; const OPTCanNegate = 1 << 8; @@ -2481,6 +2482,7 @@ Parser.prototype.OPTTokenFrame = OPTTokenFrame; Parser.prototype.OPTTokenXhr = OPTTokenXhr; Parser.prototype.OPTTokenWebrtc = OPTTokenWebrtc; Parser.prototype.OPTTokenWebsocket = OPTTokenWebsocket; +Parser.prototype.OPTTokenMethod = OPTTokenMethod; Parser.prototype.OPTCanNegate = OPTCanNegate; Parser.prototype.OPTBlockOnly = OPTBlockOnly; @@ -2527,6 +2529,7 @@ const netOptionTokenDescriptors = new Map([ [ 'inline-script', OPTTokenInlineScript | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ], [ 'match-case', OPTTokenMatchCase ], [ 'media', OPTTokenMedia | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ], + [ 'method', OPTTokenMethod | OPTNetworkType | OPTMustAssign ], [ 'mp4', OPTTokenMp4 | OPTNetworkType | OPTBlockOnly | OPTModifierType ], [ '_', OPTTokenNoop ], [ 'object', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ], @@ -2586,6 +2589,7 @@ Parser.netOptionTokenIds = new Map([ [ 'inline-script', OPTTokenInlineScript ], [ 'match-case', OPTTokenMatchCase ], [ 'media', OPTTokenMedia ], + [ 'method', OPTTokenMethod ], [ 'mp4', OPTTokenMp4 ], [ '_', OPTTokenNoop ], [ 'object', OPTTokenObject ], @@ -2635,6 +2639,7 @@ Parser.netOptionTokenNames = new Map([ [ OPTTokenInlineScript, 'inline-script' ], [ OPTTokenMatchCase, 'match-case' ], [ OPTTokenMedia, 'media' ], + [ OPTTokenMethod, 'method' ], [ OPTTokenMp4, 'mp4' ], [ OPTTokenNoop, '_' ], [ OPTTokenObject, 'object' ], diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index a81fb4646..7d8d36f09 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -199,6 +199,7 @@ const INVALID_TOKEN_HASH = 0xFFFFFFFF; // See the following as short-lived registers, used during evaluation. They are // valid until the next evaluation. +let $requestMethodBit = 0; let $requestTypeValue = 0; let $requestURL = ''; let $requestURLRaw = ''; @@ -1265,6 +1266,82 @@ registerFilterClass(FilterRegex); /******************************************************************************/ +const FilterMethod = class { + static match(idata) { + if ( $requestMethodBit === 0 ) { return false; } + const methodBits = filterData[idata+1]; + const notMethodBits = filterData[idata+2]; + return (methodBits !== 0 && ($requestMethodBit & methodBits) !== 0) || + (notMethodBits !== 0 && ($requestMethodBit & notMethodBits) === 0); + } + + static compile(details) { + return [ FilterMethod.fid, details.methodBits, details.notMethodBits ]; + } + + static fromCompiled(args) { + const idata = filterDataAllocLen(3); + filterData[idata+0] = args[0]; // fid + filterData[idata+1] = args[1]; // methodBits + filterData[idata+2] = args[2]; // notMethodBits + return idata; + } + + static dnrFromCompiled(args, rule) { + rule.condition = rule.condition || {}; + const rc = rule.condition; + let methodBits = args[1]; + let notMethodBits = args[2]; + if ( methodBits !== 0 && rc.requestMethods === undefined ) { + rc.requestMethods = []; + } + if ( notMethodBits !== 0 && rc.excludedRequestMethods === undefined ) { + rc.excludedRequestMethods = []; + } + for ( let i = 1; methodBits !== 0 || notMethodBits !== 0; i++ ) { + const bit = 1 << i; + const methodName = FilteringContext.getMethodName(bit); + if ( (methodBits & bit) !== 0 ) { + methodBits &= ~bit; + rc.requestMethods.push(methodName); + } else if ( (notMethodBits & bit) !== 0 ) { + notMethodBits &= ~bit; + rc.excludedRequestMethods.push(methodName); + } + } + } + + static keyFromArgs(args) { + return `${args[1]} ${args[2]}`; + } + + static logData(idata, details) { + const methods = []; + let methodBits = filterData[idata+1]; + let notMethodBits = filterData[idata+2]; + for ( let i = 0; methodBits !== 0 || notMethodBits !== 0; i++ ) { + const bit = 1 << i; + const methodName = FilteringContext.getMethodName(bit); + if ( (methodBits & bit) !== 0 ) { + methodBits &= ~bit; + methods.push(methodName); + } else if ( (notMethodBits & bit) !== 0 ) { + notMethodBits &= ~bit; + methods.push(`~${methodName}`); + } + } + details.options.push(`method=${methods.join('|')}`); + } + + static dumpInfo(idata) { + return `0b${filterData[idata+1].toString(2)} 0b${filterData[idata+2].toString(2)}`; + } +}; + +registerFilterClass(FilterMethod); + +/******************************************************************************/ + // stylesheet: 1 => bit 0 // image: 2 => bit 1 // object: 3 => bit 2 @@ -3025,6 +3102,8 @@ class FilterCompiler { this.tokenBeg = 0; this.typeBits = 0; this.notTypeBits = 0; + this.methodBits = 0; + this.notMethodBits = 0; this.wildcardPos = -1; this.caretPos = -1; return this; @@ -3055,6 +3134,21 @@ class FilterCompiler { } } + processMethodOption(value) { + for ( const method of value.split('|') ) { + if ( method.charCodeAt(0) === 0x7E /* '~' */ ) { + const bit = FilteringContext.getMethod(method.slice(1)) || 0; + if ( bit === 0 ) { continue; } + this.notMethodBits |= bit; + } else { + const bit = FilteringContext.getMethod(method) || 0; + if ( bit === 0 ) { continue; } + this.methodBits |= bit; + } + } + this.methodBits &= ~this.notMethodBits; + } + // https://github.com/chrisaljoudi/uBlock/issues/589 // Be ready to handle multiple negated types @@ -3225,6 +3319,9 @@ class FilterCompiler { } this.optionUnitBits |= this.REDIRECT_BIT; break; + case parser.OPTTokenMethod: + this.processMethodOption(val); + break; case parser.OPTTokenInvalid: return false; default: @@ -3573,6 +3670,11 @@ class FilterCompiler { units.push(FilterAnchorRight.compile()); } + // Method(s) + if ( this.methodBits !== 0 || this.notMethodBits !== 0 ) { + units.push(FilterMethod.compile(this)); + } + // Not types if ( this.notTypeBits !== 0 ) { units.push(FilterNotType.compile(this)); @@ -4566,6 +4668,7 @@ FilterContainer.prototype.matchAndFetchModifiers = function( $docHostname = fctxt.getDocHostname(); $docDomain = fctxt.getDocDomain(); $requestHostname = fctxt.getHostname(); + $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty; @@ -4866,6 +4969,7 @@ FilterContainer.prototype.matchRequestReverse = function(type, url) { // Prime tokenizer: we get a normalized URL in return. $requestURL = urlTokenizer.setURL(url); $requestURLRaw = url; + $requestMethodBit = 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $isBlockImportant = false; this.$filterUnit = 0; @@ -4933,6 +5037,7 @@ FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) { $docHostname = fctxt.getDocHostname(); $docDomain = fctxt.getDocDomain(); $requestHostname = fctxt.getHostname(); + $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $isBlockImportant = false; @@ -4967,6 +5072,7 @@ FilterContainer.prototype.matchHeaders = function(fctxt, headers) { $docHostname = fctxt.getDocHostname(); $docDomain = fctxt.getDocDomain(); $requestHostname = fctxt.getHostname(); + $requestMethodBit = fctxt.method || 0; $requestTypeValue = (typeBits & TypeBitsMask) >>> TypeBitsOffset; $httpHeaders.init(headers); diff --git a/src/logger-ui.html b/src/logger-ui.html index 7286d6fbc..3423a0754 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -66,6 +66,7 @@
+
getheadpost
@@ -85,7 +86,7 @@
-
00:00:00 ** 3,3inline-script 
+
00:00:00 ** 3,3optionsinline-script 
@@ -101,7 +102,7 @@