summaryrefslogtreecommitdiff
path: root/src/content/staticNS.js
blob: e1690878915402fa61053f253cba3b2eb3ffc1ca (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
'use strict';
{
  let listenersMap = new Map();
  let backlog = new Set();

  let ns = {
    debug: true, // DEV_ONLY
    get embeddingDocument() {
      delete this.embeddingDocument;
      return this.embeddingDocument = CSP.isEmbedType(document.contentType);
    },
    on(eventName, listener) {
      let listeners = listenersMap.get(eventName);
      if (!listeners) listenersMap.set(eventName, listeners = new Set());
      listeners.add(listener);
      if (backlog.has(eventName)) this.fire(eventName, listener);
    },
    detach(eventName, listener) {
      let listeners = listenersMap.get(eventName);
      if (listeners) listeners.delete(listener);
    },
    fire(eventName, listener = null) {
      if (listener) {
        listener({type:eventName, source: this});
        return;
      }
      let listeners = listenersMap.get(eventName);
      if (listeners) {
        for (let l of listeners) {
          this.fire(eventName, l);
        }
      }
      backlog.add(eventName);
    },

    async fetchPolicy() {
        let policy = await Messages.send("fetchChildPolicy", {url: document.URL});
        if (!policy) {
          debug(`No answer to fetchChildPolicy message. This should not be happening.`);
          return false;
        }
        this.setup(policy.permissions, policy.MARKER, true);
        return true;
    },

    setup(permissions, MARKER, fetched = false) {
      this.config.permissions = permissions;

      // ugly hack: since now we use registerContentScript instead of the
      // filterRequest dynamic script injection hack, we use a session cookie
      // to store per-tab information,  erasing it as soon as we see it
      // (before any content can access it)

      let checkUnrestricted = challenge => sha256(`${MARKER}:${challenge}`);

      if ((this.config.MARKER = MARKER) && permissions) {
        let cookieRx = new RegExp(`(?:^|;\\s*)(${MARKER}(?:_\\d+){2})=([^;]*)`);
        let match = document.cookie.match(cookieRx);
        if (match) {
          let [cookie, cookieName, cookieValue] = match;
          // delete cookie NOW
          document.cookie = `${cookieName}=;expires=${new Date(Date.now() - 31536000000).toGMTString()}`;
          try {
            this.config.tabInfo = JSON.parse(decodeURIComponent(cookieValue));
          } catch (e) {
            error(e);
          }
        } else if (window !== window.top) {
          // The cookie hack won't work for non-HTTP subframes (issue #48),
          // or the cookie might have been deleted in a race condition,
          // so here we try to check the parent
          let checkParent = parent.wrappedJSObject.checkNoScriptUnrestricted;
          if (typeof checkParent  === "function") {
            try {
              let challenge = uuid();
              let unrestricted = checkParent(challenge) === checkUnrestricted(challenge);
              this.config.tabInfo = {unrestricted, inherited: true};
            } catch (e) {
              debug("Exception thrown while checking parent unrestricted tab marker. Something fishy going on...")
              error(e);
            }
          }
        }
      }

      if (!this.config.permissions || this.config.tabInfo.unrestricted) {
        exportFunction(checkUnrestricted, window, {defineAs: "checkNoScriptUnrestricted"});
        debug("%s is loading unrestricted by user's choice (%o).", document.URL, this.config);
        this.allows = () => true;
        this.capabilities =  Object.assign(
          new Set(["script"]), { has() { return true; } });
      } else {
        if (!fetched) {
          let hostname = window.location.hostname;
          if (hostname && hostname.startsWith("[")) {
            // WebExt match patterns don't seem to support IPV6 (Firefox 63)...
            debug("Ignoring child policy setup parameters for IPV6 address %s, forcing IPC...", hostname);
            this.fetchPolicy();
            return;
          }
        }
        let perms = this.config.permissions;
        this.capabilities = new Set(perms.capabilities);
        new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
      }

      this.canScript = this.allows("script");
      this.fire("capabilities");
    },
    config: { permissions: null, tabInfo: {}, MARKER: "" },

    allows(cap) {
      return this.capabilities && this.capabilities.has(cap);
    },

    getWindowName() {
      return window.name;
    }
  };

  if (this.ns) {
    this.ns.merge(ns);
  } else {
    this.ns = ns;
  }
}