2017-09-12 22:34:25 +00:00
|
|
|
(function () {
|
2017-10-14 20:24:18 +00:00
|
|
|
"use strict";
|
2017-09-12 22:34:25 +00:00
|
|
|
|
2017-10-20 15:09:12 +00:00
|
|
|
const {storageLib, domainMatcherLib, constants} = window.WEB_API_MANAGER;
|
|
|
|
const {cookieEncodingLib, proxyBlockLib, httpHeadersLib} = window.WEB_API_MANAGER;
|
|
|
|
const {standards} = window.WEB_API_MANAGER;
|
2017-10-13 22:30:57 +00:00
|
|
|
const rootObject = window.browser || window.chrome;
|
2017-10-14 05:41:49 +00:00
|
|
|
const defaultKey = "(default)";
|
2017-09-12 22:34:25 +00:00
|
|
|
|
2017-10-13 22:30:57 +00:00
|
|
|
// Once loaded from storage, will be a mapping from regular expressions
|
|
|
|
// (or the default option, "(default)"), to an array of standards
|
|
|
|
// that should be blocked on matching domains.
|
|
|
|
let domainRules;
|
2017-10-15 04:23:40 +00:00
|
|
|
let shouldLog;
|
2017-09-12 22:34:25 +00:00
|
|
|
|
2017-10-14 20:24:18 +00:00
|
|
|
// The extension depends on this fetch happening before the DOM on any
|
|
|
|
// pages is loaded. The Chrome and Firefox docs *do not* promise this,
|
|
|
|
// but in testing this is always the case.
|
2017-10-15 04:23:40 +00:00
|
|
|
storageLib.get(function (storedValues) {
|
|
|
|
domainRules = storedValues.domainRules;
|
|
|
|
shouldLog = storedValues.shouldLog;
|
2017-10-13 22:30:57 +00:00
|
|
|
});
|
|
|
|
|
2017-10-14 05:41:49 +00:00
|
|
|
// Manage the state of the browser activity, by displaying the number
|
|
|
|
// of origins / frames
|
|
|
|
const updateBrowserActionBadge = function (activeInfo) {
|
2017-10-14 20:24:18 +00:00
|
|
|
const tabId = activeInfo.tabId;
|
2017-10-14 05:41:49 +00:00
|
|
|
rootObject.tabs.executeScript(
|
|
|
|
tabId,
|
|
|
|
{
|
|
|
|
allFrames: true,
|
|
|
|
code: "window.location.host"
|
|
|
|
},
|
|
|
|
function (allHosts) {
|
|
|
|
|
2017-10-14 22:26:02 +00:00
|
|
|
if (rootObject.runtime.lastError && !allHosts) {
|
2017-10-14 21:16:24 +00:00
|
|
|
rootObject.browserAction.setBadgeText({text: "-"});
|
2017-10-14 05:41:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const numFrames = allHosts
|
|
|
|
? Array.from(new Set(allHosts)).length.toString()
|
2017-10-14 21:16:24 +00:00
|
|
|
: "-";
|
2017-10-14 05:41:49 +00:00
|
|
|
|
|
|
|
rootObject.browserAction.setBadgeText({
|
|
|
|
text: numFrames,
|
|
|
|
tabId: tabId
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
2017-09-12 22:34:25 +00:00
|
|
|
|
2017-10-14 21:16:24 +00:00
|
|
|
rootObject.windows.onFocusChanged.addListener(updateBrowserActionBadge);
|
2017-10-14 05:41:49 +00:00
|
|
|
rootObject.tabs.onUpdated.addListener(updateBrowserActionBadge);
|
|
|
|
rootObject.tabs.onActivated.addListener(updateBrowserActionBadge);
|
|
|
|
|
2017-11-11 14:03:51 +00:00
|
|
|
// window.setInterval(function () {
|
|
|
|
// rootObject.tabs.getCurrent(function (currentTab) {
|
|
|
|
// if (currentTab === undefined) {
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
// updateBrowserActionBadge({tabId: currentTab.id});
|
|
|
|
// });
|
|
|
|
// }, 1000);
|
2017-10-14 21:16:24 +00:00
|
|
|
|
2017-10-14 05:41:49 +00:00
|
|
|
// Listen for updates to the domain rules from the config page.
|
2017-10-14 22:39:27 +00:00
|
|
|
// The two types of messages that are sent to the background page are
|
2017-10-15 04:23:40 +00:00
|
|
|
// "stateUpdate", which comes from the config page, indicating the domain
|
2017-10-14 22:39:27 +00:00
|
|
|
// blocking / matching rules have changed, and the "rulesForDomains"
|
|
|
|
// message, which comes from the browserAction popup, and is a request
|
|
|
|
// for information about "here are the domains of the frames on the
|
|
|
|
// current page, which rules are being used to match them".
|
2017-10-14 20:24:18 +00:00
|
|
|
rootObject.runtime.onMessage.addListener(function (request, ignore, sendResponse) {
|
2017-10-14 21:16:24 +00:00
|
|
|
|
2017-10-14 05:41:49 +00:00
|
|
|
const [label, data] = request;
|
2017-11-11 14:03:51 +00:00
|
|
|
|
|
|
|
// Sent from the config page, when the "which APIs should be
|
|
|
|
// blocked for which domains" has been changed on the config page.
|
2017-10-15 04:23:40 +00:00
|
|
|
if (label === "stateUpdate") {
|
|
|
|
domainRules = data.domainRules;
|
|
|
|
shouldLog = data.shouldLog;
|
2017-10-14 05:41:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-11-11 14:03:51 +00:00
|
|
|
// Sent from the popup / browser action, asking for infromation about
|
|
|
|
// which blocking rules are being applied to which domains in the
|
|
|
|
// tab.
|
2017-10-14 05:41:49 +00:00
|
|
|
if (label === "rulesForDomains") {
|
2017-10-14 20:24:18 +00:00
|
|
|
|
2017-10-23 08:29:59 +00:00
|
|
|
const matchHostName = domainMatcherLib.matchHostName;
|
|
|
|
const matchHostNameBound = matchHostName.bind(undefined, Object.keys(domainRules));
|
2017-10-14 20:24:18 +00:00
|
|
|
const domainToRuleMapping = {};
|
|
|
|
|
2017-11-11 14:03:51 +00:00
|
|
|
data.forEach(function (aHostName) {
|
|
|
|
const ruleNameForHost = matchHostNameBound(aHostName) || defaultKey;
|
|
|
|
domainToRuleMapping[aHostName] = {
|
|
|
|
"ruleName": ruleNameForHost,
|
|
|
|
"numRules": domainRules[ruleNameForHost].length
|
|
|
|
};
|
2017-10-14 20:24:18 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
sendResponse(domainToRuleMapping);
|
2017-10-14 05:41:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-11-11 14:03:51 +00:00
|
|
|
|
|
|
|
// Sent from the popup / browser action, saying that a given
|
|
|
|
// host name should have the default blocking rule applied
|
|
|
|
// (action === "block", or all APIs allowed
|
|
|
|
// (action === "allow").
|
|
|
|
if (label === "toggleBlocking") {
|
|
|
|
|
|
|
|
const {action, hostName} = data;
|
|
|
|
if (action === "block") {
|
|
|
|
delete domainRules[hostName];
|
|
|
|
sendResponse(["toggleBlockingResponse", domainRules[defaultKey].length]);
|
|
|
|
} else if (action === "allow") {
|
|
|
|
domainRules[hostName] = [];
|
|
|
|
sendResponse(["toggleBlockingResponse", 0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
storageLib.set({
|
|
|
|
domainRules,
|
|
|
|
shouldLog
|
|
|
|
});
|
|
|
|
}
|
2017-10-14 05:41:49 +00:00
|
|
|
});
|
|
|
|
|
2017-10-13 22:30:57 +00:00
|
|
|
const requestFilter = {
|
|
|
|
urls: ["<all_urls>"],
|
|
|
|
types: ["main_frame", "sub_frame"]
|
2017-09-12 22:34:25 +00:00
|
|
|
};
|
2017-10-22 05:40:39 +00:00
|
|
|
|
|
|
|
const cookieRemoverRegex = new RegExp(constants.cookieName + "=.*?;");
|
|
|
|
|
|
|
|
// Make sure we never send the cookie value that contains what
|
|
|
|
// standards should be blocked to any server, anytime. In the common
|
|
|
|
// case this will be a NOOP (since the cookie is deleted after being
|
|
|
|
// read), but there are some inconsitancy / timing situations where
|
|
|
|
// making multiple, simultanious requests to the same domain where
|
|
|
|
// we might make a request before deleting the cookie, so the below
|
|
|
|
// adds at least some (incertain) extra protection.
|
|
|
|
rootObject.webRequest.onBeforeSendHeaders.addListener(function (details) {
|
|
|
|
|
|
|
|
const newHeaders = details.requestHeaders.map(function (header) {
|
|
|
|
|
|
|
|
if (header.name.indexOf("Cookie") === -1) {
|
2017-10-23 08:29:59 +00:00
|
|
|
return header;
|
2017-10-22 05:40:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const cookieValue = header.value;
|
|
|
|
header.value = cookieValue.replace(cookieRemoverRegex, "").trim();
|
|
|
|
return header;
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
requestHeaders: newHeaders
|
|
|
|
};
|
|
|
|
}, requestFilter, ["blocking", "requestHeaders"]);
|
2017-10-13 22:30:57 +00:00
|
|
|
|
2017-10-14 20:24:18 +00:00
|
|
|
// Inject the blocking settings for each visited domain / frame.
|
2017-10-14 22:39:27 +00:00
|
|
|
// This needs to be done syncronously, so that the DOM of the visited
|
|
|
|
// page can be instrumented at "document_start" time. This means we
|
|
|
|
// can't do any of the "obvious" techniques for loading the "what should"
|
|
|
|
// be blocked in this frame" information (ie using the storage API).
|
|
|
|
// So, instead, we halt at the http query point, match the domain being
|
|
|
|
// loaded against the current rule set, pack the set of standards
|
|
|
|
// that should be blocked into a base64 encoded bitfield, and then
|
|
|
|
// push that to the page as a cookie.
|
|
|
|
//
|
|
|
|
// The page then reads the information about what standards to block
|
|
|
|
// out of the cookie (by decoding and unpacking the bitfield), and then
|
|
|
|
// deletes the cookie, so nothing is left behind.
|
2017-10-14 20:24:18 +00:00
|
|
|
rootObject.webRequest.onHeadersReceived.addListener(function (details) {
|
2017-10-13 22:30:57 +00:00
|
|
|
|
|
|
|
const url = details.url;
|
|
|
|
|
|
|
|
// Decide which set of blocking rules to use, depending on the host
|
|
|
|
// of the URL being requested.
|
2017-10-14 20:24:18 +00:00
|
|
|
const matchingDomainRule = domainMatcherLib.matchUrl(Object.keys(domainRules), url);
|
|
|
|
const standardsToBlock = domainRules[matchingDomainRule || defaultKey];
|
2017-10-20 15:09:12 +00:00
|
|
|
const encodedOptions = cookieEncodingLib.toCookieValue(standardsToBlock, shouldLog);
|
|
|
|
|
|
|
|
// If we're on a site thats sending the "strict-dynamic"
|
|
|
|
// Content-Security-Policy instruction, then we need to add the
|
|
|
|
// injected proxy code to the list of scripts that are allowed to
|
|
|
|
// run in the page.
|
|
|
|
const cspDynamicPolicyHeaders = details.responseHeaders
|
2017-10-23 05:17:23 +00:00
|
|
|
.filter(httpHeadersLib.isHeaderCSPScriptSrcWithOutUnsafeInline);
|
2017-10-20 15:09:12 +00:00
|
|
|
|
|
|
|
if (cspDynamicPolicyHeaders.length === 1) {
|
|
|
|
const [ignore, scriptHash] = proxyBlockLib.generateScriptPayload(
|
|
|
|
standards,
|
|
|
|
standardsToBlock,
|
|
|
|
shouldLog
|
|
|
|
);
|
2017-10-21 15:49:56 +00:00
|
|
|
|
2017-10-20 15:09:12 +00:00
|
|
|
const newCSPValue = httpHeadersLib.createCSPInstructionWithHashAllowed(
|
|
|
|
cspDynamicPolicyHeaders[0].value,
|
|
|
|
"sha256-" + scriptHash
|
|
|
|
);
|
2017-10-21 15:49:56 +00:00
|
|
|
|
2017-10-20 15:09:12 +00:00
|
|
|
if (newCSPValue !== false) {
|
|
|
|
cspDynamicPolicyHeaders[0].value = newCSPValue;
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 22:30:57 +00:00
|
|
|
|
2017-10-22 05:40:39 +00:00
|
|
|
rootObject.cookies.set({
|
|
|
|
url: details.url,
|
|
|
|
name: constants.cookieName,
|
|
|
|
value: encodedOptions
|
2017-10-22 07:07:22 +00:00
|
|
|
});
|
2017-09-12 22:34:25 +00:00
|
|
|
|
2017-10-13 22:30:57 +00:00
|
|
|
return {
|
|
|
|
responseHeaders: details.responseHeaders
|
|
|
|
};
|
2017-10-14 04:23:49 +00:00
|
|
|
|
2017-10-22 05:40:39 +00:00
|
|
|
}, requestFilter, ["blocking", "responseHeaders"]);
|
2017-10-14 20:24:18 +00:00
|
|
|
}());
|