1
0
Fork 0
mirror of https://github.com/gorhill/uMatrix.git synced 2024-06-29 11:30:20 +12:00
uMatrix/src/js/traffic.js

780 lines
26 KiB
JavaScript
Raw Normal View History

2014-10-18 08:01:09 +13:00
/*******************************************************************************
µMatrix - a Chromium browser extension to black/white list requests.
Copyright (C) 2014 Raymond Hill
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://github.com/gorhill/uMatrix
*/
/* global chrome, µMatrix */
2015-04-11 02:45:40 +12:00
/* jshint boss: true */
2014-10-18 08:01:09 +13:00
/******************************************************************************/
// Start isolation from global scope
µMatrix.webRequest = (function() {
/******************************************************************************/
// The `id='uMatrix'` is important, it allows µMatrix to detect whether a
// specific data URI originates from itself.
var rootFrameReplacement = [
'<!DOCTYPE html><html id="uMatrix">',
'<head>',
'<meta charset="utf-8" />',
2014-10-18 08:01:09 +13:00
'<style>',
'@font-face {',
'font-family:httpsb;',
'font-style:normal;',
'font-weight:400;',
'src: local("httpsb"),url("',
µMatrix.fontCSSURL,
'") format("truetype");',
'}',
'body {',
'margin:0;',
'border:0;',
'padding:0;',
'font:15px httpsb,sans-serif;',
'width:100%;',
'height:100%;',
'background-color:transparent;',
'background-size:10px 10px;',
'background-image:',
'repeating-linear-gradient(',
'-45deg,',
'rgba(204,0,0,0.5),rgba(204,0,0,0.5) 24%,',
'transparent 26%,transparent 49%,',
'rgba(204,0,0,0.5) 51%,rgba(204,0,0,0.5) 74%,',
'transparent 76%,transparent',
');',
'text-align: center;',
'}',
'#p {',
'margin:8px;',
'padding:4px;',
'display:inline-block;',
'background-color:white;',
'}',
'#t {',
'margin:2px;',
'border:0;',
'padding:0 2px;',
'display:inline-block;',
'}',
'#t b {',
'padding:0 4px;',
'background-color:#eee;',
'font-weight:normal;',
'}',
'</style>',
'<link href="{{cssURL}}?url={{originalURL}}&hostname={{hostname}}&t={{now}}" rel="stylesheet" type="text/css">',
2014-11-08 08:36:20 +13:00
'<title>Blocked by &mu;Matrix</title>',
2014-10-18 08:01:09 +13:00
'</head>',
'<body>',
'<div id="p">',
2014-11-08 08:36:20 +13:00
'<div id="t"><b>{{hostname}}</b> blocked by &mu;Matrix</div>',
2014-10-18 08:01:09 +13:00
'</div>',
'</body>',
'</html>'
].join('');
var subFrameReplacement = [
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="utf-8" />',
2014-10-18 08:01:09 +13:00
'<style>',
'@font-face{',
'font-family:httpsb;',
'font-style:normal;',
'font-weight:400;',
'src:local("httpsb"),url("',
µMatrix.fontCSSURL,
'") format("truetype");',
'}',
'body{',
'margin:0;',
'border:0;',
'padding:0;',
'font:13px httpsb,sans-serif;',
'}',
'#bg{',
'border:1px dotted {{subframeColor}};',
'position:absolute;',
'top:0;',
'right:0;',
'bottom:0;',
'left:0;',
'background-color:transparent;',
'background-size:10px 10px;',
'background-image:',
'repeating-linear-gradient(',
'-45deg,',
'{{subframeColor}},{{subframeColor}} 24%,',
'transparent 25%,transparent 49%,',
'{{subframeColor}} 50%,{{subframeColor}} 74%,',
'transparent 75%,transparent',
');',
'opacity:{{subframeOpacity}};',
'text-align:center;',
'}',
'#bg > div{',
'display:inline-block;',
'background-color:rgba(255,255,255,1);',
'}',
'#bg > div > a {',
'padding:0 2px;',
'display:inline-block;',
'color:white;',
'background-color:{{subframeColor}};',
'text-decoration:none;',
'}',
'</style>',
2014-11-08 08:36:20 +13:00
'<title>Blocked by &mu;Matrix</title>',
2014-10-18 08:01:09 +13:00
'</head>',
2014-11-08 08:36:20 +13:00
'<body title="&ldquo;{{hostname}}&rdquo; frame\nblocked by &mu;Matrix">',
2014-10-18 08:01:09 +13:00
'<div id="bg"><div><a href="{{frameSrc}}" target="_blank">{{hostname}}</a></div></div>',
'</body>',
'</html>'
].join('');
/******************************************************************************/
// If it is HTTP Switchboard's root frame replacement URL, verify that
// the page that was blacklisted is still blacklisted, and if not,
// redirect to the previously blacklisted page.
var onBeforeChromeExtensionRequestHandler = function(details) {
var requestURL = details.url;
// console.debug('onBeforeChromeExtensionRequestHandler()> "%s": %o', details.url, details);
// rhill 2013-12-10: Avoid regex whenever a faster indexOf() can be used:
// here we can use fast indexOf() as a first filter -- which is executed
// for every single request (so speed matters).
var matches = requestURL.match(/url=([^&]+)&hostname=([^&]+)/);
if ( !matches ) {
return;
}
2014-10-26 16:46:53 +13:00
var µm = µMatrix;
2014-10-18 08:01:09 +13:00
var pageURL = decodeURIComponent(matches[1]);
2014-11-19 06:04:40 +13:00
var pageHostname = decodeURIComponent(matches[2]);
// Blacklisted as per matrix?
if ( µm.mustBlock(µm.scopeFromURL(pageURL), pageHostname, 'doc') ) {
2014-10-18 08:01:09 +13:00
return;
}
µMatrix.asyncJobs.add(
'gotoURL-' + details.tabId,
{ tabId: details.tabId, url: pageURL },
2014-10-26 16:46:53 +13:00
µm.utils.gotoURL,
2014-10-18 08:01:09 +13:00
200,
false
);
};
/******************************************************************************/
// Intercept and filter web requests according to white and black lists.
var onBeforeRootFrameRequestHandler = function(details) {
var µm = µMatrix;
2015-04-12 09:15:57 +12:00
var requestURL = details.url;
2014-10-18 08:01:09 +13:00
var tabId = details.tabId;
2015-04-12 09:15:57 +12:00
µm.tabContextManager.push(tabId, requestURL);
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
2014-10-18 08:01:09 +13:00
tabId = µm.behindTheSceneTabId;
2015-04-12 09:15:57 +12:00
} else {
µm.bindTabToPageStats(tabId);
2014-10-18 08:01:09 +13:00
}
var uri = µm.URI.set(details.url);
2014-11-19 06:04:40 +13:00
if ( uri.scheme.indexOf('http') === -1 ) {
2014-10-18 08:01:09 +13:00
return;
}
var requestHostname = uri.hostname;
2014-11-19 06:04:40 +13:00
var pageStore = µm.pageStatsFromTabId(tabId);
// Disallow request as per matrix?
var block = µm.mustBlock(pageStore.pageHostname, requestHostname, 'doc');
2014-10-18 08:01:09 +13:00
// console.debug('onBeforeRequestHandler()> block=%s "%s": %o', block, details.url, details);
// whitelisted?
if ( !block ) {
// rhill 2013-11-07: Senseless to do this for behind-the-scene requests.
// rhill 2013-12-03: Do this here only for root frames.
if ( tabId !== µm.behindTheSceneTabId ) {
2014-11-19 06:04:40 +13:00
µm.cookieHunter.recordPageCookies(pageStore);
2014-10-18 08:01:09 +13:00
}
return;
}
// blacklisted
// rhill 2014-01-15: Delay logging of non-blocked top `main_frame`
// requests, in order to ensure any potential redirects is reported
// in proper chronological order.
// https://github.com/gorhill/httpswitchboard/issues/112
2014-11-19 06:04:40 +13:00
pageStore.recordRequest('doc', requestURL, block);
2014-10-18 08:01:09 +13:00
2015-04-11 02:45:40 +12:00
µm.updateBadgeAsync(tabId);
2014-10-18 08:01:09 +13:00
// If it's a blacklisted frame, redirect to frame.html
// rhill 2013-11-05: The root frame contains a link to noop.css, this
// allows to later check whether the root frame has been unblocked by the
// user, in which case we are able to force a reload using a redirect.
var html = rootFrameReplacement;
html = html.replace('{{cssURL}}', µm.noopCSSURL);
html = html.replace(/{{hostname}}/g, encodeURIComponent(requestHostname));
html = html.replace('{{originalURL}}', encodeURIComponent(requestURL));
html = html.replace('{{now}}', String(Date.now()));
var dataURI = 'data:text/html;base64,' + btoa(html);
return { 'redirectUrl': dataURI };
};
/******************************************************************************/
// Intercept and filter web requests according to white and black lists.
var onBeforeRequestHandler = function(details) {
var µm = µMatrix;
var µmuri = µm.URI.set(details.url);
var requestScheme = µmuri.scheme;
// rhill 2014-02-17: Ignore 'filesystem:': this can happen when listening
// to 'chrome-extension://'.
if ( requestScheme === 'filesystem' ) {
return;
}
// console.debug('onBeforeRequestHandler()> "%s": %o', details.url, details);
2015-04-20 08:19:14 +12:00
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 < 0 ) {
return onBeforeRootFrameRequestHandler(details);
}
var requestURL = details.url;
// Is it µMatrix's noop css file?
2015-04-20 08:19:14 +12:00
if ( requestType === 'css' && requestURL.lastIndexOf(µm.noopCSSURL, 0) === 0 ) {
return onBeforeChromeExtensionRequestHandler(details);
}
// Ignore non-http schemes
if ( requestScheme.indexOf('http') !== 0 ) {
return;
}
// Do not block myself from updating assets
// https://github.com/gorhill/httpswitchboard/issues/202
2015-04-20 08:19:14 +12:00
if ( requestType === 'xhr' && requestURL.lastIndexOf(µm.projectServerRoot, 0) === 0 ) {
return;
}
2014-10-18 08:01:09 +13:00
var requestHostname = µmuri.hostname;
2014-11-19 06:04:40 +13:00
2014-10-18 08:01:09 +13:00
// Re-classify orphan HTTP requests as behind-the-scene requests. There is
// not much else which can be done, because there are URLs
2014-11-19 06:04:40 +13:00
// which cannot be handled by µMatrix, i.e. `opera://startpage`,
2014-10-18 08:01:09 +13:00
// 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
2015-04-11 02:45:40 +12:00
var tabId = details.tabId;
var pageStore = µm.pageStatsFromTabId(tabId);
2014-11-19 06:04:40 +13:00
if ( !pageStore ) {
pageStore = µm.pageStatsFromTabId(µm.behindTheSceneTabId);
2014-10-18 08:01:09 +13:00
}
2014-11-19 06:04:40 +13:00
// Disallow request as per temporary matrix?
var block = µm.mustBlock(pageStore.pageHostname, requestHostname, requestType);
2014-10-18 08:01:09 +13:00
// 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.
pageStore.recordRequest(requestType, requestURL, block);
2014-10-18 08:01:09 +13:00
2015-04-11 02:45:40 +12:00
µm.updateBadgeAsync(tabId);
2014-10-18 08:01:09 +13:00
// whitelisted?
if ( !block ) {
// console.debug('onBeforeRequestHandler()> ALLOW "%s": %o', details.url, details);
return;
}
// blacklisted
// console.debug('onBeforeRequestHandler()> BLOCK "%s": %o', details.url, details);
// If it's a blacklisted frame, redirect to frame.html
// rhill 2013-11-05: The root frame contains a link to noop.css, this
// allows to later check whether the root frame has been unblocked by the
// user, in which case we are able to force a reload using a redirect.
if ( requestType === 'frame' ) {
var html = subFrameReplacement
.replace(/{{hostname}}/g, requestHostname)
.replace('{{frameSrc}}', requestURL)
.replace(/{{subframeColor}}/g, µm.userSettings.subframeColor)
.replace('{{subframeOpacity}}', (µm.userSettings.subframeOpacity / 100).toFixed(1));
return { 'redirectUrl': 'data:text/html,' + encodeURIComponent(html) };
}
return { 'cancel': true };
};
/******************************************************************************/
2015-04-20 08:19:14 +12:00
// Sanitize outgoing headers as per user settings.
2014-10-18 08:01:09 +13:00
var onBeforeSendHeadersHandler = function(details) {
var µm = µMatrix;
// console.debug('onBeforeSendHeadersHandler()> "%s": %o', details.url, 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 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;
2014-11-19 06:04:40 +13:00
var pageStore = µm.pageStatsFromTabId(tabId);
if ( !pageStore ) {
2014-10-18 08:01:09 +13:00
tabId = µm.behindTheSceneTabId;
2014-11-19 06:04:40 +13:00
pageStore = µm.pageStatsFromTabId(tabId);
2014-10-18 08:01:09 +13:00
}
// 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.
2015-04-20 08:19:14 +12:00
// http://www.whatwg.org/specs/web-apps/current-work/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.
var requestURL = details.url;
2015-04-20 08:19:14 +12:00
var requestType = requestTypeNormalizer[details.type] || 'other';
2015-04-20 08:30:28 +12:00
if ( requestType === 'ping' ) {
2015-04-20 08:19:14 +12:00
var linkAuditor = details.requestHeaders.getHeader('ping-to');
if ( linkAuditor !== '' ) {
var block = µm.userSettings.processHyperlinkAuditing;
pageStore.recordRequest('other', requestURL + '{Ping-To:' + linkAuditor + '}', block);
2015-04-11 02:45:40 +12:00
µm.updateBadgeAsync(tabId);
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.
2014-10-18 08:01:09 +13:00
var reqHostname = µm.hostnameFromURL(requestURL);
2014-11-19 06:04:40 +13:00
if ( µm.mustBlock(pageStore.pageHostname, reqHostname, 'cookie') ) {
2015-04-20 08:19:14 +12:00
if ( details.requestHeaders.setHeader('cookie', '') ) {
µm.cookieHeaderFoiledCounter++;
}
2014-10-18 08:01:09 +13:00
}
2014-11-20 14:17:24 +13:00
if ( µm.tMatrix.evaluateSwitchZ('referrer-spoof', pageStore.pageHostname) ) {
2015-04-20 08:19:14 +12:00
foilRefererHeaders(µm, reqHostname, details);
2014-10-18 08:01:09 +13:00
}
2014-11-19 15:18:57 +13:00
if ( µm.tMatrix.evaluateSwitchZ('ua-spoof', pageStore.pageHostname) ) {
2015-04-20 08:19:14 +12:00
details.requestHeaders.setHeader('user-agent', µm.userAgentReplaceStr);
2014-10-18 08:01:09 +13:00
}
};
/******************************************************************************/
var foilRefererHeaders = function(µm, toHostname, details) {
2015-04-20 08:19:14 +12:00
var referer = details.requestHeaders.getHeader('referer');
if ( referer === '' ) {
return;
2014-11-20 14:17:24 +13:00
}
var µmuri = µm.URI;
2015-04-20 08:19:14 +12:00
if ( µmuri.domainFromHostname(toHostname) === µmuri.domainFromURI(referer) ) {
return;
2014-11-20 14:17:24 +13:00
}
//console.debug('foilRefererHeaders()> foiled referer for "%s"', details.url);
//console.debug('\treferrer "%s"', header.value);
// https://github.com/gorhill/httpswitchboard/issues/222#issuecomment-44828402
2015-04-20 08:19:14 +12:00
details.requestHeaders.setHeader(
'referer',
µmuri.schemeFromURI(details.url) + '://' + toHostname + '/'
);
2014-11-20 14:17:24 +13:00
µm.refererHeaderFoiledCounter++;
2014-10-18 08:01:09 +13:00
};
/******************************************************************************/
// 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) {
// console.debug('onHeadersReceived()> "%s": %o', details.url, details);
// Ignore schemes other than 'http...'
2014-11-19 06:04:40 +13:00
if ( details.url.slice(0, 4) !== 'http' ) {
2014-10-18 08:01:09 +13:00
return;
}
2015-04-20 08:19:14 +12:00
var requestType = requestTypeNormalizer[details.type] || 'other';
2014-10-18 08:01:09 +13:00
if ( requestType === 'frame' ) {
return onSubDocHeadersReceived(details);
}
if ( requestType === 'doc' ) {
return onMainDocHeadersReceived(details);
}
};
/******************************************************************************/
var onMainDocHeadersReceived = function(details) {
// console.debug('onMainDocHeadersReceived()> "%s": %o', details.url, details);
var µm = µMatrix;
// Do not ignore traffic outside tabs.
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabId = details.tabId;
if ( tabId < 0 ) {
tabId = µm.behindTheSceneTabId;
}
var µmuri = µm.URI.set(details.url);
var requestURL = µmuri.normalizedURI();
var requestScheme = µmuri.scheme;
var requestHostname = µmuri.hostname;
// rhill 2013-12-07:
// Apparently in Opera, onBeforeRequest() is triggered while the
// URL is not yet bound to a tab (-1), which caused the code here
// to not be able to lookup the pageStats. So let the code here bind
// the page to a tab if not done yet.
// https://github.com/gorhill/httpswitchboard/issues/75
µm.bindTabToPageStats(tabId, requestURL);
// 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 pageStats = µm.pageStatsFromTabId(tabId);
if ( !pageStats ) {
tabId = µm.behindTheSceneTabId;
pageStats = µm.pageStatsFromTabId(tabId);
}
var headers = details.responseHeaders;
// Simplify code paths by splitting func in two different handlers, one
// for main docs, one for sub docs.
// rhill 2014-01-15: Report redirects.
// https://github.com/gorhill/httpswitchboard/issues/112
// rhill 2014-02-10: Handle all redirects.
// https://github.com/gorhill/httpswitchboard/issues/188
if ( /\s+30[12378]\s+/.test(details.statusLine) ) {
var i = headerIndexFromName('location', headers);
if ( i >= 0 ) {
// rhill 2014-01-20: Be ready to handle relative URLs.
// https://github.com/gorhill/httpswitchboard/issues/162
var locationURL = µmuri.set(headers[i].value.trim()).normalizedURI();
if ( µmuri.authority === '' ) {
locationURL = requestScheme + '://' + requestHostname + µmuri.path;
}
µm.redirectRequests[locationURL] = requestURL;
}
// console.debug('onMainDocHeadersReceived()> redirect "%s" to "%s"', requestURL, headers[i].value);
}
// rhill 2014-01-15: Report redirects if any.
// https://github.com/gorhill/httpswitchboard/issues/112
2014-10-18 08:01:09 +13:00
if ( details.statusLine.indexOf(' 200') > 0 ) {
var mainFrameStack = [requestURL];
var destinationURL = requestURL;
var sourceURL;
while ( sourceURL = µm.redirectRequests[destinationURL] ) {
mainFrameStack.push(sourceURL);
delete µm.redirectRequests[destinationURL];
destinationURL = sourceURL;
}
while ( destinationURL = mainFrameStack.pop() ) {
pageStats.recordRequest('doc', destinationURL, false);
}
2015-04-11 02:45:40 +12:00
µm.updateBadgeAsync(tabId);
2014-10-18 08:01:09 +13:00
}
2014-11-19 06:04:40 +13:00
// Maybe modify inbound headers
var csp = '';
// Enforce strict HTTPS?
if ( requestScheme === 'https' && µm.tMatrix.evaluateSwitchZ('https-strict', pageStats.pageHostname) ) {
2015-01-11 10:57:03 +13:00
csp += "default-src chrome-search: data: https: wss: 'unsafe-eval' 'unsafe-inline';";
2014-10-18 08:01:09 +13:00
}
// https://github.com/gorhill/httpswitchboard/issues/181
2014-11-19 06:04:40 +13:00
pageStats.pageScriptBlocked = µm.mustBlock(pageStats.pageHostname, requestHostname, 'script');
if ( pageStats.pageScriptBlocked ) {
// If javascript not allowed, say so through a `Content-Security-Policy` directive.
// console.debug('onMainDocHeadersReceived()> PAGE CSP "%s": %o', details.url, details);
csp += " script-src 'none'";
}
2014-10-18 08:01:09 +13:00
2014-11-19 06:04:40 +13:00
// https://github.com/gorhill/httpswitchboard/issues/181
if ( csp !== '' ) {
headers.push({
'name': 'Content-Security-Policy',
2014-11-19 06:31:37 +13:00
'value': csp.trim()
2014-11-19 06:04:40 +13:00
});
return { responseHeaders: headers };
}
2014-10-18 08:01:09 +13:00
};
/******************************************************************************/
var onSubDocHeadersReceived = function(details) {
// console.debug('onSubDocHeadersReceived()> "%s": %o', details.url, details);
var µm = µMatrix;
// Do not ignore traffic outside tabs.
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabId = details.tabId;
if ( tabId < 0 ) {
tabId = µm.behindTheSceneTabId;
}
// 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 pageStats = µm.pageStatsFromTabId(tabId);
if ( !pageStats ) {
tabId = µm.behindTheSceneTabId;
pageStats = µm.pageStatsFromTabId(tabId);
}
// Evaluate
2014-10-26 16:46:53 +13:00
if ( µm.mustAllow(pageStats.pageHostname, µm.hostnameFromURL(details.url), 'script') ) {
2014-10-18 08:01:09 +13:00
return;
}
// If javascript not allowed, say so through a `Content-Security-Policy`
// directive.
// For inline javascript within iframes, we need to sandbox.
2014-11-01 02:32:24 +13:00
2014-10-18 08:01:09 +13:00
// https://github.com/gorhill/httpswitchboard/issues/73
// Now because sandbox cancels all permissions, this means
// not just javascript is disabled. To avoid negative side
// effects, I allow some other permissions, but...
2014-11-01 02:32:24 +13:00
// https://github.com/gorhill/uMatrix/issues/27
// Need to add `allow-popups` to prevent completely breaking links on
// some sites old style sites.
2014-10-18 08:01:09 +13:00
// TODO: Reuse CSP `sandbox` directive if it's already in the
// headers (strip out `allow-scripts` if present),
// and find out if the `sandbox` in the header interfere with a
// `sandbox` attribute which might be present on the iframe.
2014-11-01 02:32:24 +13:00
2014-10-18 08:01:09 +13:00
// console.debug('onSubDocHeadersReceived()> FRAME CSP "%s": %o, scope="%s"', details.url, details, pageURL);
2014-11-01 02:32:24 +13:00
2014-10-18 08:01:09 +13:00
details.responseHeaders.push({
'name': 'Content-Security-Policy',
2014-11-01 02:46:09 +13:00
'value': 'sandbox allow-forms allow-same-origin allow-popups allow-top-navigation'
2014-10-18 08:01:09 +13:00
});
return { responseHeaders: details.responseHeaders };
};
/******************************************************************************/
var onErrorOccurredHandler = function(details) {
// console.debug('onErrorOccurred()> "%s": %o', details.url, details);
2015-04-20 08:19:14 +12:00
var requestType = requestTypeNormalizer[details.type] || 'other';
2014-10-18 08:01:09 +13:00
// Ignore all that is not a main document
if ( requestType !== 'doc'|| details.parentFrameId >= 0 ) {
return;
}
var µm = µMatrix;
var pageStats = µm.pageStatsFromPageUrl(details.url);
if ( !pageStats ) {
return;
}
// rhill 2014-01-28: Unwind the stack of redirects if any. Chromium will
// emit an error when a web page redirects apparently endlessly, so
// we need to unravel and report all these redirects upon error.
// https://github.com/gorhill/httpswitchboard/issues/171
var requestURL = µm.URI.set(details.url).normalizedURI();
var mainFrameStack = [requestURL];
var destinationURL = requestURL;
var sourceURL;
while ( sourceURL = µm.redirectRequests[destinationURL] ) {
mainFrameStack.push(sourceURL);
delete µm.redirectRequests[destinationURL];
destinationURL = sourceURL;
}
while ( destinationURL = mainFrameStack.pop() ) {
pageStats.recordRequest('doc', destinationURL, false);
}
2015-04-11 02:45:40 +12:00
µm.updateBadgeAsync(details.tabId);
2014-10-18 08:01:09 +13:00
};
/******************************************************************************/
// 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 = {
2015-04-20 08:30:28 +12:00
'font' : 'css',
2014-10-18 08:01:09 +13:00
'image' : 'image',
2015-04-20 08:30:28 +12:00
'main_frame' : 'doc',
2014-10-18 08:01:09 +13:00
'object' : 'plugin',
2015-04-20 08:19:14 +12:00
'other' : 'other',
2015-04-20 08:30:28 +12:00
'ping' : 'ping',
'script' : 'script',
'stylesheet' : 'css',
'sub_frame' : 'frame',
'xmlhttprequest': 'xhr'
2014-10-18 08:01:09 +13:00
};
/******************************************************************************/
2015-04-12 09:15:57 +12:00
vAPI.net.onBeforeRequest = {
urls: [
"http://*/*",
"https://*/*",
"chrome-extension://*/*"
],
extra: [ 'blocking' ],
callback: onBeforeRequestHandler
};
2014-10-26 16:30:43 +13:00
2015-04-12 09:15:57 +12:00
vAPI.net.onBeforeSendHeaders = {
urls: [
"http://*/*",
"https://*/*"
],
extra: [ 'blocking', 'requestHeaders' ],
callback: onBeforeSendHeadersHandler
};
2014-10-26 16:30:43 +13:00
2015-04-12 09:15:57 +12:00
vAPI.net.onHeadersReceived = {
urls: [
"http://*/*",
"https://*/*"
],
types: [
"main_frame",
"sub_frame"
],
extra: [ 'blocking', 'responseHeaders' ],
callback: onHeadersReceived
};
2014-10-26 16:30:43 +13:00
2015-04-12 09:15:57 +12:00
vAPI.net.onErrorOccurred = {
urls: [
"http://*/*",
"https://*/*"
],
callback: onErrorOccurredHandler
};
/******************************************************************************/
var start = function() {
vAPI.net.registerListeners();
2014-10-26 16:30:43 +13:00
};
2014-10-18 08:01:09 +13:00
/******************************************************************************/
return {
2014-10-26 16:30:43 +13:00
blockedRootFramePrefix: 'data:text/html;base64,' + btoa(rootFrameReplacement).slice(0, 80),
start: start
2014-10-18 08:01:09 +13:00
};
/******************************************************************************/
})();
/******************************************************************************/