lots of JSlint based cleanup

This commit is contained in:
Peter Snyder 2017-10-14 15:24:18 -05:00
parent 463b9a10b4
commit b8d2eaeca0
14 changed files with 138 additions and 95 deletions

3
.gitignore vendored
View file

@ -2,4 +2,5 @@ node_modules/
.DS_Store .DS_Store
*.swp *.swp
content_scripts/dist/*.js content_scripts/dist/*.js
web-ext-artifacts/

View file

@ -1,6 +1,7 @@
Web API Manager Web API Manager
=== ===
Overview Overview
--- ---
This extension allows users to selectively allow different hosts on the web 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 websites, and to limit websites to the functionality they actually need
to carry out user-serving purposes. to carry out user-serving purposes.
Background Background
--- ---
Web browsers gain staggering numbers of new features, without their users Web browsers gain staggering numbers of new features, without their users

View file

@ -1,8 +1,9 @@
/*jslint es6: true*/ /*jslint es6: true*/
/*global chrome, browser, window, URI*/ /*global window*/
(function () { (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 rootObject = window.browser || window.chrome;
const defaultKey = "(default)"; const defaultKey = "(default)";
@ -11,6 +12,9 @@
// that should be blocked on matching domains. // that should be blocked on matching domains.
let domainRules; 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) { storageLib.get(function (loadedDomainRules) {
domainRules = loadedDomainRules; domainRules = loadedDomainRules;
}); });
@ -18,7 +22,7 @@
// Manage the state of the browser activity, by displaying the number // Manage the state of the browser activity, by displaying the number
// of origins / frames // of origins / frames
const updateBrowserActionBadge = function (activeInfo) { const updateBrowserActionBadge = function (activeInfo) {
const {tabId, windowId} = activeInfo; const tabId = activeInfo.tabId;
rootObject.tabs.executeScript( rootObject.tabs.executeScript(
tabId, tabId,
{ {
@ -50,39 +54,8 @@
rootObject.tabs.onActivated.addListener(updateBrowserActionBadge); rootObject.tabs.onActivated.addListener(updateBrowserActionBadge);
rootObject.windows.onFocusChanged.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. // 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; const [label, data] = request;
if (label === "rulesUpdate") { if (label === "rulesUpdate") {
domainRules = data; domainRules = data;
@ -90,12 +63,16 @@
} }
if (label === "rulesForDomains") { if (label === "rulesForDomains") {
const ruleForDomain = data.map(whichDomainRuleMatches);
const mapping = {}; const matchHostNameBound = domainMatcherLib.matchHostName.bind(undefined, Object.keys(domainRules));
for (let i = 0; i < ruleForDomain.length; i += 1) { const rulesForDomains = data.map(matchHostNameBound);
mapping[data[i]] = ruleForDomain[i]; const domainToRuleMapping = {};
}
sendResponse(mapping); data.forEach(function (aHostName, index) {
domainToRuleMapping[aHostName] = rulesForDomains[index] || defaultKey;
});
sendResponse(domainToRuleMapping);
return; return;
} }
}); });
@ -106,15 +83,15 @@
}; };
const requestOptions = ["blocking", "responseHeaders"]; 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 url = details.url;
const hostName = extractHostFromUrl(url);
// Decide which set of blocking rules to use, depending on the host // Decide which set of blocking rules to use, depending on the host
// of the URL being requested. // of the URL being requested.
const matchingDomainKey = whichDomainRuleMatches(hostName); const matchingDomainRule = domainMatcherLib.matchUrl(Object.keys(domainRules), url);
const standardsToBlock = domainRules[matchingDomainKey]; const standardsToBlock = domainRules[matchingDomainRule || defaultKey];
const options = Object.keys(standards); const options = Object.keys(standards);
const packedValues = packingLib.pack(options, standardsToBlock); const packedValues = packingLib.pack(options, standardsToBlock);
@ -129,4 +106,4 @@
}; };
}, requestFilter, requestOptions); }, requestFilter, requestOptions);
}()); }());

View file

@ -34,9 +34,9 @@
</div> </div>
</section> </section>
<script src="../lib/init.js"></script> <script src="../lib/init.js"></script>
<script src="../lib/storage.js"></script>
<script src="../content_scripts/dist/defaults.js"></script> <script src="../content_scripts/dist/defaults.js"></script>
<script src="../content_scripts/dist/standards.js"></script> <script src="../content_scripts/dist/standards.js"></script>
<script src="../lib/storage.js"></script>
<script src="js/lib/vue.js"></script> <script src="js/lib/vue.js"></script>
<script src="js/state.js"></script> <script src="js/state.js"></script>
<script src="js/components/domain-rules.vue.js"></script> <script src="js/components/domain-rules.vue.js"></script>

View file

@ -1,5 +1,5 @@
/*jslint es6: true*/ /*jslint es6: true, this: true*/
/*global window, browser, Vue*/ /*global window, Vue*/
(function () { (function () {
"use strict"; "use strict";
@ -65,6 +65,6 @@
isDefault: function (domainName) { isDefault: function (domainName) {
return domainName === "(default)"; return domainName === "(default)";
} }
}, }
}); });
}()); }());

View file

@ -1,6 +1,7 @@
/*jslint es6: true*/ /*jslint es6: true, this: true*/
/*global window, browser, Vue*/ /*global window, Vue*/
(function () { (function () {
"use strict";
const standardsDefaults = window.WEB_API_MANAGER.defaults; const standardsDefaults = window.WEB_API_MANAGER.defaults;

View file

@ -6,25 +6,15 @@
const rootObject = (window.browser || window.chrome); const rootObject = (window.browser || window.chrome);
const doc = window.document; const doc = window.document;
const standards = window.WEB_API_MANAGER.standards; const standards = window.WEB_API_MANAGER.standards;
const defaultConservativeRules = window.WEB_API_MANAGER.defaults.conservative;
const {storageLib, stateLib} = window.WEB_API_MANAGER; const {storageLib, stateLib} = window.WEB_API_MANAGER;
const defaultDomain = "(default)"; const defaultDomain = "(default)";
const state = stateLib.generateStateObject(defaultDomain, standards); const state = stateLib.generateStateObject(defaultDomain, standards);
const onSettingsLoaded = function (settingsResults) { const onSettingsLoaded = function (loadedDomainRules) {
let loadedDomainRules;
if (Object.keys(settingsResults).length !== 0) {
loadedDomainRules = settingsResults;
} else {
loadedDomainRules = Object.create(null);
loadedDomainRules[defaultDomain] = defaultConservativeRules;
}
state.setDomainRules(loadedDomainRules); state.setDomainRules(loadedDomainRules);
const vm = new Vue({ const vm = new Vue({
el: doc.body, el: doc.body,
data: state data: state

View file

@ -1,6 +1,7 @@
/*jslint es6: true*/ /*jslint es6: true, this: true*/
/*global window, browser, Vue*/ /*global window, browser, Vue*/
(function () { (function () {
"use strict";
window.WEB_API_MANAGER.stateLib = {}; window.WEB_API_MANAGER.stateLib = {};
@ -12,27 +13,27 @@
domainRules: {}, domainRules: {},
domainNames: [], domainNames: [],
selectedStandards: [], selectedStandards: [],
setDomainRules: function (newDomainRules) { setDomainRules: function (newDomainRules) {
this.domainRules = newDomainRules; this.domainRules = newDomainRules;
this.domainNames = Object.keys(newDomainRules); this.domainNames = Object.keys(newDomainRules);
this.selectedStandards = this.domainRules[this.selectedDomain]; this.selectedStandards = this.domainRules[this.selectedDomain];
}, },
setSelectedDomain: function (newDomain) { setSelectedDomain: function (newDomain) {
this.selectedDomain = newDomain; this.selectedDomain = newDomain;
this.selectedStandards = this.domainRules[newDomain]; this.selectedStandards = this.domainRules[newDomain];
}, },
setSelectedStandards: function (selectedStandards) { setSelectedStandards: function (selectedStandards) {
this.selectedStandards = selectedStandards; this.selectedStandards = selectedStandards;
this.domainRules[this.selectedDomain] = selectedStandards; this.domainRules[this.selectedDomain] = selectedStandards;
}, },
deleteDomainRule: function (domainToDelete) { deleteDomainRule: function (domainToDelete) {
delete this.domainRules[domainToDelete]; delete this.domainRules[domainToDelete];
this.domainNames = Object.keys(this.domainRules); this.domainNames = Object.keys(this.domainRules);
// If we're deleted the domain thats currently selected, then // If we're deleted the domain thats currently selected, then
// select the default domain. // select the default domain.
if (this.selectedDomain === domainToDelete) { if (this.selectedDomain === domainToDelete) {

46
lib/domainmatcher.js Normal file
View file

@ -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
};
}());

View file

@ -1,3 +1,7 @@
/*global window*/
// Initial content script for the Web API manager extension, that creates // Initial content script for the Web API manager extension, that creates
// the "namespace" we'll use for all the content scripts in the extension. // the "namespace" we'll use for all the content scripts in the extension.
window.WEB_API_MANAGER = {}; (function () {
"use strict";
window.WEB_API_MANAGER = {};
}());

View file

@ -1,8 +1,9 @@
/*jslint es6: true*/ /*jslint es6: true, for: true, bitwise: true*/
/*global window*/ /*global window*/
(function () { (function () {
"use strict"; "use strict";
const {btoa, atob} = window;
const bucketSize = 8; const bucketSize = 8;
const bufferToBase64 = function (buf) { const bufferToBase64 = function (buf) {
@ -16,7 +17,7 @@
const binstr = atob(base64); const binstr = atob(base64);
const buf = new Uint8Array(binstr.length); const buf = new Uint8Array(binstr.length);
Array.prototype.forEach.call(binstr, function (ch, i) { Array.prototype.forEach.call(binstr, function (ch, i) {
buf[i] = ch.charCodeAt(0); buf[i] = ch.charCodeAt(0);
}); });
return buf; return buf;
}; };
@ -47,11 +48,13 @@
const binnedOptions = options.reduce(binToBucketSizeFunc, []); const binnedOptions = options.reduce(binToBucketSizeFunc, []);
const bitFields = new Uint8Array(numBuckets); 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 bitfield = 0;
let currentBucket = binnedOptions[i]; 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]; let currentOption = currentBucket[j];
if (selected.indexOf(currentOption) !== -1) { if (selected.indexOf(currentOption) !== -1) {
@ -68,7 +71,6 @@
const unpack = function (options, data) { const unpack = function (options, data) {
const numBuckets = Math.ceil(options.length / bucketSize);
const binToBucketSizeFunc = binOptionsReduceFunction.bind(undefined, bucketSize); const binToBucketSizeFunc = binOptionsReduceFunction.bind(undefined, bucketSize);
options.sort(); options.sort();
@ -77,11 +79,13 @@
const result = []; 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 currentBitField = bitFields[i];
let currentOptionsBin = binnedOptions[i]; let currentOptionsBin = binnedOptions[i];
for (let j = 0; j < bucketSize; j += 1) { for (j = 0; j < bucketSize; j += 1) {
if (currentBitField & (1 << j)) { if (currentBitField & (1 << j)) {
let currentOption = currentOptionsBin[j]; let currentOption = currentOptionsBin[j];
result.push(currentOption); result.push(currentOption);
@ -93,6 +97,7 @@
}; };
window.WEB_API_MANAGER.packingLib = { window.WEB_API_MANAGER.packingLib = {
pack, unpack pack,
unpack
}; };
}()); }());

View file

@ -3,13 +3,25 @@
(function () { (function () {
"use strict"; "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 webApiManagerKeySettingsKey = "webApiManagerDomainRules";
const storageObject = browserObj.storage; const storageObject = rootObject.storage;
const get = function (callback) { const get = function (callback) {
storageObject.local.get(webApiManagerKeySettingsKey, function (results) { 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);
}); });
}; };

View file

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "WebAPI Manager", "name": "WebAPI Manager",
"version": "0.5", "version": "0.7",
"description": "Improves browser security by restricting page access to parts of the Web API.", "description": "Improves browser security by restricting page access to parts of the Web API.",
"icons": { "icons": {
"48": "images/uic-48.png", "48": "images/uic-48.png",
@ -37,20 +37,21 @@
"lib/init.js", "lib/init.js",
"lib/js.cookie.js", "lib/js.cookie.js",
"lib/storage.js", "lib/storage.js",
"lib/URI.js" "lib/URI.js",
"content_scripts/dist/defaults.js"
], ],
"background": { "background": {
"scripts": [ "scripts": [
"lib/init.js", "lib/init.js",
"lib/pack.js", "lib/pack.js",
"lib/storage.js",
"lib/URI.js", "lib/URI.js",
"content_scripts/dist/standards.js", "content_scripts/dist/standards.js",
"content_scripts/dist/defaults.js",
"lib/storage.js",
"background_scripts/background.js" "background_scripts/background.js"
] ]
}, },
"options_ui": { "options_ui": {
"page": "config/index.html", "page": "config/index.html"
"chrome_style": true
} }
} }

View file

@ -1,8 +1,11 @@
/*jslint es6: true*/ /*jslint es6: true*/
/*global window*/ /*global window*/
(function () { (function () {
"use strict";
const rootObject = window.browser || window.chrome; 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) { configureButton.addEventListener("click", function (event) {
rootObject.runtime.openOptionsPage(); rootObject.runtime.openOptionsPage();
@ -21,26 +24,26 @@
const message = ["rulesForDomains", uniqueDomains]; const message = ["rulesForDomains", uniqueDomains];
rootObject.runtime.sendMessage(message, function (response) { 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); const domainNames = Object.keys(response);
domainNames.forEach(function (aDomain) { domainNames.forEach(function (aDomain) {
const domainRule = response[aDomain]; const domainRule = response[aDomain];
const liElm = document.createElement("li"); const liElm = doc.createElement("li");
liElm.className = "list-group-item"; liElm.className = "list-group-item";
if (domainRule !== "(default)") { if (domainRule !== "(default)") {
liElm.className += " list-group-item-success"; liElm.className += " list-group-item-success";
} }
const spanElm = document.createElement("span"); const spanElm = doc.createElement("span");
spanElm.className = "badge"; spanElm.className = "badge";
const badgeText = document.createTextNode(domainRule); const badgeText = doc.createTextNode(domainRule);
spanElm.appendChild(badgeText); spanElm.appendChild(badgeText);
liElm.appendChild(spanElm); liElm.appendChild(spanElm);
const textElm = document.createTextNode(aDomain); const textElm = doc.createTextNode(aDomain);
liElm.appendChild(textElm); liElm.appendChild(textElm);
listGroupElm.appendChild(liElm); listGroupElm.appendChild(liElm);
}); });