Skip to content

Commit ae3ce4e

Browse files
committed
Add support for container tabs
1 parent bf95204 commit ae3ce4e

File tree

14 files changed

+373
-68
lines changed

14 files changed

+373
-68
lines changed

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[submodule "nscl"]
22
path = src/nscl
33
url = ../nscl.git
4+
branch = container-tabs

src/bg/LifeCycle.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ var LifeCycle = (() => {
104104
let {url} = tab;
105105
let {cypherText, key, iv} = await encrypt(JSON.stringify({
106106
policy: ns.policy.dry(true),
107+
contextStore: ns.contextStore.dry(true),
107108
allSeen,
108109
unrestrictedTabs: [...ns.unrestrictedTabs]
109110
}));
@@ -188,14 +189,15 @@ var LifeCycle = (() => {
188189
iv
189190
}, key, cypherText
190191
);
191-
let {policy, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
192+
let {policy, contextStore, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
192193
if (!policy) {
193194
throw new error("Ephemeral policy not found in survival tab %s!", tabId);
194195
}
195196
ns.unrestrictedTabs = new Set(unrestrictedTabs);
196197
destroyIfNeeded();
197198
if (ns.initializing) await ns.initializing;
198199
ns.policy = new Policy(policy);
200+
ns.contextStore = new ContextStore(contextStore);
199201
await Promise.all(
200202
Object.entries(allSeen).map(
201203
async ([tabId, seen]) => {
@@ -274,6 +276,17 @@ var LifeCycle = (() => {
274276
if (changed) {
275277
await ns.savePolicy();
276278
}
279+
if (ns.contextStore) {
280+
changed = false;
281+
for (let k of Object.keys(ns.contextStore.policies)){
282+
for (let p of ns.contextStore.policies[k].getPresets(presetNames)) {
283+
if (callback(p)) changed = true;
284+
}
285+
}
286+
if (changed) {
287+
await ns.saveContextStore();
288+
}
289+
}
277290
};
278291

279292
let configureNewCap = async (cap, presetNames, capsFilter) => {

src/bg/RequestGuard.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,10 @@ var RequestGuard = (() => {
276276
}
277277
let key = [siteKey, origin][ret.option || 0];
278278
if (!key) return;
279+
let cookieStoreId = sender.tab && sender.tab.cookieStoreId;
280+
let policy = ns.getPolicy(cookieStoreId);
279281
let contextUrl = sender.tab.url || documentUrl;
280-
let {siteMatch, contextMatch, perms} = ns.policy.get(key, contextUrl);
282+
let {siteMatch, contextMatch, perms} = policy.get(key, contextUrl);
281283
let {capabilities} = perms;
282284
if (!capabilities.has(policyType)) {
283285
let temp = sender.tab.incognito; // we don't want to store in PBM
@@ -290,8 +292,9 @@ var RequestGuard = (() => {
290292
perms = new Permissions(new Set(capabilities), false, contextualSites);
291293
}
292294
*/
293-
ns.policy.set(key, perms);
295+
policy.set(key, perms);
294296
await ns.savePolicy();
297+
await ns.saveContextStore();
295298
}
296299
return {enable: key};
297300
},
@@ -393,7 +396,7 @@ var RequestGuard = (() => {
393396
};
394397

395398
function intersectCapabilities(perms, request) {
396-
let {frameId, frameAncestors, tabId} = request;
399+
let {frameId, frameAncestors, tabId, cookieStoreId} = request;
397400
if (frameId !== 0 && ns.sync.cascadeRestrictions) {
398401
let topUrl = frameAncestors && frameAncestors.length
399402
&& frameAncestors[frameAncestors.length - 1].url;
@@ -402,7 +405,8 @@ var RequestGuard = (() => {
402405
if (tab) topUrl = tab.url;
403406
}
404407
if (topUrl) {
405-
return ns.policy.cascadeRestrictions(perms, topUrl).capabilities;
408+
let policy = ns.getPolicy(cookieStoreId);
409+
return policy.cascadeRestrictions(perms, topUrl).capabilities;
406410
}
407411
}
408412
return perms.capabilities;
@@ -464,9 +468,10 @@ var RequestGuard = (() => {
464468

465469
function checkLANRequest(request) {
466470
if (!ns.isEnforced(request.tabId)) return ALLOW;
467-
let {originUrl, url} = request;
471+
let {originUrl, url, cookieStoreId} = request;
472+
let policy = ns.getPolicy(cookieStoreId);
468473
if (originUrl && !Sites.isInternal(originUrl) && url.startsWith("http") &&
469-
!ns.policy.can(originUrl, "lan", ns.policyContext(request))) {
474+
!policy.can(originUrl, "lan", ns.policyContext(request))) {
470475
// we want to block any request whose origin resolves to at least one external WAN IP
471476
// and whose destination resolves to at least one LAN IP
472477
let {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
@@ -500,7 +505,6 @@ var RequestGuard = (() => {
500505
normalizeRequest(request);
501506
initPendingRequest(request);
502507

503-
let {policy} = ns
504508
let {tabId, type, url, originUrl} = request;
505509

506510
if (type in policyTypesMap) {
@@ -523,7 +527,9 @@ var RequestGuard = (() => {
523527
}
524528
return ALLOW;
525529
}
530+
let {cookieStoreId} = request;
526531
let isFetch = "fetch" === policyType;
532+
let policy = ns.getPolicy(cookieStoreId);
527533

528534
if ((isFetch || "frame" === policyType) &&
529535
(((isFetch && !originUrl
@@ -636,12 +642,12 @@ var RequestGuard = (() => {
636642
let headersModified = false;
637643

638644
pending.headersProcessed = true;
639-
let {url, documentUrl, tabId, responseHeaders, type} = request;
645+
let {url, documentUrl, tabId, cookieStoreId, responseHeaders, type} = request;
640646
let isMainFrame = type === "main_frame";
641647
try {
642648
let capabilities;
643649
if (ns.isEnforced(tabId)) {
644-
let policy = ns.policy;
650+
let policy = ns.getPolicy(cookieStoreId);
645651
let {perms} = policy.get(url, ns.policyContext(request));
646652
if (isMainFrame) {
647653
if (policy.autoAllowTop && perms === policy.DEFAULT) {
@@ -765,8 +771,8 @@ var RequestGuard = (() => {
765771
}
766772

767773
function injectPolicyScript(details) {
768-
let {url, tabId, frameId} = details;
769-
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId});
774+
let {url, tabId, frameId, cookieStoreId} = details;
775+
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId, cookieStoreId});
770776
policy.navigationURL = url;
771777
let debugStatement = ns.local.debug ? `
772778
let mark = Date.now() + ":" + Math.random();

src/bg/Settings.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ var Settings = {
9898
async update(settings) {
9999
let {
100100
policy,
101+
contextStore,
101102
xssUserChoices,
102103
tabId,
103104
unrestrictedTab,
@@ -146,6 +147,7 @@ var Settings = {
146147
if (settings.sync === null) {
147148
// user is resetting options
148149
policy = this.createDefaultDryPolicy();
150+
contextStore = new ContextStore().dry();
149151

150152
// overriden defaults when user manually resets options
151153

@@ -170,6 +172,12 @@ var Settings = {
170172
await ns.savePolicy();
171173
}
172174

175+
if (contextStore) {
176+
let newContextStore = new ContextStore(contextStore);
177+
ns.contextStore = newContextStore
178+
await ns.saveContextStore();
179+
}
180+
173181
if (typeof unrestrictedTab === "boolean") {
174182
ns.unrestrictedTabs[unrestrictedTab ? "add" : "delete"](tabId);
175183
}
@@ -213,6 +221,7 @@ var Settings = {
213221
export() {
214222
return JSON.stringify({
215223
policy: ns.policy.dry(),
224+
contextStore: ns.contextStore.dry(),
216225
local: ns.local,
217226
sync: ns.sync,
218227
xssUserChoices: XSS.getUserChoices(),

src/bg/main.js

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@
6767
}
6868
}
6969

70+
if (!ns.contextStore) { // it could have been already retrieved by LifeCycle
71+
let contextStoreData = (await Storage.get("sync", "contextStore")).contextStore;
72+
if (contextStoreData) {
73+
ns.contextStore = new ContextStore(contextStoreData);
74+
await ns.contextStore.updateContainers(ns.policy);
75+
} else {
76+
log("No container data found. Initializing new policies.")
77+
ns.contextStore = new ContextStore();
78+
await ns.contextStore.updateContainers(ns.policy);
79+
await ns.saveContextStore();
80+
}
81+
}
82+
7083
let {isTorBrowser} = ns.local;
7184
Sites.onionSecure = isTorBrowser;
7285

@@ -163,10 +176,12 @@
163176
tabId = -1
164177
}) {
165178
let policy = ns.policy.dry(true);
179+
let contextStore = ns.contextStore.dry(true);
166180
let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
167181
let xssUserChoices = await XSS.getUserChoices();
168182
await Messages.send("settings", {
169183
policy,
184+
contextStore,
170185
seen,
171186
xssUserChoices,
172187
local: ns.local,
@@ -250,6 +265,7 @@
250265
var ns = {
251266
running: false,
252267
policy: null,
268+
contextStore: null,
253269
local: null,
254270
sync: null,
255271
initializing: null,
@@ -271,21 +287,22 @@
271287
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
272288
},
273289

274-
computeChildPolicy({url, contextUrl}, sender) {
275-
let {tab, frameId} = sender;
276-
let policy = ns.policy;
277-
let {isTorBrowser} = ns.local;
278-
if (!policy) {
279-
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
280-
return {
281-
permissions: new Permissions(Permissions.DEFAULT).dry(),
282-
unrestricted: false,
283-
cascaded: false,
284-
fallback: true,
285-
isTorBrowser,
286-
};
290+
getPolicy(cookieStoreId){
291+
if (
292+
ns.contextStore &&
293+
ns.contextStore.enabled &&
294+
ns.contextStore.policies.hasOwnProperty(cookieStoreId)
295+
) {
296+
let currentPolicy = ns.contextStore.policies[cookieStoreId];
297+
debug("id", cookieStoreId, "has cookiestore", currentPolicy);
298+
if (currentPolicy) return currentPolicy;
287299
}
300+
debug("default cookiestore", cookieStoreId);
301+
return ns.policy;
302+
},
288303

304+
computeChildPolicy({url, contextUrl}, sender) {
305+
let {tab, frameId, cookieStoreId} = sender;
289306
let tabId = tab ? tab.id : -1;
290307
let topUrl;
291308
if (frameId === 0) {
@@ -297,6 +314,20 @@
297314
if (!topUrl) topUrl = url;
298315
if (!contextUrl) contextUrl = topUrl;
299316

317+
if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
318+
let policy = ns.getPolicy(cookieStoreId);
319+
let {isTorBrowser} = ns.local;
320+
if (!policy) {
321+
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
322+
return {
323+
permissions: new Permissions(Permissions.DEFAULT).dry(),
324+
unrestricted: false,
325+
cascaded: false,
326+
fallback: true,
327+
isTorBrowser,
328+
};
329+
}
330+
300331
if (Sites.isInternal(url) || !ns.isEnforced(tabId)) {
301332
policy = null;
302333
}
@@ -362,7 +393,7 @@
362393
await Storage.set("sync", {
363394
policy: this.policy.dry()
364395
});
365-
await browser.webRequest.handlerBehaviorChanged()
396+
await browser.webRequest.handlerBehaviorChanged();
366397
}
367398
return this.policy;
368399
},
@@ -379,6 +410,16 @@
379410
browser.tabs.create({url: url.toString() });
380411
},
381412

413+
async saveContextStore() {
414+
if (this.contextStore) {
415+
await Storage.set("sync", {
416+
contextStore: this.contextStore.dry()
417+
});
418+
await browser.webRequest.handlerBehaviorChanged();
419+
}
420+
return this.contextStore;
421+
},
422+
382423
async save(obj) {
383424
if (obj && obj.storage) {
384425
let toBeSaved = {

src/manifest.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"webRequest",
3030
"webRequestBlocking",
3131
"dns",
32-
"<all_urls>"
32+
"<all_urls>",
33+
"contextualIdentities"
3334
],
3435

3536
"background": {
@@ -51,6 +52,7 @@
5152
"/nscl/common/Sites.js",
5253
"/nscl/common/Permissions.js",
5354
"/nscl/common/Policy.js",
55+
"/nscl/common/ContextStore.js",
5456
"/nscl/common/locale.js",
5557
"/nscl/common/Storage.js",
5658
"/nscl/common/include.js",

src/ui/options.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ fieldset:disabled {
8282
flex: 2 2;
8383
}
8484

85+
.per-site-buttons {
86+
display: flex;
87+
flex-flow: row wrap;
88+
justify-content: flex-end;
89+
width: 100%;
90+
text-align: right;
91+
margin: .5em 0 0 0;
92+
}
93+
#btn-clear-container {
94+
margin-inline-start: .5em;
95+
}
96+
#copy-container {
97+
margin-inline: .5em;
98+
}
99+
#copy-container-label {
100+
margin-block: auto;
101+
}
102+
85103
#policy {
86104
display: block;
87105
margin-top: .5em;
@@ -91,6 +109,12 @@ fieldset:disabled {
91109
.hide, body:not(.debug) div.debug {
92110
display: none;
93111
}
112+
#context-store {
113+
display: block;
114+
margin-top: .5em;
115+
min-height: 20em;
116+
width: 90%;
117+
}
94118

95119
#debug-tools {
96120
padding-left: 2.5em;
@@ -110,6 +134,14 @@ fieldset:disabled {
110134
font-weight: bold;
111135
}
112136

137+
#context-store-error {
138+
background: red;
139+
color: #ff8;
140+
padding: 0;
141+
margin: 0;
142+
font-weight: bold;
143+
}
144+
113145
input, button {
114146
font-size: 1em;
115147
}

0 commit comments

Comments
 (0)