123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 Raymond Hill
- Copyright (C) 2019-2022 Alessio Vanni
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
- Home: https://gitlab.com/vannilla/ematrix
- uMatrix Home: https://github.com/gorhill/uMatrix
- */
- 'use strict';
- /******************************************************************************/
- ηMatrix.webRequest = (function() {
- Cu.import('chrome://ematrix/content/lib/UriTools.jsm');
- // Intercept and filter web requests according to white and black lists.
- var onBeforeRootFrameRequestHandler = function (details) {
- let ηm = ηMatrix;
- let requestURL = details.url;
- let requestHostname = UriTools.hostnameFromURI(requestURL);
- let tabId = details.tabId;
- ηm.tabContextManager.push(tabId, requestURL);
- let tabContext = ηm.tabContextManager.mustLookup(tabId);
- let rootHostname = tabContext.rootHostname;
- // Disallow request as per matrix?
- let block = ηm.mustBlock(rootHostname, requestHostname, 'doc');
- let pageStore = ηm.pageStoreFromTabId(tabId);
- pageStore.recordRequest('doc', requestURL, block);
- ηm.logger.writeOne(tabId, 'net', rootHostname, requestURL, 'doc', block);
- // Not blocked
- if (!block) {
- // rhill 2013-11-07: Senseless to do this for
- // behind-the-scene requests.
- ηm.cookieHunter.recordPageCookies(pageStore);
- return;
- }
- // Blocked
- var query = btoa(JSON.stringify({
- url: requestURL,
- hn: requestHostname,
- why: '?'
- }));
- vAPI.tabs.replace(tabId,
- vAPI.getURL('main-blocked.html?details=') + query);
- return {
- cancel: true
- };
- };
- // Intercept and filter web requests according to white and black lists.
- var onBeforeRequestHandler = function (details) {
- let ηm = ηMatrix;
- let requestURL = details.url;
- let requestScheme = UriTools.schemeFromURI(requestURL);
- if (UriTools.isNetworkScheme(requestScheme) === false) {
- return;
- }
- var requestType = requestTypeNormalizer[details.type] || 'other';
- // https://github.com/gorhill/httpswitchboard/issues/303
- // Wherever the main doc comes from, create a receiver page
- // URL: synthetize one if needed.
- if (requestType === 'doc' && details.parentFrameId === -1) {
- return onBeforeRootFrameRequestHandler(details);
- }
- // Re-classify orphan HTTP requests as behind-the-scene
- // requests. There is not much else which can be done, because
- // there are URLs which cannot be handled by ηMatrix,
- // i.e. `opera://startpage`, as this would lead to
- // complications with no obvious solution, like how to scope
- // on unknown scheme? Etc.
- // https://github.com/gorhill/httpswitchboard/issues/191
- // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
- let tabContext = ηm.tabContextManager.mustLookup(details.tabId);
- let tabId = tabContext.tabId;
- let rootHostname = tabContext.rootHostname;
- let specificity = 0;
- // Filter through matrix
- let block = ηm.tMatrix.mustBlock(rootHostname,
- UriTools.hostnameFromURI(requestURL),
- requestType);
- if (block) {
- specificity = ηm.tMatrix.specificityRegister;
- }
- // Record request.
- // https://github.com/gorhill/httpswitchboard/issues/342
- // The way requests are handled now, it may happen at this
- // point some processing has already been performed, and that
- // a synthetic URL has been constructed for logging
- // purpose. Use this synthetic URL if it is available.
- let pageStore = ηm.mustPageStoreFromTabId(tabId);
- // Enforce strict secure connection?
- if (tabContext.secure
- && UriTools.isSecureScheme(requestScheme) === false) {
- pageStore.hasMixedContent = true;
- if (block === false) {
- block = ηm.tMatrix.evaluateSwitchZ('https-strict', rootHostname);
- }
- }
- pageStore.recordRequest(requestType, requestURL, block);
- ηm.logger.writeOne(tabId, 'net', rootHostname,
- requestURL, details.type, block);
- if (block) {
- pageStore.cacheBlockedCollapsible(requestType,
- requestURL, specificity);
- return {
- 'cancel': true
- };
- }
- };
- // Sanitize outgoing headers as per user settings.
- var onBeforeSendHeadersHandler = function (details) {
- let ηm = ηMatrix;
- let requestURL = details.url;
- let requestScheme = UriTools.schemeFromURI(requestURL);
- // Ignore non-network schemes
- if (UriTools.isNetworkScheme(requestScheme) === false) {
- return;
- }
- // Re-classify orphan HTTP requests as behind-the-scene
- // requests. There is not much else which can be done, because
- // there are URLs which cannot be handled by HTTP Switchboard,
- // i.e. `opera://startpage`, as this would lead to
- // complications with no obvious solution, like how to scope
- // on unknown scheme? Etc.
- // https://github.com/gorhill/httpswitchboard/issues/191
- // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
- let tabId = details.tabId;
- let pageStore = ηm.mustPageStoreFromTabId(tabId);
- let requestType = requestTypeNormalizer[details.type] || 'other';
- let requestHeaders = details.requestHeaders;
- let headerIndex, headerValue;
- // https://github.com/gorhill/httpswitchboard/issues/342
- // Is this hyperlink auditing? If yes, create a synthetic URL
- // for reporting hyperlink auditing in request log. This way
- // the user is better informed of what went on.
- // https://html.spec.whatwg.org/multipage/links.html#hyperlink-auditing
- //
- // Target URL = the href of the link
- // Doc URL = URL of the document containing the target URL
- // Ping URLs = servers which will be told that user clicked target URL
- //
- // `Content-Type` = `text/ping` (always present)
- // `Ping-To` = target URL (always present)
- // `Ping-From` = doc URL
- // `Referer` = doc URL
- // request URL = URL which will receive the information
- //
- // With hyperlink-auditing, removing header(s) is pointless, the whole
- // request must be cancelled.
- headerIndex = headerIndexFromName('ping-to', requestHeaders);
- if (headerIndex !== -1) {
- headerValue = requestHeaders[headerIndex].value;
- if (headerValue !== '') {
- let block = ηm.userSettings.processHyperlinkAuditing;
- pageStore.recordRequest('other',
- requestURL
- + '{Ping-To:'
- + headerValue
- + '}', block);
- ηm.logger.writeOne(tabId, 'net', '', requestURL, 'ping', block);
- if (block) {
- ηm.hyperlinkAuditingFoiledCounter += 1;
- return {
- 'cancel': true
- };
- }
- }
- }
- // If we reach this point, request is not blocked, so what is
- // left to do is to sanitize headers.
- let rootHostname = pageStore.pageHostname;
- let requestHostname = UriTools.hostnameFromURI(requestURL);
- let modified = false;
- // Process `Cookie` header.
- headerIndex = headerIndexFromName('cookie', requestHeaders);
- if (headerIndex !== -1
- && ηm.mustBlock(rootHostname, requestHostname, 'cookie')) {
- modified = true;
- headerValue = requestHeaders[headerIndex].value;
- requestHeaders.splice(headerIndex, 1);
- ηm.cookieHeaderFoiledCounter++;
- if (requestType === 'doc') {
- pageStore.perLoadBlockedRequestCount++;
- ηm.logger.writeOne(tabId, 'net', '', headerValue, 'COOKIE', true);
- }
- }
- // Process `Referer` header.
- // https://github.com/gorhill/httpswitchboard/issues/222#issuecomment-44828402
- // https://github.com/gorhill/uMatrix/issues/320
- // http://tools.ietf.org/html/rfc6454#section-7.3
- // "The user agent MAY include an Origin header field in any HTTP
- // "request.
- // "The user agent MUST NOT include more than one Origin header field in
- // "any HTTP request.
- // "Whenever a user agent issues an HTTP request from a "privacy-
- // "sensitive" context, the user agent MUST send the value "null" in the
- // "Origin header field."
- // https://github.com/gorhill/uMatrix/issues/358
- // Do not spoof `Origin` header for the time being.
- // https://github.com/gorhill/uMatrix/issues/773
- // For non-GET requests, remove `Referer` header instead of spoofing it.
- headerIndex = headerIndexFromName('referer', requestHeaders);
- if (headerIndex !== -1) {
- headerValue = requestHeaders[headerIndex].value;
- if (headerValue !== '') {
- let toDomain = UriTools.domainFromHostname(requestHostname);
- if (toDomain !== ''
- && toDomain !== UriTools.domainFromURI(headerValue)) {
- pageStore.has3pReferrer = true;
- if (ηm.tMatrix.evaluateSwitchZ('referrer-spoof',
- rootHostname)) {
- modified = true;
- let newValue;
- if (details.method === 'GET') {
- newValue = requestHeaders[headerIndex].value =
- requestScheme + '://' + requestHostname + '/';
- } else {
- requestHeaders.splice(headerIndex, 1);
- }
- ηm.refererHeaderFoiledCounter++;
- if (requestType === 'doc') {
- pageStore.perLoadBlockedRequestCount++;
- ηm.logger.writeOne(tabId, 'net', '',
- headerValue, 'REFERER', true);
- if (newValue !== undefined) {
- ηm.logger.writeOne(tabId, 'net', '',
- newValue, 'REFERER', false);
- }
- }
- }
- }
- }
- }
- if (modified) {
- return {
- requestHeaders: requestHeaders
- };
- }
- };
- // To prevent inline javascript from being executed.
- // Prevent inline scripting using `Content-Security-Policy`:
- // https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html
- // This fixes:
- // https://github.com/gorhill/httpswitchboard/issues/35
- var onHeadersReceived = function (details) {
- // Ignore schemes other than 'http...'
- let ηm = ηMatrix;
- let tabId = details.tabId;
- let requestURL = details.url;
- let requestType = requestTypeNormalizer[details.type] || 'other';
- // https://github.com/gorhill/uMatrix/issues/145
- // Check if the main_frame is a download
- if (requestType === 'doc') {
- ηm.tabContextManager.push(tabId, requestURL);
- }
- let tabContext = ηm.tabContextManager.lookup(tabId);
- if (tabContext === null) {
- return;
- }
- let csp = [];
- let cspReport = [];
- let rootHostname = tabContext.rootHostname;
- let requestHostname = UriTools.hostnameFromURI(requestURL);
- // Inline script tags.
- if (ηm.mustAllow(rootHostname, requestHostname, 'script') !== true) {
- csp.push(ηm.cspNoInlineScript);
- }
- // Inline style tags.
- if (ηm.mustAllow(rootHostname, requestHostname, 'css') !== true) {
- csp.push(ηm.cspNoInlineStyle);
- }
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1302667
- let cspNoWorker = ηm.cspNoWorker;
- if (cspNoWorker === undefined) {
- cspNoWorker = cspNoWorkerInit();
- }
- if (ηm.tMatrix.evaluateSwitchZ('no-workers', rootHostname)) {
- csp.push(cspNoWorker);
- } else if (ηm.rawSettings.disableCSPReportInjection === false) {
- cspReport.push(cspNoWorker);
- }
- let headers = details.responseHeaders;
- let cspDirectives, i;
- if (csp.length !== 0) {
- cspDirectives = csp.join(',');
- i = headerIndexFromName('content-security-policy', headers);
- if (i !== -1) {
- headers[i].value += ',' + cspDirectives;
- } else {
- headers.push({
- name: 'Content-Security-Policy',
- value: cspDirectives
- });
- }
- if (requestType === 'doc') {
- ηm.logger.writeOne(tabId, 'net', '', cspDirectives, 'CSP', false);
- }
- }
- if (cspReport.length !== 0) {
- cspDirectives = cspReport.join(',');
- i = headerIndexFromName('content-security-policy-report-only',
- headers);
- if (i !== -1) {
- headers[i].value += ',' + cspDirectives;
- } else {
- headers.push({
- name: 'Content-Security-Policy-Report-Only',
- value: cspDirectives
- });
- }
- }
- return {
- responseHeaders: headers
- };
- };
- let cspNoWorkerInit = function () {
- if (ηMatrix.cspNoWorker === undefined) {
- ηMatrix.cspNoWorker = "worker-src 'none'; "
- +"frame-src data: blob: *; "
- +"report-uri about:blank";
- }
- return ηMatrix.cspNoWorker;
- };
- // Caller must ensure headerName is normalized to lower case.
- let headerIndexFromName = function (headerName, headers) {
- for (let i=0; i<headers.length; ++i) {
- if ( headers[i].name.toLowerCase() === headerName ) {
- return i;
- }
- }
- return -1;
- };
- let requestTypeNormalizer = {
- 'font' : 'css',
- 'image' : 'image',
- 'imageset' : 'image',
- 'main_frame' : 'doc',
- 'media' : 'media',
- 'object' : 'media',
- 'other' : 'other',
- 'script' : 'script',
- 'stylesheet' : 'css',
- 'sub_frame' : 'frame',
- 'websocket' : 'xhr',
- 'xmlhttprequest': 'xhr'
- };
- vAPI.net.onBeforeRequest = {
- extra: [ 'blocking' ],
- callback: onBeforeRequestHandler
- };
- vAPI.net.onBeforeSendHeaders = {
- extra: [ 'blocking', 'requestHeaders' ],
- callback: onBeforeSendHeadersHandler
- };
- vAPI.net.onHeadersReceived = {
- urls: [ 'http://*/*', 'https://*/*' ],
- types: [ 'main_frame', 'sub_frame' ],
- extra: [ 'blocking', 'responseHeaders' ],
- callback: onHeadersReceived
- };
- return {
- start: function () {
- vAPI.net.registerListeners();
- },
- };
- })();
|