paraMatrix/src/bg.js

238 行
7.2 KiB
JavaScript

/* global browser */
import * as shared from './shared.js';
import * as storage from './storage.js';
var glob = function(s, pattern) {
var p = pattern.split('*');
return s.startsWith(p[0]) && s.endsWith(p.at(-1));
};
var getHostname = function(url, patterns) {
var u = new URL(url);
for (var pattern of patterns) {
if (glob(u.hostname, pattern)) {
return pattern;
}
}
return u.hostname;
};
var setRule = async function(context, hostname, type, rule) {
var savedRules = await storage.get('savedRules');
await storage.change('rules', rules => {
if (hostname === 'first-party') {
context = '*';
}
if (!rules[context]) {
rules[context] = savedRules[context] || {};
}
if (!rules[context][hostname]) {
rules[context][hostname] = {};
}
if (rule) {
rules[context][hostname][type] = rule;
} else {
delete rules[context][hostname][type];
if (Object.keys(rules[context][hostname]).length === 0) {
delete rules[context][hostname];
}
if (Object.keys(rules[context]).length === 0 && !savedRules[context]) {
delete rules[context];
}
}
return rules;
});
};
var getPatterns = async function() {
var savedRules = await storage.get('savedRules');
return savedRules._patterns || [];
};
var getRules = async function(context) {
var [rules, savedRules] = await Promise.all([
storage.get('rules'),
storage.get('savedRules'),
]);
var restricted = {};
restricted['*'] = rules['*'] || savedRules['*'] || {};
restricted[context] = rules[context] || savedRules[context] || {};
restricted.dirty = !!rules[context];
return restricted;
};
var pushRequest = async function(tabId, hostname, type) {
var recording = await storage.get('recording');
if (recording) {
await storage.change('requests', requests => {
if (!requests[tabId]) {
requests[tabId] = {};
}
if (!requests[tabId][hostname]) {
requests[tabId][hostname] = {};
}
if (!requests[tabId][hostname][type]) {
requests[tabId][hostname][type] = 0;
}
requests[tabId][hostname][type] += 1;
return requests;
});
}
};
var clearRequests = async function(tabId) {
await storage.change('requests', requests => {
if (requests[tabId]) {
delete requests[tabId];
}
return requests;
});
};
var getCurrentTab = async function() {
var tabs = await browser.tabs.query({
active: true,
currentWindow: true,
});
return tabs[0];
};
browser.runtime.onMessage.addListener(async (msg, sender) => {
if (msg.type === 'get') {
const [tab, patterns] = await Promise.all([
getCurrentTab(),
getPatterns(),
]);
const context = getHostname(tab.url, patterns);
const [rules, requests, recording] = await Promise.all([
getRules(context),
storage.get('requests'),
storage.get('recording'),
]);
return {
context: context,
rules: rules,
requests: requests[tab.id] || {},
recording: recording,
};
} else if (msg.type === 'setRule') {
await setRule(
msg.data.context,
msg.data.hostname,
msg.data.type,
msg.data.value,
);
return await getRules(msg.data.context);
} else if (msg.type === 'commit') {
let r;
await storage.change('rules', rules => {
r = rules[msg.data];
delete rules[msg.data];
return rules;
});
await storage.change('savedRules', savedRules => {
if (Object.keys(r).length === 0) {
delete savedRules[msg.data];
} else {
savedRules[msg.data] = r;
}
return savedRules;
});
} else if (msg.type === 'reset') {
await storage.change('rules', rules => {
delete rules[msg.data];
return rules;
});
} else if (msg.type === 'securitypolicyviolation') {
await pushRequest(sender.tab.id, 'inline', msg.data);
} else if (msg.type === 'toggleRecording') {
await storage.change('recording', recording => !recording);
}
});
browser.tabs.onRemoved.addListener(clearRequests);
browser.webNavigation.onBeforeNavigate.addListener(async details => {
if (details.frameId === 0) {
await clearRequests(details.tabId);
}
});
browser.webRequest.onBeforeSendHeaders.addListener(async details => {
var patterns = await getPatterns();
var context = getHostname(details.documentUrl || details.url, patterns);
if (details.frameAncestors && details.frameAncestors.length) {
var last = details.frameAncestors.length - 1;
context = getHostname(details.frameAncestors[last].url, patterns);
}
var hostname = getHostname(details.url, patterns);
var type = shared.TYPE_MAP[details.type] || 'other';
var promises = [
getRules(context),
];
if (details.type !== 'main_frame') {
promises.push(pushRequest(details.tabId, hostname, type));
}
var isCookie = h => h.name.toLowerCase() === 'cookie';
if (details.requestHeaders.some(isCookie)) {
promises.push(pushRequest(details.tabId, hostname, 'cookie'));
}
var [rules, ..._rest] = await Promise.all(promises);
if (
details.type !== 'main_frame'
&& !shared.shouldAllow(rules, context, hostname, type)
) {
if (details.type === 'sub_frame') {
// this can in turn be blocked by a local CSP
return {redirectUrl: 'data:,' + encodeURIComponent(details.url)};
} else {
return {cancel: true};
}
}
if (shared.shouldAllow(rules, context, hostname, 'cookie')) {
return {requestHeaders: details.requestHeaders};
} else {
var filtered = details.requestHeaders.filter(h => !isCookie(h));
return {requestHeaders: filtered};
}
}, {urls: ['<all_urls>']}, ['blocking', 'requestHeaders']);
browser.webRequest.onHeadersReceived.addListener(async details => {
var patterns = await getPatterns();
var context = getHostname(details.url, patterns);
var [rules, recording] = await Promise.all([
getRules(context),
storage.get('recording'),
]);
var csp = (type, value) => {
var name = 'Content-Security-Policy';
if (shared.shouldAllow(rules, context, 'inline', type)) {
if (recording) {
name = 'Content-Security-Policy-Report-Only';
} else {
return;
}
}
details.responseHeaders.push({
name: name,
value: value,
});
};
csp('css', "style-src 'self' *");
csp('script', "script-src 'self' *");
csp('media', "img-src 'self' *");
return {responseHeaders: details.responseHeaders};
}, {
urls: ['<all_urls>'],
types: ['main_frame'],
}, ['blocking', 'responseHeaders']);