[tbb-commits] [tor-browser/tor-browser-91.4.0esr-11.0-1] Bug 40597: Implement TorSettings module

sysrqb at torproject.org sysrqb at torproject.org
Thu Dec 2 23:12:16 UTC 2021


commit dd142c5a83dbee21d4e060bad404e8f20a9e30ab
Author: Richard Pospesel <richard at torproject.org>
Date:   Fri Aug 6 16:39:03 2021 +0200

    Bug 40597: Implement TorSettings module
    
    - migrated in-page settings read/write implementation from about:preferences#tor
      to the TorSettings module
    - TorSettings initially loads settings from the tor daemon, and saves them to
      firefox prefs
    - TorSettings notifies observers when a setting has changed; currently only
      QuickStart notification is implemented for parity with previous preference
      notify logic in about:torconnect and about:preferences#tor
    - about:preferences#tor, and about:torconnect now read and write settings
      thorugh the TorSettings module
    - all tor settings live in the torbrowser.settings.* preference branch
    - removed unused pref modify permission for about:torconnect content page from
      AsyncPrefs.jsm
---
 browser/components/torconnect/TorConnectParent.jsm |  25 +-
 .../torpreferences/content/parseFunctions.jsm      |  89 ---
 .../torpreferences/content/torBridgeSettings.jsm   | 325 --------
 .../torpreferences/content/torFirewallSettings.jsm |  72 --
 .../components/torpreferences/content/torPane.js   | 301 ++++----
 .../torpreferences/content/torProxySettings.jsm    | 245 -------
 browser/components/torpreferences/jar.mn           |  12 +-
 browser/modules/TorConnect.jsm                     |  47 +-
 browser/modules/TorSettings.jsm                    | 814 +++++++++++++++++++++
 browser/modules/moz.build                          |   1 +
 .../processsingleton/MainProcessSingleton.jsm      |   5 +
 toolkit/modules/AsyncPrefs.jsm                     |   1 -
 12 files changed, 989 insertions(+), 948 deletions(-)

diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
index 526c588a423e..2fbc2a5c7c7c 100644
--- a/browser/components/torconnect/TorConnectParent.jsm
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -7,10 +7,9 @@ const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
 const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
   "resource:///modules/TorConnect.jsm"
 );
-
-const TorLauncherPrefs = Object.freeze({
-  quickstart: "extensions.torlauncher.quickstart",
-});
+const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import(
+  "resource:///modules/TorSettings.jsm"
+);
 
 /*
 This object is basically a marshalling interface between the TorConnect module
@@ -31,7 +30,7 @@ class TorConnectParent extends JSWindowActorParent {
       BootstrapProgress: TorConnect.bootstrapProgress,
       BootstrapStatus: TorConnect.bootstrapStatus,
       ShowCopyLog: TorConnect.logHasWarningOrError,
-      QuickStartEnabled: Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false),
+      QuickStartEnabled: TorSettings.quickstart.enabled,
     };
 
     // JSWindowActiveParent derived objects cannot observe directly, so create a member
@@ -78,9 +77,12 @@ class TorConnectParent extends JSWindowActorParent {
             // TODO: handle
             break;
           }
-          case "nsPref:changed": {
-            if (aData === TorLauncherPrefs.quickstart) {
-              self.state.QuickStartEnabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+          case TorSettingsTopics.SettingChanged:{
+            if (aData === TorSettingsData.QuickStartEnabled) {
+              self.state.QuickStartEnabled = obj.value;
+            } else {
+              // this isn't a setting torconnect cares about
+              return;
             }
             break;
           }
@@ -98,7 +100,7 @@ class TorConnectParent extends JSWindowActorParent {
       const topic = TorConnectTopics[key];
       Services.obs.addObserver(this.torConnectObserver, topic);
     }
-    Services.prefs.addObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
+    Services.obs.addObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged);
   }
 
   willDestroy() {
@@ -107,13 +109,14 @@ class TorConnectParent extends JSWindowActorParent {
       const topic = TorConnectTopics[key];
       Services.obs.removeObserver(this.torConnectObserver, topic);
     }
-    Services.prefs.removeObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
+    Services.obs.removeObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged);
   }
 
   receiveMessage(message) {
     switch (message.name) {
       case "torconnect:set-quickstart":
-        Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, message.data);
+        TorSettings.quickstart.enabled = message.data;
+        TorSettings.saveToPrefs().applySettings();
         break;
       case "torconnect:open-tor-preferences":
         TorConnect.openTorPreferences();
diff --git a/browser/components/torpreferences/content/parseFunctions.jsm b/browser/components/torpreferences/content/parseFunctions.jsm
deleted file mode 100644
index 954759de63a5..000000000000
--- a/browser/components/torpreferences/content/parseFunctions.jsm
+++ /dev/null
@@ -1,89 +0,0 @@
-"use strict";
-
-var EXPORTED_SYMBOLS = [
-  "parsePort",
-  "parseAddrPort",
-  "parseUsernamePassword",
-  "parseAddrPortList",
-  "parseBridgeStrings",
-  "parsePortList",
-];
-
-// expects a string representation of an integer from 1 to 65535
-let parsePort = function(aPort) {
-  // ensure port string is a valid positive integer
-  const validIntRegex = /^[0-9]+$/;
-  if (!validIntRegex.test(aPort)) {
-    throw new Error(`Invalid PORT string : '${aPort}'`);
-  }
-
-  // ensure port value is on valid range
-  let port = Number.parseInt(aPort);
-  if (port < 1 || port > 65535) {
-    throw new Error(
-      `Invalid PORT value, needs to be on range [1,65535] : '${port}'`
-    );
-  }
-
-  return port;
-};
-// expects a string in the format: "ADDRESS:PORT"
-let parseAddrPort = function(aAddrColonPort) {
-  let tokens = aAddrColonPort.split(":");
-  if (tokens.length != 2) {
-    throw new Error(`Invalid ADDRESS:PORT string : '${aAddrColonPort}'`);
-  }
-  let address = tokens[0];
-  let port = parsePort(tokens[1]);
-  return [address, port];
-};
-
-// expects a string in the format: "USERNAME:PASSWORD"
-// split on the first colon and any subsequent go into password
-let parseUsernamePassword = function(aUsernameColonPassword) {
-  let colonIndex = aUsernameColonPassword.indexOf(":");
-  if (colonIndex < 0) {
-    // we don't log the contents of the potentially password containing string
-    throw new Error("Invalid USERNAME:PASSWORD string");
-  }
-
-  let username = aUsernameColonPassword.substring(0, colonIndex);
-  let password = aUsernameColonPassword.substring(colonIndex + 1);
-
-  return [username, password];
-};
-
-// expects a string in the format: ADDRESS:PORT,ADDRESS:PORT,...
-// returns array of ports (as ints)
-let parseAddrPortList = function(aAddrPortList) {
-  let addrPorts = aAddrPortList.split(",");
-  // parse ADDRESS:PORT string and only keep the port (second element in returned array)
-  let retval = addrPorts.map(addrPort => parseAddrPort(addrPort)[1]);
-  return retval;
-};
-
-// expects a '/n' or '/r/n' delimited bridge string, which we split and trim
-// each bridge string can also optionally have 'bridge' at the beginning ie:
-// bridge $(type) $(address):$(port) $(certificate)
-// we strip out the 'bridge' prefix here
-let parseBridgeStrings = function(aBridgeStrings) {
-
-  // replace carriage returns ('\r') with new lines ('\n')
-  aBridgeStrings = aBridgeStrings.replace(/\r/g, "\n");
-  // then replace contiguous new lines ('\n') with a single one
-  aBridgeStrings = aBridgeStrings.replace(/[\n]+/g, "\n");
-
-  // split on the newline and for each bridge string: trim, remove starting 'bridge' string
-  // finally discard entries that are empty strings; empty strings could occur if we receive
-  // a new line containing only whitespace
-  let splitStrings = aBridgeStrings.split("\n");
-  return splitStrings.map(val => val.trim().replace(/^bridge\s+/i, ""))
-                     .filter(bridgeString => bridgeString != "");
-};
-
-// expecting a ',' delimited list of ints with possible white space between
-// returns an array of ints
-let parsePortList = function(aPortListString) {
-  let splitStrings = aPortListString.split(",");
-  return splitStrings.map(val => parsePort(val.trim()));
-};
diff --git a/browser/components/torpreferences/content/torBridgeSettings.jsm b/browser/components/torpreferences/content/torBridgeSettings.jsm
deleted file mode 100644
index ceb61d3ec972..000000000000
--- a/browser/components/torpreferences/content/torBridgeSettings.jsm
+++ /dev/null
@@ -1,325 +0,0 @@
-"use strict";
-
-var EXPORTED_SYMBOLS = [
-  "TorBridgeSource",
-  "TorBridgeSettings",
-  "makeTorBridgeSettingsNone",
-  "makeTorBridgeSettingsBuiltin",
-  "makeTorBridgeSettingsBridgeDB",
-  "makeTorBridgeSettingsUserProvided",
-];
-
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
-);
-const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
-
-const TorBridgeSource = {
-  NONE: "NONE",
-  BUILTIN: "BUILTIN",
-  BRIDGEDB: "BRIDGEDB",
-  USERPROVIDED: "USERPROVIDED",
-};
-
-class TorBridgeSettings {
-  constructor() {
-    this._bridgeSource = TorBridgeSource.NONE;
-    this._selectedDefaultBridgeType = null;
-    this._bridgeStrings = [];
-  }
-
-  get selectedDefaultBridgeType() {
-    if (this._bridgeSource == TorBridgeSource.BUILTIN) {
-      return this._selectedDefaultBridgeType;
-    }
-    return undefined;
-  }
-
-  get bridgeSource() {
-    return this._bridgeSource;
-  }
-
-  // for display
-  get bridgeStrings() {
-    return this._bridgeStrings.join("\n");
-  }
-
-  // raw
-  get bridgeStringsArray() {
-    return this._bridgeStrings;
-  }
-
-  static get defaultBridgeTypes() {
-    if (TorBridgeSettings._defaultBridgeTypes) {
-      return TorBridgeSettings._defaultBridgeTypes;
-    }
-
-    let bridgeListBranch = Services.prefs.getBranch(
-      TorStrings.preferenceBranches.defaultBridge
-    );
-    let bridgePrefs = bridgeListBranch.getChildList("", {});
-
-    // an unordered set for shoving bridge types into
-    let bridgeTypes = new Set();
-    // look for keys ending in ".N" and treat string before that as the bridge type
-    const pattern = /\.[0-9]+$/;
-    for (const key of bridgePrefs) {
-      const offset = key.search(pattern);
-      if (offset != -1) {
-        const bt = key.substring(0, offset);
-        bridgeTypes.add(bt);
-      }
-    }
-
-    // recommended bridge type goes first in the list
-    let recommendedBridgeType = Services.prefs.getCharPref(
-      TorStrings.preferenceKeys.recommendedBridgeType,
-      null
-    );
-
-    let retval = [];
-    if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) {
-      retval.push(recommendedBridgeType);
-    }
-
-    for (const bridgeType of bridgeTypes.values()) {
-      if (bridgeType != recommendedBridgeType) {
-        retval.push(bridgeType);
-      }
-    }
-
-    // cache off
-    TorBridgeSettings._defaultBridgeTypes = retval;
-    return retval;
-  }
-
-  _readDefaultBridges(aBridgeType) {
-    let bridgeBranch = Services.prefs.getBranch(
-      TorStrings.preferenceBranches.defaultBridge
-    );
-    let bridgeBranchPrefs = bridgeBranch.getChildList("", {});
-
-    let retval = [];
-
-    // regex matches against strings ending in ".N" where N is a positive integer
-    let pattern = /\.[0-9]+$/;
-    for (const key of bridgeBranchPrefs) {
-      // verify the location of the match is the correct offset required for aBridgeType
-      // to fit, and that the string begins with aBridgeType
-      if (
-        key.search(pattern) == aBridgeType.length &&
-        key.startsWith(aBridgeType)
-      ) {
-        let bridgeStr = bridgeBranch.getCharPref(key);
-        retval.push(bridgeStr);
-      }
-    }
-
-    // fisher-yates shuffle
-    // shuffle so that Tor Browser users don't all try the built-in bridges in the same order
-    for (let i = retval.length - 1; i > 0; --i) {
-      // number n such that 0.0 <= n < 1.0
-      const n = Math.random();
-      // integer j such that 0 <= j <= i
-      const j = Math.floor(n * (i + 1));
-
-      // swap values at indices i and j
-      const tmp = retval[i];
-      retval[i] = retval[j];
-      retval[j] = tmp;
-    }
-
-    return retval;
-  }
-
-  _readBridgeDBBridges() {
-    let bridgeBranch = Services.prefs.getBranch(
-      `${TorStrings.preferenceBranches.bridgeDBBridges}`
-    );
-    let bridgeBranchPrefs = bridgeBranch.getChildList("", {});
-    // the child prefs do not come in any particular order so sort the keys
-    // so the values can be compared to what we get out off torrc
-    bridgeBranchPrefs.sort();
-
-    // just assume all of the prefs under the parent point to valid bridge string
-    let retval = bridgeBranchPrefs.map(key =>
-      bridgeBranch.getCharPref(key).trim()
-    );
-
-    return retval;
-  }
-
-  _readTorrcBridges() {
-    let bridgeList = TorProtocolService.readStringArraySetting(
-      TorStrings.configKeys.bridgeList
-    );
-
-    let retval = [];
-    for (const line of bridgeList) {
-      let trimmedLine = line.trim();
-      if (trimmedLine) {
-        retval.push(trimmedLine);
-      }
-    }
-
-    return retval;
-  }
-
-  // analagous to initBridgeSettings()
-  readSettings() {
-    // restore to defaults
-    this._bridgeSource = TorBridgeSource.NONE;
-    this._selectedDefaultBridgeType = null;
-    this._bridgeStrings = [];
-
-    // So the way tor-launcher determines the origin of the configured bridges is a bit
-    // weird and depends on inferring our scenario based on some firefox prefs and the
-    // relationship between the saved list of bridges in about:config vs the list saved in torrc
-
-    // first off, if "extensions.torlauncher.default_bridge_type" is set to one of our
-    // builtin default types (obfs4, meek-azure, snowflake, etc) then we provide the
-    // bridges in "extensions.torlauncher.default_bridge.*" (filtered by our default_bridge_type)
-
-    // next, we compare the list of bridges saved in torrc to the bridges stored in the
-    // "extensions.torlauncher.bridgedb_bridge."" branch. If they match *exactly* then we assume
-    // the bridges were retrieved from BridgeDB and use those. If the torrc list is empty then we know
-    // we have no bridge settings
-
-    // finally, if none of the previous conditions are not met, it is assumed the bridges stored in
-    // torrc are user-provided
-
-    // what we should(?) do once we excise tor-launcher entirely is explicitly store an int/enum in
-    // about:config that tells us which scenario we are in so we don't have to guess
-
-    let defaultBridgeType = Services.prefs.getCharPref(
-      TorStrings.preferenceKeys.defaultBridgeType,
-      null
-    );
-
-    // check if source is BUILTIN
-    if (defaultBridgeType) {
-      this._bridgeStrings = this._readDefaultBridges(defaultBridgeType);
-      this._bridgeSource = TorBridgeSource.BUILTIN;
-      this._selectedDefaultBridgeType = defaultBridgeType;
-      return;
-    }
-
-    let torrcBridges = this._readTorrcBridges();
-
-    // no stored bridges means no bridge is in use
-    if (torrcBridges.length == 0) {
-      this._bridgeStrings = [];
-      this._bridgeSource = TorBridgeSource.NONE;
-      return;
-    }
-
-    let bridgedbBridges = this._readBridgeDBBridges();
-
-    // if these two lists are equal then we got our bridges from bridgedb
-    // ie: same element in identical order
-    let arraysEqual = (left, right) => {
-      if (left.length != right.length) {
-        return false;
-      }
-      const length = left.length;
-      for (let i = 0; i < length; ++i) {
-        if (left[i] != right[i]) {
-          return false;
-        }
-      }
-      return true;
-    };
-
-    // agreement between prefs and torrc means bridgedb bridges
-    if (arraysEqual(torrcBridges, bridgedbBridges)) {
-      this._bridgeStrings = torrcBridges;
-      this._bridgeSource = TorBridgeSource.BRIDGEDB;
-      return;
-    }
-
-    // otherwise they must be user provided
-    this._bridgeStrings = torrcBridges;
-    this._bridgeSource = TorBridgeSource.USERPROVIDED;
-  }
-
-  writeSettings() {
-    let settingsObject = new Map();
-
-    // init tor bridge settings to null
-    settingsObject.set(TorStrings.configKeys.useBridges, null);
-    settingsObject.set(TorStrings.configKeys.bridgeList, null);
-
-    // clear bridge related firefox prefs
-    Services.prefs.setCharPref(TorStrings.preferenceKeys.defaultBridgeType, "");
-    let bridgeBranch = Services.prefs.getBranch(
-      `${TorStrings.preferenceBranches.bridgeDBBridges}`
-    );
-    let bridgeBranchPrefs = bridgeBranch.getChildList("", {});
-    for (const pref of bridgeBranchPrefs) {
-      Services.prefs.clearUserPref(
-        `${TorStrings.preferenceBranches.bridgeDBBridges}${pref}`
-      );
-    }
-
-    switch (this._bridgeSource) {
-      case TorBridgeSource.BUILTIN:
-        // set builtin bridge type to use in prefs
-        Services.prefs.setCharPref(
-          TorStrings.preferenceKeys.defaultBridgeType,
-          this._selectedDefaultBridgeType
-        );
-        break;
-      case TorBridgeSource.BRIDGEDB:
-        // save bridges off to prefs
-        for (let i = 0; i < this.bridgeStringsArray.length; ++i) {
-          Services.prefs.setCharPref(
-            `${TorStrings.preferenceBranches.bridgeDBBridges}${i}`,
-            this.bridgeStringsArray[i]
-          );
-        }
-        break;
-    }
-
-    // write over our bridge list if bridges are enabled
-    if (this._bridgeSource != TorBridgeSource.NONE) {
-      settingsObject.set(TorStrings.configKeys.useBridges, true);
-      settingsObject.set(
-        TorStrings.configKeys.bridgeList,
-        this.bridgeStringsArray
-      );
-    }
-    TorProtocolService.writeSettings(settingsObject);
-  }
-}
-
-function makeTorBridgeSettingsNone() {
-  return new TorBridgeSettings();
-}
-
-function makeTorBridgeSettingsBuiltin(aBridgeType) {
-  let retval = new TorBridgeSettings();
-  retval._bridgeSource = TorBridgeSource.BUILTIN;
-  retval._selectedDefaultBridgeType = aBridgeType;
-  retval._bridgeStrings = retval._readDefaultBridges(aBridgeType);
-
-  return retval;
-}
-
-function makeTorBridgeSettingsBridgeDB(aBridges) {
-  let retval = new TorBridgeSettings();
-  retval._bridgeSource = TorBridgeSource.BRIDGEDB;
-  retval._selectedDefaultBridgeType = null;
-  retval._bridgeStrings = aBridges;
-
-  return retval;
-}
-
-function makeTorBridgeSettingsUserProvided(aBridges) {
-  let retval = new TorBridgeSettings();
-  retval._bridgeSource = TorBridgeSource.USERPROVIDED;
-  retval._selectedDefaultBridgeType = null;
-  retval._bridgeStrings = aBridges;
-
-  return retval;
-}
diff --git a/browser/components/torpreferences/content/torFirewallSettings.jsm b/browser/components/torpreferences/content/torFirewallSettings.jsm
deleted file mode 100644
index e77f18ef2fae..000000000000
--- a/browser/components/torpreferences/content/torFirewallSettings.jsm
+++ /dev/null
@@ -1,72 +0,0 @@
-"use strict";
-
-var EXPORTED_SYMBOLS = [
-  "TorFirewallSettings",
-  "makeTorFirewallSettingsNone",
-  "makeTorFirewallSettingsCustom",
-];
-
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
-);
-const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
-const { parseAddrPortList } = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/parseFunctions.jsm"
-);
-
-class TorFirewallSettings {
-  constructor() {
-    this._allowedPorts = [];
-  }
-
-  get portsConfigurationString() {
-    let portStrings = this._allowedPorts.map(port => `*:${port}`);
-    return portStrings.join(",");
-  }
-
-  get commaSeparatedListString() {
-    return this._allowedPorts.join(",");
-  }
-
-  get hasPorts() {
-    return this._allowedPorts.length > 0;
-  }
-
-  readSettings() {
-    let addressPortList = TorProtocolService.readStringSetting(
-      TorStrings.configKeys.reachableAddresses
-    );
-
-    let allowedPorts = [];
-    if (addressPortList) {
-      allowedPorts = parseAddrPortList(addressPortList);
-    }
-    this._allowedPorts = allowedPorts;
-  }
-
-  writeSettings() {
-    let settingsObject = new Map();
-
-    // init to null so Tor daemon resets if no ports
-    settingsObject.set(TorStrings.configKeys.reachableAddresses, null);
-
-    if (this._allowedPorts.length > 0) {
-      settingsObject.set(
-        TorStrings.configKeys.reachableAddresses,
-        this.portsConfigurationString
-      );
-    }
-
-    TorProtocolService.writeSettings(settingsObject);
-  }
-}
-
-function makeTorFirewallSettingsNone() {
-  return new TorFirewallSettings();
-}
-
-function makeTorFirewallSettingsCustom(aPortsList) {
-  let retval = new TorFirewallSettings();
-  retval._allowedPorts = aPortsList;
-  return retval;
-}
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
index 1f169cbe1a55..6905cbe473ac 100644
--- a/browser/components/torpreferences/content/torPane.js
+++ b/browser/components/torpreferences/content/torPane.js
@@ -2,6 +2,10 @@
 
 /* global Services */
 
+const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource, TorBuiltinBridgeTypes, TorProxyType } = ChromeUtils.import(
+  "resource:///modules/TorSettings.jsm"
+);
+
 const { TorProtocolService } = ChromeUtils.import(
   "resource:///modules/TorProtocolService.jsm"
 );
@@ -10,35 +14,6 @@ const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
   "resource:///modules/TorConnect.jsm"
 );
 
-const {
-  TorBridgeSource,
-  TorBridgeSettings,
-  makeTorBridgeSettingsNone,
-  makeTorBridgeSettingsBuiltin,
-  makeTorBridgeSettingsBridgeDB,
-  makeTorBridgeSettingsUserProvided,
-} = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/torBridgeSettings.jsm"
-);
-
-const {
-  TorProxyType,
-  TorProxySettings,
-  makeTorProxySettingsNone,
-  makeTorProxySettingsSocks4,
-  makeTorProxySettingsSocks5,
-  makeTorProxySettingsHTTPS,
-} = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/torProxySettings.jsm"
-);
-const {
-  TorFirewallSettings,
-  makeTorFirewallSettingsNone,
-  makeTorFirewallSettingsCustom,
-} = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/torFirewallSettings.jsm"
-);
-
 const { TorLogDialog } = ChromeUtils.import(
   "chrome://browser/content/torpreferences/torLogDialog.jsm"
 );
@@ -53,14 +28,6 @@ ChromeUtils.defineModuleGetter(
   "resource:///modules/TorStrings.jsm"
 );
 
-const { parsePort, parseBridgeStrings, parsePortList } = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/parseFunctions.jsm"
-);
-
-const TorLauncherPrefs = {
-  quickstart: "extensions.torlauncher.quickstart",
-}
-
 /*
   Tor Pane
 
@@ -261,10 +228,11 @@ const gTorPane = (function() {
       );
       this._enableQuickstartCheckbox.addEventListener("command", e => {
         const checked = this._enableQuickstartCheckbox.checked;
-        Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, checked);
+        TorSettings.quickstart.enabled = checked;
+        TorSettings.saveToPrefs().applySettings();
       });
-      this._enableQuickstartCheckbox.checked = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
-      Services.prefs.addObserver(TorLauncherPrefs.quickstart, this);
+      this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled;
+      Services.obs.addObserver(this, TorSettingsTopics.SettingChanged);
 
       // Bridge setup
       prefpane.querySelector(selectors.bridges.header).innerText =
@@ -291,7 +259,7 @@ const gTorPane = (function() {
       this._bridgeSelectionRadiogroup = prefpane.querySelector(
         selectors.bridges.bridgeSelectionRadiogroup
       );
-      this._bridgeSelectionRadiogroup.value = TorBridgeSource.BUILTIN;
+      this._bridgeSelectionRadiogroup.value = TorBridgeSource.BuiltIn;
       this._bridgeSelectionRadiogroup.addEventListener("command", e => {
         const value = this._bridgeSelectionRadiogroup.value;
         gTorPane.onSelectBridgeOption(value).onUpdateBridgeSettings();
@@ -305,7 +273,7 @@ const gTorPane = (function() {
         "label",
         TorStrings.settings.selectBridge
       );
-      this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BUILTIN);
+      this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BuiltIn);
       this._builtinBridgeMenulist = prefpane.querySelector(
         selectors.bridges.builtinBridgeList
       );
@@ -321,7 +289,7 @@ const gTorPane = (function() {
         "label",
         TorStrings.settings.requestBridgeFromTorProject
       );
-      this._requestBridgeOption.setAttribute("value", TorBridgeSource.BRIDGEDB);
+      this._requestBridgeOption.setAttribute("value", TorBridgeSource.BridgeDB);
       this._requestBridgeButton = prefpane.querySelector(
         selectors.bridges.requestBridgeButton
       );
@@ -346,7 +314,7 @@ const gTorPane = (function() {
       );
       this._provideBridgeOption.setAttribute(
         "value",
-        TorBridgeSource.USERPROVIDED
+        TorBridgeSource.UserProvided
       );
       prefpane.querySelector(
         selectors.bridges.provideBridgeDescription
@@ -395,11 +363,11 @@ const gTorPane = (function() {
 
       let mockProxies = [
         {
-          value: TorProxyType.SOCKS4,
+          value: TorProxyType.Socks4,
           label: TorStrings.settings.proxyTypeSOCKS4,
         },
         {
-          value: TorProxyType.SOCKS5,
+          value: TorProxyType.Socks5,
           label: TorStrings.settings.proxyTypeSOCKS5,
         },
         { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP },
@@ -550,12 +518,8 @@ const gTorPane = (function() {
         true
       );
 
-      // load bridge settings
-      let torBridgeSettings = new TorBridgeSettings();
-      torBridgeSettings.readSettings();
-
-      // populate the bridge list
-      for (let currentBridge of TorBridgeSettings.defaultBridgeTypes) {
+      // init bridge UI
+      for (let currentBridge of TorBuiltinBridgeTypes) {
         let menuEntry = document.createXULElement("menuitem");
         menuEntry.setAttribute("value", currentBridge);
         menuEntry.setAttribute("label", currentBridge);
@@ -564,53 +528,41 @@ const gTorPane = (function() {
           .appendChild(menuEntry);
       }
 
-      this.onSelectBridgeOption(torBridgeSettings.bridgeSource);
-      this.onToggleBridge(
-        torBridgeSettings.bridgeSource != TorBridgeSource.NONE
-      );
-      switch (torBridgeSettings.bridgeSource) {
-        case TorBridgeSource.NONE:
-          break;
-        case TorBridgeSource.BUILTIN:
-          this._builtinBridgeMenulist.value =
-            torBridgeSettings.selectedDefaultBridgeType;
-          break;
-        case TorBridgeSource.BRIDGEDB:
-          this._requestBridgeTextarea.value = torBridgeSettings.bridgeStrings;
-          break;
-        case TorBridgeSource.USERPROVIDED:
-          this._provideBridgeTextarea.value = torBridgeSettings.bridgeStrings;
-          break;
+      if (TorSettings.bridges.enabled) {
+        this.onSelectBridgeOption(TorSettings.bridges.source);
+        this.onToggleBridge(
+          TorSettings.bridges.source != TorBridgeSource.Invalid
+        );
+        switch (TorSettings.bridges.source) {
+          case TorBridgeSource.Invalid:
+            break;
+          case TorBridgeSource.BuiltIn:
+            this._builtinBridgeMenulist.value = TorSettings.bridges.builtin_type;
+            break;
+          case TorBridgeSource.BridgeDB:
+            this._requestBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n");
+            break;
+          case TorBridgeSource.UserProvided:
+            this._provideBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n");
+            break;
+        }
       }
 
-      this._bridgeSettings = torBridgeSettings;
-
-      // load proxy settings
-      let torProxySettings = new TorProxySettings();
-      torProxySettings.readSettings();
-
-      if (torProxySettings.type != TorProxyType.NONE) {
+      // init proxy UI
+      if (TorSettings.proxy.enabled) {
         this.onToggleProxy(true);
-        this.onSelectProxyType(torProxySettings.type);
-        this._proxyAddressTextbox.value = torProxySettings.address;
-        this._proxyPortTextbox.value = torProxySettings.port;
-        this._proxyUsernameTextbox.value = torProxySettings.username;
-        this._proxyPasswordTextbox.value = torProxySettings.password;
+        this.onSelectProxyType(TorSettings.proxy.type);
+        this._proxyAddressTextbox.value = TorSettings.proxy.address;
+        this._proxyPortTextbox.value = TorSettings.proxy.port;
+        this._proxyUsernameTextbox.value = TorSettings.proxy.username;
+        this._proxyPasswordTextbox.value = TorSettings.proxy.password;
       }
 
-      this._proxySettings = torProxySettings;
-
-      // load firewall settings
-      let torFirewallSettings = new TorFirewallSettings();
-      torFirewallSettings.readSettings();
-
-      if (torFirewallSettings.hasPorts) {
+      // init firewall
+      if (TorSettings.firewall.enabled) {
         this.onToggleFirewall(true);
-        this._allowedPortsTextbox.value =
-          torFirewallSettings.commaSeparatedListString;
+        this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join(", ");
       }
-
-      this._firewallSettings = torFirewallSettings;
     },
 
     init() {
@@ -683,13 +635,17 @@ const gTorPane = (function() {
       if (enabled) {
         this.onSelectBridgeOption(this._bridgeSelectionRadiogroup.value);
       } else {
-        this.onSelectBridgeOption(TorBridgeSource.NONE);
+        this.onSelectBridgeOption(TorBridgeSource.Invalid);
       }
       return this;
     },
 
     // callback when a bridge option is selected
     onSelectBridgeOption(source) {
+      if (typeof source === "string") {
+        source = parseInt(source);
+      }
+
       // disable all of the bridge elements under radio buttons
       this._setElementsDisabled(
         [
@@ -701,23 +657,23 @@ const gTorPane = (function() {
         true
       );
 
-      if (source != TorBridgeSource.NONE) {
+      if (source != TorBridgeSource.Invalid) {
         this._bridgeSelectionRadiogroup.value = source;
       }
 
       switch (source) {
-        case TorBridgeSource.BUILTIN: {
+        case TorBridgeSource.BuiltIn: {
           this._setElementsDisabled([this._builtinBridgeMenulist], false);
           break;
         }
-        case TorBridgeSource.BRIDGEDB: {
+        case TorBridgeSource.BridgeDB: {
           this._setElementsDisabled(
             [this._requestBridgeButton, this._requestBridgeTextarea],
             false
           );
           break;
         }
-        case TorBridgeSource.USERPROVIDED: {
+        case TorBridgeSource.UserProvided: {
           this._setElementsDisabled([this._provideBridgeTextarea], false);
           break;
         }
@@ -730,14 +686,17 @@ const gTorPane = (function() {
       let requestBridgeDialog = new RequestBridgeDialog();
       requestBridgeDialog.openDialog(
         gSubDialog,
-        this._proxySettings.proxyURI,
+        TorSettings.proxy.uri,
         aBridges => {
           if (aBridges.length > 0) {
-            let bridgeSettings = makeTorBridgeSettingsBridgeDB(aBridges);
-            bridgeSettings.writeSettings();
-            this._bridgeSettings = bridgeSettings;
-
-            this._requestBridgeTextarea.value = bridgeSettings.bridgeStrings;
+            let bridgeStrings = aBridges.join("\n");
+            TorSettings.bridges.enabled = true;
+            TorSettings.bridges.source = TorBridgeSource.BridgeDB;
+            TorSettings.bridges.bridge_strings = bridgeStrings;
+            TorSettings.saveToPrefs();
+            TorSettings.applySettings().then((result) => {
+              this._requestBridgeTextarea.value = bridgeStrings;
+            });
           }
         }
       );
@@ -746,53 +705,56 @@ const gTorPane = (function() {
 
     // pushes bridge settings from UI to tor
     onUpdateBridgeSettings() {
-      let bridgeSettings = null;
-
       let source = this._useBridgeCheckbox.checked
-        ? this._bridgeSelectionRadiogroup.value
-        : TorBridgeSource.NONE;
+        ? parseInt(this._bridgeSelectionRadiogroup.value)
+        : TorBridgeSource.Invalid;
+
       switch (source) {
-        case TorBridgeSource.NONE: {
-          bridgeSettings = makeTorBridgeSettingsNone();
-          break;
+        case TorBridgeSource.Invalid: {
+          TorSettings.bridges.enabled = false;
         }
-        case TorBridgeSource.BUILTIN: {
+        break;
+        case TorBridgeSource.BuiltIn: {
           // if there is a built-in bridge already selected, use that
           let bridgeType = this._builtinBridgeMenulist.value;
+          console.log(`bridge type: ${bridgeType}`);
           if (bridgeType) {
-            bridgeSettings = makeTorBridgeSettingsBuiltin(bridgeType);
+            TorSettings.bridges.enabled = true;
+            TorSettings.bridges.source = TorBridgeSource.BuiltIn;
+            TorSettings.bridges.builtin_type = bridgeType;
           } else {
-            bridgeSettings = makeTorBridgeSettingsNone();
+            TorSettings.bridges.enabled = false;
           }
           break;
         }
-        case TorBridgeSource.BRIDGEDB: {
+        case TorBridgeSource.BridgeDB: {
           // if there are bridgedb bridges saved in the text area, use them
           let bridgeStrings = this._requestBridgeTextarea.value;
           if (bridgeStrings) {
-            let bridgeStringList = parseBridgeStrings(bridgeStrings);
-            bridgeSettings = makeTorBridgeSettingsBridgeDB(bridgeStringList);
+            TorSettings.bridges.enabled = true;
+            TorSettings.bridges.source = TorBridgeSource.BridgeDB;
+            TorSettings.bridges.bridge_strings = bridgeStrings;
           } else {
-            bridgeSettings = makeTorBridgeSettingsNone();
+            TorSettings.bridges.enabled = false;
           }
           break;
         }
-        case TorBridgeSource.USERPROVIDED: {
+        case TorBridgeSource.UserProvided: {
           // if bridges already exist in the text area, use them
           let bridgeStrings = this._provideBridgeTextarea.value;
           if (bridgeStrings) {
-            let bridgeStringList = parseBridgeStrings(bridgeStrings);
-            bridgeSettings = makeTorBridgeSettingsUserProvided(
-              bridgeStringList
-            );
+            TorSettings.bridges.enabled = true;
+            TorSettings.bridges.source = TorBridgeSource.UserProvided;
+            TorSettings.bridges.bridge_strings = bridgeStrings;
           } else {
-            bridgeSettings = makeTorBridgeSettingsNone();
+            TorSettings.bridges.enabled = false;
           }
           break;
         }
       }
-      bridgeSettings.writeSettings();
-      this._bridgeSettings = bridgeSettings;
+      TorSettings.saveToPrefs();
+      TorSettings.applySettings();
+
       return this;
     },
 
@@ -822,12 +784,13 @@ const gTorPane = (function() {
 
     // callback when proxy type is changed
     onSelectProxyType(value) {
-      if (value == "") {
-        value = TorProxyType.NONE;
+      if (typeof value === "string") {
+        value = parseInt(value);
       }
+
       this._proxyTypeMenulist.value = value;
       switch (value) {
-        case TorProxyType.NONE: {
+        case TorProxyType.Invalid: {
           this._setElementsDisabled(
             [
               this._proxyAddressLabel,
@@ -848,7 +811,7 @@ const gTorPane = (function() {
           this._proxyPasswordTextbox.value = "";
           break;
         }
-        case TorProxyType.SOCKS4: {
+        case TorProxyType.Socks4: {
           this._setElementsDisabled(
             [
               this._proxyAddressLabel,
@@ -872,7 +835,7 @@ const gTorPane = (function() {
           this._proxyPasswordTextbox.value = "";
           break;
         }
-        case TorProxyType.SOCKS5:
+        case TorProxyType.Socks5:
         case TorProxyType.HTTPS: {
           this._setElementsDisabled(
             [
@@ -895,46 +858,45 @@ const gTorPane = (function() {
 
     // pushes proxy settings from UI to tor
     onUpdateProxySettings() {
-      const proxyType = this._useProxyCheckbox.checked
-        ? this._proxyTypeMenulist.value
-        : TorProxyType.NONE;
-      const addressString = this._proxyAddressTextbox.value;
-      const portString = this._proxyPortTextbox.value;
-      const usernameString = this._proxyUsernameTextbox.value;
-      const passwordString = this._proxyPasswordTextbox.value;
-
-      let proxySettings = null;
-
-      switch (proxyType) {
-        case TorProxyType.NONE:
-          proxySettings = makeTorProxySettingsNone();
+      const type = this._useProxyCheckbox.checked
+        ? parseInt(this._proxyTypeMenulist.value)
+        : TorProxyType.Invalid;
+      const address = this._proxyAddressTextbox.value;
+      const port = this._proxyPortTextbox.value;
+      const username = this._proxyUsernameTextbox.value;
+      const password = this._proxyPasswordTextbox.value;
+
+      switch (type) {
+        case TorProxyType.Invalid:
+          TorSettings.proxy.enabled = false;
           break;
-        case TorProxyType.SOCKS4:
-          proxySettings = makeTorProxySettingsSocks4(
-            addressString,
-            parsePort(portString)
-          );
+        case TorProxyType.Socks4:
+          TorSettings.proxy.enabled = true;
+          TorSettings.proxy.type = type;
+          TorSettings.proxy.address = address;
+          TorSettings.proxy.port = port;
+
           break;
-        case TorProxyType.SOCKS5:
-          proxySettings = makeTorProxySettingsSocks5(
-            addressString,
-            parsePort(portString),
-            usernameString,
-            passwordString
-          );
+        case TorProxyType.Socks5:
+          TorSettings.proxy.enabled = true;
+          TorSettings.proxy.type = type;
+          TorSettings.proxy.address = address;
+          TorSettings.proxy.port = port;
+          TorSettings.proxy.username = username;
+          TorSettings.proxy.password = password;
           break;
         case TorProxyType.HTTPS:
-          proxySettings = makeTorProxySettingsHTTPS(
-            addressString,
-            parsePort(portString),
-            usernameString,
-            passwordString
-          );
+          TorSettings.proxy.enabled = true;
+          TorSettings.proxy.type = type;
+          TorSettings.proxy.address = address;
+          TorSettings.proxy.port = port;
+          TorSettings.proxy.username = username;
+          TorSettings.proxy.password = password;
           break;
       }
+      TorSettings.saveToPrefs();
+      TorSettings.applySettings();
 
-      proxySettings.writeSettings();
-      this._proxySettings = proxySettings;
       return this;
     },
 
@@ -953,21 +915,20 @@ const gTorPane = (function() {
 
     // pushes firewall settings from UI to tor
     onUpdateFirewallSettings() {
+
       let portListString = this._useFirewallCheckbox.checked
         ? this._allowedPortsTextbox.value
         : "";
-      let firewallSettings = null;
 
       if (portListString) {
-        firewallSettings = makeTorFirewallSettingsCustom(
-          parsePortList(portListString)
-        );
+        TorSettings.firewall.enabled = true;
+        TorSettings.firewall.allowed_ports = portListString;
       } else {
-        firewallSettings = makeTorFirewallSettingsNone();
+        TorSettings.firewall.enabled = false;
       }
+      TorSettings.saveToPrefs();
+      TorSettings.applySettings();
 
-      firewallSettings.writeSettings();
-      this._firewallSettings = firewallSettings;
       return this;
     },
 
diff --git a/browser/components/torpreferences/content/torProxySettings.jsm b/browser/components/torpreferences/content/torProxySettings.jsm
deleted file mode 100644
index 98bb5e8d5cbf..000000000000
--- a/browser/components/torpreferences/content/torProxySettings.jsm
+++ /dev/null
@@ -1,245 +0,0 @@
-"use strict";
-
-var EXPORTED_SYMBOLS = [
-  "TorProxyType",
-  "TorProxySettings",
-  "makeTorProxySettingsNone",
-  "makeTorProxySettingsSocks4",
-  "makeTorProxySettingsSocks5",
-  "makeTorProxySettingsHTTPS",
-];
-
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
-);
-const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
-const { parseAddrPort, parseUsernamePassword } = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/parseFunctions.jsm"
-);
-
-const TorProxyType = {
-  NONE: "NONE",
-  SOCKS4: "SOCKS4",
-  SOCKS5: "SOCKS5",
-  HTTPS: "HTTPS",
-};
-
-class TorProxySettings {
-  constructor() {
-    this._proxyType = TorProxyType.NONE;
-    this._proxyAddress = undefined;
-    this._proxyPort = undefined;
-    this._proxyUsername = undefined;
-    this._proxyPassword = undefined;
-  }
-
-  get type() {
-    return this._proxyType;
-  }
-  get address() {
-    return this._proxyAddress;
-  }
-  get port() {
-    return this._proxyPort;
-  }
-  get username() {
-    return this._proxyUsername;
-  }
-  get password() {
-    return this._proxyPassword;
-  }
-  get proxyURI() {
-    switch (this._proxyType) {
-      case TorProxyType.SOCKS4:
-        return `socks4a://${this._proxyAddress}:${this._proxyPort}`;
-      case TorProxyType.SOCKS5:
-        if (this._proxyUsername) {
-          return `socks5://${this._proxyUsername}:${this._proxyPassword}@${
-            this._proxyAddress
-          }:${this._proxyPort}`;
-        }
-        return `socks5://${this._proxyAddress}:${this._proxyPort}`;
-      case TorProxyType.HTTPS:
-        if (this._proxyUsername) {
-          return `http://${this._proxyUsername}:${this._proxyPassword}@${
-            this._proxyAddress
-          }:${this._proxyPort}`;
-        }
-        return `http://${this._proxyAddress}:${this._proxyPort}`;
-    }
-    return undefined;
-  }
-
-  // attempts to read proxy settings from Tor daemon
-  readSettings() {
-    // SOCKS4
-    {
-      let addressPort = TorProtocolService.readStringSetting(
-        TorStrings.configKeys.socks4Proxy
-      );
-      if (addressPort) {
-        // address+port
-        let [proxyAddress, proxyPort] = parseAddrPort(addressPort);
-
-        this._proxyType = TorProxyType.SOCKS4;
-        this._proxyAddress = proxyAddress;
-        this._proxyPort = proxyPort;
-        this._proxyUsername = "";
-        this._proxyPassword = "";
-
-        return;
-      }
-    }
-
-    // SOCKS5
-    {
-      let addressPort = TorProtocolService.readStringSetting(
-        TorStrings.configKeys.socks5Proxy
-      );
-
-      if (addressPort) {
-        // address+port
-        let [proxyAddress, proxyPort] = parseAddrPort(addressPort);
-        // username
-        let proxyUsername = TorProtocolService.readStringSetting(
-          TorStrings.configKeys.socks5ProxyUsername
-        );
-        // password
-        let proxyPassword = TorProtocolService.readStringSetting(
-          TorStrings.configKeys.socks5ProxyPassword
-        );
-
-        this._proxyType = TorProxyType.SOCKS5;
-        this._proxyAddress = proxyAddress;
-        this._proxyPort = proxyPort;
-        this._proxyUsername = proxyUsername;
-        this._proxyPassword = proxyPassword;
-
-        return;
-      }
-    }
-
-    // HTTP
-    {
-      let addressPort = TorProtocolService.readStringSetting(
-        TorStrings.configKeys.httpsProxy
-      );
-
-      if (addressPort) {
-        // address+port
-        let [proxyAddress, proxyPort] = parseAddrPort(addressPort);
-
-        // username:password
-        let proxyAuthenticator = TorProtocolService.readStringSetting(
-          TorStrings.configKeys.httpsProxyAuthenticator
-        );
-
-        let [proxyUsername, proxyPassword] = ["", ""];
-        if (proxyAuthenticator) {
-          [proxyUsername, proxyPassword] = parseUsernamePassword(
-            proxyAuthenticator
-          );
-        }
-
-        this._proxyType = TorProxyType.HTTPS;
-        this._proxyAddress = proxyAddress;
-        this._proxyPort = proxyPort;
-        this._proxyUsername = proxyUsername;
-        this._proxyPassword = proxyPassword;
-      }
-    }
-    // no proxy settings
-  } /* TorProxySettings::ReadFromTor() */
-
-  // attempts to write proxy settings to Tor daemon
-  // throws on error
-  writeSettings() {
-    let settingsObject = new Map();
-
-    // init proxy related settings to null so Tor daemon resets them
-    settingsObject.set(TorStrings.configKeys.socks4Proxy, null);
-    settingsObject.set(TorStrings.configKeys.socks5Proxy, null);
-    settingsObject.set(TorStrings.configKeys.socks5ProxyUsername, null);
-    settingsObject.set(TorStrings.configKeys.socks5ProxyPassword, null);
-    settingsObject.set(TorStrings.configKeys.httpsProxy, null);
-    settingsObject.set(TorStrings.configKeys.httpsProxyAuthenticator, null);
-
-    switch (this._proxyType) {
-      case TorProxyType.SOCKS4:
-        settingsObject.set(
-          TorStrings.configKeys.socks4Proxy,
-          `${this._proxyAddress}:${this._proxyPort}`
-        );
-        break;
-      case TorProxyType.SOCKS5:
-        settingsObject.set(
-          TorStrings.configKeys.socks5Proxy,
-          `${this._proxyAddress}:${this._proxyPort}`
-        );
-        settingsObject.set(
-          TorStrings.configKeys.socks5ProxyUsername,
-          this._proxyUsername
-        );
-        settingsObject.set(
-          TorStrings.configKeys.socks5ProxyPassword,
-          this._proxyPassword
-        );
-        break;
-      case TorProxyType.HTTPS:
-        settingsObject.set(
-          TorStrings.configKeys.httpsProxy,
-          `${this._proxyAddress}:${this._proxyPort}`
-        );
-        settingsObject.set(
-          TorStrings.configKeys.httpsProxyAuthenticator,
-          `${this._proxyUsername}:${this._proxyPassword}`
-        );
-        break;
-    }
-
-    TorProtocolService.writeSettings(settingsObject);
-  } /* TorProxySettings::WriteToTor() */
-}
-
-// factory methods for our various supported proxies
-function makeTorProxySettingsNone() {
-  return new TorProxySettings();
-}
-
-function makeTorProxySettingsSocks4(aProxyAddress, aProxyPort) {
-  let retval = new TorProxySettings();
-  retval._proxyType = TorProxyType.SOCKS4;
-  retval._proxyAddress = aProxyAddress;
-  retval._proxyPort = aProxyPort;
-  return retval;
-}
-
-function makeTorProxySettingsSocks5(
-  aProxyAddress,
-  aProxyPort,
-  aProxyUsername,
-  aProxyPassword
-) {
-  let retval = new TorProxySettings();
-  retval._proxyType = TorProxyType.SOCKS5;
-  retval._proxyAddress = aProxyAddress;
-  retval._proxyPort = aProxyPort;
-  retval._proxyUsername = aProxyUsername;
-  retval._proxyPassword = aProxyPassword;
-  return retval;
-}
-
-function makeTorProxySettingsHTTPS(
-  aProxyAddress,
-  aProxyPort,
-  aProxyUsername,
-  aProxyPassword
-) {
-  let retval = new TorProxySettings();
-  retval._proxyType = TorProxyType.HTTPS;
-  retval._proxyAddress = aProxyAddress;
-  retval._proxyPort = aProxyPort;
-  retval._proxyUsername = aProxyUsername;
-  retval._proxyPassword = aProxyPassword;
-  return retval;
-}
diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn
index 857bc9ee3eac..552c92b2feff 100644
--- a/browser/components/torpreferences/jar.mn
+++ b/browser/components/torpreferences/jar.mn
@@ -1,14 +1,10 @@
 browser.jar:
-    content/browser/torpreferences/parseFunctions.jsm                (content/parseFunctions.jsm)
-    content/browser/torpreferences/requestBridgeDialog.xhtml           (content/requestBridgeDialog.xhtml)
+    content/browser/torpreferences/requestBridgeDialog.xhtml         (content/requestBridgeDialog.xhtml)
     content/browser/torpreferences/requestBridgeDialog.jsm           (content/requestBridgeDialog.jsm)
-    content/browser/torpreferences/torBridgeSettings.jsm             (content/torBridgeSettings.jsm)
-    content/browser/torpreferences/torCategory.inc.xhtml               (content/torCategory.inc.xhtml)
-    content/browser/torpreferences/torFirewallSettings.jsm           (content/torFirewallSettings.jsm)
+    content/browser/torpreferences/torCategory.inc.xhtml             (content/torCategory.inc.xhtml)
     content/browser/torpreferences/torLogDialog.jsm                  (content/torLogDialog.jsm)
-    content/browser/torpreferences/torLogDialog.xhtml                  (content/torLogDialog.xhtml)
+    content/browser/torpreferences/torLogDialog.xhtml                (content/torLogDialog.xhtml)
     content/browser/torpreferences/torPane.js                        (content/torPane.js)
-    content/browser/torpreferences/torPane.xhtml                       (content/torPane.xhtml)
+    content/browser/torpreferences/torPane.xhtml                     (content/torPane.xhtml)
     content/browser/torpreferences/torPreferences.css                (content/torPreferences.css)
     content/browser/torpreferences/torPreferencesIcon.svg            (content/torPreferencesIcon.svg)
-    content/browser/torpreferences/torProxySettings.jsm              (content/torProxySettings.jsm)
diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm
index 19a3f595d490..e7b8b364db86 100644
--- a/browser/modules/TorConnect.jsm
+++ b/browser/modules/TorConnect.jsm
@@ -18,6 +18,10 @@ const { TorLauncherUtil } = ChromeUtils.import(
     "resource://torlauncher/modules/tl-util.jsm"
 );
 
+const { TorSettings, TorSettingsTopics } = ChromeUtils.import(
+    "resource:///modules/TorSettings.jsm"
+);
+
 /* Browser observer topis */
 const BrowserTopics = Object.freeze({
     ProfileAfterChange: "profile-after-change",
@@ -25,7 +29,6 @@ const BrowserTopics = Object.freeze({
 
 /* tor-launcher observer topics */
 const TorTopics = Object.freeze({
-    ProcessIsReady: "TorProcessIsReady",
     BootstrapStatus: "TorBootstrapStatus",
     BootstrapError: "TorBootstrapError",
     ProcessExited: "TorProcessExited",
@@ -34,7 +37,6 @@ const TorTopics = Object.freeze({
 
 /* Relevant prefs used by tor-launcher */
 const TorLauncherPrefs = Object.freeze({
-  quickstart: "extensions.torlauncher.quickstart",
   prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
 });
 
@@ -250,38 +252,29 @@ const TorConnect = (() => {
                     // Disabled
                     this.legacyOrSystemTor();
                 } else {
-                    // register the Tor topics we always care about
-                    for (const topicKey in TorTopics) {
-                        const topic = TorTopics[topicKey];
+                    let observeTopic = (topic) => {
                         Services.obs.addObserver(this, topic);
                         console.log(`TorConnect: observing topic '${topic}'`);
-                    }
+                    };
 
-                    if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) {
-                        if (this.shouldQuickStart) {
-                            // Quickstart
-                            this.beginBootstrap();
-                        } else {
-                            // Configuring
-                            this.beginConfigure();
-                        }
+                   // register the Tor topics we always care about
+                    for (const topicKey in TorTopics) {
+                        const topic = TorTopics[topicKey];
+                        observeTopic(topic);
                     }
+                    observeTopic(TorSettingsTopics.Ready);
                 }
-
                 Services.obs.removeObserver(this, topic);
                 break;
             }
-            /* Transition out of Initial if Tor daemon wasn't running yet in BrowserTopics.ProfileAfterChange */
-            case TorTopics.ProcessIsReady: {
-                if (this.state === TorConnectState.Initial)
-                {
-                    if (this.shouldQuickStart) {
-                        // Quickstart
-                        this.beginBootstrap();
-                    } else {
-                        // Configuring
-                        this.beginConfigure();
-                    }
+            /* We need to wait until TorSettings have been loaded and applied before we can Quickstart */
+            case TorSettingsTopics.Ready: {
+                if (this.shouldQuickStart) {
+                    // Quickstart
+                    this.beginBootstrap();
+                } else {
+                    // Configuring
+                    this.beginConfigure();
                 }
                 break;
             }
@@ -342,7 +335,7 @@ const TorConnect = (() => {
 
         get shouldQuickStart() {
                    // quickstart must be enabled
-            return Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false) &&
+            return TorSettings.quickstart.enabled &&
                    // and the previous bootstrap attempt must have succeeded
                    !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true);
         },
diff --git a/browser/modules/TorSettings.jsm b/browser/modules/TorSettings.jsm
new file mode 100644
index 000000000000..6d2a6c4a07cf
--- /dev/null
+++ b/browser/modules/TorSettings.jsm
@@ -0,0 +1,814 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorSettings", "TorSettingsTopics", "TorSettingsData", "TorBridgeSource", "TorBuiltinBridgeTypes", "TorProxyType"];
+
+const { Services } = ChromeUtils.import(
+    "resource://gre/modules/Services.jsm"
+);
+
+const { TorProtocolService, TorProcessStatus } = ChromeUtils.import(
+    "resource:///modules/TorProtocolService.jsm"
+);
+
+/* Browser observer topics */
+const BrowserTopics = Object.freeze({
+    ProfileAfterChange: "profile-after-change",
+});
+
+/* tor-launcher observer topics */
+const TorTopics = Object.freeze({
+    ProcessIsReady: "TorProcessIsReady",
+});
+
+/* TorSettings observer topics */
+const TorSettingsTopics = Object.freeze({
+    Ready: "torsettings:ready",
+    SettingChanged: "torsettings:setting-changed",
+});
+
+/* TorSettings observer data (for SettingChanged topic) */
+const TorSettingsData = Object.freeze({
+    QuickStartEnabled : "torsettings:quickstart_enabled",
+});
+
+/* Prefs used to store settings in TorBrowser prefs */
+const TorSettingsPrefs = Object.freeze({
+    /* bool: are we pulling tor settings from the preferences */
+    enabled: 'torbrowser.settings.enabled',
+    quickstart : {
+        /* bool: does tor connect automatically on launch */
+        enabled: 'torbrowser.settings.quickstart.enabled',
+    },
+    bridges : {
+        /* bool:  does tor use bridges */
+        enabled : 'torbrowser.settings.bridges.enabled',
+        /* int: -1=invalid|0=builtin|1=bridge_db|2=user_provided */
+        source : 'torbrowser.settings.bridges.source',
+        /* string: obfs4|meek_azure|snowflake|etc */
+        builtin_type : 'torbrowser.settings.bridges.builtin_type',
+        /* preference branch: each child branch should be a bridge string */
+        bridge_strings : 'torbrowser.settings.bridges.bridge_strings',
+    },
+    proxy : {
+        /* bool: does tor use a proxy */
+        enabled : 'torbrowser.settings.proxy.enabled',
+        /* -1=invalid|0=socks4,1=socks5,2=https */
+        type: 'torbrowser.settings.proxy.type',
+        /* string: proxy server address */
+        address: 'torbrowser.settings.proxy.address',
+        /* int: [1,65535], proxy port */
+        port: 'torbrowser.settings.proxy.port',
+        /* string: username */
+        username: 'torbrowser.settings.proxy.username',
+        /* string: password */
+        password: 'torbrowser.settings.proxy.password',
+    },
+    firewall : {
+        /* bool: does tor have a port allow list */
+        enabled: 'torbrowser.settings.firewall.enabled',
+        /* string: comma-delimitted list of port numbers */
+        allowed_ports: 'torbrowser.settings.firewall.allowed_ports',
+    },
+});
+
+/* Legacy tor-launcher prefs and pref branches*/
+const TorLauncherPrefs = Object.freeze({
+    quickstart: "extensions.torlauncher.quickstart",
+    default_bridge_type: "extensions.torlauncher.default_bridge_type",
+    default_bridge: "extensions.torlauncher.default_bridge.",
+    default_bridge_recommended_type: "extensions.torlauncher.default_bridge_recommended_type",
+    bridgedb_bridge: "extensions.torlauncher.bridgedb_bridge.",
+});
+
+/* Config Keys used to configure tor daemon */
+const TorConfigKeys = Object.freeze({
+    useBridges: "UseBridges",
+    bridgeList: "Bridge",
+    socks4Proxy: "Socks4Proxy",
+    socks5Proxy: "Socks5Proxy",
+    socks5ProxyUsername: "Socks5ProxyUsername",
+    socks5ProxyPassword: "Socks5ProxyPassword",
+    httpsProxy: "HTTPSProxy",
+    httpsProxyAuthenticator: "HTTPSProxyAuthenticator",
+    reachableAddresses: "ReachableAddresses",
+    clientTransportPlugin: "ClientTransportPlugin",
+});
+
+const TorBridgeSource = Object.freeze({
+    Invalid: -1,
+    BuiltIn: 0,
+    BridgeDB: 1,
+    UserProvided: 2,
+});
+
+const TorProxyType = Object.freeze({
+    Invalid: -1,
+    Socks4: 0,
+    Socks5: 1,
+    HTTPS: 2,
+});
+
+
+const TorBuiltinBridgeTypes = Object.freeze(
+    (() => {
+      let bridgeListBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge);
+      let bridgePrefs = bridgeListBranch.getChildList("");
+
+      // an unordered set for shoving bridge types into
+      let bridgeTypes = new Set();
+      // look for keys ending in ".N" and treat string before that as the bridge type
+      const pattern = /\.[0-9]+$/;
+      for (const key of bridgePrefs) {
+        const offset = key.search(pattern);
+        if (offset != -1) {
+          const bt = key.substring(0, offset);
+          bridgeTypes.add(bt);
+        }
+      }
+
+      // recommended bridge type goes first in the list
+      let recommendedBridgeType = Services.prefs.getCharPref(TorLauncherPrefs.default_bridge_recommended_type, null);
+
+      let retval = [];
+      if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) {
+        retval.push(recommendedBridgeType);
+      }
+
+      for (const bridgeType of bridgeTypes.values()) {
+        if (bridgeType != recommendedBridgeType) {
+          retval.push(bridgeType);
+        }
+      }
+      return retval;
+  })()
+);
+
+/* Parsing Methods */
+
+// expects a string representation of an integer from 1 to 65535
+let parsePort = function(aPort) {
+  // ensure port string is a valid positive integer
+  const validIntRegex = /^[0-9]+$/;
+  if (!validIntRegex.test(aPort)) {
+    return 0;
+  }
+
+  // ensure port value is on valid range
+  let port = Number.parseInt(aPort);
+  if (port < 1 || port > 65535) {
+    return 0;
+  }
+
+  return port;
+};
+// expects a string in the format: "ADDRESS:PORT"
+let parseAddrPort = function(aAddrColonPort) {
+  let tokens = aAddrColonPort.split(":");
+  if (tokens.length != 2) {
+    return ["", 0];
+  }
+  let address = tokens[0];
+  let port = parsePort(tokens[1]);
+  return [address, port];
+};
+
+// expects a string in the format: "USERNAME:PASSWORD"
+// split on the first colon and any subsequent go into password
+let parseUsernamePassword = function(aUsernameColonPassword) {
+  let colonIndex = aUsernameColonPassword.indexOf(":");
+  if (colonIndex < 0) {
+    return ["", ""];
+  }
+
+  let username = aUsernameColonPassword.substring(0, colonIndex);
+  let password = aUsernameColonPassword.substring(colonIndex + 1);
+
+  return [username, password];
+};
+
+// expects a string in the format: ADDRESS:PORT,ADDRESS:PORT,...
+// returns array of ports (as ints)
+let parseAddrPortList = function(aAddrPortList) {
+  let addrPorts = aAddrPortList.split(",");
+  // parse ADDRESS:PORT string and only keep the port (second element in returned array)
+  let retval = addrPorts.map(addrPort => parseAddrPort(addrPort)[1]);
+  return retval;
+};
+
+// expects a '/n' or '/r/n' delimited bridge string, which we split and trim
+// each bridge string can also optionally have 'bridge' at the beginning ie:
+// bridge $(type) $(address):$(port) $(certificate)
+// we strip out the 'bridge' prefix here
+let parseBridgeStrings = function(aBridgeStrings) {
+
+  // replace carriage returns ('\r') with new lines ('\n')
+  aBridgeStrings = aBridgeStrings.replace(/\r/g, "\n");
+  // then replace contiguous new lines ('\n') with a single one
+  aBridgeStrings = aBridgeStrings.replace(/[\n]+/g, "\n");
+
+  // split on the newline and for each bridge string: trim, remove starting 'bridge' string
+  // finally discard entries that are empty strings; empty strings could occur if we receive
+  // a new line containing only whitespace
+  let splitStrings = aBridgeStrings.split("\n");
+  return splitStrings.map(val => val.trim().replace(/^bridge\s+/i, ""))
+                     .filter(bridgeString => bridgeString != "");
+};
+
+// expecting a ',' delimited list of ints with possible white space between
+// returns an array of ints
+let parsePortList = function(aPortListString) {
+  let splitStrings = aPortListString.split(",");
+  // parse and remove duplicates
+  let portSet = new Set(splitStrings.map(val => parsePort(val.trim())));
+  // parsePort returns 0 for failed parses, so remove 0 from list
+  portSet.delete(0);
+  return Array.from(portSet);
+};
+
+let getBuiltinBridgeStrings = function(builtinType) {
+    let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge);
+    let bridgeBranchPrefs = bridgeBranch.getChildList("");
+    let retval = [];
+
+    // regex matches against strings ending in ".N" where N is a positive integer
+    let pattern = /\.[0-9]+$/;
+    for (const key of bridgeBranchPrefs) {
+      // verify the location of the match is the correct offset required for aBridgeType
+      // to fit, and that the string begins with aBridgeType
+      if (key.search(pattern) == builtinType.length &&
+          key.startsWith(builtinType)) {
+        let bridgeStr = bridgeBranch.getCharPref(key);
+        retval.push(bridgeStr);
+      }
+    }
+
+    // shuffle so that Tor Browser users don't all try the built-in bridges in the same order
+    arrayShuffle(retval);
+
+    return retval;
+};
+
+/* Array methods */
+
+let arrayShuffle = function(array) {
+    // fisher-yates shuffle
+    for (let i = array.length - 1; i > 0; --i) {
+      // number n such that 0.0 <= n < 1.0
+      const n = Math.random();
+      // integer j such that 0 <= j <= i
+      const j = Math.floor(n * (i + 1));
+
+      // swap values at indices i and j
+      const tmp = array[i];
+      array[i] = array[j];
+      array[j] = tmp;
+    }
+}
+
+let arrayCopy = function(array) {
+    return [].concat(array);
+}
+
+/* TorSettings module */
+
+const TorSettings = (() => {
+    let self = {
+        _settings: null,
+
+        // tor daemon related settings
+        defaultSettings: function() {
+            let settings = {
+                quickstart: {
+                    enabled: false
+                },
+                bridges : {
+                    enabled: false,
+                    source: TorBridgeSource.Invalid,
+                    builtin_type: null,
+                    bridge_strings: [],
+                },
+                proxy: {
+                    enabled: false,
+                    type: TorProxyType.Invalid,
+                    address: null,
+                    port: 0,
+                    username: null,
+                    password: null,
+                },
+                firewall: {
+                    enabled: false,
+                    allowed_ports: [],
+                },
+            };
+            return settings;
+        },
+
+        /* try and load our settings, and register observers */
+        init: function() {
+            if (TorProtocolService.ownsTorDaemon) {
+                // if the settings branch exists, load settings from prefs
+                if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
+                    this.loadFromPrefs();
+                    Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
+                }
+                Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
+                Services.obs.addObserver(this, TorTopics.ProcessIsReady);
+            }
+        },
+
+        /* wait for relevant life-cycle events to load and/or apply saved settings */
+        observe: async function(subject, topic, data) {
+            console.log(`TorSettings: observed ${topic}`);
+
+            // once the process is ready, we need to apply our settings
+            let handleProcessReady = async () => {
+                Services.obs.removeObserver(this, TorTopics.ProcessIsReady);
+                if (this._settings == null) {
+                    // load settings from tor if our load in init() failed and save them to prefs
+                    await this.loadLegacy();
+                    this.saveToPrefs();
+                } else {
+                    // push down settings to tor
+                    await this.applySettings();
+                }
+                Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
+            };
+
+            switch (topic) {
+                case BrowserTopics.ProfileAfterChange: {
+                    if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) {
+                        await handleProcessReady();
+                    }
+                }
+                break;
+                case TorTopics.ProcessIsReady: {
+                    await handleProcessReady();
+                }
+                break;
+            }
+        },
+
+        // load our settings from old locations (misc prefs and from tor daemon)
+        // TODO: remove this after some time has elapsed to ensure users have migrated to pref settings
+        loadLegacy: async function() {
+            console.log("TorSettings: loadLegacy()");
+
+            let settings = this.defaultSettings();
+
+            /* Quickstart */
+            settings.quickstart.enabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false);
+
+            /* Bridges
+
+            So the way tor-launcher determines the origin of the configured bridges is a bit
+            weird and depends on inferring our scenario based on some firefox prefs and the
+            relationship between the saved list of bridges in about:config vs the list saved in torrc
+
+            first off, if "extensions.torlauncher.default_bridge_type" is set to one of our
+            builtin default types (obfs4, meek-azure, snowflake, etc) then we provide the
+            bridges in "extensions.torlauncher.default_bridge.*" (filtered by our default_bridge_type)
+
+            next, we compare the list of bridges saved in torrc to the bridges stored in the
+            "extensions.torlauncher.bridgedb_bridge."" branch. If they match *exactly* then we assume
+            the bridges were retrieved from BridgeDB and use those. If the torrc list is empty then we know
+            we have no bridge settings
+
+            finally, if none of the previous conditions are not met, it is assumed the bridges stored in
+            torrc are user-provided
+            */
+
+            let builtinType = Services.prefs.getCharPref(TorLauncherPrefs.default_bridge_type, null);
+
+            // check if source is built-in
+            if (builtinType) {
+                let builtinBridgeStrings = getBuiltinBridgeStrings(builtinType);
+                if (builtinBridgeStrings.length > 0) {
+                    settings.bridges.enabled = true;
+                    settings.bridges.source = TorBridgeSource.BuiltIn;
+                    settings.bridges.builtin_type = builtinType;
+                    settings.bridges.bridge_strings = builtinBridgeStrings;
+                }
+            } else  {
+                // get our currently configured bridges from tor
+                let torrcBridgeStrings = await (async () => {
+                    let bridgeList = await TorProtocolService.readStringArraySetting(TorConfigKeys.bridgeList);
+                    let retval = [];
+                    for (const line of bridgeList) {
+                      let trimmedLine = line.trim();
+                      if (trimmedLine) {
+                        retval.push(trimmedLine);
+                      }
+                    }
+                    return retval;
+                })();
+
+                // torrc has bridges configured
+                if (torrcBridgeStrings.length > 0) {
+                    // compare tor's bridges to our saved bridgedb bridges
+                    let bridgedbBBridgeStrings = (() => {
+                        let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.bridgedb_bridge);
+                        let bridgeBranchPrefs = bridgeBranch.getChildList("");
+                        // the child prefs do not come in any particular order so sort the keys
+                        // so the values can be compared to what we get out off torrc
+                        bridgeBranchPrefs.sort();
+
+                        // just assume all of the prefs under the parent point to valid bridge string
+                        let retval = bridgeBranchPrefs.map(key =>
+                          bridgeBranch.getCharPref(key).trim()
+                        );
+                        return retval;
+                    })();
+
+                    let arraysEqual = (left, right) => {
+                        if (left.length != right.length) {
+                            return false;
+                        }
+                        const length = left.length;
+                        for (let i = 0; i < length; ++i) {
+                            if (left[i] != right[i]) {
+                                return false;
+                            }
+                        }
+                        return true;
+                    };
+
+                    if (arraysEqual(torrcBridgeStrings, bridgedbBBridgeStrings)) {
+                        settings.bridges.enabled = true;
+                        settings.bridges.source = TorBridgeSource.BridgeDB;
+                        settings.bridges.builtin_type = null;
+                        settings.bridges.bridge_strings = torrcBridgeStrings;
+                    } else {
+                        settings.bridges.enabled = true;
+                        settings.bridges.source = TorBridgeSource.UserProvided;
+                        settings.bridges.builtin_type = null;
+                        settings.bridges.bridge_strings = torrcBridgeStrings;
+                    }
+                } else {
+                    // tor has no bridge strings saved, so bridges not in use
+                    settings.bridges.enabled = false;
+                    settings.bridges.source = TorBridgeSource.Invalid;
+                    settings.bridges.builtin_type = null;
+                    settings.bridges.bridge_strings = [];
+                }
+            }
+
+            /* Proxy */
+
+            let proxyString = null;
+            if (proxyString = await TorProtocolService.readStringSetting(TorConfigKeys.socks4Proxy)) {
+                let [address, port] = parseAddrPort(proxyString);
+
+                settings.proxy.enabled = true;
+                settings.proxy.type = TorProxyType.Socks4;
+                settings.proxy.address = address;
+                settings.proxy.port = port;
+                settings.proxy.username = null;
+                settings.proxy.password = null;
+            } else if (proxyString = await TorProtocolService.readStringSetting(TorConfigKeys.socks5Proxy)) {
+                let [address, port] = parseAddrPort(proxyString);
+                let username = await TorProtocolService.readStringSetting(TorConfigKeys.socks5ProxyUsername);
+                let password = await TorProtocolService.readStringSetting(TorConfigKeys.socks5ProxyPassword);
+
+                settings.proxy.enabled = true;
+                settings.proxy.type = TorProxyType.Socks5;
+                settings.proxy.address = address;
+                settings.proxy.port = port;
+                settings.proxy.username = username;
+                settings.proxy.password = password;
+            } else if (proxyString = await TorProtocolService.readStringSetting(TorConfigKeys.httpsProxy)) {
+                let [address, port] = parseAddrPort(proxyString);
+                let authenticator = await TorProtocolService.readStringSetting(TorConfigKeys.httpsProxyAuthenticator);
+                let [username, password] = parseUsernamePassword(authenticator);
+
+                settings.proxy.enabled = true;
+                settings.proxy.type = TorProxyType.HTTPS;
+                settings.proxy.address = address;
+                settings.proxy.port = port;
+                settings.proxy.username = username;
+                settings.proxy.password = password;
+            } else {
+                settings.proxy.enabled = false;
+                settings.proxy.type = TorProxyType.Invalid;
+                settings.proxy.address = null;
+                settings.proxy.port = 0;
+                settings.proxy.username = null;
+                settings.proxy.password = null;
+            }
+
+            /* Firewall */
+            let firewallString = await TorProtocolService.readStringSetting(TorConfigKeys.reachableAddresses);
+            if (firewallString) {
+                let allowedPorts = parseAddrPortList(firewallString);
+                settings.firewall.enabled = allowedPorts.length > 0;
+                settings.firewall.allowed_ports = allowedPorts;
+            } else {
+                settings.firewall.enabled = false;
+                settings.firewall.allowed_ports = [];
+            }
+
+            this._settings = settings;
+
+            return this;
+        },
+
+        // load our settings from prefs
+        loadFromPrefs: function() {
+            console.log("TorSettings: loadFromPrefs()");
+
+            let settings = this.defaultSettings();
+
+            /* Quickstart */
+            settings.quickstart.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.quickstart.enabled);
+            /* Bridges */
+            settings.bridges.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.bridges.enabled);
+            if (settings.bridges.enabled) {
+                settings.bridges.source = Services.prefs.getIntPref(TorSettingsPrefs.bridges.source);
+                // builtin bridge (obfs4, meek, snowlfake, etc)
+                if (settings.bridges.source == TorBridgeSource.BuiltIn) {
+                    let builtinType = Services.prefs.getStringPref(TorSettingsPrefs.bridges.builtin_type);
+                    settings.bridges.builtin_type = builtinType;
+                    // always dynamically load builtin bridges rather than loading the cached versions
+                    // if the user upgrades and the builtin bridges have changed, we want to ensure the user
+                    // can still bootstrap using the provided bridges
+                    let bridgeStrings = getBuiltinBridgeStrings(builtinType);
+                    if (bridgeStrings.length > 0) {
+                        settings.bridges.bridge_strings = bridgeStrings;
+                    } else {
+                        // in this case the user is using a builtin bridge that is no longer supported,
+                        // reset to settings to default values
+                        settings.bridges.enabled = false;
+                        settings.bridges.source = TorBridgeSource.Invalid;
+                        settings.bridges.builtin_type = null;
+                    }
+                } else {
+                    settings.bridges.bridge_strings = [];
+                    let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList("");
+                    bridgeBranchPrefs.forEach(pref => {
+                        let bridgeString = Services.prefs.getStringPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`);
+                        settings.bridges.bridge_strings.push(bridgeString);
+                    });
+                }
+            } else {
+                settings.bridges.source = TorBridgeSource.Invalid;
+                settings.bridges.builtin_type = null;
+                settings.bridges.bridge_strings = [];
+            }
+            /* Proxy */
+            settings.proxy.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.proxy.enabled);
+            if (settings.proxy.enabled) {
+                settings.proxy.type = Services.prefs.getIntPref(TorSettingsPrefs.proxy.type);
+                settings.proxy.address = Services.prefs.getStringPref(TorSettingsPrefs.proxy.address);
+                settings.proxy.port = Services.prefs.getIntPref(TorSettingsPrefs.proxy.port);
+                settings.proxy.username = Services.prefs.getStringPref(TorSettingsPrefs.proxy.username);
+                settings.proxy.password = Services.prefs.getStringPref(TorSettingsPrefs.proxy.password);
+            } else {
+                settings.proxy.type = TorProxyType.Invalid;
+                settings.proxy.address = null;
+                settings.proxy.port = 0;
+                settings.proxy.username = null;
+                settings.proxy.password = null;
+            }
+
+            /* Firewall */
+            settings.firewall.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.firewall.enabled);
+            if(settings.firewall.enabled) {
+                let portList = Services.prefs.getStringPref(TorSettingsPrefs.firewall.allowed_ports);
+                settings.firewall.allowed_ports = parsePortList(portList);
+            } else {
+                settings.firewall.allowed_ports = 0;
+            }
+
+            this._settings = settings;
+
+            return this;
+        },
+
+        // save our settings to prefs
+        saveToPrefs: function() {
+            console.log("TorSettings: saveToPrefs()");
+
+            let settings = this._settings;
+
+            /* Quickstart */
+            Services.prefs.setBoolPref(TorSettingsPrefs.quickstart.enabled, settings.quickstart.enabled);
+            /* Bridges */
+            Services.prefs.setBoolPref(TorSettingsPrefs.bridges.enabled, settings.bridges.enabled);
+            if (settings.bridges.enabled) {
+                Services.prefs.setIntPref(TorSettingsPrefs.bridges.source, settings.bridges.source);
+                if (settings.bridges.source === TorBridgeSource.BuiltIn) {
+                    Services.prefs.setStringPref(TorSettingsPrefs.bridges.builtin_type, settings.bridges.builtin_type);
+                } else {
+                    Services.prefs.clearUserPref(TorSettingsPrefs.bridges.builtin_type);
+                }
+                // erase existing bridge strings
+                let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList("");
+                bridgeBranchPrefs.forEach(pref => {
+                    Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`);
+                });
+                // write new ones
+                settings.bridges.bridge_strings.forEach((string, index) => {
+                    Services.prefs.setStringPref(`${TorSettingsPrefs.bridges.bridge_strings}.${index}`, string);
+                });
+            } else {
+                Services.prefs.clearUserPref(TorSettingsPrefs.bridges.source);
+                Services.prefs.clearUserPref(TorSettingsPrefs.bridges.builtin_type);
+                let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList("");
+                bridgeBranchPrefs.forEach(pref => {
+                    Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`);
+                });
+            }
+            /* Proxy */
+            Services.prefs.setBoolPref(TorSettingsPrefs.proxy.enabled, settings.proxy.enabled);
+            if (settings.proxy.enabled) {
+                Services.prefs.setIntPref(TorSettingsPrefs.proxy.type, settings.proxy.type);
+                Services.prefs.setStringPref(TorSettingsPrefs.proxy.address, settings.proxy.address);
+                Services.prefs.setIntPref(TorSettingsPrefs.proxy.port, settings.proxy.port);
+                Services.prefs.setStringPref(TorSettingsPrefs.proxy.username, settings.proxy.username);
+                Services.prefs.setStringPref(TorSettingsPrefs.proxy.password, settings.proxy.password);
+            } else {
+                Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type);
+                Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address);
+                Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port);
+                Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username);
+                Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password);
+            }
+            /* Firewall */
+            Services.prefs.setBoolPref(TorSettingsPrefs.firewall.enabled, settings.firewall.enabled);
+            if (settings.firewall.enabled) {
+                Services.prefs.setStringPref(TorSettingsPrefs.firewall.allowed_ports, settings.firewall.allowed_ports.join(","));
+            } else {
+                Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports);
+            }
+
+            // all tor settings now stored in prefs :)
+            Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true);
+
+            return this;
+        },
+
+        // push our settings down to the tor daemon
+        applySettings: async function() {
+            console.log("TorSettings: applySettings()");
+            let settings = this._settings;
+            let settingsMap = new Map();
+
+            /* Bridges */
+            settingsMap.set(TorConfigKeys.useBridges, settings.bridges.enabled);
+            if (settings.bridges.enabled) {
+                settingsMap.set(TorConfigKeys.bridgeList, settings.bridges.bridge_strings);
+            } else {
+                // shuffle bridge list
+                settingsMap.set(TorConfigKeys.bridgeList, null);
+            }
+
+            /* Proxy */
+            settingsMap.set(TorConfigKeys.socks4Proxy, null);
+            settingsMap.set(TorConfigKeys.socks5Proxy, null);
+            settingsMap.set(TorConfigKeys.socks5ProxyUsername, null);
+            settingsMap.set(TorConfigKeys.socks5ProxyPassword, null);
+            settingsMap.set(TorConfigKeys.httpsProxy, null);
+            settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null);
+            if (settings.proxy.enabled) {
+                let address = settings.proxy.address;
+                let port = settings.proxy.port;
+                let username = settings.proxy.username;
+                let password = settings.proxy.password;
+
+                switch (settings.proxy.type) {
+                  case TorProxyType.Socks4:
+                    settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`);
+                    break;
+                  case TorProxyType.Socks5:
+                    settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`);
+                    settingsMap.set(TorConfigKeys.socks5ProxyUsername, username);
+                    settingsMap.set(TorConfigKeys.socks5ProxyPassword, password);
+                    break;
+                  case TorProxyType.HTTPS:
+                    settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`);
+                    settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, `${username}:${password}`);
+                    break;
+                }
+            }
+
+            /* Firewall */
+            if (settings.firewall.enabled) {
+                let reachableAddresses = settings.firewall.allowed_ports.map(port => `*:${port}`).join(",");
+                settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses);
+            } else {
+                settingsMap.set(TorConfigKeys.reachableAddresses, null);
+            }
+
+            /* Push to Tor */
+            await TorProtocolService.writeSettings(settingsMap);
+
+            return this;
+        },
+
+        /* Getters and Setters */
+
+
+        // Quickstart
+        get quickstart() {
+            return {
+                // Avoid a race-condition on first-start where this property
+                // may be accessed before `self._settings` is initialized.
+                // This work-around can be removed when #40598 is resolved.
+                get enabled() { return (self._settings ? self._settings.quickstart.enabled : false); },
+                set enabled(val) {
+                    if (val != self._settings.quickstart.enabled)
+                    {
+                        self._settings.quickstart.enabled = val;
+                        Services.obs.notifyObservers({value: val}, TorSettingsTopics.SettingChanged, TorSettingsData.QuickStartEnabled);
+                    }
+                },
+            };
+        },
+
+        // Bridges
+        get bridges() {
+            return {
+                get enabled() { return self._settings.bridges.enabled; },
+                set enabled(val) {
+                    self._settings.bridges.enabled = val;
+                    // reset bridge settings
+                    self._settings.bridges.source = TorBridgeSource.Invalid;
+                    self._settings.bridges.builtin_type = null;
+                    self._settings.bridges.bridge_strings = [];
+                },
+                get source() { return self._settings.bridges.source; },
+                set source(val) { self._settings.bridges.source = val; },
+                get builtin_type() { return self._settings.bridges.builtin_type; },
+                set builtin_type(val) {
+                    let bridgeStrings = getBuiltinBridgeStrings(val);
+                    if (bridgeStrings.length > 0) {
+                        self._settings.bridges.builtin_type = val;
+                        self._settings.bridges.bridge_strings = bridgeStrings;
+                    }
+                },
+                get bridge_strings() { return arrayCopy(self._settings.bridges.bridge_strings); },
+                set bridge_strings(val) {
+                    self._settings.bridges.bridge_strings = parseBridgeStrings(val);
+                },
+            };
+        },
+
+        // Proxy
+        get proxy() {
+            return {
+                get enabled() { return self._settings.proxy.enabled; },
+                set enabled(val) {
+                    self._settings.proxy.enabled = val;
+                    // reset proxy settings
+                    self._settings.proxy.type = TorProxyType.Invalid;
+                    self._settings.proxy.address = null;
+                    self._settings.proxy.port = 0;
+                    self._settings.proxy.username = null;
+                    self._settings.proxy.password = null;
+                },
+                get type() { return self._settings.proxy.type; },
+                set type(val) { self._settings.proxy.type = val; },
+                get address() { return self._settings.proxy.address; },
+                set address(val) { self._settings.proxy.address = val; },
+                get port() { return arrayCopy(self._settings.proxy.port); },
+                set port(val) { self._settings.proxy.port = parsePort(val); },
+                get username() { return self._settings.proxy.username; },
+                set username(val) { self._settings.proxy.username = val; },
+                get password() { return self._settings.proxy.password; },
+                set password(val) { self._settings.proxy.password = val; },
+                get uri() {
+                    switch (this.type) {
+                      case TorProxyType.Socks4:
+                        return `socks4a://${this.address}:${this.port}`;
+                      case TorProxyType.Socks5:
+                        if (this.username) {
+                          return `socks5://${this.username}:${this.password}@${this.address}:${this.port}`;
+                        }
+                        return `socks5://${this.address}:${this.port}`;
+                      case TorProxyType.HTTPS:
+                        if (this._proxyUsername) {
+                          return `http://${this.username}:${this.password}@${this.address}:${this.port}`;
+                        }
+                        return `http://${this.address}:${this.port}`;
+                    }
+                    return null;
+                },
+            };
+        },
+
+        // Firewall
+        get firewall() {
+            return {
+                get enabled() { return self._settings.firewall.enabled; },
+                set enabled(val) {
+                    self._settings.firewall.enabled = val;
+                    // reset firewall settings
+                    self._settings.firewall.allowed_ports = [];
+                },
+                get allowed_ports() { return self._settings.firewall.allowed_ports; },
+                set allowed_ports(val) { self._settings.firewall.allowed_ports = parsePortList(val); },
+            };
+        },
+    };
+    self.init();
+    return self;
+})();
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
index 1ea57aba1a93..8c001d4ac6ed 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -156,6 +156,7 @@ EXTRA_JS_MODULES += [
     'TorConnect.jsm',
     'TorProcessService.jsm',
     "TorProtocolService.jsm",
+    "TorSettings.jsm",
     "TorStrings.jsm",
     "TransientPrefs.jsm",
     "webrtcUI.jsm",
diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm
index 62afa98e1ffc..ba8cd0f3f97d 100644
--- a/toolkit/components/processsingleton/MainProcessSingleton.jsm
+++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm
@@ -24,6 +24,11 @@ MainProcessSingleton.prototype = {
           null
         );
 
+        ChromeUtils.import(
+          "resource:///modules/TorSettings.jsm",
+          null
+        );
+
         ChromeUtils.import(
           "resource:///modules/TorConnect.jsm",
           null
diff --git a/toolkit/modules/AsyncPrefs.jsm b/toolkit/modules/AsyncPrefs.jsm
index f7d867e47dc0..2834c484c919 100644
--- a/toolkit/modules/AsyncPrefs.jsm
+++ b/toolkit/modules/AsyncPrefs.jsm
@@ -20,7 +20,6 @@ const kAllowedPrefs = new Set([
 
   "browser.contentblocking.report.hide_vpn_banner",
   "browser.contentblocking.report.show_mobile_app",
-  "extensions.torlauncher.quickstart",
 
   "narrate.rate",
   "narrate.voice",





More information about the tbb-commits mailing list