Skip to content

Commit ee1188e

Browse files
committed
Add support for container tabs
1 parent 34736ce commit ee1188e

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
@@ -275,8 +275,10 @@ var RequestGuard = (() => {
275275
}
276276
let key = [siteKey, origin][ret.option || 0];
277277
if (!key) return;
278+
let cookieStoreId = sender.tab && sender.tab.cookieStoreId;
279+
let policy = ns.getPolicy(cookieStoreId);
278280
let contextUrl = sender.tab.url || documentUrl;
279-
let {siteMatch, contextMatch, perms} = ns.policy.get(key, contextUrl);
281+
let {siteMatch, contextMatch, perms} = policy.get(key, contextUrl);
280282
let {capabilities} = perms;
281283
if (!capabilities.has(policyType)) {
282284
let temp = sender.tab.incognito; // we don't want to store in PBM
@@ -289,8 +291,9 @@ var RequestGuard = (() => {
289291
perms = new Permissions(new Set(capabilities), false, contextualSites);
290292
}
291293
*/
292-
ns.policy.set(key, perms);
294+
policy.set(key, perms);
293295
await ns.savePolicy();
296+
await ns.saveContextStore();
294297
}
295298
return {enable: key};
296299
},
@@ -365,7 +368,7 @@ var RequestGuard = (() => {
365368
};
366369

367370
function intersectCapabilities(perms, request) {
368-
let {frameId, frameAncestors, tabId} = request;
371+
let {frameId, frameAncestors, tabId, cookieStoreId} = request;
369372
if (frameId !== 0 && ns.sync.cascadeRestrictions) {
370373
let topUrl = frameAncestors && frameAncestors.length
371374
&& frameAncestors[frameAncestors.length - 1].url;
@@ -374,7 +377,8 @@ var RequestGuard = (() => {
374377
if (tab) topUrl = tab.url;
375378
}
376379
if (topUrl) {
377-
return ns.policy.cascadeRestrictions(perms, topUrl).capabilities;
380+
let policy = ns.getPolicy(cookieStoreId);
381+
return policy.cascadeRestrictions(perms, topUrl).capabilities;
378382
}
379383
}
380384
return perms.capabilities;
@@ -436,9 +440,10 @@ var RequestGuard = (() => {
436440

437441
function checkLANRequest(request) {
438442
if (!ns.isEnforced(request.tabId)) return ALLOW;
439-
let {originUrl, url} = request;
443+
let {originUrl, url, cookieStoreId} = request;
444+
let policy = ns.getPolicy(cookieStoreId);
440445
if (originUrl && !Sites.isInternal(originUrl) && url.startsWith("http") &&
441-
!ns.policy.can(originUrl, "lan", ns.policyContext(request))) {
446+
!policy.can(originUrl, "lan", ns.policyContext(request))) {
442447
// we want to block any request whose origin resolves to at least one external WAN IP
443448
// and whose destination resolves to at least one LAN IP
444449
let {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
@@ -472,7 +477,6 @@ var RequestGuard = (() => {
472477
normalizeRequest(request);
473478
initPendingRequest(request);
474479

475-
let {policy} = ns
476480
let {tabId, type, url, originUrl} = request;
477481

478482
if (type in policyTypesMap) {
@@ -495,7 +499,9 @@ var RequestGuard = (() => {
495499
}
496500
return ALLOW;
497501
}
502+
let {cookieStoreId} = request;
498503
let isFetch = "fetch" === policyType;
504+
let policy = ns.getPolicy(cookieStoreId);
499505

500506
if ((isFetch || "frame" === policyType) &&
501507
(((isFetch && !originUrl
@@ -608,12 +614,12 @@ var RequestGuard = (() => {
608614
let headersModified = false;
609615

610616
pending.headersProcessed = true;
611-
let {url, documentUrl, tabId, responseHeaders, type} = request;
617+
let {url, documentUrl, tabId, cookieStoreId, responseHeaders, type} = request;
612618
let isMainFrame = type === "main_frame";
613619
try {
614620
let capabilities;
615621
if (ns.isEnforced(tabId)) {
616-
let policy = ns.policy;
622+
let policy = ns.getPolicy(cookieStoreId);
617623
let {perms} = policy.get(url, ns.policyContext(request));
618624
if (isMainFrame) {
619625
if (policy.autoAllowTop && perms === policy.DEFAULT) {
@@ -737,8 +743,8 @@ var RequestGuard = (() => {
737743
}
738744

739745
function injectPolicyScript(details) {
740-
let {url, tabId, frameId} = details;
741-
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId});
746+
let {url, tabId, frameId, cookieStoreId} = details;
747+
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId, cookieStoreId});
742748
policy.navigationURL = url;
743749
let debugStatement = ns.local.debug ? `
744750
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

@@ -168,10 +181,12 @@
168181
tabId = -1
169182
}) {
170183
let policy = ns.policy.dry(true);
184+
let contextStore = ns.contextStore.dry(true);
171185
let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
172186
let xssUserChoices = await XSS.getUserChoices();
173187
await Messages.send("settings", {
174188
policy,
189+
contextStore,
175190
seen,
176191
xssUserChoices,
177192
local: ns.local,
@@ -229,6 +244,7 @@
229244
var ns = {
230245
running: false,
231246
policy: null,
247+
contextStore: null,
232248
local: null,
233249
sync: null,
234250
initializing: null,
@@ -250,21 +266,22 @@
250266
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
251267
},
252268

253-
computeChildPolicy({url, contextUrl}, sender) {
254-
let {tab, frameId} = sender;
255-
let policy = ns.policy;
256-
let {isTorBrowser} = ns.local;
257-
if (!policy) {
258-
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
259-
return {
260-
permissions: new Permissions(Permissions.DEFAULT).dry(),
261-
unrestricted: false,
262-
cascaded: false,
263-
fallback: true,
264-
isTorBrowser,
265-
};
269+
getPolicy(cookieStoreId){
270+
if (
271+
ns.contextStore &&
272+
ns.contextStore.enabled &&
273+
ns.contextStore.policies.hasOwnProperty(cookieStoreId)
274+
) {
275+
let currentPolicy = ns.contextStore.policies[cookieStoreId];
276+
debug("id", cookieStoreId, "has cookiestore", currentPolicy);
277+
if (currentPolicy) return currentPolicy;
266278
}
279+
debug("default cookiestore", cookieStoreId);
280+
return ns.policy;
281+
},
267282

283+
computeChildPolicy({url, contextUrl}, sender) {
284+
let {tab, frameId, cookieStoreId} = sender;
268285
let tabId = tab ? tab.id : -1;
269286
let topUrl;
270287
if (frameId === 0) {
@@ -276,6 +293,20 @@
276293
if (!topUrl) topUrl = url;
277294
if (!contextUrl) contextUrl = topUrl;
278295

296+
if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
297+
let policy = ns.getPolicy(cookieStoreId);
298+
let {isTorBrowser} = ns.local;
299+
if (!policy) {
300+
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
301+
return {
302+
permissions: new Permissions(Permissions.DEFAULT).dry(),
303+
unrestricted: false,
304+
cascaded: false,
305+
fallback: true,
306+
isTorBrowser,
307+
};
308+
}
309+
279310
if (Sites.isInternal(url) || !ns.isEnforced(tabId)) {
280311
policy = null;
281312
}
@@ -341,7 +372,7 @@
341372
await Storage.set("sync", {
342373
policy: this.policy.dry()
343374
});
344-
await browser.webRequest.handlerBehaviorChanged()
375+
await browser.webRequest.handlerBehaviorChanged();
345376
}
346377
return this.policy;
347378
},
@@ -358,6 +389,16 @@
358389
browser.tabs.create({url: url.toString() });
359390
},
360391

392+
async saveContextStore() {
393+
if (this.contextStore) {
394+
await Storage.set("sync", {
395+
contextStore: this.contextStore.dry()
396+
});
397+
await browser.webRequest.handlerBehaviorChanged();
398+
}
399+
return this.contextStore;
400+
},
401+
361402
async save(obj) {
362403
if (obj && obj.storage) {
363404
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/nscl

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)