Verified Commit 3c944124 authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame 🎃 Committed by ma1
Browse files

TB 40933: Add tor-launcher functionality

Bug 41926: Reimplement the control port
parent 5a17b593
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -128,3 +128,5 @@ pref("extensions.torlauncher.bridgedb_reflector", "https://1723079976.rsc.cdn77.
pref("extensions.torlauncher.moat_service", "https://bridges.torproject.org/moat");

// Log levels
pref("browser.tor_provider.log_level", "Warn");
pref("browser.tor_provider.cp_log_level", "Warn");
+3 −0
Original line number Diff line number Diff line
@@ -93,6 +93,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
  TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
  TabUnloader: "resource:///modules/TabUnloader.sys.mjs",
  TelemetryUtils: "resource://gre/modules/TelemetryUtils.sys.mjs",
  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
  UIState: "resource://services-sync/UIState.sys.mjs",
  UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
  WebChannel: "resource://gre/modules/WebChannel.sys.mjs",
@@ -1972,6 +1973,8 @@ BrowserGlue.prototype = {

    lazy.DoHController.init();

    lazy.TorProviderBuilder.firstWindowLoaded();

    ClipboardPrivacy.startup();

    this._firstWindowTelemetry(aWindow);
+1 −0
Original line number Diff line number Diff line
@@ -213,6 +213,7 @@
@RESPATH@/browser/chrome/browser.manifest
@RESPATH@/chrome/pdfjs.manifest
@RESPATH@/chrome/pdfjs/*
@RESPATH@/components/tor-launcher.manifest
@RESPATH@/chrome/torbutton.manifest
@RESPATH@/chrome/torbutton/*
@RESPATH@/chrome/toolkit@JAREXT@
+1 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ DIRS += [
    "thumbnails",
    "timermanager",
    "tooltiptext",
    "tor-launcher",
    "typeaheadfind",
    "utils",
    "url-classifier",
+147 −0
Original line number Diff line number Diff line
import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
  TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
});

const log = console.createInstance({
  maxLogLevel: "Info",
  prefix: "TorBootstrapRequest",
});

/**
 * This class encapsulates the observer register/unregister logic to provide an
 * XMLHttpRequest-like API to bootstrap tor.
 * TODO: Remove this class, and move its logic inside the TorProvider.
 */
export class TorBootstrapRequest {
  // number of ms to wait before we abandon the bootstrap attempt
  // a value of 0 implies we never wait
  timeout = 0;

  // callbacks for bootstrap process status updates
  onbootstrapstatus = (_progress, _status) => {};
  onbootstrapcomplete = () => {};
  onbootstraperror = _error => {};

  // internal resolve() method for bootstrap
  #bootstrapPromiseResolve = null;
  #bootstrapPromise = null;
  #timeoutID = null;

  observe(subject, topic) {
    const obj = subject?.wrappedJSObject;
    switch (topic) {
      case lazy.TorProviderTopics.BootstrapStatus: {
        const progress = obj.PROGRESS;
        if (this.onbootstrapstatus) {
          const status = obj.TAG;
          this.onbootstrapstatus(progress, status);
        }
        if (progress === 100) {
          if (this.onbootstrapcomplete) {
            this.onbootstrapcomplete();
          }
          this.#bootstrapPromiseResolve(true);
          clearTimeout(this.#timeoutID);
          this.#timeoutID = null;
        }

        break;
      }
      case lazy.TorProviderTopics.BootstrapError: {
        log.info("TorBootstrapRequest: observerd TorBootstrapError", obj);
        const error = new Error(obj.summary);
        Object.assign(error, obj);
        this.#stop(error);
        break;
      }
    }
  }

  // resolves 'true' if bootstrap succeeds, false otherwise
  bootstrap() {
    if (this.#bootstrapPromise) {
      return this.#bootstrapPromise;
    }

    this.#bootstrapPromise = new Promise(resolve => {
      this.#bootstrapPromiseResolve = resolve;

      // register ourselves to listen for bootstrap events
      Services.obs.addObserver(this, lazy.TorProviderTopics.BootstrapStatus);
      Services.obs.addObserver(this, lazy.TorProviderTopics.BootstrapError);

      // optionally cancel bootstrap after a given timeout
      if (this.timeout > 0) {
        this.#timeoutID = setTimeout(() => {
          this.#timeoutID = null;
          this.#stop(
            new Error(
              `Bootstrap attempt abandoned after waiting ${this.timeout} ms`
            )
          );
        }, this.timeout);
      }

      // Wait for bootstrapping to begin and maybe handle error.
      // Notice that we do not resolve the promise here in case of success, but
      // we do it from the BootstrapStatus observer.
      // NOTE: After TorProviderBuilder.build resolves, TorProvider.init will
      // have completed. In particular, assuming no errors, the TorSettings will
      // have been initialised and passed on to the provider via
      // TorProvider.writeSettings. Therefore we should be safe to immediately
      // call `connect` using the latest user settings.
      lazy.TorProviderBuilder.build()
        .then(provider => provider.connect())
        .catch(err => {
          this.#stop(err);
        });
    }).finally(() => {
      // and remove ourselves once bootstrap is resolved
      Services.obs.removeObserver(this, lazy.TorProviderTopics.BootstrapStatus);
      Services.obs.removeObserver(this, lazy.TorProviderTopics.BootstrapError);
      this.#bootstrapPromise = null;
    });

    return this.#bootstrapPromise;
  }

  async cancel() {
    await this.#stop();
  }

  // Internal implementation. Do not use directly, but call cancel, instead.
  async #stop(error) {
    // first stop our bootstrap timeout before handling the error
    if (this.#timeoutID !== null) {
      clearTimeout(this.#timeoutID);
      this.#timeoutID = null;
    }

    let provider;
    try {
      provider = await lazy.TorProviderBuilder.build();
    } catch {
      // This was probably the error that lead to stop in the first place.
      // No need to continue propagating it.
    }
    try {
      await provider?.stopBootstrap();
    } catch (e) {
      console.error("Failed to stop the bootstrap.", e);
      if (!error) {
        error = e;
      }
    }

    if (this.onbootstraperror && error) {
      this.onbootstraperror(error);
    }

    this.#bootstrapPromiseResolve(false);
  }
}
Loading