tor-browser

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

connectionSettingsDialog.js (11446B)


      1 "use strict";
      2 
      3 const { TorSettings, TorProxyType } = ChromeUtils.importESModule(
      4  "resource://gre/modules/TorSettings.sys.mjs"
      5 );
      6 
      7 const gConnectionSettingsDialog = {
      8  _acceptButton: null,
      9  _useProxyCheckbox: null,
     10  _proxyTypeLabel: null,
     11  _proxyTypeMenulist: null,
     12  _proxyAddressLabel: null,
     13  _proxyAddressTextbox: null,
     14  _proxyPortLabel: null,
     15  _proxyPortTextbox: null,
     16  _proxyUsernameLabel: null,
     17  _proxyUsernameTextbox: null,
     18  _proxyPasswordLabel: null,
     19  _proxyPasswordTextbox: null,
     20  _useFirewallCheckbox: null,
     21  _allowedPortsLabel: null,
     22  _allowedPortsTextbox: null,
     23 
     24  selectors: {
     25    useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy",
     26    proxyTypeLabel: "label#torPreferences-localProxy-type",
     27    proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
     28    proxyAddressLabel: "label#torPreferences-localProxy-address",
     29    proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
     30    proxyPortLabel: "label#torPreferences-localProxy-port",
     31    proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
     32    proxyUsernameLabel: "label#torPreferences-localProxy-username",
     33    proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
     34    proxyPasswordLabel: "label#torPreferences-localProxy-password",
     35    proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
     36    useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall",
     37    firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts",
     38    firewallAllowedPortsTextbox:
     39      "input#torPreferences-connection-textboxAllowedPorts",
     40  },
     41 
     42  /**
     43   * The "proxy" and "firewall" settings to pass on to TorSettings.
     44   *
     45   * Each group is `null` whilst the inputs are invalid.
     46   *
     47   * @type {{proxy: ?object, firewall: ?object}}
     48   */
     49  _settings: { proxy: null, firewall: null },
     50 
     51  init() {
     52    const currentSettings = TorSettings.getSettings();
     53 
     54    const dialog = document.getElementById("torPreferences-connection-dialog");
     55    dialog.addEventListener("dialogaccept", event => {
     56      if (!this._settings.proxy || !this._settings.firewall) {
     57        // Do not close yet.
     58        event.preventDefault();
     59        return;
     60      }
     61      // TODO: Maybe wait for the method to resolve before closing. Although
     62      // this can take a few seconds. See tor-browser#43467.
     63      TorSettings.changeSettings(this._settings);
     64    });
     65    this._acceptButton = dialog.getButton("accept");
     66 
     67    const selectors = this.selectors;
     68 
     69    // Local Proxy
     70    this._useProxyCheckbox = document.querySelector(selectors.useProxyCheckbox);
     71    this._useProxyCheckbox.checked = currentSettings.proxy.enabled;
     72    this._useProxyCheckbox.addEventListener("command", () => {
     73      this.updateProxyType();
     74    });
     75 
     76    this._proxyTypeLabel = document.querySelector(selectors.proxyTypeLabel);
     77 
     78    const mockProxies = [
     79      {
     80        value: TorProxyType.Socks4,
     81        l10nId: "tor-advanced-dialog-proxy-socks4-menuitem",
     82      },
     83      {
     84        value: TorProxyType.Socks5,
     85        l10nId: "tor-advanced-dialog-proxy-socks5-menuitem",
     86      },
     87      {
     88        value: TorProxyType.HTTPS,
     89        l10nId: "tor-advanced-dialog-proxy-http-menuitem",
     90      },
     91    ];
     92    this._proxyTypeMenulist = document.querySelector(selectors.proxyTypeList);
     93    this._proxyTypeMenulist.addEventListener("command", () => {
     94      this.updateProxyType();
     95    });
     96    for (const currentProxy of mockProxies) {
     97      let menuEntry = window.document.createXULElement("menuitem");
     98      menuEntry.setAttribute("value", currentProxy.value);
     99      menuEntry.setAttribute("data-l10n-id", currentProxy.l10nId);
    100      this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry);
    101    }
    102    this._proxyTypeMenulist.value = currentSettings.proxy.enabled
    103      ? currentSettings.proxy.type
    104      : "";
    105 
    106    this._proxyAddressLabel = document.querySelector(
    107      selectors.proxyAddressLabel
    108    );
    109    this._proxyAddressTextbox = document.querySelector(
    110      selectors.proxyAddressTextbox
    111    );
    112    this._proxyAddressTextbox.addEventListener("blur", () => {
    113      // If the address includes a port move it to the port input instead.
    114      let value = this._proxyAddressTextbox.value.trim();
    115      let colon = value.lastIndexOf(":");
    116      if (colon != -1) {
    117        let maybePort = this.parsePort(value.substr(colon + 1));
    118        if (maybePort !== null) {
    119          this._proxyAddressTextbox.value = value.substr(0, colon);
    120          this._proxyPortTextbox.value = maybePort;
    121          this.updateProxy();
    122        }
    123      }
    124    });
    125    this._proxyPortLabel = document.querySelector(selectors.proxyPortLabel);
    126    this._proxyPortTextbox = document.querySelector(selectors.proxyPortTextbox);
    127    this._proxyUsernameLabel = document.querySelector(
    128      selectors.proxyUsernameLabel
    129    );
    130    this._proxyUsernameTextbox = document.querySelector(
    131      selectors.proxyUsernameTextbox
    132    );
    133    this._proxyPasswordLabel = document.querySelector(
    134      selectors.proxyPasswordLabel
    135    );
    136    this._proxyPasswordTextbox = document.querySelector(
    137      selectors.proxyPasswordTextbox
    138    );
    139 
    140    if (currentSettings.proxy.enabled) {
    141      this._proxyAddressTextbox.value = currentSettings.proxy.address;
    142      this._proxyPortTextbox.value = currentSettings.proxy.port;
    143      this._proxyUsernameTextbox.value = currentSettings.proxy.username;
    144      this._proxyPasswordTextbox.value = currentSettings.proxy.password;
    145    } else {
    146      this._proxyAddressTextbox.value = "";
    147      this._proxyPortTextbox.value = "";
    148      this._proxyUsernameTextbox.value = "";
    149      this._proxyPasswordTextbox.value = "";
    150    }
    151 
    152    for (const el of [
    153      this._proxyAddressTextbox,
    154      this._proxyPortTextbox,
    155      this._proxyUsernameTextbox,
    156      this._proxyPasswordTextbox,
    157    ]) {
    158      el.addEventListener("input", () => {
    159        this.updateProxy();
    160      });
    161    }
    162 
    163    // Local firewall
    164    this._useFirewallCheckbox = document.querySelector(
    165      selectors.useFirewallCheckbox
    166    );
    167    this._useFirewallCheckbox.checked = currentSettings.firewall.enabled;
    168    this._useFirewallCheckbox.addEventListener("command", () => {
    169      this.updateFirewallEnabled();
    170    });
    171    this._allowedPortsLabel = document.querySelector(
    172      selectors.firewallAllowedPortsLabel
    173    );
    174    this._allowedPortsTextbox = document.querySelector(
    175      selectors.firewallAllowedPortsTextbox
    176    );
    177    this._allowedPortsTextbox.value = currentSettings.firewall.enabled
    178      ? currentSettings.firewall.allowed_ports.join(",")
    179      : "80,443";
    180 
    181    this._allowedPortsTextbox.addEventListener("input", () => {
    182      this.updateFirewall();
    183    });
    184 
    185    this.updateProxyType();
    186    this.updateFirewallEnabled();
    187  },
    188 
    189  /**
    190   * Convert a string into a port number.
    191   *
    192   * @param {string} portStr - The string to convert.
    193   * @returns {?integer} - The port number, or `null` if the given string could
    194   *   not be converted.
    195   */
    196  parsePort(portStr) {
    197    const portRegex = /^[1-9][0-9]*$/; // Strictly-positive decimal integer.
    198    if (!portRegex.test(portStr)) {
    199      return null;
    200    }
    201    const port = parseInt(portStr, 10);
    202    if (TorSettings.validPort(port)) {
    203      return port;
    204    }
    205    return null;
    206  },
    207 
    208  /**
    209   * Update the disabled state of the accept button.
    210   */
    211  updateAcceptButton() {
    212    this._acceptButton.disabled =
    213      !this._settings.proxy || !this._settings.firewall;
    214  },
    215 
    216  /**
    217   * Update the UI when the proxy setting is enabled or disabled, or the proxy
    218   * type changes.
    219   */
    220  updateProxyType() {
    221    const enabled = this._useProxyCheckbox.checked;
    222    const haveType = enabled && Boolean(this._proxyTypeMenulist.value);
    223    const type = parseInt(this._proxyTypeMenulist.value, 10);
    224 
    225    this._proxyTypeLabel.disabled = !enabled;
    226    this._proxyTypeMenulist.disabled = !enabled;
    227    this._proxyAddressLabel.disabled = !haveType;
    228    this._proxyAddressTextbox.disabled = !haveType;
    229    this._proxyPortLabel.disabled = !haveType;
    230    this._proxyPortTextbox.disabled = !haveType;
    231    this._proxyUsernameTextbox.disabled =
    232      !haveType || type === TorProxyType.Socks4;
    233    this._proxyPasswordTextbox.disabled =
    234      !haveType || type === TorProxyType.Socks4;
    235    if (type === TorProxyType.Socks4) {
    236      // Clear unused value.
    237      this._proxyUsernameTextbox.value = "";
    238      this._proxyPasswordTextbox.value = "";
    239    }
    240 
    241    this.updateProxy();
    242  },
    243 
    244  /**
    245   * Update the dialog's stored proxy values.
    246   */
    247  updateProxy() {
    248    if (this._useProxyCheckbox.checked) {
    249      const typeStr = this._proxyTypeMenulist.value;
    250      const type = parseInt(typeStr, 10);
    251      // TODO: Validate the address. See tor-browser#43467.
    252      const address = this._proxyAddressTextbox.value;
    253      const portStr = this._proxyPortTextbox.value;
    254      const username =
    255        type === TorProxyType.Socks4 ? "" : this._proxyUsernameTextbox.value;
    256      const password =
    257        type === TorProxyType.Socks4 ? "" : this._proxyPasswordTextbox.value;
    258      const port = parseInt(portStr, 10);
    259      if (
    260        !typeStr ||
    261        !address ||
    262        !portStr ||
    263        !TorSettings.validPort(port) ||
    264        // SOCKS5 needs either both username and password, or neither.
    265        (type === TorProxyType.Socks5 &&
    266          !TorSettings.validSocks5Credentials(username, password))
    267      ) {
    268        // Invalid.
    269        this._settings.proxy = null;
    270      } else {
    271        this._settings.proxy = {
    272          enabled: true,
    273          type,
    274          address,
    275          port,
    276          username,
    277          password,
    278        };
    279      }
    280    } else {
    281      this._settings.proxy = { enabled: false };
    282    }
    283 
    284    this.updateAcceptButton();
    285  },
    286 
    287  /**
    288   * Update the UI when the firewall setting is enabled or disabled.
    289   */
    290  updateFirewallEnabled() {
    291    const enabled = this._useFirewallCheckbox.checked;
    292    this._allowedPortsLabel.disabled = !enabled;
    293    this._allowedPortsTextbox.disabled = !enabled;
    294 
    295    this.updateFirewall();
    296  },
    297 
    298  /**
    299   * Update the dialog's stored firewall values.
    300   */
    301  updateFirewall() {
    302    if (this._useFirewallCheckbox.checked) {
    303      const portList = [];
    304      let listInvalid = false;
    305      for (const portStr of this._allowedPortsTextbox.value.split(
    306        /(?:\s*,\s*)+/g
    307      )) {
    308        if (!portStr) {
    309          // Trailing or leading comma.
    310          continue;
    311        }
    312        const port = this.parsePort(portStr);
    313        if (port === null) {
    314          listInvalid = true;
    315          break;
    316        }
    317        portList.push(port);
    318      }
    319      if (!listInvalid && portList.length) {
    320        this._settings.firewall = {
    321          enabled: true,
    322          allowed_ports: portList,
    323        };
    324      } else {
    325        this._settings.firewall = null;
    326      }
    327    } else {
    328      this._settings.firewall = { enabled: false };
    329    }
    330 
    331    this.updateAcceptButton();
    332  },
    333 };
    334 
    335 // Initial focus is not visible, even if opened with a keyboard. We avoid the
    336 // default handler and manage the focus ourselves, which will paint the focus
    337 // ring by default.
    338 // NOTE: A side effect is that the focus ring will show even if the user opened
    339 // with a mouse event.
    340 // TODO: Remove this once bugzilla bug 1708261 is resolved.
    341 document.subDialogSetDefaultFocus = () => {
    342  document.getElementById("torPreferences-connection-toggleProxy").focus();
    343 };
    344 
    345 window.addEventListener(
    346  "DOMContentLoaded",
    347  () => {
    348    gConnectionSettingsDialog.init();
    349  },
    350  { once: true }
    351 );