Fixed possible surprises in background script message handling.

This commit is contained in:
hackademix 2018-08-21 23:54:04 +02:00
parent 91334fe944
commit 1de1db3c29
7 changed files with 321 additions and 311 deletions

View File

@ -213,71 +213,71 @@ var RequestGuard = (() => {
if (!("setIcon" in browser.browserAction)) { // unsupported on Android
TabStatus._updateTabNow = TabStatus.updateTab = () => {};
}
const Content = {
async hearFrom(message, sender) {
debug("Received message from content", message, sender);
switch (message.type) {
case "pageshow":
TabStatus.recordAll(sender.tab.id, message.seen);
return true;
case "enable": {
let {url, documentUrl, policyType} = message;
let TAG = `<${policyType.toUpperCase()}>`;
let origin = Sites.origin(url);
let {siteKey} = Sites.parse(url);
let options;
if (siteKey === origin) {
TAG += `@${siteKey}`;
} else {
options = [
{label: _("allowLocal", siteKey), checked: true},
{label: _("allowLocal", origin)}
];
}
// let parsedDoc = Sites.parse(documentUrl);
let t = u => `${TAG}@${u}`;
let ret = await Prompts.prompt({
title: _("BlockedObjects"),
message: _("allowLocal", TAG),
options});
debug(`Prompt returned %o`);
if (ret.button !== 0) return;
let key = [siteKey, origin][ret.option || 0];
if (!key) return;
let {siteMatch, contextMatch, perms} = ns.policy.get(key, documentUrl);
let {capabilities} = perms;
if (!capabilities.has(policyType)) {
perms = new Permissions(new Set(capabilities), false);
perms.capabilities.add(policyType);
/* TODO: handle contextual permissions
if (documentUrl) {
let context = new URL(documentUrl).origin;
let contextualSites = new Sites([context, perms]);
perms = new Permissions(new Set(capabilities), false, contextualSites);
}
*/
ns.policy.set(key, perms);
ns.savePolicy();
}
return true;
}
case "docStatus": {
let {frameId, tab} = sender;
let {url} = message;
let tabId = tab.id;
let records = TabStatus.map.get(tabId);
let noscriptFrames = records && records.noscriptFrames;
let canScript = !(noscriptFrames && noscriptFrames[sender.frameId]);
let shouldScript = !ns.isEnforced(tabId) || !url.startsWith("http") || ns.policy.can(url, "script");
debug("Frame %s %s of %o, canScript: %s, shouldScript: %s", frameId, url, noscriptFrames, canScript, shouldScript);
return {canScript, shouldScript};
}
}
let messageHandler = {
async pageshow(message, sender) {
TabStatus.recordAll(sender.tab.id, message.seen);
return true;
},
async enable(message, sender) {
let {url, documentUrl, policyType} = message;
let TAG = `<${policyType.toUpperCase()}>`;
let origin = Sites.origin(url);
let {siteKey} = Sites.parse(url);
let options;
if (siteKey === origin) {
TAG += `@${siteKey}`;
} else {
options = [
{label: _("allowLocal", siteKey), checked: true},
{label: _("allowLocal", origin)}
];
}
// let parsedDoc = Sites.parse(documentUrl);
let t = u => `${TAG}@${u}`;
let ret = await Prompts.prompt({
title: _("BlockedObjects"),
message: _("allowLocal", TAG),
options});
debug(`Prompt returned %o`);
if (ret.button !== 0) return;
let key = [siteKey, origin][ret.option || 0];
if (!key) return;
let {siteMatch, contextMatch, perms} = ns.policy.get(key, documentUrl);
let {capabilities} = perms;
if (!capabilities.has(policyType)) {
perms = new Permissions(new Set(capabilities), false);
perms.capabilities.add(policyType);
/* TODO: handle contextual permissions
if (documentUrl) {
let context = new URL(documentUrl).origin;
let contextualSites = new Sites([context, perms]);
perms = new Permissions(new Set(capabilities), false, contextualSites);
}
*/
ns.policy.set(key, perms);
await ns.savePolicy();
}
return true;
},
async docStatus(message, sender) {
let {frameId, tab} = sender;
let {url} = message;
let tabId = tab.id;
let records = TabStatus.map.get(tabId);
let noscriptFrames = records && records.noscriptFrames;
let canScript = !(noscriptFrames && noscriptFrames[sender.frameId]);
let shouldScript = !ns.isEnforced(tabId) || !url.startsWith("http") || ns.policy.can(url, "script");
debug("Frame %s %s of %o, canScript: %s, shouldScript: %s", frameId, url, noscriptFrames, canScript, shouldScript);
return {canScript, shouldScript};
}
}
const Content = {
async reportTo(request, allowed, policyType) {
let {requestId, tabId, frameId, type, url, documentUrl, originUrl} = request;
@ -312,7 +312,6 @@ var RequestGuard = (() => {
}
}
};
browser.runtime.onMessage.addListener(Content.hearFrom);
const pendingRequests = new Map();
function initPendingRequest(request) {
@ -551,6 +550,8 @@ var RequestGuard = (() => {
const RequestGuard = {
async start() {
Messages.addHandler(messageHandler);
let wr = browser.webRequest;
let listen = (what, ...args) => wr[what].addListener(listeners[what], ...args);
@ -608,6 +609,7 @@ var RequestGuard = (() => {
}
}
wr.onBeforeRequest.removeListener(onViolationReport);
Messages.removeHandler(messageHandler);
}
};

View File

@ -1,272 +1,248 @@
var ns = (() => {
'use strict';
{
'use strict';
const popupURL = browser.extension.getURL("/ui/popup.html");
let popupFor = tabId => `${popupURL}#tab${tabId}`;
let popupURL = browser.extension.getURL("/ui/popup.html");
let popupFor = tabId => `${popupURL}#tab${tabId}`;
let ctxMenuId = "noscript-ctx-menu";
let ctxMenuId = "noscript-ctx-menu";
async function toggleCtxMenuItem(show = ns.local.showCtxMenuItem) {
if (!"contextMenus" in browser) return;
let id = ctxMenuId;
try {
await browser.contextMenus.remove(id);
} catch (e) {}
async function toggleCtxMenuItem(show = ns.local.showCtxMenuItem) {
if (!"contextMenus" in browser) return;
let id = ctxMenuId;
try {
await browser.contextMenus.remove(id);
} catch (e) {}
if (show) {
browser.contextMenus.create({
id,
title: "NoScript",
contexts: ["all"]
});
}
}
if (show) {
browser.contextMenus.create({
id,
title: "NoScript",
contexts: ["all"]
});
}
}
async function init() {
let policyData = (await Storage.get("sync", "policy")).policy;
if (policyData && policyData.DEFAULT) {
ns.policy = new Policy(policyData);
await ChildPolicies.update(policyData);
} else {
await include("/legacy/Legacy.js");
ns.policy = await Legacy.createOrMigratePolicy();
ns.savePolicy();
}
await include("/bg/defaults.js");
await ns.defaults;
await include("/bg/RequestGuard.js");
await RequestGuard.start();
await XSS.start(); // we must start it anyway to initialize sub-objects
if (!ns.sync.xss) {
XSS.stop();
}
Commands.install();
};
async function init() {
let policyData = (await Storage.get("sync", "policy")).policy;
if (policyData && policyData.DEFAULT) {
ns.policy = new Policy(policyData);
await ChildPolicies.update(policyData);
} else {
await include("/legacy/Legacy.js");
ns.policy = await Legacy.createOrMigratePolicy();
ns.savePolicy();
}
await include("/bg/defaults.js");
await ns.defaults;
await include("/bg/RequestGuard.js");
await RequestGuard.start();
await XSS.start(); // we must start it anyway to initialize sub-objects
if (!ns.sync.xss) {
XSS.stop();
}
Commands.install();
};
var Commands = {
openPageUI() {
try {
browser.browserAction.openPopup();
return;
} catch (e) {
debug(e);
}
browser.windows.create({
url: popupURL,
width: 800,
height: 600,
type: "panel"
});
},
let Commands = {
openPageUI() {
try {
browser.browserAction.openPopup();
return;
} catch (e) {
debug(e);
}
browser.windows.create({
url: popupURL,
width: 800,
height: 600,
type: "panel"
});
},
togglePermissions() {},
install() {
togglePermissions() {},
install() {
if ("command" in browser) {
// keyboard shortcuts
browser.commands.onCommand.addListener(cmd => {
if (cmd in Commands) {
Commands[cmd]();
}
});
}
if ("command" in browser) {
// keyboard shortcuts
browser.commands.onCommand.addListener(cmd => {
if (cmd in Commands) {
Commands[cmd]();
}
});
}
if ("contextMenus" in browser) {
toggleCtxMenuItem();
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId == ctxMenuId) {
this.openPageUI();
}
});
}
if ("contextMenus" in browser) {
toggleCtxMenuItem();
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId == ctxMenuId) {
this.openPageUI();
}
});
}
// wiring main UI
let ba = browser.browserAction;
if ("setIcon" in ba) {
//desktop
ba.setPopup({
popup: popupURL
});
} else {
// mobile
ba.onClicked.addListener(async tab => {
try {
await browser.tabs.remove(await browser.tabs.query({
url: popupURL
}));
} catch (e) {}
await browser.tabs.create({
url: popupFor(tab.id)
});
});
}
}
}
// wiring main UI
let ba = browser.browserAction;
if ("setIcon" in ba) {
//desktop
ba.setPopup({
popup: popupURL
});
} else {
// mobile
ba.onClicked.addListener(async tab => {
try {
await browser.tabs.remove(await browser.tabs.query({
url: popupURL
}));
} catch (e) {}
await browser.tabs.create({
url: popupFor(tab.id)
});
});
}
}
}
var MessageHandler = {
responders: {
let messageHandler = {
async updateSettings(settings, sender) {
await Settings.update(settings);
toggleCtxMenuItem();
},
async broadcastSettings({
tabId = -1
}) {
let policy = ns.policy.dry(true);
let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
let xssUserChoices = await XSS.getUserChoices();
browser.runtime.sendMessage({
type: "settings",
policy,
seen,
xssUserChoices,
local: ns.local,
sync: ns.sync,
unrestrictedTab: ns.unrestrictedTabs.has(tabId),
});
},
async updateSettings(settings, sender) {
await Settings.update(settings);
toggleCtxMenuItem();
},
async broadcastSettings({
tabId = -1
}) {
let policy = ns.policy.dry(true);
let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
let xssUserChoices = await XSS.getUserChoices();
browser.runtime.sendMessage({
type: "settings",
policy,
seen,
xssUserChoices,
local: ns.local,
sync: ns.sync,
unrestrictedTab: ns.unrestrictedTabs.has(tabId),
});
},
async exportSettings() {
return Settings.export();
},
exportSettings(m, sender, sendResponse) {
sendResponse(Settings.export());
return false;
},
async importSettings({data}) {
return await Settings.import(data);
},
async importSettings({
data
}) {
return await Settings.import(data);
},
async openStandalonePopup() {
let win = await browser.windows.getLastFocused();
let [tab] = (await browser.tabs.query({
lastFocusedWindow: true,
active: true
}));
async openStandalonePopup() {
let win = await browser.windows.getLastFocused();
let [tab] = (await browser.tabs.query({
lastFocusedWindow: true,
active: true
}));
if (!tab || tab.id === -1) {
log("No tab found to open the UI for");
return;
}
browser.windows.create({
url: popupFor(tab.id),
width: 800,
height: 600,
top: win.top + 48,
left: win.left + 48,
type: "panel"
});
}
},
onMessage(m, sender, sendResponse) {
let {
type
} = m;
let {
responders
} = MessageHandler;
if (type && (type = type.replace(/^NoScript\./, '')) in responders) {
return responders[type](m, sender, sendResponse);
} else {
debug("Received unkown message", m, sender);
}
return false;
},
listen() {
browser.runtime.onMessage.addListener(this.onMessage);
},
}
if (!tab || tab.id === -1) {
log("No tab found to open the UI for");
return;
}
browser.windows.create({
url: popupFor(tab.id),
width: 800,
height: 600,
top: win.top + 48,
left: win.left + 48,
type: "panel"
});
},
};
return {
running: false,
policy: null,
local: null,
sync: null,
unrestrictedTabs: new Set(),
isEnforced(tabId = -1) {
return this.policy.enforced && (tabId === -1 || !this.unrestrictedTabs.has(tabId));
},
var ns = {
running: false,
policy: null,
local: null,
sync: null,
unrestrictedTabs: new Set(),
isEnforced(tabId = -1) {
return this.policy.enforced && (tabId === -1 || !this.unrestrictedTabs.has(tabId));
},
start() {
if (this.running) return;
this.running = true;
start() {
if (this.running) return;
this.running = true;
deferWebTraffic(init(),
async () => {
deferWebTraffic(init(),
async () => {
await include("/bg/Settings.js");
MessageHandler.listen();
await include("/bg/Settings.js");
Messages.addHandler(messageHandler);
log("STARTED");
log("STARTED");
this.devMode = (await browser.management.getSelf()).installType === "development";
if (this.local.debug) {
if (this.devMode) {
include("/test/run.js");
}
} else {
debug = () => {}; // suppress verbosity
}
});
},
this.devMode = (await browser.management.getSelf()).installType === "development";
if (this.local.debug) {
if (this.devMode) {
include("/test/run.js");
}
} else {
debug = () => {}; // suppress verbosity
}
});
},
stop() {
if (!this.running) return;
this.running = false;
RequestGuard.stop();
log("STOPPED");
},
stop() {
if (!this.running) return;
this.running = false;
Messages.removeHandler(messageHandler);
RequestGuard.stop();
log("STOPPED");
},
async savePolicy() {
if (this.policy) {
await ChildPolicies.update(this.policy);
await Storage.set("sync", {
policy: this.policy.dry()
});
await browser.webRequest.handlerBehaviorChanged()
}
return this.policy;
},
async savePolicy() {
if (this.policy) {
await ChildPolicies.update(this.policy);
await Storage.set("sync", {
policy: this.policy.dry()
});
await browser.webRequest.handlerBehaviorChanged()
}
return this.policy;
},
async save(obj) {
if (obj && obj.storage) {
let toBeSaved = {
[obj.storage]: obj
};
Storage.set(obj.storage, toBeSaved);
}
return obj;
},
async save(obj) {
if (obj && obj.storage) {
let toBeSaved = {
[obj.storage]: obj
};
Storage.set(obj.storage, toBeSaved);
}
return obj;
},
async collectSeen(tabId) {
async collectSeen(tabId) {
try {
let seen = Array.from(await browser.tabs.sendMessage(tabId, {
type: "collect"
}, {
frameId: 0
}));
debug("Collected seen", seen);
return seen;
} catch (e) {
// probably a page where content scripts cannot run, let's open the options instead
error(e, "Cannot collect noscript activity data");
}
try {
let seen = Array.from(await browser.tabs.sendMessage(tabId, {
type: "collect"
}, {
frameId: 0
}));
debug("Collected seen", seen);
return seen;
} catch (e) {
// probably a page where content scripts cannot run, let's open the options instead
error(e, "Cannot collect noscript activity data");
}
return null;
},
};
})();
return null;
},
};
}
ns.start();

View File

@ -117,7 +117,7 @@ var PlaceHolder = (() => {
async enable(replacement) {
debug("Enabling %o", this.request, this.policyType);
let ok = await browser.runtime.sendMessage({
type: "enable",
action: "enable",
url: this.request.url,
policyType: this.policyType,
documentUrl: document.URL

View File

@ -88,7 +88,7 @@ function probe() {
try {
debug("Probing execution...");
let s = document.createElement("script");
s.textContent=";";
s.textContent = ";";
document.documentElement.appendChild(s);
s.remove();
} catch(e) {
@ -157,7 +157,7 @@ let notifyPage = async () => {
debug("Page %s shown, %s", document.URL, document.readyState);
if (document.readyState === "complete") {
try {
await browser.runtime.sendMessage({type: "pageshow", seen: seen.list, canScript});
await browser.runtime.sendMessage({action: "pageshow", seen: seen.list, canScript});
return true;
} catch (e) {
debug(e);
@ -184,7 +184,7 @@ async function init(oldPage = false) {
document.URL, document.contentType, document.readyState, window.frameElement && frameElement.data);
try {
({canScript, shouldScript} = await browser.runtime.sendMessage({type: "docStatus", url: document.URL}));
({canScript, shouldScript} = await browser.runtime.sendMessage({action: "docStatus", url: document.URL}));
debug(`document %s, canScript=%s, shouldScript=%s, readyState %s`, document.URL, canScript, shouldScript, document.readyState);
if (canScript) {
if (oldPage) {

31
src/lib/Messages.js Normal file
View File

@ -0,0 +1,31 @@
"use strict";
{
let handlers = new Set();
let dispatch = async (msg, sender) => {
let {action} = msg;
for (let h of handlers) {
let f = h[action];
if (typeof f === "function") {
return await f(msg, sender);
}
}
};
var Messages = {
addHandler(handler) {
let originalSize = handlers.size;
handlers.add(handler);
if (originalSize === 0 && handlers.size === 1) {
browser.runtime.onMessage.addListener(dispatch);
}
},
removeHandler(handler) {
let originalSize = handlers.size;
handlers.delete(handler);
if (originalSize === 1 && handlers.size === 0) {
browser.runtime.onMessage.remveListener(dispatch);
}
}
}
}

View File

@ -40,6 +40,7 @@
"lib/punycode.js",
"lib/tld.js",
"lib/LastListener.js",
"lib/Messages.js",
"common/Policy.js",
"common/locale.js",
"common/Entities.js",

View File

@ -59,11 +59,11 @@ var UI = (() => {
debug("Imported", Policy);
},
async pullSettings() {
browser.runtime.sendMessage({type: "NoScript.broadcastSettings", tabId: UI.tabId});
browser.runtime.sendMessage({action: "broadcastSettings", tabId: UI.tabId});
},
async updateSettings({policy, xssUserChoices, unrestrictedTab, local, sync, reloadAffected}) {
if (policy) policy = policy.dry(true);
return await browser.runtime.sendMessage({type: "NoScript.updateSettings",
return await browser.runtime.sendMessage({action: "updateSettings",
policy,
xssUserChoices,
unrestrictedTab,
@ -75,10 +75,10 @@ var UI = (() => {
},
async exportSettings() {
return await browser.runtime.sendMessage({type: "NoScript.exportSettings"});
return await browser.runtime.sendMessage({action: "exportSettings"});
},
async importSettings(data) {
return await browser.runtime.sendMessage({type: "NoScript.importSettings", data});
return await browser.runtime.sendMessage({action: "importSettings", data});
},
async revokeTemp() {