diff options
Diffstat (limited to 'js/traffic.js')
-rw-r--r-- | js/traffic.js | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/js/traffic.js b/js/traffic.js new file mode 100644 index 0000000..3e6a6ac --- /dev/null +++ b/js/traffic.js @@ -0,0 +1,444 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 Raymond Hill + Copyright (C) 2019 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/}. + + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +/******************************************************************************/ + +// Start isolation from global scope + +µMatrix.webRequest = (function() { + +/******************************************************************************/ + +// Intercept and filter web requests according to white and black lists. + +var onBeforeRootFrameRequestHandler = function(details) { + var µm = µMatrix; + var requestURL = details.url; + var requestHostname = µm.URI.hostnameFromURI(requestURL); + var tabId = details.tabId; + + µm.tabContextManager.push(tabId, requestURL); + + var tabContext = µm.tabContextManager.mustLookup(tabId); + var rootHostname = tabContext.rootHostname; + + // Disallow request as per matrix? + var block = µm.mustBlock(rootHostname, requestHostname, 'doc'); + + var 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) { + var µm = µMatrix, + µmuri = µm.URI, + requestURL = details.url, + requestScheme = µmuri.schemeFromURI(requestURL); + + if ( µmuri.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 + var tabContext = µm.tabContextManager.mustLookup(details.tabId), + tabId = tabContext.tabId, + rootHostname = tabContext.rootHostname, + specificity = 0; + + // Filter through matrix + var block = µm.tMatrix.mustBlock( + rootHostname, + µmuri.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. + var pageStore = µm.mustPageStoreFromTabId(tabId); + + // Enforce strict secure connection? + if ( tabContext.secure && µmuri.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) { + var µm = µMatrix, + µmuri = µm.URI, + requestURL = details.url, + requestScheme = µmuri.schemeFromURI(requestURL); + + // Ignore non-network schemes + if ( µmuri.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 + var tabId = details.tabId, + pageStore = µm.mustPageStoreFromTabId(tabId), + requestType = requestTypeNormalizer[details.type] || 'other', + requestHeaders = details.requestHeaders, + 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/semantics.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 !== '' ) { + var 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. + + var rootHostname = pageStore.pageHostname, + requestHostname = µmuri.hostnameFromURI(requestURL), + 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' ) { + µ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 !== '' ) { + var toDomain = µmuri.domainFromHostname(requestHostname); + if ( toDomain !== '' && toDomain !== µmuri.domainFromURI(headerValue) ) { + pageStore.has3pReferrer = true; + if ( µm.tMatrix.evaluateSwitchZ('referrer-spoof', rootHostname) ) { + modified = true; + var newValue; + if ( details.method === 'GET' ) { + newValue = requestHeaders[headerIndex].value = + requestScheme + '://' + requestHostname + '/'; + } else { + requestHeaders.splice(headerIndex, 1); + } + µm.refererHeaderFoiledCounter++; + if ( requestType === 'doc' ) { + µ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...' + var µm = µMatrix, + tabId = details.tabId, + requestURL = details.url, + 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); + } + + var tabContext = µm.tabContextManager.lookup(tabId); + if ( tabContext === null ) { return; } + + var csp = [], + cspReport = [], + rootHostname = tabContext.rootHostname, + requestHostname = µm.URI.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 + var 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); + } + + var headers = details.responseHeaders, + 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 }; +}; + +/******************************************************************************/ + +var cspNoWorkerInit = function() { + if ( vAPI.webextFlavor === undefined ) { + return "child-src 'none'; frame-src data: blob: *; report-uri about:blank"; + } + µMatrix.cspNoWorker = /^Mozilla-Firefox-5[67]/.test(vAPI.webextFlavor) ? + "child-src 'none'; frame-src data: blob: *; report-uri about:blank" : + "worker-src 'none'; report-uri about:blank" ; + return µMatrix.cspNoWorker; +}; + +/******************************************************************************/ + +// Caller must ensure headerName is normalized to lower case. + +var headerIndexFromName = function(headerName, headers) { + var i = headers.length; + while ( i-- ) { + if ( headers[i].name.toLowerCase() === headerName ) { + return i; + } + } + return -1; +}; + +/******************************************************************************/ + +var 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 +}; + +/******************************************************************************/ + +var start = function() { + vAPI.net.registerListeners(); +}; + +/******************************************************************************/ + +return { + start: start +}; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ + |