| ... | ... | @@ -10,15 +10,21 @@ ChromeUtils.defineESModuleGetters(lazy, { | 
| 10 | 10 |    TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
 | 
| 11 | 11 |  });
 | 
| 12 | 12 |  
 | 
|  | 13 | +ChromeUtils.defineLazyGetter(lazy, "logger", () => {
 | 
|  | 14 | +  let { ConsoleAPI } = ChromeUtils.importESModule(
 | 
|  | 15 | +    "resource://gre/modules/Console.sys.mjs"
 | 
|  | 16 | +  );
 | 
|  | 17 | +  return new ConsoleAPI({
 | 
|  | 18 | +    maxLogLevel: "warn",
 | 
|  | 19 | +    maxLogLevelPref: "browser.torsettings.log_level",
 | 
|  | 20 | +    prefix: "TorSettings",
 | 
|  | 21 | +  });
 | 
|  | 22 | +});
 | 
|  | 23 | +
 | 
| 13 | 24 |  /* TorSettings observer topics */
 | 
| 14 | 25 |  export const TorSettingsTopics = Object.freeze({
 | 
| 15 | 26 |    Ready: "torsettings:ready",
 | 
| 16 |  | -  SettingChanged: "torsettings:setting-changed",
 | 
| 17 |  | -});
 | 
| 18 |  | -
 | 
| 19 |  | -/* TorSettings observer data (for SettingChanged topic) */
 | 
| 20 |  | -export const TorSettingsData = Object.freeze({
 | 
| 21 |  | -  QuickStartEnabled: "torsettings:quickstart_enabled",
 | 
|  | 27 | +  SettingsChanged: "torsettings:settings-changed",
 | 
| 22 | 28 |  });
 | 
| 23 | 29 |  
 | 
| 24 | 30 |  /* Prefs used to store settings in TorBrowser prefs */
 | 
| ... | ... | @@ -32,7 +38,7 @@ const TorSettingsPrefs = Object.freeze({ | 
| 32 | 38 |    bridges: {
 | 
| 33 | 39 |      /* bool:  does tor use bridges */
 | 
| 34 | 40 |      enabled: "torbrowser.settings.bridges.enabled",
 | 
| 35 |  | -    /* int: -1=invalid|0=builtin|1=bridge_db|2=user_provided */
 | 
|  | 41 | +    /* int: See TorBridgeSource */
 | 
| 36 | 42 |      source: "torbrowser.settings.bridges.source",
 | 
| 37 | 43 |      /* string: obfs4|meek_azure|snowflake|etc */
 | 
| 38 | 44 |      builtin_type: "torbrowser.settings.bridges.builtin_type",
 | 
| ... | ... | @@ -42,7 +48,7 @@ const TorSettingsPrefs = Object.freeze({ | 
| 42 | 48 |    proxy: {
 | 
| 43 | 49 |      /* bool: does tor use a proxy */
 | 
| 44 | 50 |      enabled: "torbrowser.settings.proxy.enabled",
 | 
| 45 |  | -    /* -1=invalid|0=socks4,1=socks5,2=https */
 | 
|  | 51 | +    /* See TorProxyType */
 | 
| 46 | 52 |      type: "torbrowser.settings.proxy.type",
 | 
| 47 | 53 |      /* string: proxy server address */
 | 
| 48 | 54 |      address: "torbrowser.settings.proxy.address",
 | 
| ... | ... | @@ -140,23 +146,6 @@ export const TorBuiltinBridgeTypes = Object.freeze( | 
| 140 | 146 |  
 | 
| 141 | 147 |  /* Parsing Methods */
 | 
| 142 | 148 |  
 | 
| 143 |  | -// expects a string representation of an integer from 1 to 65535
 | 
| 144 |  | -const parsePort = function (aPort) {
 | 
| 145 |  | -  // ensure port string is a valid positive integer
 | 
| 146 |  | -  const validIntRegex = /^[0-9]+$/;
 | 
| 147 |  | -  if (!validIntRegex.test(aPort)) {
 | 
| 148 |  | -    return 0;
 | 
| 149 |  | -  }
 | 
| 150 |  | -
 | 
| 151 |  | -  // ensure port value is on valid range
 | 
| 152 |  | -  const port = Number.parseInt(aPort);
 | 
| 153 |  | -  if (port < 1 || port > 65535) {
 | 
| 154 |  | -    return 0;
 | 
| 155 |  | -  }
 | 
| 156 |  | -
 | 
| 157 |  | -  return port;
 | 
| 158 |  | -};
 | 
| 159 |  | -
 | 
| 160 | 149 |  // expects a '\n' or '\r\n' delimited bridge string, which we split and trim
 | 
| 161 | 150 |  // each bridge string can also optionally have 'bridge' at the beginning ie:
 | 
| 162 | 151 |  // bridge $(type) $(address):$(port) $(certificate)
 | 
| ... | ... | @@ -176,17 +165,6 @@ const parseBridgeStrings = function (aBridgeStrings) { | 
| 176 | 165 |      .filter(bridgeString => bridgeString != "");
 | 
| 177 | 166 |  };
 | 
| 178 | 167 |  
 | 
| 179 |  | -// expecting a ',' delimited list of ints with possible white space between
 | 
| 180 |  | -// returns an array of ints
 | 
| 181 |  | -const parsePortList = function (aPortListString) {
 | 
| 182 |  | -  const splitStrings = aPortListString.split(",");
 | 
| 183 |  | -  // parse and remove duplicates
 | 
| 184 |  | -  const portSet = new Set(splitStrings.map(val => parsePort(val.trim())));
 | 
| 185 |  | -  // parsePort returns 0 for failed parses, so remove 0 from list
 | 
| 186 |  | -  portSet.delete(0);
 | 
| 187 |  | -  return Array.from(portSet);
 | 
| 188 |  | -};
 | 
| 189 |  | -
 | 
| 190 | 168 |  const getBuiltinBridgeStrings = function (builtinType) {
 | 
| 191 | 169 |    if (!builtinType) {
 | 
| 192 | 170 |      return [];
 | 
| ... | ... | @@ -235,558 +213,679 @@ const arrayShuffle = function (array) { | 
| 235 | 213 |    }
 | 
| 236 | 214 |  };
 | 
| 237 | 215 |  
 | 
| 238 |  | -const arrayCopy = function (array) {
 | 
| 239 |  | -  return [].concat(array);
 | 
| 240 |  | -};
 | 
| 241 |  | -
 | 
| 242 | 216 |  /* TorSettings module */
 | 
| 243 | 217 |  
 | 
| 244 |  | -export const TorSettings = (() => {
 | 
| 245 |  | -  const self = {
 | 
| 246 |  | -    _settings: null,
 | 
|  | 218 | +export const TorSettings = {
 | 
|  | 219 | +  /**
 | 
|  | 220 | +   * The underlying settings values.
 | 
|  | 221 | +   *
 | 
|  | 222 | +   * @type {object}
 | 
|  | 223 | +   */
 | 
|  | 224 | +  _settings: {
 | 
|  | 225 | +    quickstart: {
 | 
|  | 226 | +      enabled: false,
 | 
|  | 227 | +    },
 | 
|  | 228 | +    bridges: {
 | 
|  | 229 | +      enabled: false,
 | 
|  | 230 | +      source: TorBridgeSource.Invalid,
 | 
|  | 231 | +      builtin_type: "",
 | 
|  | 232 | +      bridge_strings: [],
 | 
|  | 233 | +    },
 | 
|  | 234 | +    proxy: {
 | 
|  | 235 | +      enabled: false,
 | 
|  | 236 | +      type: TorProxyType.Invalid,
 | 
|  | 237 | +      address: "",
 | 
|  | 238 | +      port: 0,
 | 
|  | 239 | +      username: "",
 | 
|  | 240 | +      password: "",
 | 
|  | 241 | +    },
 | 
|  | 242 | +    firewall: {
 | 
|  | 243 | +      enabled: false,
 | 
|  | 244 | +      allowed_ports: [],
 | 
|  | 245 | +    },
 | 
|  | 246 | +  },
 | 
|  | 247 | +
 | 
|  | 248 | +  /**
 | 
|  | 249 | +   * The current number of freezes applied to the notifications.
 | 
|  | 250 | +   *
 | 
|  | 251 | +   * @type {integer}
 | 
|  | 252 | +   */
 | 
|  | 253 | +  _freezeNotificationsCount: 0,
 | 
|  | 254 | +  /**
 | 
|  | 255 | +   * The queue for settings that have changed. To be broadcast in the
 | 
|  | 256 | +   * notification when not frozen.
 | 
|  | 257 | +   *
 | 
|  | 258 | +   * @type {Set<string>}
 | 
|  | 259 | +   */
 | 
|  | 260 | +  _notificationQueue: new Set(),
 | 
|  | 261 | +  /**
 | 
|  | 262 | +   * Send a notification if we have any queued and we are not frozen.
 | 
|  | 263 | +   */
 | 
|  | 264 | +  _tryNotification() {
 | 
|  | 265 | +    if (this._freezeNotificationsCount || !this._notificationQueue.size) {
 | 
|  | 266 | +      return;
 | 
|  | 267 | +    }
 | 
|  | 268 | +    Services.obs.notifyObservers(
 | 
|  | 269 | +      { changes: [...this._notificationQueue] },
 | 
|  | 270 | +      TorSettingsTopics.SettingsChanged
 | 
|  | 271 | +    );
 | 
|  | 272 | +    this._notificationQueue.clear();
 | 
|  | 273 | +  },
 | 
|  | 274 | +  /**
 | 
|  | 275 | +   * Pause notifications for changes in setting values. This is useful if you
 | 
|  | 276 | +   * need to make batch changes to settings.
 | 
|  | 277 | +   *
 | 
|  | 278 | +   * This should always be paired with a call to thawNotifications once
 | 
|  | 279 | +   * notifications should be released. Usually you should wrap whatever
 | 
|  | 280 | +   * changes you make with a `try` block and call thawNotifications in the
 | 
|  | 281 | +   * `finally` block.
 | 
|  | 282 | +   */
 | 
|  | 283 | +  freezeNotifications() {
 | 
|  | 284 | +    this._freezeNotificationsCount++;
 | 
|  | 285 | +  },
 | 
|  | 286 | +  /**
 | 
|  | 287 | +   * Release the hold on notifications so they may be sent out.
 | 
|  | 288 | +   *
 | 
|  | 289 | +   * Note, if some other method has also frozen the notifications, this will
 | 
|  | 290 | +   * only release them once it has also called this method.
 | 
|  | 291 | +   */
 | 
|  | 292 | +  thawNotifications() {
 | 
|  | 293 | +    this._freezeNotificationsCount--;
 | 
|  | 294 | +    this._tryNotification();
 | 
|  | 295 | +  },
 | 
|  | 296 | +  /**
 | 
|  | 297 | +   * @typedef {object} TorSettingProperty
 | 
|  | 298 | +   *
 | 
|  | 299 | +   * @property {function} [getter] - A getter for the property. If this is
 | 
|  | 300 | +   *   given, the property cannot be set.
 | 
|  | 301 | +   * @property {function} [transform] - Called in the setter for the property,
 | 
|  | 302 | +   *   with the new value given. Should transform the given value into the
 | 
|  | 303 | +   *   right type.
 | 
|  | 304 | +   * @property {function} [equal] - Test whether two values for the property
 | 
|  | 305 | +   *   are considered equal. Otherwise uses `===`.
 | 
|  | 306 | +   * @property {function} [callback] - Called whenever the property value
 | 
|  | 307 | +   *   changes, with the new value given. Should be used to trigger any other
 | 
|  | 308 | +   *   required changes for the new value.
 | 
|  | 309 | +   * @property {function} [copy] - Called whenever the property is read, with
 | 
|  | 310 | +   *   the stored value given. Should return a copy of the value. Otherwise
 | 
|  | 311 | +   *   returns the stored value.
 | 
|  | 312 | +   */
 | 
|  | 313 | +  /**
 | 
|  | 314 | +   * Add properties to the TorSettings instance, to be read or set.
 | 
|  | 315 | +   *
 | 
|  | 316 | +   * @param {string} groupname - The name of the setting group. The given
 | 
|  | 317 | +   *   settings will be accessible from the TorSettings property of the same
 | 
|  | 318 | +   *   name.
 | 
|  | 319 | +   * @param {object<string, TorSettingProperty>} propParams - An object that
 | 
|  | 320 | +   *   defines the settings to add to this group. The object property names
 | 
|  | 321 | +   *   will be mapped to properties of TorSettings under the given groupname
 | 
|  | 322 | +   *   property. Details about the setting should be described in the
 | 
|  | 323 | +   *   TorSettingProperty property value.
 | 
|  | 324 | +   */
 | 
|  | 325 | +  _addProperties(groupname, propParams) {
 | 
|  | 326 | +    // Create a new object to hold all these settings.
 | 
|  | 327 | +    const group = {};
 | 
|  | 328 | +    for (const name in propParams) {
 | 
|  | 329 | +      const { getter, transform, callback, copy, equal } = propParams[name];
 | 
|  | 330 | +      Object.defineProperty(group, name, {
 | 
|  | 331 | +        get: getter
 | 
|  | 332 | +          ? getter
 | 
|  | 333 | +          : () => {
 | 
|  | 334 | +              let val = this._settings[groupname][name];
 | 
|  | 335 | +              if (copy) {
 | 
|  | 336 | +                val = copy(val);
 | 
|  | 337 | +              }
 | 
|  | 338 | +              // Assume string or number value.
 | 
|  | 339 | +              return val;
 | 
|  | 340 | +            },
 | 
|  | 341 | +        set: getter
 | 
|  | 342 | +          ? undefined
 | 
|  | 343 | +          : val => {
 | 
|  | 344 | +              const prevVal = this._settings[groupname][name];
 | 
|  | 345 | +              this.freezeNotifications();
 | 
|  | 346 | +              try {
 | 
|  | 347 | +                if (transform) {
 | 
|  | 348 | +                  val = transform(val);
 | 
|  | 349 | +                }
 | 
|  | 350 | +                const isEqual = equal ? equal(val, prevVal) : val === prevVal;
 | 
|  | 351 | +                if (!isEqual) {
 | 
|  | 352 | +                  if (callback) {
 | 
|  | 353 | +                    callback(val);
 | 
|  | 354 | +                  }
 | 
|  | 355 | +                  this._settings[groupname][name] = val;
 | 
|  | 356 | +                  this._notificationQueue.add(`${groupname}.${name}`);
 | 
|  | 357 | +                }
 | 
|  | 358 | +              } finally {
 | 
|  | 359 | +                this.thawNotifications();
 | 
|  | 360 | +              }
 | 
|  | 361 | +            },
 | 
|  | 362 | +      });
 | 
|  | 363 | +    }
 | 
|  | 364 | +    // The group object itself should not be writable.
 | 
|  | 365 | +    Object.preventExtensions(group);
 | 
|  | 366 | +    Object.defineProperty(this, groupname, {
 | 
|  | 367 | +      writable: false,
 | 
|  | 368 | +      value: group,
 | 
|  | 369 | +    });
 | 
|  | 370 | +  },
 | 
| 247 | 371 |  
 | 
| 248 |  | -    // tor daemon related settings
 | 
| 249 |  | -    defaultSettings() {
 | 
| 250 |  | -      const settings = {
 | 
| 251 |  | -        quickstart: {
 | 
| 252 |  | -          enabled: false,
 | 
|  | 372 | +  /**
 | 
|  | 373 | +   * Regular _expression_ for a decimal non-negative integer.
 | 
|  | 374 | +   *
 | 
|  | 375 | +   * @type {RegExp}
 | 
|  | 376 | +   */
 | 
|  | 377 | +  _portRegex: /^[0-9]+$/,
 | 
|  | 378 | +  /**
 | 
|  | 379 | +   * Parse a string as a port number.
 | 
|  | 380 | +   *
 | 
|  | 381 | +   * @param {string|integer} val - The value to parse.
 | 
|  | 382 | +   * @param {boolean} trim - Whether a string value can be stripped of
 | 
|  | 383 | +   *   whitespace before parsing.
 | 
|  | 384 | +   *
 | 
|  | 385 | +   * @return {integer?} - The port number, or null if the given value was not
 | 
|  | 386 | +   *   valid.
 | 
|  | 387 | +   */
 | 
|  | 388 | +  _parsePort(val, trim) {
 | 
|  | 389 | +    if (typeof val === "string") {
 | 
|  | 390 | +      if (trim) {
 | 
|  | 391 | +        val = val.trim();
 | 
|  | 392 | +      }
 | 
|  | 393 | +      // ensure port string is a valid positive integer
 | 
|  | 394 | +      if (this._portRegex.test(val)) {
 | 
|  | 395 | +        val = Number.parseInt(val, 10);
 | 
|  | 396 | +      } else {
 | 
|  | 397 | +        lazy.logger.error(`Invalid port string "${val}"`);
 | 
|  | 398 | +        return null;
 | 
|  | 399 | +      }
 | 
|  | 400 | +    }
 | 
|  | 401 | +    if (!Number.isInteger(val) || val < 1 || val > 65535) {
 | 
|  | 402 | +      lazy.logger.error(`Port out of range: ${val}`);
 | 
|  | 403 | +      return null;
 | 
|  | 404 | +    }
 | 
|  | 405 | +    return val;
 | 
|  | 406 | +  },
 | 
|  | 407 | +  /**
 | 
|  | 408 | +   * Test whether two arrays have equal members and order.
 | 
|  | 409 | +   *
 | 
|  | 410 | +   * @param {Array} val1 - The first array to test.
 | 
|  | 411 | +   * @param {Array} val2 - The second array to compare against.
 | 
|  | 412 | +   *
 | 
|  | 413 | +   * @return {boolean} - Whether the two arrays are equal.
 | 
|  | 414 | +   */
 | 
|  | 415 | +  _arrayEqual(val1, val2) {
 | 
|  | 416 | +    if (val1.length !== val2.length) {
 | 
|  | 417 | +      return false;
 | 
|  | 418 | +    }
 | 
|  | 419 | +    return val1.every((v, i) => v === val2[i]);
 | 
|  | 420 | +  },
 | 
|  | 421 | +
 | 
|  | 422 | +  /* load or init our settings, and register observers */
 | 
|  | 423 | +  async init() {
 | 
|  | 424 | +    this._addProperties("quickstart", {
 | 
|  | 425 | +      enabled: {},
 | 
|  | 426 | +    });
 | 
|  | 427 | +    this._addProperties("bridges", {
 | 
|  | 428 | +      enabled: {},
 | 
|  | 429 | +      source: {
 | 
|  | 430 | +        transform: val => {
 | 
|  | 431 | +          if (Object.values(TorBridgeSource).includes(val)) {
 | 
|  | 432 | +            return val;
 | 
|  | 433 | +          }
 | 
|  | 434 | +          lazy.logger.error(`Not a valid bridge source: "${val}"`);
 | 
|  | 435 | +          return TorBridgeSource.Invalid;
 | 
|  | 436 | +        },
 | 
|  | 437 | +      },
 | 
|  | 438 | +      bridge_strings: {
 | 
|  | 439 | +        transform: val => {
 | 
|  | 440 | +          if (Array.isArray(val)) {
 | 
|  | 441 | +            return [...val];
 | 
|  | 442 | +          }
 | 
|  | 443 | +          return parseBridgeStrings(val);
 | 
|  | 444 | +        },
 | 
|  | 445 | +        copy: val => [...val],
 | 
|  | 446 | +        equal: (val1, val2) => this._arrayEqual(val1, val2),
 | 
|  | 447 | +      },
 | 
|  | 448 | +      builtin_type: {
 | 
|  | 449 | +        callback: val => {
 | 
|  | 450 | +          if (!val) {
 | 
|  | 451 | +            // Make sure that the source is not BuiltIn
 | 
|  | 452 | +            if (this.bridges.source === TorBridgeSource.BuiltIn) {
 | 
|  | 453 | +              this.bridges.source = TorBridgeSource.Invalid;
 | 
|  | 454 | +            }
 | 
|  | 455 | +            return;
 | 
|  | 456 | +          }
 | 
|  | 457 | +          const bridgeStrings = getBuiltinBridgeStrings(val);
 | 
|  | 458 | +          if (bridgeStrings.length) {
 | 
|  | 459 | +            this.bridges.bridge_strings = bridgeStrings;
 | 
|  | 460 | +            return;
 | 
|  | 461 | +          }
 | 
|  | 462 | +          lazy.logger.error(`No built-in ${val} bridges found`);
 | 
|  | 463 | +          // Change to be empty, this will trigger this callback again,
 | 
|  | 464 | +          // but with val as "".
 | 
|  | 465 | +          this.bridges.builtin_type == "";
 | 
| 253 | 466 |          },
 | 
| 254 |  | -        bridges: {
 | 
| 255 |  | -          enabled: false,
 | 
| 256 |  | -          source: TorBridgeSource.Invalid,
 | 
| 257 |  | -          builtin_type: null,
 | 
| 258 |  | -          bridge_strings: [],
 | 
|  | 467 | +      },
 | 
|  | 468 | +    });
 | 
|  | 469 | +    this._addProperties("proxy", {
 | 
|  | 470 | +      enabled: {
 | 
|  | 471 | +        callback: val => {
 | 
|  | 472 | +          if (val) {
 | 
|  | 473 | +            return;
 | 
|  | 474 | +          }
 | 
|  | 475 | +          // Reset proxy settings.
 | 
|  | 476 | +          this.proxy.type = TorProxyType.Invalid;
 | 
|  | 477 | +          this.proxy.address = "";
 | 
|  | 478 | +          this.proxy.port = 0;
 | 
|  | 479 | +          this.proxy.username = "";
 | 
|  | 480 | +          this.proxy.password = "";
 | 
| 259 | 481 |          },
 | 
| 260 |  | -        proxy: {
 | 
| 261 |  | -          enabled: false,
 | 
| 262 |  | -          type: TorProxyType.Invalid,
 | 
| 263 |  | -          address: null,
 | 
| 264 |  | -          port: 0,
 | 
| 265 |  | -          username: null,
 | 
| 266 |  | -          password: null,
 | 
|  | 482 | +      },
 | 
|  | 483 | +      type: {
 | 
|  | 484 | +        transform: val => {
 | 
|  | 485 | +          if (Object.values(TorProxyType).includes(val)) {
 | 
|  | 486 | +            return val;
 | 
|  | 487 | +          }
 | 
|  | 488 | +          lazy.logger.error(`Not a valid proxy type: "${val}"`);
 | 
|  | 489 | +          return TorProxyType.Invalid;
 | 
| 267 | 490 |          },
 | 
| 268 |  | -        firewall: {
 | 
| 269 |  | -          enabled: false,
 | 
| 270 |  | -          allowed_ports: [],
 | 
|  | 491 | +      },
 | 
|  | 492 | +      address: {},
 | 
|  | 493 | +      port: {
 | 
|  | 494 | +        transform: val => {
 | 
|  | 495 | +          if (val === 0) {
 | 
|  | 496 | +            // This is a valid value that "unsets" the port.
 | 
|  | 497 | +            // Keep this value without giving a warning.
 | 
|  | 498 | +            // NOTE: In contrast, "0" is not valid.
 | 
|  | 499 | +            return 0;
 | 
|  | 500 | +          }
 | 
|  | 501 | +          // Unset to 0 if invalid null is returned.
 | 
|  | 502 | +          return this._parsePort(val, false) ?? 0;
 | 
| 271 | 503 |          },
 | 
| 272 |  | -      };
 | 
| 273 |  | -      return settings;
 | 
| 274 |  | -    },
 | 
| 275 |  | -
 | 
| 276 |  | -    /* load or init our settings, and register observers */
 | 
| 277 |  | -    async init() {
 | 
| 278 |  | -      // TODO: We could use a shared promise, and wait for it to be fullfilled
 | 
| 279 |  | -      // instead of Service.obs.
 | 
| 280 |  | -      if (lazy.TorLauncherUtil.shouldStartAndOwnTor) {
 | 
| 281 |  | -        // if the settings branch exists, load settings from prefs
 | 
| 282 |  | -        if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
 | 
|  | 504 | +      },
 | 
|  | 505 | +      username: {},
 | 
|  | 506 | +      password: {},
 | 
|  | 507 | +      uri: {
 | 
|  | 508 | +        getter: () => {
 | 
|  | 509 | +          const { type, address, port, username, password } = this.proxy;
 | 
|  | 510 | +          switch (type) {
 | 
|  | 511 | +            case TorProxyType.Socks4:
 | 
|  | 512 | +              return `socks4a://${address}:${port}`;
 | 
|  | 513 | +            case TorProxyType.Socks5:
 | 
|  | 514 | +              if (username) {
 | 
|  | 515 | +                return `socks5://${username}:${password}@${address}:${port}`;
 | 
|  | 516 | +              }
 | 
|  | 517 | +              return `socks5://${address}:${port}`;
 | 
|  | 518 | +            case TorProxyType.HTTPS:
 | 
|  | 519 | +              if (username) {
 | 
|  | 520 | +                return `http://${username}:${password}@${address}:${port}`;
 | 
|  | 521 | +              }
 | 
|  | 522 | +              return `http://${address}:${port}`;
 | 
|  | 523 | +          }
 | 
|  | 524 | +          return null;
 | 
|  | 525 | +        },
 | 
|  | 526 | +      },
 | 
|  | 527 | +    });
 | 
|  | 528 | +    this._addProperties("firewall", {
 | 
|  | 529 | +      enabled: {
 | 
|  | 530 | +        callback: val => {
 | 
|  | 531 | +          if (!val) {
 | 
|  | 532 | +            this.firewall.allowed_ports = "";
 | 
|  | 533 | +          }
 | 
|  | 534 | +        },
 | 
|  | 535 | +      },
 | 
|  | 536 | +      allowed_ports: {
 | 
|  | 537 | +        transform: val => {
 | 
|  | 538 | +          if (!Array.isArray(val)) {
 | 
|  | 539 | +            val = val === "" ? [] : val.split(",");
 | 
|  | 540 | +          }
 | 
|  | 541 | +          // parse and remove duplicates
 | 
|  | 542 | +          const portSet = new Set(val.map(p => this._parsePort(p, true)));
 | 
|  | 543 | +          // parsePort returns null for failed parses, so remove it.
 | 
|  | 544 | +          portSet.delete(null);
 | 
|  | 545 | +          return [...portSet];
 | 
|  | 546 | +        },
 | 
|  | 547 | +        copy: val => [...val],
 | 
|  | 548 | +        equal: (val1, val2) => this._arrayEqual(val1, val2),
 | 
|  | 549 | +      },
 | 
|  | 550 | +    });
 | 
|  | 551 | +
 | 
|  | 552 | +    // TODO: We could use a shared promise, and wait for it to be fullfilled
 | 
|  | 553 | +    // instead of Service.obs.
 | 
|  | 554 | +    if (lazy.TorLauncherUtil.shouldStartAndOwnTor) {
 | 
|  | 555 | +      // if the settings branch exists, load settings from prefs
 | 
|  | 556 | +      if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
 | 
|  | 557 | +        // Do not want notifications for initially loaded prefs.
 | 
|  | 558 | +        this.freezeNotifications();
 | 
|  | 559 | +        try {
 | 
| 283 | 560 |            this.loadFromPrefs();
 | 
| 284 |  | -        } else {
 | 
| 285 |  | -          // otherwise load defaults
 | 
| 286 |  | -          this._settings = this.defaultSettings();
 | 
|  | 561 | +        } finally {
 | 
|  | 562 | +          this._notificationQueue.clear();
 | 
|  | 563 | +          this.thawNotifications();
 | 
| 287 | 564 |          }
 | 
| 288 |  | -        Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
 | 
| 289 |  | -
 | 
| 290 |  | -        try {
 | 
| 291 |  | -          const provider = await lazy.TorProviderBuilder.build();
 | 
| 292 |  | -          if (provider.isRunning) {
 | 
| 293 |  | -            this.handleProcessReady();
 | 
| 294 |  | -          }
 | 
| 295 |  | -        } catch {}
 | 
| 296 | 565 |        }
 | 
| 297 |  | -    },
 | 
|  | 566 | +      try {
 | 
|  | 567 | +        const provider = await lazy.TorProviderBuilder.build();
 | 
|  | 568 | +        if (provider.isRunning) {
 | 
|  | 569 | +          this.handleProcessReady();
 | 
|  | 570 | +          // No need to add an observer to call this again.
 | 
|  | 571 | +          return;
 | 
|  | 572 | +        }
 | 
|  | 573 | +      } catch {}
 | 
| 298 | 574 |  
 | 
| 299 |  | -    /* wait for relevant life-cycle events to apply saved settings */
 | 
| 300 |  | -    async observe(subject, topic, data) {
 | 
| 301 |  | -      console.log(`TorSettings: Observed ${topic}`);
 | 
|  | 575 | +      Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
 | 
|  | 576 | +    }
 | 
|  | 577 | +  },
 | 
| 302 | 578 |  
 | 
| 303 |  | -      switch (topic) {
 | 
| 304 |  | -        case lazy.TorProviderTopics.ProcessIsReady:
 | 
| 305 |  | -          Services.obs.removeObserver(
 | 
| 306 |  | -            this,
 | 
| 307 |  | -            lazy.TorProviderTopics.ProcessIsReady
 | 
| 308 |  | -          );
 | 
| 309 |  | -          await this.handleProcessReady();
 | 
| 310 |  | -          break;
 | 
| 311 |  | -      }
 | 
| 312 |  | -    },
 | 
|  | 579 | +  /* wait for relevant life-cycle events to apply saved settings */
 | 
|  | 580 | +  async observe(subject, topic, data) {
 | 
|  | 581 | +    lazy.logger.debug(`Observed ${topic}`);
 | 
| 313 | 582 |  
 | 
| 314 |  | -    // once the tor daemon is ready, we need to apply our settings
 | 
| 315 |  | -    async handleProcessReady() {
 | 
| 316 |  | -      // push down settings to tor
 | 
| 317 |  | -      await this.applySettings();
 | 
| 318 |  | -      console.log("TorSettings: Ready");
 | 
| 319 |  | -      Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
 | 
| 320 |  | -    },
 | 
|  | 583 | +    switch (topic) {
 | 
|  | 584 | +      case lazy.TorProviderTopics.ProcessIsReady:
 | 
|  | 585 | +        Services.obs.removeObserver(
 | 
|  | 586 | +          this,
 | 
|  | 587 | +          lazy.TorProviderTopics.ProcessIsReady
 | 
|  | 588 | +        );
 | 
|  | 589 | +        await this.handleProcessReady();
 | 
|  | 590 | +        break;
 | 
|  | 591 | +    }
 | 
|  | 592 | +  },
 | 
| 321 | 593 |  
 | 
| 322 |  | -    // load our settings from prefs
 | 
| 323 |  | -    loadFromPrefs() {
 | 
| 324 |  | -      console.log("TorSettings: loadFromPrefs()");
 | 
|  | 594 | +  // once the tor daemon is ready, we need to apply our settings
 | 
|  | 595 | +  async handleProcessReady() {
 | 
|  | 596 | +    // push down settings to tor
 | 
|  | 597 | +    await this.applySettings();
 | 
|  | 598 | +    lazy.logger.info("Ready");
 | 
|  | 599 | +    Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
 | 
|  | 600 | +  },
 | 
| 325 | 601 |  
 | 
| 326 |  | -      const settings = this.defaultSettings();
 | 
|  | 602 | +  // load our settings from prefs
 | 
|  | 603 | +  loadFromPrefs() {
 | 
|  | 604 | +    lazy.logger.debug("loadFromPrefs()");
 | 
| 327 | 605 |  
 | 
| 328 |  | -      /* Quickstart */
 | 
| 329 |  | -      settings.quickstart.enabled = Services.prefs.getBoolPref(
 | 
| 330 |  | -        TorSettingsPrefs.quickstart.enabled,
 | 
| 331 |  | -        false
 | 
|  | 606 | +    /* Quickstart */
 | 
|  | 607 | +    this.quickstart.enabled = Services.prefs.getBoolPref(
 | 
|  | 608 | +      TorSettingsPrefs.quickstart.enabled,
 | 
|  | 609 | +      false
 | 
|  | 610 | +    );
 | 
|  | 611 | +    /* Bridges */
 | 
|  | 612 | +    this.bridges.enabled = Services.prefs.getBoolPref(
 | 
|  | 613 | +      TorSettingsPrefs.bridges.enabled,
 | 
|  | 614 | +      false
 | 
|  | 615 | +    );
 | 
|  | 616 | +    this.bridges.source = Services.prefs.getIntPref(
 | 
|  | 617 | +      TorSettingsPrefs.bridges.source,
 | 
|  | 618 | +      TorBridgeSource.Invalid
 | 
|  | 619 | +    );
 | 
|  | 620 | +    if (this.bridges.source == TorBridgeSource.BuiltIn) {
 | 
|  | 621 | +      this.bridges.builtin_type = Services.prefs.getStringPref(
 | 
|  | 622 | +        TorSettingsPrefs.bridges.builtin_type,
 | 
|  | 623 | +        ""
 | 
| 332 | 624 |        );
 | 
| 333 |  | -      /* Bridges */
 | 
| 334 |  | -      settings.bridges.enabled = Services.prefs.getBoolPref(
 | 
| 335 |  | -        TorSettingsPrefs.bridges.enabled,
 | 
| 336 |  | -        false
 | 
|  | 625 | +    } else {
 | 
|  | 626 | +      const bridgeBranchPrefs = Services.prefs
 | 
|  | 627 | +        .getBranch(TorSettingsPrefs.bridges.bridge_strings)
 | 
|  | 628 | +        .getChildList("");
 | 
|  | 629 | +      this.bridges.bridge_strings = Array.from(bridgeBranchPrefs, pref =>
 | 
|  | 630 | +        Services.prefs.getStringPref(
 | 
|  | 631 | +          `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
 | 
|  | 632 | +        )
 | 
| 337 | 633 |        );
 | 
| 338 |  | -      settings.bridges.source = Services.prefs.getIntPref(
 | 
| 339 |  | -        TorSettingsPrefs.bridges.source,
 | 
| 340 |  | -        TorBridgeSource.Invalid
 | 
|  | 634 | +    }
 | 
|  | 635 | +    /* Proxy */
 | 
|  | 636 | +    this.proxy.enabled = Services.prefs.getBoolPref(
 | 
|  | 637 | +      TorSettingsPrefs.proxy.enabled,
 | 
|  | 638 | +      false
 | 
|  | 639 | +    );
 | 
|  | 640 | +    if (this.proxy.enabled) {
 | 
|  | 641 | +      this.proxy.type = Services.prefs.getIntPref(
 | 
|  | 642 | +        TorSettingsPrefs.proxy.type,
 | 
|  | 643 | +        TorProxyType.Invalid
 | 
| 341 | 644 |        );
 | 
| 342 |  | -      if (settings.bridges.source == TorBridgeSource.BuiltIn) {
 | 
| 343 |  | -        const builtinType = Services.prefs.getStringPref(
 | 
| 344 |  | -          TorSettingsPrefs.bridges.builtin_type,
 | 
| 345 |  | -          ""
 | 
| 346 |  | -        );
 | 
| 347 |  | -        settings.bridges.builtin_type = builtinType;
 | 
| 348 |  | -        settings.bridges.bridge_strings = getBuiltinBridgeStrings(builtinType);
 | 
| 349 |  | -        if (!settings.bridges.bridge_strings.length) {
 | 
| 350 |  | -          // in this case the user is using a builtin bridge that is no longer supported,
 | 
| 351 |  | -          // reset to settings to default values
 | 
| 352 |  | -          console.warn(
 | 
| 353 |  | -            `[TorSettings] Cannot find any bridge line for the configured bridge type ${builtinType}`
 | 
| 354 |  | -          );
 | 
| 355 |  | -          settings.bridges.source = TorBridgeSource.Invalid;
 | 
| 356 |  | -          settings.bridges.builtin_type = null;
 | 
| 357 |  | -        }
 | 
| 358 |  | -      } else {
 | 
| 359 |  | -        settings.bridges.bridge_strings = [];
 | 
| 360 |  | -        const bridgeBranchPrefs = Services.prefs
 | 
| 361 |  | -          .getBranch(TorSettingsPrefs.bridges.bridge_strings)
 | 
| 362 |  | -          .getChildList("");
 | 
| 363 |  | -        bridgeBranchPrefs.forEach(pref => {
 | 
| 364 |  | -          const bridgeString = Services.prefs.getStringPref(
 | 
| 365 |  | -            `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
 | 
| 366 |  | -          );
 | 
| 367 |  | -          settings.bridges.bridge_strings.push(bridgeString);
 | 
| 368 |  | -        });
 | 
| 369 |  | -      }
 | 
| 370 |  | -      /* Proxy */
 | 
| 371 |  | -      settings.proxy.enabled = Services.prefs.getBoolPref(
 | 
| 372 |  | -        TorSettingsPrefs.proxy.enabled,
 | 
| 373 |  | -        false
 | 
|  | 645 | +      this.proxy.address = Services.prefs.getStringPref(
 | 
|  | 646 | +        TorSettingsPrefs.proxy.address,
 | 
|  | 647 | +        ""
 | 
| 374 | 648 |        );
 | 
| 375 |  | -      if (settings.proxy.enabled) {
 | 
| 376 |  | -        settings.proxy.type = Services.prefs.getIntPref(
 | 
| 377 |  | -          TorSettingsPrefs.proxy.type,
 | 
| 378 |  | -          TorProxyType.Invalid
 | 
| 379 |  | -        );
 | 
| 380 |  | -        settings.proxy.address = Services.prefs.getStringPref(
 | 
| 381 |  | -          TorSettingsPrefs.proxy.address,
 | 
| 382 |  | -          ""
 | 
| 383 |  | -        );
 | 
| 384 |  | -        settings.proxy.port = Services.prefs.getIntPref(
 | 
| 385 |  | -          TorSettingsPrefs.proxy.port,
 | 
| 386 |  | -          0
 | 
| 387 |  | -        );
 | 
| 388 |  | -        settings.proxy.username = Services.prefs.getStringPref(
 | 
| 389 |  | -          TorSettingsPrefs.proxy.username,
 | 
| 390 |  | -          ""
 | 
| 391 |  | -        );
 | 
| 392 |  | -        settings.proxy.password = Services.prefs.getStringPref(
 | 
| 393 |  | -          TorSettingsPrefs.proxy.password,
 | 
| 394 |  | -          ""
 | 
| 395 |  | -        );
 | 
| 396 |  | -      } else {
 | 
| 397 |  | -        settings.proxy.type = TorProxyType.Invalid;
 | 
| 398 |  | -        settings.proxy.address = null;
 | 
| 399 |  | -        settings.proxy.port = 0;
 | 
| 400 |  | -        settings.proxy.username = null;
 | 
| 401 |  | -        settings.proxy.password = null;
 | 
| 402 |  | -      }
 | 
| 403 |  | -
 | 
| 404 |  | -      /* Firewall */
 | 
| 405 |  | -      settings.firewall.enabled = Services.prefs.getBoolPref(
 | 
| 406 |  | -        TorSettingsPrefs.firewall.enabled,
 | 
| 407 |  | -        false
 | 
|  | 649 | +      this.proxy.port = Services.prefs.getIntPref(
 | 
|  | 650 | +        TorSettingsPrefs.proxy.port,
 | 
|  | 651 | +        0
 | 
| 408 | 652 |        );
 | 
| 409 |  | -      if (settings.firewall.enabled) {
 | 
| 410 |  | -        const portList = Services.prefs.getStringPref(
 | 
| 411 |  | -          TorSettingsPrefs.firewall.allowed_ports,
 | 
| 412 |  | -          ""
 | 
| 413 |  | -        );
 | 
| 414 |  | -        settings.firewall.allowed_ports = parsePortList(portList);
 | 
| 415 |  | -      } else {
 | 
| 416 |  | -        settings.firewall.allowed_ports = 0;
 | 
| 417 |  | -      }
 | 
| 418 |  | -
 | 
| 419 |  | -      this._settings = settings;
 | 
| 420 |  | -
 | 
| 421 |  | -      return this;
 | 
| 422 |  | -    },
 | 
|  | 653 | +      this.proxy.username = Services.prefs.getStringPref(
 | 
|  | 654 | +        TorSettingsPrefs.proxy.username,
 | 
|  | 655 | +        ""
 | 
|  | 656 | +      );
 | 
|  | 657 | +      this.proxy.password = Services.prefs.getStringPref(
 | 
|  | 658 | +        TorSettingsPrefs.proxy.password,
 | 
|  | 659 | +        ""
 | 
|  | 660 | +      );
 | 
|  | 661 | +    }
 | 
| 423 | 662 |  
 | 
| 424 |  | -    // save our settings to prefs
 | 
| 425 |  | -    saveToPrefs() {
 | 
| 426 |  | -      console.log("TorSettings: saveToPrefs()");
 | 
|  | 663 | +    /* Firewall */
 | 
|  | 664 | +    this.firewall.enabled = Services.prefs.getBoolPref(
 | 
|  | 665 | +      TorSettingsPrefs.firewall.enabled,
 | 
|  | 666 | +      false
 | 
|  | 667 | +    );
 | 
|  | 668 | +    if (this.firewall.enabled) {
 | 
|  | 669 | +      this.firewall.allowed_ports = Services.prefs.getStringPref(
 | 
|  | 670 | +        TorSettingsPrefs.firewall.allowed_ports,
 | 
|  | 671 | +        ""
 | 
|  | 672 | +      );
 | 
|  | 673 | +    }
 | 
|  | 674 | +  },
 | 
| 427 | 675 |  
 | 
| 428 |  | -      const settings = this._settings;
 | 
|  | 676 | +  // save our settings to prefs
 | 
|  | 677 | +  saveToPrefs() {
 | 
|  | 678 | +    lazy.logger.debug("saveToPrefs()");
 | 
| 429 | 679 |  
 | 
| 430 |  | -      /* Quickstart */
 | 
| 431 |  | -      Services.prefs.setBoolPref(
 | 
| 432 |  | -        TorSettingsPrefs.quickstart.enabled,
 | 
| 433 |  | -        settings.quickstart.enabled
 | 
| 434 |  | -      );
 | 
| 435 |  | -      /* Bridges */
 | 
| 436 |  | -      Services.prefs.setBoolPref(
 | 
| 437 |  | -        TorSettingsPrefs.bridges.enabled,
 | 
| 438 |  | -        settings.bridges.enabled
 | 
|  | 680 | +    /* Quickstart */
 | 
|  | 681 | +    Services.prefs.setBoolPref(
 | 
|  | 682 | +      TorSettingsPrefs.quickstart.enabled,
 | 
|  | 683 | +      this.quickstart.enabled
 | 
|  | 684 | +    );
 | 
|  | 685 | +    /* Bridges */
 | 
|  | 686 | +    Services.prefs.setBoolPref(
 | 
|  | 687 | +      TorSettingsPrefs.bridges.enabled,
 | 
|  | 688 | +      this.bridges.enabled
 | 
|  | 689 | +    );
 | 
|  | 690 | +    Services.prefs.setIntPref(
 | 
|  | 691 | +      TorSettingsPrefs.bridges.source,
 | 
|  | 692 | +      this.bridges.source
 | 
|  | 693 | +    );
 | 
|  | 694 | +    Services.prefs.setStringPref(
 | 
|  | 695 | +      TorSettingsPrefs.bridges.builtin_type,
 | 
|  | 696 | +      this.bridges.builtin_type
 | 
|  | 697 | +    );
 | 
|  | 698 | +    // erase existing bridge strings
 | 
|  | 699 | +    const bridgeBranchPrefs = Services.prefs
 | 
|  | 700 | +      .getBranch(TorSettingsPrefs.bridges.bridge_strings)
 | 
|  | 701 | +      .getChildList("");
 | 
|  | 702 | +    bridgeBranchPrefs.forEach(pref => {
 | 
|  | 703 | +      Services.prefs.clearUserPref(
 | 
|  | 704 | +        `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
 | 
| 439 | 705 |        );
 | 
| 440 |  | -      Services.prefs.setIntPref(
 | 
| 441 |  | -        TorSettingsPrefs.bridges.source,
 | 
| 442 |  | -        settings.bridges.source
 | 
|  | 706 | +    });
 | 
|  | 707 | +    // write new ones
 | 
|  | 708 | +    if (this.bridges.source !== TorBridgeSource.BuiltIn) {
 | 
|  | 709 | +      this.bridges.bridge_strings.forEach((string, index) => {
 | 
|  | 710 | +        Services.prefs.setStringPref(
 | 
|  | 711 | +          `${TorSettingsPrefs.bridges.bridge_strings}.${index}`,
 | 
|  | 712 | +          string
 | 
|  | 713 | +        );
 | 
|  | 714 | +      });
 | 
|  | 715 | +    }
 | 
|  | 716 | +    /* Proxy */
 | 
|  | 717 | +    Services.prefs.setBoolPref(
 | 
|  | 718 | +      TorSettingsPrefs.proxy.enabled,
 | 
|  | 719 | +      this.proxy.enabled
 | 
|  | 720 | +    );
 | 
|  | 721 | +    if (this.proxy.enabled) {
 | 
|  | 722 | +      Services.prefs.setIntPref(TorSettingsPrefs.proxy.type, this.proxy.type);
 | 
|  | 723 | +      Services.prefs.setStringPref(
 | 
|  | 724 | +        TorSettingsPrefs.proxy.address,
 | 
|  | 725 | +        this.proxy.address
 | 
| 443 | 726 |        );
 | 
|  | 727 | +      Services.prefs.setIntPref(TorSettingsPrefs.proxy.port, this.proxy.port);
 | 
| 444 | 728 |        Services.prefs.setStringPref(
 | 
| 445 |  | -        TorSettingsPrefs.bridges.builtin_type,
 | 
| 446 |  | -        settings.bridges.builtin_type
 | 
|  | 729 | +        TorSettingsPrefs.proxy.username,
 | 
|  | 730 | +        this.proxy.username
 | 
| 447 | 731 |        );
 | 
| 448 |  | -      // erase existing bridge strings
 | 
| 449 |  | -      const bridgeBranchPrefs = Services.prefs
 | 
| 450 |  | -        .getBranch(TorSettingsPrefs.bridges.bridge_strings)
 | 
| 451 |  | -        .getChildList("");
 | 
| 452 |  | -      bridgeBranchPrefs.forEach(pref => {
 | 
| 453 |  | -        Services.prefs.clearUserPref(
 | 
| 454 |  | -          `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
 | 
| 455 |  | -        );
 | 
| 456 |  | -      });
 | 
| 457 |  | -      // write new ones
 | 
| 458 |  | -      if (settings.bridges.source !== TorBridgeSource.BuiltIn) {
 | 
| 459 |  | -        settings.bridges.bridge_strings.forEach((string, index) => {
 | 
| 460 |  | -          Services.prefs.setStringPref(
 | 
| 461 |  | -            `${TorSettingsPrefs.bridges.bridge_strings}.${index}`,
 | 
| 462 |  | -            string
 | 
| 463 |  | -          );
 | 
| 464 |  | -        });
 | 
| 465 |  | -      }
 | 
| 466 |  | -      /* Proxy */
 | 
| 467 |  | -      Services.prefs.setBoolPref(
 | 
| 468 |  | -        TorSettingsPrefs.proxy.enabled,
 | 
| 469 |  | -        settings.proxy.enabled
 | 
|  | 732 | +      Services.prefs.setStringPref(
 | 
|  | 733 | +        TorSettingsPrefs.proxy.password,
 | 
|  | 734 | +        this.proxy.password
 | 
| 470 | 735 |        );
 | 
| 471 |  | -      if (settings.proxy.enabled) {
 | 
| 472 |  | -        Services.prefs.setIntPref(
 | 
| 473 |  | -          TorSettingsPrefs.proxy.type,
 | 
| 474 |  | -          settings.proxy.type
 | 
| 475 |  | -        );
 | 
| 476 |  | -        Services.prefs.setStringPref(
 | 
| 477 |  | -          TorSettingsPrefs.proxy.address,
 | 
| 478 |  | -          settings.proxy.address
 | 
| 479 |  | -        );
 | 
| 480 |  | -        Services.prefs.setIntPref(
 | 
| 481 |  | -          TorSettingsPrefs.proxy.port,
 | 
| 482 |  | -          settings.proxy.port
 | 
| 483 |  | -        );
 | 
| 484 |  | -        Services.prefs.setStringPref(
 | 
| 485 |  | -          TorSettingsPrefs.proxy.username,
 | 
| 486 |  | -          settings.proxy.username
 | 
| 487 |  | -        );
 | 
| 488 |  | -        Services.prefs.setStringPref(
 | 
| 489 |  | -          TorSettingsPrefs.proxy.password,
 | 
| 490 |  | -          settings.proxy.password
 | 
| 491 |  | -        );
 | 
| 492 |  | -      } else {
 | 
| 493 |  | -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type);
 | 
| 494 |  | -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address);
 | 
| 495 |  | -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port);
 | 
| 496 |  | -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username);
 | 
| 497 |  | -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password);
 | 
| 498 |  | -      }
 | 
| 499 |  | -      /* Firewall */
 | 
| 500 |  | -      Services.prefs.setBoolPref(
 | 
| 501 |  | -        TorSettingsPrefs.firewall.enabled,
 | 
| 502 |  | -        settings.firewall.enabled
 | 
|  | 736 | +    } else {
 | 
|  | 737 | +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type);
 | 
|  | 738 | +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address);
 | 
|  | 739 | +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port);
 | 
|  | 740 | +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username);
 | 
|  | 741 | +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password);
 | 
|  | 742 | +    }
 | 
|  | 743 | +    /* Firewall */
 | 
|  | 744 | +    Services.prefs.setBoolPref(
 | 
|  | 745 | +      TorSettingsPrefs.firewall.enabled,
 | 
|  | 746 | +      this.firewall.enabled
 | 
|  | 747 | +    );
 | 
|  | 748 | +    if (this.firewall.enabled) {
 | 
|  | 749 | +      Services.prefs.setStringPref(
 | 
|  | 750 | +        TorSettingsPrefs.firewall.allowed_ports,
 | 
|  | 751 | +        this.firewall.allowed_ports.join(",")
 | 
| 503 | 752 |        );
 | 
| 504 |  | -      if (settings.firewall.enabled) {
 | 
| 505 |  | -        Services.prefs.setStringPref(
 | 
| 506 |  | -          TorSettingsPrefs.firewall.allowed_ports,
 | 
| 507 |  | -          settings.firewall.allowed_ports.join(",")
 | 
| 508 |  | -        );
 | 
| 509 |  | -      } else {
 | 
| 510 |  | -        Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports);
 | 
| 511 |  | -      }
 | 
|  | 753 | +    } else {
 | 
|  | 754 | +      Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports);
 | 
|  | 755 | +    }
 | 
| 512 | 756 |  
 | 
| 513 |  | -      // all tor settings now stored in prefs :)
 | 
| 514 |  | -      Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true);
 | 
|  | 757 | +    // all tor settings now stored in prefs :)
 | 
|  | 758 | +    Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true);
 | 
| 515 | 759 |  
 | 
| 516 |  | -      return this;
 | 
| 517 |  | -    },
 | 
| 518 |  | -
 | 
| 519 |  | -    // push our settings down to the tor daemon
 | 
| 520 |  | -    async applySettings() {
 | 
| 521 |  | -      console.log("TorSettings: applySettings()");
 | 
| 522 |  | -      const settings = this._settings;
 | 
| 523 |  | -      const settingsMap = new Map();
 | 
| 524 |  | -
 | 
| 525 |  | -      /* Bridges */
 | 
| 526 |  | -      const haveBridges =
 | 
| 527 |  | -        settings.bridges.enabled && !!settings.bridges.bridge_strings.length;
 | 
| 528 |  | -      settingsMap.set(TorConfigKeys.useBridges, haveBridges);
 | 
| 529 |  | -      if (haveBridges) {
 | 
| 530 |  | -        settingsMap.set(
 | 
| 531 |  | -          TorConfigKeys.bridgeList,
 | 
| 532 |  | -          settings.bridges.bridge_strings
 | 
| 533 |  | -        );
 | 
| 534 |  | -      } else {
 | 
| 535 |  | -        settingsMap.set(TorConfigKeys.bridgeList, null);
 | 
| 536 |  | -      }
 | 
|  | 760 | +    return this;
 | 
|  | 761 | +  },
 | 
| 537 | 762 |  
 | 
| 538 |  | -      /* Proxy */
 | 
| 539 |  | -      settingsMap.set(TorConfigKeys.socks4Proxy, null);
 | 
| 540 |  | -      settingsMap.set(TorConfigKeys.socks5Proxy, null);
 | 
| 541 |  | -      settingsMap.set(TorConfigKeys.socks5ProxyUsername, null);
 | 
| 542 |  | -      settingsMap.set(TorConfigKeys.socks5ProxyPassword, null);
 | 
| 543 |  | -      settingsMap.set(TorConfigKeys.httpsProxy, null);
 | 
| 544 |  | -      settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null);
 | 
| 545 |  | -      if (settings.proxy.enabled) {
 | 
| 546 |  | -        const address = settings.proxy.address;
 | 
| 547 |  | -        const port = settings.proxy.port;
 | 
| 548 |  | -        const username = settings.proxy.username;
 | 
| 549 |  | -        const password = settings.proxy.password;
 | 
| 550 |  | -
 | 
| 551 |  | -        switch (settings.proxy.type) {
 | 
| 552 |  | -          case TorProxyType.Socks4:
 | 
| 553 |  | -            settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`);
 | 
| 554 |  | -            break;
 | 
| 555 |  | -          case TorProxyType.Socks5:
 | 
| 556 |  | -            settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`);
 | 
| 557 |  | -            settingsMap.set(TorConfigKeys.socks5ProxyUsername, username);
 | 
| 558 |  | -            settingsMap.set(TorConfigKeys.socks5ProxyPassword, password);
 | 
| 559 |  | -            break;
 | 
| 560 |  | -          case TorProxyType.HTTPS:
 | 
| 561 |  | -            settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`);
 | 
| 562 |  | -            settingsMap.set(
 | 
| 563 |  | -              TorConfigKeys.httpsProxyAuthenticator,
 | 
| 564 |  | -              `${username}:${password}`
 | 
| 565 |  | -            );
 | 
| 566 |  | -            break;
 | 
| 567 |  | -        }
 | 
| 568 |  | -      }
 | 
|  | 763 | +  // push our settings down to the tor daemon
 | 
|  | 764 | +  async applySettings() {
 | 
|  | 765 | +    lazy.logger.debug("applySettings()");
 | 
|  | 766 | +    const settingsMap = new Map();
 | 
|  | 767 | +
 | 
|  | 768 | +    /* Bridges */
 | 
|  | 769 | +    const haveBridges =
 | 
|  | 770 | +      this.bridges.enabled && !!this.bridges.bridge_strings.length;
 | 
|  | 771 | +    settingsMap.set(TorConfigKeys.useBridges, haveBridges);
 | 
|  | 772 | +    if (haveBridges) {
 | 
|  | 773 | +      settingsMap.set(TorConfigKeys.bridgeList, this.bridges.bridge_strings);
 | 
|  | 774 | +    } else {
 | 
|  | 775 | +      settingsMap.set(TorConfigKeys.bridgeList, null);
 | 
|  | 776 | +    }
 | 
| 569 | 777 |  
 | 
| 570 |  | -      /* Firewall */
 | 
| 571 |  | -      if (settings.firewall.enabled) {
 | 
| 572 |  | -        const reachableAddresses = settings.firewall.allowed_ports
 | 
| 573 |  | -          .map(port => `*:${port}`)
 | 
| 574 |  | -          .join(",");
 | 
| 575 |  | -        settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses);
 | 
| 576 |  | -      } else {
 | 
| 577 |  | -        settingsMap.set(TorConfigKeys.reachableAddresses, null);
 | 
|  | 778 | +    /* Proxy */
 | 
|  | 779 | +    settingsMap.set(TorConfigKeys.socks4Proxy, null);
 | 
|  | 780 | +    settingsMap.set(TorConfigKeys.socks5Proxy, null);
 | 
|  | 781 | +    settingsMap.set(TorConfigKeys.socks5ProxyUsername, null);
 | 
|  | 782 | +    settingsMap.set(TorConfigKeys.socks5ProxyPassword, null);
 | 
|  | 783 | +    settingsMap.set(TorConfigKeys.httpsProxy, null);
 | 
|  | 784 | +    settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null);
 | 
|  | 785 | +    if (this.proxy.enabled) {
 | 
|  | 786 | +      const address = this.proxy.address;
 | 
|  | 787 | +      const port = this.proxy.port;
 | 
|  | 788 | +      const username = this.proxy.username;
 | 
|  | 789 | +      const password = this.proxy.password;
 | 
|  | 790 | +
 | 
|  | 791 | +      switch (this.proxy.type) {
 | 
|  | 792 | +        case TorProxyType.Socks4:
 | 
|  | 793 | +          settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`);
 | 
|  | 794 | +          break;
 | 
|  | 795 | +        case TorProxyType.Socks5:
 | 
|  | 796 | +          settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`);
 | 
|  | 797 | +          settingsMap.set(TorConfigKeys.socks5ProxyUsername, username);
 | 
|  | 798 | +          settingsMap.set(TorConfigKeys.socks5ProxyPassword, password);
 | 
|  | 799 | +          break;
 | 
|  | 800 | +        case TorProxyType.HTTPS:
 | 
|  | 801 | +          settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`);
 | 
|  | 802 | +          settingsMap.set(
 | 
|  | 803 | +            TorConfigKeys.httpsProxyAuthenticator,
 | 
|  | 804 | +            `${username}:${password}`
 | 
|  | 805 | +          );
 | 
|  | 806 | +          break;
 | 
| 578 | 807 |        }
 | 
|  | 808 | +    }
 | 
| 579 | 809 |  
 | 
| 580 |  | -      /* Push to Tor */
 | 
| 581 |  | -      const provider = await lazy.TorProviderBuilder.build();
 | 
| 582 |  | -      await provider.writeSettings(settingsMap);
 | 
|  | 810 | +    /* Firewall */
 | 
|  | 811 | +    if (this.firewall.enabled) {
 | 
|  | 812 | +      const reachableAddresses = this.firewall.allowed_ports
 | 
|  | 813 | +        .map(port => `*:${port}`)
 | 
|  | 814 | +        .join(",");
 | 
|  | 815 | +      settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses);
 | 
|  | 816 | +    } else {
 | 
|  | 817 | +      settingsMap.set(TorConfigKeys.reachableAddresses, null);
 | 
|  | 818 | +    }
 | 
| 583 | 819 |  
 | 
| 584 |  | -      return this;
 | 
| 585 |  | -    },
 | 
|  | 820 | +    /* Push to Tor */
 | 
|  | 821 | +    const provider = await lazy.TorProviderBuilder.build();
 | 
|  | 822 | +    await provider.writeSettings(settingsMap);
 | 
| 586 | 823 |  
 | 
| 587 |  | -    // set all of our settings at once from a settings object
 | 
| 588 |  | -    setSettings(settings) {
 | 
| 589 |  | -      console.log("TorSettings: setSettings()");
 | 
| 590 |  | -      const backup = this.getSettings();
 | 
|  | 824 | +    return this;
 | 
|  | 825 | +  },
 | 
| 591 | 826 |  
 | 
| 592 |  | -      try {
 | 
| 593 |  | -        this._settings.bridges.enabled = !!settings.bridges.enabled;
 | 
| 594 |  | -        this._settings.bridges.source = settings.bridges.source;
 | 
| 595 |  | -        switch (settings.bridges.source) {
 | 
| 596 |  | -          case TorBridgeSource.BridgeDB:
 | 
| 597 |  | -          case TorBridgeSource.UserProvided:
 | 
| 598 |  | -            this._settings.bridges.bridge_strings =
 | 
| 599 |  | -              settings.bridges.bridge_strings;
 | 
| 600 |  | -            break;
 | 
| 601 |  | -          case TorBridgeSource.BuiltIn: {
 | 
| 602 |  | -            this._settings.bridges.builtin_type = settings.bridges.builtin_type;
 | 
| 603 |  | -            settings.bridges.bridge_strings = getBuiltinBridgeStrings(
 | 
| 604 |  | -              settings.bridges.builtin_type
 | 
|  | 827 | +  // set all of our settings at once from a settings object
 | 
|  | 828 | +  setSettings(settings) {
 | 
|  | 829 | +    lazy.logger.debug("setSettings()");
 | 
|  | 830 | +    const backup = this.getSettings();
 | 
|  | 831 | +    const backup_notifications = [...this._notificationQueue];
 | 
|  | 832 | +
 | 
|  | 833 | +    // Hold off on lots of notifications until all settings are changed.
 | 
|  | 834 | +    this.freezeNotifications();
 | 
|  | 835 | +    try {
 | 
|  | 836 | +      this.bridges.enabled = !!settings.bridges.enabled;
 | 
|  | 837 | +      this.bridges.source = settings.bridges.source;
 | 
|  | 838 | +      switch (settings.bridges.source) {
 | 
|  | 839 | +        case TorBridgeSource.BridgeDB:
 | 
|  | 840 | +        case TorBridgeSource.UserProvided:
 | 
|  | 841 | +          this.bridges.bridge_strings = settings.bridges.bridge_strings;
 | 
|  | 842 | +          break;
 | 
|  | 843 | +        case TorBridgeSource.BuiltIn: {
 | 
|  | 844 | +          this.bridges.builtin_type = settings.bridges.builtin_type;
 | 
|  | 845 | +          if (!this.bridges.bridge_strings.length) {
 | 
|  | 846 | +            // No bridges were found when setting the builtin_type.
 | 
|  | 847 | +            throw new Error(
 | 
|  | 848 | +              `No available builtin bridges of type ${settings.bridges.builtin_type}`
 | 
| 605 | 849 |              );
 | 
| 606 |  | -            if (
 | 
| 607 |  | -              !settings.bridges.bridge_strings.length &&
 | 
| 608 |  | -              settings.bridges.enabled
 | 
| 609 |  | -            ) {
 | 
| 610 |  | -              throw new Error(
 | 
| 611 |  | -                `No available builtin bridges of type ${settings.bridges.builtin_type}`
 | 
| 612 |  | -              );
 | 
| 613 |  | -            }
 | 
| 614 |  | -            this._settings.bridges.bridge_strings =
 | 
| 615 |  | -              settings.bridges.bridge_strings;
 | 
| 616 |  | -            break;
 | 
| 617 | 850 |            }
 | 
| 618 |  | -          case TorBridgeSource.Invalid:
 | 
| 619 |  | -            break;
 | 
| 620 |  | -          default:
 | 
| 621 |  | -            if (settings.bridges.enabled) {
 | 
| 622 |  | -              throw new Error(
 | 
| 623 |  | -                `Bridge source '${settings.source}' is not a valid source`
 | 
| 624 |  | -              );
 | 
| 625 |  | -            }
 | 
| 626 |  | -            break;
 | 
|  | 851 | +          break;
 | 
| 627 | 852 |          }
 | 
| 628 |  | -
 | 
| 629 |  | -        // TODO: proxy and firewall
 | 
| 630 |  | -      } catch (ex) {
 | 
| 631 |  | -        this._settings = backup;
 | 
| 632 |  | -        console.log(`TorSettings: setSettings failed => ${ex.message}`);
 | 
| 633 |  | -      }
 | 
| 634 |  | -
 | 
| 635 |  | -      console.log("TorSettings: setSettings result");
 | 
| 636 |  | -      console.log(this._settings);
 | 
| 637 |  | -    },
 | 
| 638 |  | -
 | 
| 639 |  | -    // get a copy of all our settings
 | 
| 640 |  | -    getSettings() {
 | 
| 641 |  | -      console.log("TorSettings: getSettings()");
 | 
| 642 |  | -      // TODO: replace with structuredClone someday (post esr94): https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
 | 
| 643 |  | -      return JSON.parse(JSON.stringify(this._settings));
 | 
| 644 |  | -    },
 | 
| 645 |  | -
 | 
| 646 |  | -    /* Getters and Setters */
 | 
| 647 |  | -
 | 
| 648 |  | -    // Quickstart
 | 
| 649 |  | -    get quickstart() {
 | 
| 650 |  | -      return {
 | 
| 651 |  | -        get enabled() {
 | 
| 652 |  | -          return self._settings.quickstart.enabled;
 | 
| 653 |  | -        },
 | 
| 654 |  | -        set enabled(val) {
 | 
| 655 |  | -          if (val != self._settings.quickstart.enabled) {
 | 
| 656 |  | -            self._settings.quickstart.enabled = val;
 | 
| 657 |  | -            Services.obs.notifyObservers(
 | 
| 658 |  | -              { value: val },
 | 
| 659 |  | -              TorSettingsTopics.SettingChanged,
 | 
| 660 |  | -              TorSettingsData.QuickStartEnabled
 | 
|  | 853 | +        case TorBridgeSource.Invalid:
 | 
|  | 854 | +          break;
 | 
|  | 855 | +        default:
 | 
|  | 856 | +          if (settings.bridges.enabled) {
 | 
|  | 857 | +            throw new Error(
 | 
|  | 858 | +              `Bridge source '${settings.source}' is not a valid source`
 | 
| 661 | 859 |              );
 | 
| 662 | 860 |            }
 | 
| 663 |  | -        },
 | 
| 664 |  | -      };
 | 
| 665 |  | -    },
 | 
|  | 861 | +          break;
 | 
|  | 862 | +      }
 | 
| 666 | 863 |  
 | 
| 667 |  | -    // Bridges
 | 
| 668 |  | -    get bridges() {
 | 
| 669 |  | -      return {
 | 
| 670 |  | -        get enabled() {
 | 
| 671 |  | -          return self._settings.bridges.enabled;
 | 
| 672 |  | -        },
 | 
| 673 |  | -        set enabled(val) {
 | 
| 674 |  | -          self._settings.bridges.enabled = val;
 | 
| 675 |  | -        },
 | 
| 676 |  | -        get source() {
 | 
| 677 |  | -          return self._settings.bridges.source;
 | 
| 678 |  | -        },
 | 
| 679 |  | -        set source(val) {
 | 
| 680 |  | -          self._settings.bridges.source = val;
 | 
| 681 |  | -        },
 | 
| 682 |  | -        get builtin_type() {
 | 
| 683 |  | -          return self._settings.bridges.builtin_type;
 | 
| 684 |  | -        },
 | 
| 685 |  | -        set builtin_type(val) {
 | 
| 686 |  | -          const bridgeStrings = getBuiltinBridgeStrings(val);
 | 
| 687 |  | -          if (bridgeStrings.length) {
 | 
| 688 |  | -            self._settings.bridges.builtin_type = val;
 | 
| 689 |  | -            self._settings.bridges.bridge_strings = bridgeStrings;
 | 
| 690 |  | -          } else {
 | 
| 691 |  | -            self._settings.bridges.builtin_type = "";
 | 
| 692 |  | -            if (self._settings.bridges.source === TorBridgeSource.BuiltIn) {
 | 
| 693 |  | -              self._settings.bridges.source = TorBridgeSource.Invalid;
 | 
| 694 |  | -            }
 | 
| 695 |  | -          }
 | 
| 696 |  | -        },
 | 
| 697 |  | -        get bridge_strings() {
 | 
| 698 |  | -          return arrayCopy(self._settings.bridges.bridge_strings);
 | 
| 699 |  | -        },
 | 
| 700 |  | -        set bridge_strings(val) {
 | 
| 701 |  | -          self._settings.bridges.bridge_strings = parseBridgeStrings(val);
 | 
| 702 |  | -        },
 | 
| 703 |  | -      };
 | 
| 704 |  | -    },
 | 
|  | 864 | +      // TODO: proxy and firewall
 | 
|  | 865 | +    } catch (ex) {
 | 
|  | 866 | +      // Restore the old settings without any new notifications generated from
 | 
|  | 867 | +      // the above code.
 | 
|  | 868 | +      // NOTE: Since this code is not async, it should not be possible for
 | 
|  | 869 | +      // some other call to TorSettings to change anything whilst we are
 | 
|  | 870 | +      // in this context (other than lower down in this call stack), so it is
 | 
|  | 871 | +      // safe to discard all changes to settings and notifications.
 | 
|  | 872 | +      this._settings = backup;
 | 
|  | 873 | +      this._notificationQueue.clear();
 | 
|  | 874 | +      for (const notification of backup_notifications) {
 | 
|  | 875 | +        this._notificationQueue.add(notification);
 | 
|  | 876 | +      }
 | 
| 705 | 877 |  
 | 
| 706 |  | -    // Proxy
 | 
| 707 |  | -    get proxy() {
 | 
| 708 |  | -      return {
 | 
| 709 |  | -        get enabled() {
 | 
| 710 |  | -          return self._settings.proxy.enabled;
 | 
| 711 |  | -        },
 | 
| 712 |  | -        set enabled(val) {
 | 
| 713 |  | -          self._settings.proxy.enabled = val;
 | 
| 714 |  | -          // reset proxy settings
 | 
| 715 |  | -          self._settings.proxy.type = TorProxyType.Invalid;
 | 
| 716 |  | -          self._settings.proxy.address = null;
 | 
| 717 |  | -          self._settings.proxy.port = 0;
 | 
| 718 |  | -          self._settings.proxy.username = null;
 | 
| 719 |  | -          self._settings.proxy.password = null;
 | 
| 720 |  | -        },
 | 
| 721 |  | -        get type() {
 | 
| 722 |  | -          return self._settings.proxy.type;
 | 
| 723 |  | -        },
 | 
| 724 |  | -        set type(val) {
 | 
| 725 |  | -          self._settings.proxy.type = val;
 | 
| 726 |  | -        },
 | 
| 727 |  | -        get address() {
 | 
| 728 |  | -          return self._settings.proxy.address;
 | 
| 729 |  | -        },
 | 
| 730 |  | -        set address(val) {
 | 
| 731 |  | -          self._settings.proxy.address = val;
 | 
| 732 |  | -        },
 | 
| 733 |  | -        get port() {
 | 
| 734 |  | -          return arrayCopy(self._settings.proxy.port);
 | 
| 735 |  | -        },
 | 
| 736 |  | -        set port(val) {
 | 
| 737 |  | -          self._settings.proxy.port = parsePort(val);
 | 
| 738 |  | -        },
 | 
| 739 |  | -        get username() {
 | 
| 740 |  | -          return self._settings.proxy.username;
 | 
| 741 |  | -        },
 | 
| 742 |  | -        set username(val) {
 | 
| 743 |  | -          self._settings.proxy.username = val;
 | 
| 744 |  | -        },
 | 
| 745 |  | -        get password() {
 | 
| 746 |  | -          return self._settings.proxy.password;
 | 
| 747 |  | -        },
 | 
| 748 |  | -        set password(val) {
 | 
| 749 |  | -          self._settings.proxy.password = val;
 | 
| 750 |  | -        },
 | 
| 751 |  | -        get uri() {
 | 
| 752 |  | -          switch (this.type) {
 | 
| 753 |  | -            case TorProxyType.Socks4:
 | 
| 754 |  | -              return `socks4a://${this.address}:${this.port}`;
 | 
| 755 |  | -            case TorProxyType.Socks5:
 | 
| 756 |  | -              if (this.username) {
 | 
| 757 |  | -                return `socks5://${this.username}:${this.password}@${this.address}:${this.port}`;
 | 
| 758 |  | -              }
 | 
| 759 |  | -              return `socks5://${this.address}:${this.port}`;
 | 
| 760 |  | -            case TorProxyType.HTTPS:
 | 
| 761 |  | -              if (this._proxyUsername) {
 | 
| 762 |  | -                return `http://${this.username}:${this.password}@${this.address}:${this.port}`;
 | 
| 763 |  | -              }
 | 
| 764 |  | -              return `http://${this.address}:${this.port}`;
 | 
| 765 |  | -          }
 | 
| 766 |  | -          return null;
 | 
| 767 |  | -        },
 | 
| 768 |  | -      };
 | 
| 769 |  | -    },
 | 
|  | 878 | +      lazy.logger.error("setSettings failed", ex);
 | 
|  | 879 | +    } finally {
 | 
|  | 880 | +      this.thawNotifications();
 | 
|  | 881 | +    }
 | 
| 770 | 882 |  
 | 
| 771 |  | -    // Firewall
 | 
| 772 |  | -    get firewall() {
 | 
| 773 |  | -      return {
 | 
| 774 |  | -        get enabled() {
 | 
| 775 |  | -          return self._settings.firewall.enabled;
 | 
| 776 |  | -        },
 | 
| 777 |  | -        set enabled(val) {
 | 
| 778 |  | -          self._settings.firewall.enabled = val;
 | 
| 779 |  | -          // reset firewall settings
 | 
| 780 |  | -          self._settings.firewall.allowed_ports = [];
 | 
| 781 |  | -        },
 | 
| 782 |  | -        get allowed_ports() {
 | 
| 783 |  | -          return self._settings.firewall.allowed_ports;
 | 
| 784 |  | -        },
 | 
| 785 |  | -        set allowed_ports(val) {
 | 
| 786 |  | -          self._settings.firewall.allowed_ports = parsePortList(val);
 | 
| 787 |  | -        },
 | 
| 788 |  | -      };
 | 
| 789 |  | -    },
 | 
| 790 |  | -  };
 | 
| 791 |  | -  return self;
 | 
| 792 |  | -})(); | 
|  | 883 | +    lazy.logger.debug("setSettings result", this._settings);
 | 
|  | 884 | +  },
 | 
|  | 885 | +
 | 
|  | 886 | +  // get a copy of all our settings
 | 
|  | 887 | +  getSettings() {
 | 
|  | 888 | +    lazy.logger.debug("getSettings()");
 | 
|  | 889 | +    return structuredClone(this._settings);
 | 
|  | 890 | +  },
 | 
|  | 891 | +}; |