diff --git a/.gitignore b/.gitignore
index 5205c64..0614f9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ node_modules/
.DS_Store
*.swp
-content_scripts/dist/*.js
\ No newline at end of file
+content_scripts/dist/*.js
+web-ext-artifacts/
diff --git a/README.md b/README.md
index b1685ef..0d0e772 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
Web API Manager
===
+
Overview
---
This extension allows users to selectively allow different hosts on the web
@@ -9,6 +10,7 @@ security and privacy sensitive web users to limit the attack surface presented
to websites, and to limit websites to the functionality they actually need
to carry out user-serving purposes.
+
Background
---
Web browsers gain staggering numbers of new features, without their users
diff --git a/background_scripts/background.js b/background_scripts/background.js
index ae840ca..b0b7b2f 100644
--- a/background_scripts/background.js
+++ b/background_scripts/background.js
@@ -1,8 +1,9 @@
/*jslint es6: true*/
-/*global chrome, browser, window, URI*/
+/*global window*/
(function () {
+ "use strict";
- const {packingLib, standards, storageLib} = window.WEB_API_MANAGER;
+ const {packingLib, standards, storageLib, domainMatcherLib} = window.WEB_API_MANAGER;
const rootObject = window.browser || window.chrome;
const defaultKey = "(default)";
@@ -11,6 +12,9 @@
// that should be blocked on matching domains.
let domainRules;
+ // 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.
storageLib.get(function (loadedDomainRules) {
domainRules = loadedDomainRules;
});
@@ -18,7 +22,7 @@
// Manage the state of the browser activity, by displaying the number
// of origins / frames
const updateBrowserActionBadge = function (activeInfo) {
- const {tabId, windowId} = activeInfo;
+ const tabId = activeInfo.tabId;
rootObject.tabs.executeScript(
tabId,
{
@@ -50,39 +54,8 @@
rootObject.tabs.onActivated.addListener(updateBrowserActionBadge);
rootObject.windows.onFocusChanged.addListener(updateBrowserActionBadge);
- // Inject the blocking settings for each visited domain / frame.
- const extractHostFromUrl = function (url) {
- const uri = URI(url);
- return uri.hostname();
- };
-
- const matchingUrlReduceFunction = function (domain, prev, next) {
- if (prev) {
- return prev;
- }
-
- const domainRegex = new RegExp(next);
- if (domainRegex.test(domain)) {
- return next;
- }
-
- return prev;
- };
-
- const whichDomainRuleMatches = function (hostName) {
- // of the URL being requested.
- const matchingUrlReduceFunctionBound = matchingUrlReduceFunction.bind(undefined, hostName);
- const matchingPattern = Object
- .keys(domainRules)
- .filter((aRule) => aRule !== defaultKey)
- .sort()
- .reduce(matchingUrlReduceFunctionBound, undefined);
-
- return matchingPattern || defaultKey;
- };
-
// Listen for updates to the domain rules from the config page.
- rootObject.runtime.onMessage.addListener(function (request, sender, sendResponse) {
+ rootObject.runtime.onMessage.addListener(function (request, ignore, sendResponse) {
const [label, data] = request;
if (label === "rulesUpdate") {
domainRules = data;
@@ -90,12 +63,16 @@
}
if (label === "rulesForDomains") {
- const ruleForDomain = data.map(whichDomainRuleMatches);
- const mapping = {};
- for (let i = 0; i < ruleForDomain.length; i += 1) {
- mapping[data[i]] = ruleForDomain[i];
- }
- sendResponse(mapping);
+
+ const matchHostNameBound = domainMatcherLib.matchHostName.bind(undefined, Object.keys(domainRules));
+ const rulesForDomains = data.map(matchHostNameBound);
+ const domainToRuleMapping = {};
+
+ data.forEach(function (aHostName, index) {
+ domainToRuleMapping[aHostName] = rulesForDomains[index] || defaultKey;
+ });
+
+ sendResponse(domainToRuleMapping);
return;
}
});
@@ -106,15 +83,15 @@
};
const requestOptions = ["blocking", "responseHeaders"];
- chrome.webRequest.onHeadersReceived.addListener(function (details) {
+ // Inject the blocking settings for each visited domain / frame.
+ rootObject.webRequest.onHeadersReceived.addListener(function (details) {
const url = details.url;
- const hostName = extractHostFromUrl(url);
// Decide which set of blocking rules to use, depending on the host
// of the URL being requested.
- const matchingDomainKey = whichDomainRuleMatches(hostName);
- const standardsToBlock = domainRules[matchingDomainKey];
+ const matchingDomainRule = domainMatcherLib.matchUrl(Object.keys(domainRules), url);
+ const standardsToBlock = domainRules[matchingDomainRule || defaultKey];
const options = Object.keys(standards);
const packedValues = packingLib.pack(options, standardsToBlock);
@@ -129,4 +106,4 @@
};
}, requestFilter, requestOptions);
-}());
\ No newline at end of file
+}());
diff --git a/config/index.html b/config/index.html
index 2ceffb5..20f2e0e 100644
--- a/config/index.html
+++ b/config/index.html
@@ -34,9 +34,9 @@
-
+
diff --git a/config/js/components/domain-rules.vue.js b/config/js/components/domain-rules.vue.js
index 7875c97..83e688a 100644
--- a/config/js/components/domain-rules.vue.js
+++ b/config/js/components/domain-rules.vue.js
@@ -1,5 +1,5 @@
-/*jslint es6: true*/
-/*global window, browser, Vue*/
+/*jslint es6: true, this: true*/
+/*global window, Vue*/
(function () {
"use strict";
@@ -65,6 +65,6 @@
isDefault: function (domainName) {
return domainName === "(default)";
}
- },
+ }
});
}());
\ No newline at end of file
diff --git a/config/js/components/web-api-standards.vue.js b/config/js/components/web-api-standards.vue.js
index acc78d9..0a0e9a8 100644
--- a/config/js/components/web-api-standards.vue.js
+++ b/config/js/components/web-api-standards.vue.js
@@ -1,6 +1,7 @@
-/*jslint es6: true*/
-/*global window, browser, Vue*/
+/*jslint es6: true, this: true*/
+/*global window, Vue*/
(function () {
+ "use strict";
const standardsDefaults = window.WEB_API_MANAGER.defaults;
diff --git a/config/js/config.js b/config/js/config.js
index 79f694d..4f86cfb 100644
--- a/config/js/config.js
+++ b/config/js/config.js
@@ -6,25 +6,15 @@
const rootObject = (window.browser || window.chrome);
const doc = window.document;
const standards = window.WEB_API_MANAGER.standards;
- const defaultConservativeRules = window.WEB_API_MANAGER.defaults.conservative;
const {storageLib, stateLib} = window.WEB_API_MANAGER;
const defaultDomain = "(default)";
const state = stateLib.generateStateObject(defaultDomain, standards);
- const onSettingsLoaded = function (settingsResults) {
-
- let loadedDomainRules;
-
- if (Object.keys(settingsResults).length !== 0) {
- loadedDomainRules = settingsResults;
- } else {
- loadedDomainRules = Object.create(null);
- loadedDomainRules[defaultDomain] = defaultConservativeRules;
- }
+ const onSettingsLoaded = function (loadedDomainRules) {
state.setDomainRules(loadedDomainRules);
-
+
const vm = new Vue({
el: doc.body,
data: state
diff --git a/config/js/state.js b/config/js/state.js
index d8da92b..8dfba14 100644
--- a/config/js/state.js
+++ b/config/js/state.js
@@ -1,6 +1,7 @@
-/*jslint es6: true*/
+/*jslint es6: true, this: true*/
/*global window, browser, Vue*/
(function () {
+ "use strict";
window.WEB_API_MANAGER.stateLib = {};
@@ -12,27 +13,27 @@
domainRules: {},
domainNames: [],
selectedStandards: [],
-
+
setDomainRules: function (newDomainRules) {
this.domainRules = newDomainRules;
this.domainNames = Object.keys(newDomainRules);
this.selectedStandards = this.domainRules[this.selectedDomain];
},
-
+
setSelectedDomain: function (newDomain) {
this.selectedDomain = newDomain;
this.selectedStandards = this.domainRules[newDomain];
},
-
+
setSelectedStandards: function (selectedStandards) {
this.selectedStandards = selectedStandards;
this.domainRules[this.selectedDomain] = selectedStandards;
},
-
+
deleteDomainRule: function (domainToDelete) {
delete this.domainRules[domainToDelete];
this.domainNames = Object.keys(this.domainRules);
-
+
// If we're deleted the domain thats currently selected, then
// select the default domain.
if (this.selectedDomain === domainToDelete) {
diff --git a/lib/domainmatcher.js b/lib/domainmatcher.js
new file mode 100644
index 0000000..5e5986d
--- /dev/null
+++ b/lib/domainmatcher.js
@@ -0,0 +1,46 @@
+/*jslint es6: true*/
+/*global window*/
+(function () {
+ "use strict";
+ const defaultKey = "(default)";
+
+ const extractHostNameFromUrl = function (url) {
+ const uri = window.URI(url);
+ return uri.hostname();
+ };
+
+ const matchingUrlReduceFunction = function (hostName, prev, next) {
+ if (prev) {
+ return prev;
+ }
+
+ const domainRegex = new RegExp(next);
+ if (domainRegex.test(hostName)) {
+ return next;
+ }
+
+ return prev;
+ };
+
+ const matchHostName = function (domainRegExes, hostName) {
+ // of the URL being requested.
+ const matchingUrlReduceFunctionBound = matchingUrlReduceFunction.bind(undefined, hostName);
+ const matchingPattern = domainRegExes
+ .filter((aRule) => aRule !== defaultKey)
+ .sort()
+ .reduce(matchingUrlReduceFunctionBound, undefined);
+
+ return matchingPattern || undefined;
+ };
+
+ const matchUrl = function (domainRegExes, url) {
+ const hostName = extractHostNameFromUrl(url);
+ return matchHostName(domainRegExes, hostName);
+ };
+
+ window.WEB_API_MANAGER.domainMatcherLib = {
+ matchHostName,
+ matchUrl
+ };
+
+}());
\ No newline at end of file
diff --git a/lib/init.js b/lib/init.js
index 8585f2b..1e772b3 100644
--- a/lib/init.js
+++ b/lib/init.js
@@ -1,3 +1,7 @@
+/*global window*/
// Initial content script for the Web API manager extension, that creates
// the "namespace" we'll use for all the content scripts in the extension.
-window.WEB_API_MANAGER = {};
\ No newline at end of file
+(function () {
+ "use strict";
+ window.WEB_API_MANAGER = {};
+}());
diff --git a/lib/pack.js b/lib/pack.js
index 2504eeb..ec580c6 100644
--- a/lib/pack.js
+++ b/lib/pack.js
@@ -1,8 +1,9 @@
-/*jslint es6: true*/
+/*jslint es6: true, for: true, bitwise: true*/
/*global window*/
(function () {
"use strict";
+ const {btoa, atob} = window;
const bucketSize = 8;
const bufferToBase64 = function (buf) {
@@ -16,7 +17,7 @@
const binstr = atob(base64);
const buf = new Uint8Array(binstr.length);
Array.prototype.forEach.call(binstr, function (ch, i) {
- buf[i] = ch.charCodeAt(0);
+ buf[i] = ch.charCodeAt(0);
});
return buf;
};
@@ -47,11 +48,13 @@
const binnedOptions = options.reduce(binToBucketSizeFunc, []);
const bitFields = new Uint8Array(numBuckets);
- for (let i = 0; i < numBuckets; i += 1) {
+ let i, j;
+
+ for (i = 0; i < numBuckets; i += 1) {
let bitfield = 0;
let currentBucket = binnedOptions[i];
- for (let j = 0; j < currentBucket.length; j += 1) {
+ for (j = 0; j < currentBucket.length; j += 1) {
let currentOption = currentBucket[j];
if (selected.indexOf(currentOption) !== -1) {
@@ -68,7 +71,6 @@
const unpack = function (options, data) {
- const numBuckets = Math.ceil(options.length / bucketSize);
const binToBucketSizeFunc = binOptionsReduceFunction.bind(undefined, bucketSize);
options.sort();
@@ -77,11 +79,13 @@
const result = [];
- for (let i = 0; i < bitFields.length; i += 1) {
+ let i, j;
+
+ for (i = 0; i < bitFields.length; i += 1) {
let currentBitField = bitFields[i];
let currentOptionsBin = binnedOptions[i];
- for (let j = 0; j < bucketSize; j += 1) {
+ for (j = 0; j < bucketSize; j += 1) {
if (currentBitField & (1 << j)) {
let currentOption = currentOptionsBin[j];
result.push(currentOption);
@@ -93,6 +97,7 @@
};
window.WEB_API_MANAGER.packingLib = {
- pack, unpack
+ pack,
+ unpack
};
}());
\ No newline at end of file
diff --git a/lib/storage.js b/lib/storage.js
index 3ffbd87..b8e93d7 100644
--- a/lib/storage.js
+++ b/lib/storage.js
@@ -3,13 +3,25 @@
(function () {
"use strict";
- const browserObj = window.browser || window.chrome;
+ const rootObject = window.browser || window.chrome;
+ const defaultConservativeRules = window.WEB_API_MANAGER.defaults.conservative;
const webApiManagerKeySettingsKey = "webApiManagerDomainRules";
- const storageObject = browserObj.storage;
+ const storageObject = rootObject.storage;
const get = function (callback) {
storageObject.local.get(webApiManagerKeySettingsKey, function (results) {
- callback(results && results[webApiManagerKeySettingsKey]);
+
+ let loadedDomainRules = results && results[webApiManagerKeySettingsKey];
+
+ // If there are no currently saved domain rules, then create
+ // a stubbed out one, using the conservative blocking rule set.
+ if (!loadedDomainRules || Object.keys(loadedDomainRules).length === 0) {
+ loadedDomainRules = {
+ "(default)": defaultConservativeRules
+ };
+ }
+
+ callback(loadedDomainRules);
});
};
diff --git a/manifest.json b/manifest.json
index 84ee897..a3026d7 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "WebAPI Manager",
- "version": "0.5",
+ "version": "0.7",
"description": "Improves browser security by restricting page access to parts of the Web API.",
"icons": {
"48": "images/uic-48.png",
@@ -37,20 +37,21 @@
"lib/init.js",
"lib/js.cookie.js",
"lib/storage.js",
- "lib/URI.js"
+ "lib/URI.js",
+ "content_scripts/dist/defaults.js"
],
"background": {
"scripts": [
"lib/init.js",
"lib/pack.js",
- "lib/storage.js",
"lib/URI.js",
"content_scripts/dist/standards.js",
+ "content_scripts/dist/defaults.js",
+ "lib/storage.js",
"background_scripts/background.js"
]
},
"options_ui": {
- "page": "config/index.html",
- "chrome_style": true
+ "page": "config/index.html"
}
}
diff --git a/popup/js/popup.js b/popup/js/popup.js
index 27cfc4c..a0473ae 100644
--- a/popup/js/popup.js
+++ b/popup/js/popup.js
@@ -1,8 +1,11 @@
/*jslint es6: true*/
/*global window*/
(function () {
+ "use strict";
+
const rootObject = window.browser || window.chrome;
- const configureButton = window.document.getElementById("config-page-link");
+ const doc = window.document;
+ const configureButton = doc.getElementById("config-page-link");
configureButton.addEventListener("click", function (event) {
rootObject.runtime.openOptionsPage();
@@ -21,26 +24,26 @@
const message = ["rulesForDomains", uniqueDomains];
rootObject.runtime.sendMessage(message, function (response) {
- const listGroupElm = document.querySelector("ul.list-group");
+ const listGroupElm = doc.querySelector("ul.list-group");
const domainNames = Object.keys(response);
domainNames.forEach(function (aDomain) {
const domainRule = response[aDomain];
-
- const liElm = document.createElement("li");
+
+ const liElm = doc.createElement("li");
liElm.className = "list-group-item";
if (domainRule !== "(default)") {
liElm.className += " list-group-item-success";
}
- const spanElm = document.createElement("span");
+ const spanElm = doc.createElement("span");
spanElm.className = "badge";
- const badgeText = document.createTextNode(domainRule);
+ const badgeText = doc.createTextNode(domainRule);
spanElm.appendChild(badgeText);
liElm.appendChild(spanElm);
- const textElm = document.createTextNode(aDomain);
+ const textElm = doc.createTextNode(aDomain);
liElm.appendChild(textElm);
listGroupElm.appendChild(liElm);
});