tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

IPPNetworkErrorObserver.sys.mjs (3701B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 /**
      6 * Service Class to observe and record proxy-errors related to IP-Protection
      7 *
      8 * @fires IPPNetworkErrorObserver#"proxy-http-error"
      9 * Fired when the Proxy has recieved the Connect Request and responded with
     10 * a non-2xx HTTP status code
     11 */
     12 export class IPPNetworkErrorObserver {
     13  constructor() {}
     14 
     15  start() {
     16    if (this.#active) {
     17      return;
     18    }
     19    Services.obs.addObserver(this, "http-on-stop-request");
     20    Services.obs.addObserver(this, "http-on-failed-opening-request");
     21    this.#active = true;
     22  }
     23  stop() {
     24    if (!this.#active) {
     25      return;
     26    }
     27    this.#active = false;
     28    this.#isolationKeys.clear();
     29    Services.obs.removeObserver(this, "http-on-failed-opening-request");
     30    Services.obs.removeObserver(this, "http-on-stop-request");
     31  }
     32 
     33  addIsolationKey(key) {
     34    if (typeof key !== "string" || !key) {
     35      throw new Error("Isolation key must be a non-empty string");
     36    }
     37    this.#isolationKeys.add(key);
     38  }
     39  removeIsolationKey(key) {
     40    if (typeof key !== "string" || !key) {
     41      throw new Error("Isolation key must be a non-empty string");
     42    }
     43    this.#isolationKeys.delete(key);
     44  }
     45  addEventListener(...args) {
     46    this._event.addEventListener(...args);
     47  }
     48 
     49  removeEventListener(...args) {
     50    this._event.removeEventListener(...args);
     51  }
     52 
     53  observe(subject, topic, _data) {
     54    if (
     55      topic !== "http-on-stop-request" &&
     56      topic !== "http-on-failed-opening-request"
     57    ) {
     58      return;
     59    }
     60    try {
     61      const chan = subject.QueryInterface(Ci.nsIHttpChannel);
     62      const key = this.getKey(chan);
     63      if (!key) {
     64        // If the isolation key is unknown to us or does not
     65        // exist, no need to care.
     66        return;
     67      }
     68      const proxiedChannel = chan.QueryInterface(Ci.nsIProxiedChannel);
     69      const proxycode = proxiedChannel.httpProxyConnectResponseCode;
     70      switch (proxycode) {
     71        case 0:
     72        case 200:
     73          // All good :)
     74          return;
     75        default:
     76          this.#emitProxyHTTPError(this.#classifyLoad(chan), key, proxycode);
     77      }
     78    } catch (err) {
     79      // If the channel is not an nsIHttpChannel or not proxied - all good.
     80    }
     81  }
     82  /**
     83   * Checks if a channel should be counted.
     84   *
     85   * @param {nsIHttpChannel} channel
     86   * @returns {boolean} true if the channel should be counted.
     87   */
     88  getKey(channel) {
     89    try {
     90      const proxiedChannel = channel.QueryInterface(Ci.nsIProxiedChannel);
     91      const proxyInfo = proxiedChannel.proxyInfo;
     92      if (!proxyInfo) {
     93        // No proxy info, nothing to do.
     94        return null;
     95      }
     96      const isolationKey = proxyInfo.connectionIsolationKey;
     97      if (!isolationKey || !this.#isolationKeys.has(isolationKey)) {
     98        return null;
     99      }
    100      return isolationKey;
    101    } catch (err) {
    102      // If the channel is not an nsIHttpChannel or nsIProxiedChannel, as it's irrelevant
    103      // for this class.
    104    }
    105    return null;
    106  }
    107 
    108  #classifyLoad(channel) {
    109    try {
    110      if (channel.isMainDocumentChannel) {
    111        return "error";
    112      }
    113      return "warning";
    114    } catch (_) {}
    115    return "unknown";
    116  }
    117 
    118  #emitProxyHTTPError(level, isolationKey, httpStatus) {
    119    this._event.dispatchEvent(
    120      new CustomEvent("proxy-http-error", {
    121        detail: { level, isolationKey, httpStatus },
    122      })
    123    );
    124  }
    125  _event = new EventTarget();
    126 
    127  #active = false;
    128  #isolationKeys = new Set();
    129 }
    130 
    131 IPPNetworkErrorObserver.prototype.QueryInterface = ChromeUtils.generateQI([
    132  Ci.nsIObserver,
    133 ]);