Verified Commit d339153b authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame 🎃
Browse files

fixup! Bug 40933: Add tor-launcher functionality

Use the new functions whenever possible, adjust some property names and
other minor fixes.
parent ab8a15b7
Loading
Loading
Loading
Loading
+47 −35
Original line number Diff line number Diff line
import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";

import { TorProtocolService } from "resource://gre/modules/TorProtocolService.sys.mjs";
import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";

/* tor-launcher observer topics */
@@ -13,19 +13,23 @@ export const TorTopics = Object.freeze({
// modeled after XMLHttpRequest
// nicely encapsulates the observer register/unregister logic
export class TorBootstrapRequest {
  constructor() {
  // number of ms to wait before we abandon the bootstrap attempt
  // a value of 0 implies we never wait
    this.timeout = 0;
  timeout = 0;

  // callbacks for bootstrap process status updates
    this.onbootstrapstatus = (progress, status) => {};
    this.onbootstrapcomplete = () => {};
    this.onbootstraperror = (message, details) => {};
  onbootstrapstatus = (progress, status) => {};
  onbootstrapcomplete = () => {};
  onbootstraperror = (message, details) => {};

  // internal resolve() method for bootstrap
    this._bootstrapPromiseResolve = null;
    this._bootstrapPromise = null;
    this._timeoutID = null;
  #bootstrapPromiseResolve = null;
  #bootstrapPromise = null;
  #timeoutID = null;
  #provider = null;

  constructor() {
    this.#provider = TorProviderBuilder.build();
  }

  observe(subject, topic, data) {
@@ -41,15 +45,16 @@ export class TorBootstrapRequest {
          if (this.onbootstrapcomplete) {
            this.onbootstrapcomplete();
          }
          this._bootstrapPromiseResolve(true);
          clearTimeout(this._timeoutID);
          this.#bootstrapPromiseResolve(true);
          clearTimeout(this.#timeoutID);
          this.#timeoutID = null;
        }

        break;
      }
      case TorTopics.BootstrapError: {
        console.info("TorBootstrapRequest: observerd TorBootstrapError", obj);
        this._stop(obj?.message, obj?.details);
        this.#stop(obj?.message, obj?.details);
        break;
      }
    }
@@ -57,12 +62,12 @@ export class TorBootstrapRequest {

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

    this._bootstrapPromise = new Promise((resolve, reject) => {
      this._bootstrapPromiseResolve = resolve;
    this.#bootstrapPromise = new Promise((resolve, reject) => {
      this.#bootstrapPromiseResolve = resolve;

      // register ourselves to listen for bootstrap events
      Services.obs.addObserver(this, TorTopics.BootstrapStatus);
@@ -70,10 +75,10 @@ export class TorBootstrapRequest {

      // optionally cancel bootstrap after a given timeout
      if (this.timeout > 0) {
        this._timeoutID = setTimeout(async () => {
          this._timeoutID = null;
        this.#timeoutID = setTimeout(async () => {
          this.#timeoutID = null;
          // TODO: Translate, if really used
          await this._stop(
          await this.#stop(
            "Tor Bootstrap process timed out",
            `Bootstrap attempt abandoned after waiting ${this.timeout} ms`
          );
@@ -81,38 +86,45 @@ export class TorBootstrapRequest {
      }

      // wait for bootstrapping to begin and maybe handle error
      TorProtocolService.connect().catch(err => {
        this._stop(err.message, "");
      this.#provider.connect().catch(err => {
        this.#stop(err.message, "");
      });
    }).finally(() => {
      // and remove ourselves once bootstrap is resolved
      Services.obs.removeObserver(this, TorTopics.BootstrapStatus);
      Services.obs.removeObserver(this, TorTopics.BootstrapError);
      this._bootstrapPromise = null;
      this.#bootstrapPromise = null;
    });

    return this._bootstrapPromise;
    return this.#bootstrapPromise;
  }

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

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

    // stopBootstrap never throws
    await TorProtocolService.stopBootstrap();
    try {
      await this.#provider.stopBootstrap();
    } catch (e) {
      console.error("Failed to stop the bootstrap.", e);
      if (!message) {
        message = e.message;
        details = "";
      }
    }

    if (this.onbootstraperror && message) {
      this.onbootstraperror(message, details);
    }

    this._bootstrapPromiseResolve(false);
    this.#bootstrapPromiseResolve(false);
  }
}
+6 −48
Original line number Diff line number Diff line
@@ -981,18 +981,6 @@ class TorController {
    await this.#sendCommandSimple(`authenticate ${password || ""}`);
  }

  /**
   * Sends a GETINFO for a single key.
   *
   * @param {string} key The key to get value for
   * @returns {any} The return value depends on the requested key
   */
  async getInfo(key) {
    this.#expectString(key, "key");
    const response = await this.sendCommand(`getinfo ${key}`);
    return this.#getMultipleResponseValues(response)[0];
  }

  /**
   * Sends a GETINFO for a single key.
   * control-spec.txt says "one ReplyLine is sent for each requested value", so,
@@ -1054,9 +1042,7 @@ class TorController {
    const addresses = [v4[5]];
    // a address:port
    // dir-spec.txt also states only the first one should be taken
    // TODO: The consumers do not care about the port or the square brackets
    // either. Remove them when integrating this function with the rest
    const v6 = reply.match(/^a\s+(\[[0-9a-fA-F:]+\]:[0-9]{1,5})$/m);
    const v6 = reply.match(/^a\s+\[([0-9a-fA-F:]+)\]:\d{1,5}$/m);
    if (v6) {
      addresses.push(v6[1]);
    }
@@ -1091,23 +1077,6 @@ class TorController {

  // Configuration

  /**
   * Sends a GETCONF for a single key.
   * GETCONF with a single argument returns results with one or more lines that
   * look like `250[- ]key=value`.
   * Any GETCONF lines that contain a single keyword only are currently dropped.
   * So we can use similar parsing to that for getInfo.
   *
   * @param {string} key The key to get value for
   * @returns {any} A parsed config value (it depends if a parser is known)
   */
  async getConf(key) {
    this.#expectString(key, "key");
    return this.#getMultipleResponseValues(
      await this.sendCommand(`getconf ${key}`)
    );
  }

  /**
   * Sends a GETCONF for a single key.
   * The function could be easily generalized to get multiple keys at once, but
@@ -1264,12 +1233,14 @@ class TorController {
      // TODO: Change the consumer and make the fields more consistent with what
      // we get (e.g., separate key and type, and use a boolen for permanent).
      const info = {
        hsAddress: match.groups.HSAddress,
        typeAndKey: `${match.groups.KeyType}:${match.groups.PrivateKeyBlob}`,
        address: match.groups.HSAddress,
        keyType: match.groups.KeyType,
        keyBlob: match.groups.PrivateKeyBlob,
        flags: [],
      };
      const maybeFlags = match.groups.other?.match(/Flags=(\S+)/);
      if (maybeFlags) {
        info.Flags = maybeFlags[1];
        info.flags = maybeFlags[1].split(",");
      }
      return info;
    });
@@ -1453,19 +1424,6 @@ class TorController {
      )
    );
  }

  /**
   * Process multiple responses to a GETINFO or GETCONF request.
   *
   * @param {string} message The message to process
   * @returns {object[]} The keys depend on the message
   */
  #getMultipleResponseValues(message) {
    return info
      .keyValueStringsFromMessage(message)
      .map(info.stringToValue)
      .filter(x => x);
  }
}

const controlPortInfo = {};
+4 −1
Original line number Diff line number Diff line
@@ -269,11 +269,14 @@ export const TorParsers = Object.freeze({
  },

  parseBridgeLine(line) {
    if (!line) {
      return null;
    }
    const re =
      /\s*(?:(?<transport>\S+)\s+)?(?<addr>[0-9a-fA-F\.\[\]\:]+:\d{1,5})(?:\s+(?<id>[0-9a-fA-F]{40}))?(?:\s+(?<args>.+))?/;
    const match = re.exec(line);
    if (!match) {
      throw new Error("Invalid bridge line.");
      throw new Error(`Invalid bridge line: ${line}.`);
    }
    const bridge = match.groups;
    if (!bridge.transport) {
+96 −373

File changed.

Preview size limit exceeded, changes collapsed.