summaryrefslogtreecommitdiff
path: root/src/bg/ChildPolicies.js
blob: 32abafec818ed1a214184b4906896634ba31ee91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"use strict";
{
  let marker = JSON.stringify(uuid());
  
  let Scripts = {
    references: new Set(),
    opts: {
      js: [{}],
      allFrames: true,
      matchAboutBlank: true,
      runAt: "document_start"
    },
    forget() {
      for (let script of [...this.references]) {
        script.unregister();
        this.references.delete(script);
      }
    },
    async register(code, matches, excludeMatches) {
      debug("Registering child policy.", code, matches, excludeMatches);
      if (!matches.length) return;
      try {
        this.opts.js[0].code = code;
        this.opts.matches = matches;
        if (excludeMatches && excludeMatches.length) {
          this.opts.excludeMatches = excludeMatches;
        } else {
          delete this.opts.excludeMatches;
        }
        this.references.add(await browser.contentScripts.register(this.opts));
      } catch (e) {
        error(e);
      }
    }
  };
  
  let flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
  
  let protocolRx = /^(https?):/i;
  let pathRx = /[^:/]\//;
  let portRx = /:\d+(?=\/|$)/;
  let validMatchPatternRx = /^(?:https?|\*):\/\/(?:\*\.)?(?:[\w\u0100-\uf000][\w\u0100-\uf000.-]*)?[\w\u0100-\uf000]\/(\*|[^*]*)$/;
  
  let siteKey2MatchPattern = site => {
    let hasProtocol = site.match(protocolRx);
    let protocol = hasProtocol ? ''
      : Sites.isSecureDomainKey(site) ? "https://" : "*://";
    let hostname = Sites.toggleSecureDomainKey(site, false)
      .replace(portRx, '');
    if (!hasProtocol) hostname = `*.${hostname}`;
    let path = pathRx.test(hostname) ? "" : "/*";
    let mp = `${protocol}${hostname}${path}`;
    return  validMatchPatternRx.test(mp) && (path ? mp : [mp, `${mp}?*`, `${mp}#*`]);
  };
  
  let siteKeys2MatchPatterns = keys => keys && flatten(keys.map(siteKey2MatchPattern)).filter(p => !!p) || [];  

  var ChildPolicies = {
    async storeTabInfo(tabId, info) {
      try {
        let preamble = info ? `${marker} + ${JSON.stringify(JSON.stringify([info]))} + ${marker} + "," + ` : "";
        await browser.tabs.executeScript(tabId, {
          code: `window.name = ${preamble}window.name.split(${marker} + ",").pop();`,
          allFrames: false,
          matchAboutBlank: true,
          runAt: "document_start",
        });    
      } catch (e) {
        error(e);
      }
    },
    async update(policy) {
      Scripts.forget();
      
      if (!policy.enforced) {
        await Scripts.register(`ns.setup(null, ${marker});`, 
           ["<all_urls>"]);
        return;
      }
      
      let serialized = policy.dry ? policy.dry(true) : policy;
      let permsMap = new Map();
      let trusted = JSON.stringify(serialized.TRUSTED);
      let untrusted = JSON.stringify(serialized.UNTRUSTED);
      let presets = {
        trusted,
        untrusted,
        temp: trusted
      };
      // map presets to site keys
      for (let [container, perms] of Object.entries(presets)) {
        let newKeys = serialized.sites[container];
        if (!(newKeys && newKeys.length)) continue;
        let keys = permsMap.get(perms);
        if (keys) {
          newKeys = keys.concat(newKeys); 
        }
        permsMap.set(perms, newKeys);
      }
      // map custom permissions to site keys
      for (let [key, perms] of Object.entries(serialized.sites.custom)) {
        let permsKey = JSON.stringify(perms);
        let keys = permsMap.get(permsKey);
        if (keys) {
          keys.push(key);
        } else {
          permsMap.set(permsKey, [key]);
        }
      }
      
      // compute exclusions
      let permsMapEntries = [...permsMap];
      let excludeMap = new Map();
      
      for (let [perms, keys] of permsMapEntries) {
        excludeMap.set(perms, siteKeys2MatchPatterns(flatten(
          permsMapEntries.filter(([other]) => other !== perms)
            .map(([otherPerms, otherKeys]) => otherKeys))
              .filter(k => k && k.includes("/") && keys.some(by => Sites.isImplied(k, by)))
          ));
      }
      
      // register new content scripts
      for (let [perms, keys] of [...permsMap]) {
        await Scripts.register(`ns.perms.CURRENT = ${perms};`, siteKeys2MatchPatterns(keys), excludeMap.get(perms));
      }
      await Scripts.register(`ns.setup(${JSON.stringify(serialized.DEFAULT)}, ${marker});`, 
         ["<all_urls>"]);
    }
  }
}