[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [tor-browser] 40/73: Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#tor
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.0-1
in repository tor-browser.
commit af1522e9c26d2388a77154b061bf1f56e3bcb225
Author: Richard Pospesel <richard@xxxxxxxxxxxxxx>
AuthorDate: Mon Sep 16 15:25:39 2019 -0700
Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#tor
This patch adds a new about:preferences#tor page which allows modifying
bridge, proxy, and firewall settings from within Tor Browser. All of the
functionality present in tor-launcher's Network Configuration panel is
present:
- Setting built-in bridges
- Requesting bridges from BridgeDB via moat
- Using user-provided bridges
- Configuring SOCKS4, SOCKS5, and HTTP/HTTPS proxies
- Setting firewall ports
- Viewing and Copying Tor's logs
- The Networking Settings in General preferences has been removed
---
browser/components/moz.build | 1 +
browser/components/preferences/main.inc.xhtml | 54 --
browser/components/preferences/main.js | 14 -
browser/components/preferences/preferences.js | 9 +
browser/components/preferences/preferences.xhtml | 5 +
browser/components/preferences/privacy.js | 1 +
.../torpreferences/content/parseFunctions.jsm | 89 +++
.../torpreferences/content/requestBridgeDialog.jsm | 204 +++++
.../content/requestBridgeDialog.xhtml | 35 +
.../torpreferences/content/torBridgeSettings.jsm | 325 ++++++++
.../torpreferences/content/torCategory.inc.xhtml | 9 +
.../torpreferences/content/torFirewallSettings.jsm | 72 ++
.../torpreferences/content/torLogDialog.jsm | 66 ++
.../torpreferences/content/torLogDialog.xhtml | 23 +
.../components/torpreferences/content/torPane.js | 857 +++++++++++++++++++++
.../torpreferences/content/torPane.xhtml | 123 +++
.../torpreferences/content/torPreferences.css | 77 ++
.../torpreferences/content/torPreferencesIcon.svg | 8 +
.../torpreferences/content/torProxySettings.jsm | 245 ++++++
browser/components/torpreferences/jar.mn | 14 +
browser/components/torpreferences/moz.build | 1 +
browser/modules/BridgeDB.jsm | 110 +++
browser/modules/TorProtocolService.jsm | 323 ++++++++
browser/modules/moz.build | 2 +
24 files changed, 2599 insertions(+), 68 deletions(-)
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 57ec3c51c5e99..b409974a965cc 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -54,6 +54,7 @@ DIRS += [
"syncedtabs",
"uitour",
"urlbar",
+ "torpreferences",
"translation",
]
diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml
index a89b89f723a80..594711e614747 100644
--- a/browser/components/preferences/main.inc.xhtml
+++ b/browser/components/preferences/main.inc.xhtml
@@ -671,58 +671,4 @@
<label id="cfrFeaturesLearnMore" class="learnMore" data-l10n-id="browsing-cfr-recommendations-learn-more" is="text-link"/>
</hbox>
</groupbox>
-
-<hbox id="networkProxyCategory"
- class="subcategory"
- hidden="true"
- data-category="paneGeneral">
- <html:h1 data-l10n-id="network-settings-title"/>
-</hbox>
-
-<!-- Network Settings-->
-<groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
- <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label>
-
- <hbox align="center">
- <hbox align="center" flex="1">
- <description id="connectionSettingsDescription" control="connectionSettings"/>
- <spacer width="5"/>
- <label id="connectionSettingsLearnMore" class="learnMore" is="text-link"
- data-l10n-id="network-proxy-connection-learn-more">
- </label>
- <separator orient="vertical"/>
- </hbox>
-
- <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
- <hbox>
- <button id="connectionSettings"
- is="highlightable-button"
- class="accessory-button"
- data-l10n-id="network-proxy-connection-settings"
- searchkeywords="doh trr"
- search-l10n-ids="
- connection-window.title,
- connection-proxy-option-no.label,
- connection-proxy-option-auto.label,
- connection-proxy-option-system.label,
- connection-proxy-option-manual.label,
- connection-proxy-http,
- connection-proxy-https,
- connection-proxy-http-port,
- connection-proxy-socks,
- connection-proxy-socks4,
- connection-proxy-socks5,
- connection-proxy-noproxy,
- connection-proxy-noproxy-desc,
- connection-proxy-https-sharing.label,
- connection-proxy-autotype.label,
- connection-proxy-reload.label,
- connection-proxy-autologin.label,
- connection-proxy-socks-remote-dns.label,
- connection-dns-over-https.label,
- connection-dns-over-https-url-custom.label,
- " />
- </hbox>
- </hbox>
-</groupbox>
</html:template>
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
index 2a6ba4a3d8e42..501ba9144a314 100644
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -368,15 +368,6 @@ var gMainPane = {
});
this.updatePerformanceSettingsBox({ duringChangeEvent: false });
this.displayUseSystemLocale();
- let connectionSettingsLink = document.getElementById(
- "connectionSettingsLearnMore"
- );
- let connectionSettingsUrl =
- Services.urlFormatter.formatURLPref("app.support.baseURL") +
- "prefs-connection-settings";
- connectionSettingsLink.setAttribute("href", connectionSettingsUrl);
- this.updateProxySettingsUI();
- initializeProxyUI(gMainPane);
if (Services.prefs.getBoolPref("intl.multilingual.enabled")) {
gMainPane.initBrowserLocale();
@@ -510,11 +501,6 @@ var gMainPane = {
"change",
gMainPane.updateHardwareAcceleration.bind(gMainPane)
);
- setEventListener(
- "connectionSettings",
- "command",
- gMainPane.showConnections
- );
setEventListener(
"browserContainersCheckbox",
"command",
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index 91e9e469cea26..a89fddd0306dc 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -13,6 +13,7 @@
/* import-globals-from findInPage.js */
/* import-globals-from ../../base/content/utilityOverlay.js */
/* import-globals-from ../../../toolkit/content/preferencesBindings.js */
+/* import-globals-from ../torpreferences/content/torPane.js */
"use strict";
@@ -136,6 +137,14 @@ function init_all() {
register_module("paneSync", gSyncPane);
}
register_module("paneSearchResults", gSearchResultsPane);
+ if (gTorPane.enabled) {
+ document.getElementById("category-tor").hidden = false;
+ register_module("paneTor", gTorPane);
+ } else {
+ // Remove the pane from the DOM so it doesn't get incorrectly included in search results.
+ document.getElementById("template-paneTor").remove();
+ }
+
gSearchResultsPane.init();
gMainPane.preInit();
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 2d29b382350d3..0139abf95cbd3 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -13,6 +13,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [
<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
@@ -155,6 +156,9 @@
<image class="category-icon"/>
<label class="category-name" flex="1" data-l10n-id="pane-experimental-title"></label>
</richlistitem>
+
+#include ../torpreferences/content/torCategory.inc.xhtml
+
</richlistbox>
<spacer flex="1"/>
@@ -208,6 +212,7 @@
#include containers.inc.xhtml
#include sync.inc.xhtml
#include experimental.inc.xhtml
+#include ../torpreferences/content/torPane.xhtml
</vbox>
</vbox>
</vbox>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 42b899e0552fc..bce7bb7e8a9c6 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -80,6 +80,7 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
}
});
+// TODO: module import via ChromeUtils.defineModuleGetter
XPCOMUtils.defineLazyScriptGetter(
this,
["SecurityLevelPreferences"],
diff --git a/browser/components/torpreferences/content/parseFunctions.jsm b/browser/components/torpreferences/content/parseFunctions.jsm
new file mode 100644
index 0000000000000..954759de63a5e
--- /dev/null
+++ b/browser/components/torpreferences/content/parseFunctions.jsm
@@ -0,0 +1,89 @@
+"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/requestBridgeDialog.jsm b/browser/components/torpreferences/content/requestBridgeDialog.jsm
new file mode 100644
index 0000000000000..807d46cdfb18d
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm
@@ -0,0 +1,204 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["RequestBridgeDialog"];
+
+const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm");
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class RequestBridgeDialog {
+ constructor() {
+ this._dialog = null;
+ this._submitButton = null;
+ this._dialogDescription = null;
+ this._captchaImage = null;
+ this._captchaEntryTextbox = null;
+ this._captchaRefreshButton = null;
+ this._incorrectCaptchaHbox = null;
+ this._incorrectCaptchaLabel = null;
+ this._bridges = [];
+ this._proxyURI = null;
+ }
+
+ static get selectors() {
+ return {
+ submitButton:
+ "accept" /* not really a selector but a key for dialog's getButton */,
+ dialogDescription: "description#torPreferences-requestBridge-description",
+ captchaImage: "image#torPreferences-requestBridge-captchaImage",
+ captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
+ refreshCaptchaButton:
+ "button#torPreferences-requestBridge-refreshCaptchaButton",
+ incorrectCaptchaHbox:
+ "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
+ incorrectCaptchaLabel:
+ "label#torPreferences-requestBridge-incorrectCaptchaError",
+ };
+ }
+
+ _populateXUL(dialog) {
+ const selectors = RequestBridgeDialog.selectors;
+
+ this._dialog = dialog;
+ const dialogWin = dialog.parentElement;
+ dialogWin.setAttribute(
+ "title",
+ TorStrings.settings.requestBridgeDialogTitle
+ );
+ // user may have opened a Request Bridge dialog in another tab, so update the
+ // CAPTCHA image or close out the dialog if we have a bridge list
+ this._dialog.addEventListener("focusin", () => {
+ const uri = BridgeDB.currentCaptchaImage;
+ const bridges = BridgeDB.currentBridges;
+
+ // new captcha image
+ if (uri) {
+ this._setcaptchaImage(uri);
+ } else if (bridges) {
+ this._bridges = bridges;
+ this._submitButton.disabled = false;
+ this._dialog.cancelDialog();
+ }
+ });
+
+ this._submitButton = this._dialog.getButton(selectors.submitButton);
+ this._submitButton.setAttribute("label", TorStrings.settings.submitCaptcha);
+ this._submitButton.disabled = true;
+ this._dialog.addEventListener("dialogaccept", e => {
+ e.preventDefault();
+ this.onSubmitCaptcha();
+ });
+
+ this._dialogDescription = this._dialog.querySelector(
+ selectors.dialogDescription
+ );
+ this._dialogDescription.textContent =
+ TorStrings.settings.contactingBridgeDB;
+
+ this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
+
+ // request captcha from bridge db
+ BridgeDB.requestNewCaptchaImage(this._proxyURI).then(uri => {
+ this._setcaptchaImage(uri);
+ });
+
+ this._captchaEntryTextbox = this._dialog.querySelector(
+ selectors.captchaEntryTextbox
+ );
+ this._captchaEntryTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.captchaTextboxPlaceholder
+ );
+ this._captchaEntryTextbox.disabled = true;
+ // disable submit if entry textbox is empty
+ this._captchaEntryTextbox.oninput = () => {
+ this._submitButton.disabled = this._captchaEntryTextbox.value == "";
+ };
+
+ this._captchaRefreshButton = this._dialog.querySelector(
+ selectors.refreshCaptchaButton
+ );
+ this._captchaRefreshButton.disabled = true;
+
+ this._incorrectCaptchaHbox = this._dialog.querySelector(
+ selectors.incorrectCaptchaHbox
+ );
+ this._incorrectCaptchaLabel = this._dialog.querySelector(
+ selectors.incorrectCaptchaLabel
+ );
+ this._incorrectCaptchaLabel.setAttribute(
+ "value",
+ TorStrings.settings.incorrectCaptcha
+ );
+
+ return true;
+ }
+
+ _setcaptchaImage(uri) {
+ if (uri != this._captchaImage.src) {
+ this._captchaImage.src = uri;
+ this._dialogDescription.textContent = TorStrings.settings.solveTheCaptcha;
+ this._setUIDisabled(false);
+ this._captchaEntryTextbox.focus();
+ this._captchaEntryTextbox.select();
+ }
+ }
+
+ _setUIDisabled(disabled) {
+ this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled;
+ this._captchaEntryTextbox.disabled = disabled;
+ this._captchaRefreshButton.disabled = disabled;
+ }
+
+ _captchaGuessIsEmpty() {
+ return this._captchaEntryTextbox.value == "";
+ }
+
+ init(window, dialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(dialog);
+ }, 0);
+ }
+
+ close() {
+ BridgeDB.close();
+ }
+
+ /*
+ Event Handlers
+ */
+ onSubmitCaptcha() {
+ let captchaText = this._captchaEntryTextbox.value.trim();
+ // noop if the field is empty
+ if (captchaText == "") {
+ return;
+ }
+
+ // freeze ui while we make request
+ this._setUIDisabled(true);
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.submitCaptchaGuess(captchaText)
+ .then(aBridges => {
+ this._bridges = aBridges;
+
+ this._submitButton.disabled = false;
+ // This was successful, but use cancelDialog() to close, since
+ // we intercept the `dialogaccept` event.
+ this._dialog.cancelDialog();
+ })
+ .catch(aError => {
+ this._bridges = [];
+ this._setUIDisabled(false);
+ this._incorrectCaptchaHbox.style.visibility = "visible";
+ });
+ }
+
+ onRefreshCaptcha() {
+ this._setUIDisabled(true);
+ this._captchaImage.src = "";
+ this._dialogDescription.textContent =
+ TorStrings.settings.contactingBridgeDB;
+ this._captchaEntryTextbox.value = "";
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.requestNewCaptchaImage(this._proxyURI).then(uri => {
+ this._setcaptchaImage(uri);
+ });
+ }
+
+ openDialog(gSubDialog, aProxyURI, aCloseCallback) {
+ this._proxyURI = aProxyURI;
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ this.close();
+ aCloseCallback(this._bridges);
+ }
+ },
+ this,
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
new file mode 100644
index 0000000000000..64c4507807fbf
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-requestBridge-dialog"
+ buttons="accept,cancel">
+ <!-- ok, so ​ is a zero-width space. We need to have *something* in the innerText so that XUL knows how tall the
+ description node is so that it can determine how large to make the dialog element's inner draw area. If we have
+ nothing in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( -->
+ <description id="torPreferences-requestBridge-description">​</description>
+ <!-- init to transparent 400x125 png -->
+ <image id="torPreferences-requestBridge-captchaImage" flex="1"/>
+ <hbox id="torPreferences-requestBridge-inputHbox">
+ <html:input id="torPreferences-requestBridge-captchaTextbox" type="text" style="-moz-box-flex: 1;"/>
+ <button id="torPreferences-requestBridge-refreshCaptchaButton"
+ image="chrome://browser/skin/reload.svg"
+ oncommand="requestBridgeDialog.onRefreshCaptcha();"/>
+ </hbox>
+ <hbox id="torPreferences-requestBridge-incorrectCaptchaHbox" align="center">
+ <image id="torPreferences-requestBridge-errorIcon" />
+ <label id="torPreferences-requestBridge-incorrectCaptchaError" flex="1"/>
+ </hbox>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let requestBridgeDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-requestBridge-dialog");
+ requestBridgeDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torBridgeSettings.jsm b/browser/components/torpreferences/content/torBridgeSettings.jsm
new file mode 100644
index 0000000000000..ceb61d3ec9720
--- /dev/null
+++ b/browser/components/torpreferences/content/torBridgeSettings.jsm
@@ -0,0 +1,325 @@
+"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/torCategory.inc.xhtml b/browser/components/torpreferences/content/torCategory.inc.xhtml
new file mode 100644
index 0000000000000..abe56200f571b
--- /dev/null
+++ b/browser/components/torpreferences/content/torCategory.inc.xhtml
@@ -0,0 +1,9 @@
+<richlistitem id="category-tor"
+ class="category"
+ value="paneTor"
+ helpTopic="prefs-tor"
+ align="center"
+ hidden="true">
+ <image class="category-icon"/>
+ <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Tor"/>
+</richlistitem>
diff --git a/browser/components/torpreferences/content/torFirewallSettings.jsm b/browser/components/torpreferences/content/torFirewallSettings.jsm
new file mode 100644
index 0000000000000..e77f18ef2fae8
--- /dev/null
+++ b/browser/components/torpreferences/content/torFirewallSettings.jsm
@@ -0,0 +1,72 @@
+"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/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm
new file mode 100644
index 0000000000000..ecc684d878c20
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.jsm
@@ -0,0 +1,66 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorLogDialog"];
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class TorLogDialog {
+ constructor() {
+ this._dialog = null;
+ this._logTextarea = null;
+ this._copyLogButton = null;
+ }
+
+ static get selectors() {
+ return {
+ copyLogButton: "extra1",
+ logTextarea: "textarea#torPreferences-torDialog-textarea",
+ };
+ }
+
+ _populateXUL(aDialog) {
+ this._dialog = aDialog;
+ const dialogWin = this._dialog.parentElement;
+ dialogWin.setAttribute("title", TorStrings.settings.torLogDialogTitle);
+
+ this._logTextarea = this._dialog.querySelector(
+ TorLogDialog.selectors.logTextarea
+ );
+
+ this._copyLogButton = this._dialog.getButton(
+ TorLogDialog.selectors.copyLogButton
+ );
+ this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
+ this._copyLogButton.addEventListener("command", () => {
+ this.copyTorLog();
+ });
+
+ this._logTextarea.value = TorProtocolService.getLog();
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(aDialog);
+ }, 0);
+ }
+
+ copyTorLog() {
+ // Copy tor log messages to the system clipboard.
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(this._logTextarea.value);
+ }
+
+ openDialog(gSubDialog) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/torLogDialog.xhtml",
+ { features: "resizable=yes" },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/torLogDialog.xhtml b/browser/components/torpreferences/content/torLogDialog.xhtml
new file mode 100644
index 0000000000000..9c17f8132978d
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-torLog-dialog"
+ buttons="accept,extra1">
+ <html:textarea
+ id="torPreferences-torDialog-textarea"
+ multiline="true"
+ readonly="true"/>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let torLogDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-torLog-dialog");
+ torLogDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
new file mode 100644
index 0000000000000..49054b5dac6a9
--- /dev/null
+++ b/browser/components/torpreferences/content/torPane.js
@@ -0,0 +1,857 @@
+"use strict";
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.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"
+);
+
+const { RequestBridgeDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+const { parsePort, parseBridgeStrings, parsePortList } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/parseFunctions.jsm"
+);
+
+/*
+ Tor Pane
+
+ Code for populating the XUL in about:preferences#tor, handling input events, interfacing with tor-launcher
+*/
+const gTorPane = (function() {
+ /* CSS selectors for all of the Tor Network DOM elements we need to access */
+ const selectors = {
+ category: {
+ title: "label#torPreferences-labelCategory",
+ },
+ torPreferences: {
+ header: "h1#torPreferences-header",
+ description: "span#torPreferences-description",
+ learnMore: "label#torPreferences-learnMore",
+ },
+ bridges: {
+ header: "h2#torPreferences-bridges-header",
+ description: "span#torPreferences-bridges-description",
+ learnMore: "label#torPreferences-bridges-learnMore",
+ useBridgeCheckbox: "checkbox#torPreferences-bridges-toggle",
+ bridgeSelectionRadiogroup:
+ "radiogroup#torPreferences-bridges-bridgeSelection",
+ builtinBridgeOption: "radio#torPreferences-bridges-radioBuiltin",
+ builtinBridgeList: "menulist#torPreferences-bridges-builtinList",
+ requestBridgeOption: "radio#torPreferences-bridges-radioRequestBridge",
+ requestBridgeButton: "button#torPreferences-bridges-buttonRequestBridge",
+ requestBridgeTextarea:
+ "textarea#torPreferences-bridges-textareaRequestBridge",
+ provideBridgeOption: "radio#torPreferences-bridges-radioProvideBridge",
+ provideBridgeDescription:
+ "description#torPreferences-bridges-descriptionProvideBridge",
+ provideBridgeTextarea:
+ "textarea#torPreferences-bridges-textareaProvideBridge",
+ },
+ advanced: {
+ header: "h2#torPreferences-advanced-header",
+ description: "span#torPreferences-advanced-description",
+ learnMore: "label#torPreferences-advanced-learnMore",
+ useProxyCheckbox: "checkbox#torPreferences-advanced-toggleProxy",
+ proxyTypeLabel: "label#torPreferences-localProxy-type",
+ proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
+ proxyAddressLabel: "label#torPreferences-localProxy-address",
+ proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
+ proxyPortLabel: "label#torPreferences-localProxy-port",
+ proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
+ proxyUsernameLabel: "label#torPreferences-localProxy-username",
+ proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
+ proxyPasswordLabel: "label#torPreferences-localProxy-password",
+ proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
+ useFirewallCheckbox: "checkbox#torPreferences-advanced-toggleFirewall",
+ firewallAllowedPortsLabel: "label#torPreferences-advanced-allowedPorts",
+ firewallAllowedPortsTextbox:
+ "input#torPreferences-advanced-textboxAllowedPorts",
+ torLogsLabel: "label#torPreferences-torLogs",
+ torLogsButton: "button#torPreferences-buttonTorLogs",
+ },
+ }; /* selectors */
+
+ let retval = {
+ // cached frequently accessed DOM elements
+ _useBridgeCheckbox: null,
+ _bridgeSelectionRadiogroup: null,
+ _builtinBridgeOption: null,
+ _builtinBridgeMenulist: null,
+ _requestBridgeOption: null,
+ _requestBridgeButton: null,
+ _requestBridgeTextarea: null,
+ _provideBridgeOption: null,
+ _provideBridgeTextarea: null,
+ _useProxyCheckbox: null,
+ _proxyTypeLabel: null,
+ _proxyTypeMenulist: null,
+ _proxyAddressLabel: null,
+ _proxyAddressTextbox: null,
+ _proxyPortLabel: null,
+ _proxyPortTextbox: null,
+ _proxyUsernameLabel: null,
+ _proxyUsernameTextbox: null,
+ _proxyPasswordLabel: null,
+ _proxyPasswordTextbox: null,
+ _useFirewallCheckbox: null,
+ _allowedPortsLabel: null,
+ _allowedPortsTextbox: null,
+
+ // tor network settings
+ _bridgeSettings: null,
+ _proxySettings: null,
+ _firewallSettings: null,
+
+ // disables the provided list of elements
+ _setElementsDisabled(elements, disabled) {
+ for (let currentElement of elements) {
+ currentElement.disabled = disabled;
+ }
+ },
+
+ // populate xul with strings and cache the relevant elements
+ _populateXUL() {
+ // saves tor settings to disk when navigate away from about:preferences
+ window.addEventListener("blur", val => {
+ TorProtocolService.flushSettings();
+ });
+
+ document
+ .querySelector(selectors.category.title)
+ .setAttribute("value", TorStrings.settings.categoryTitle);
+
+ let prefpane = document.getElementById("mainPrefPane");
+
+ // Heading
+ prefpane.querySelector(selectors.torPreferences.header).innerText =
+ TorStrings.settings.torPreferencesHeading;
+ prefpane.querySelector(selectors.torPreferences.description).textContent =
+ TorStrings.settings.torPreferencesDescription;
+ {
+ let learnMore = prefpane.querySelector(
+ selectors.torPreferences.learnMore
+ );
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute(
+ "href",
+ TorStrings.settings.learnMoreTorBrowserURL
+ );
+ }
+
+ // Bridge setup
+ prefpane.querySelector(selectors.bridges.header).innerText =
+ TorStrings.settings.bridgesHeading;
+ prefpane.querySelector(selectors.bridges.description).textContent =
+ TorStrings.settings.bridgesDescription;
+ {
+ let learnMore = prefpane.querySelector(selectors.bridges.learnMore);
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL);
+ }
+
+ this._useBridgeCheckbox = prefpane.querySelector(
+ selectors.bridges.useBridgeCheckbox
+ );
+ this._useBridgeCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useBridge
+ );
+ this._useBridgeCheckbox.addEventListener("command", e => {
+ const checked = this._useBridgeCheckbox.checked;
+ gTorPane.onToggleBridge(checked).onUpdateBridgeSettings();
+ });
+ this._bridgeSelectionRadiogroup = prefpane.querySelector(
+ selectors.bridges.bridgeSelectionRadiogroup
+ );
+ this._bridgeSelectionRadiogroup.value = TorBridgeSource.BUILTIN;
+ this._bridgeSelectionRadiogroup.addEventListener("command", e => {
+ const value = this._bridgeSelectionRadiogroup.value;
+ gTorPane.onSelectBridgeOption(value).onUpdateBridgeSettings();
+ });
+
+ // Builtin bridges
+ this._builtinBridgeOption = prefpane.querySelector(
+ selectors.bridges.builtinBridgeOption
+ );
+ this._builtinBridgeOption.setAttribute(
+ "label",
+ TorStrings.settings.selectBridge
+ );
+ this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BUILTIN);
+ this._builtinBridgeMenulist = prefpane.querySelector(
+ selectors.bridges.builtinBridgeList
+ );
+ this._builtinBridgeMenulist.addEventListener("command", e => {
+ gTorPane.onUpdateBridgeSettings();
+ });
+
+ // Request bridge
+ this._requestBridgeOption = prefpane.querySelector(
+ selectors.bridges.requestBridgeOption
+ );
+ this._requestBridgeOption.setAttribute(
+ "label",
+ TorStrings.settings.requestBridgeFromTorProject
+ );
+ this._requestBridgeOption.setAttribute("value", TorBridgeSource.BRIDGEDB);
+ this._requestBridgeButton = prefpane.querySelector(
+ selectors.bridges.requestBridgeButton
+ );
+ this._requestBridgeButton.setAttribute(
+ "label",
+ TorStrings.settings.requestNewBridge
+ );
+ this._requestBridgeButton.addEventListener("command", () =>
+ gTorPane.onRequestBridge()
+ );
+ this._requestBridgeTextarea = prefpane.querySelector(
+ selectors.bridges.requestBridgeTextarea
+ );
+
+ // Provide a bridge
+ this._provideBridgeOption = prefpane.querySelector(
+ selectors.bridges.provideBridgeOption
+ );
+ this._provideBridgeOption.setAttribute(
+ "label",
+ TorStrings.settings.provideBridge
+ );
+ this._provideBridgeOption.setAttribute(
+ "value",
+ TorBridgeSource.USERPROVIDED
+ );
+ prefpane.querySelector(
+ selectors.bridges.provideBridgeDescription
+ ).textContent = TorStrings.settings.provideBridgeDirections;
+ this._provideBridgeTextarea = prefpane.querySelector(
+ selectors.bridges.provideBridgeTextarea
+ );
+ this._provideBridgeTextarea.setAttribute(
+ "placeholder",
+ TorStrings.settings.provideBridgePlaceholder
+ );
+ this._provideBridgeTextarea.addEventListener("blur", () => {
+ gTorPane.onUpdateBridgeSettings();
+ });
+
+ // Advanced setup
+ prefpane.querySelector(selectors.advanced.header).innerText =
+ TorStrings.settings.advancedHeading;
+ prefpane.querySelector(selectors.advanced.description).textContent =
+ TorStrings.settings.advancedDescription;
+ {
+ let learnMore = prefpane.querySelector(selectors.advanced.learnMore);
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute(
+ "href",
+ TorStrings.settings.learnMoreNetworkSettingsURL
+ );
+ }
+
+ // Local Proxy
+ this._useProxyCheckbox = prefpane.querySelector(
+ selectors.advanced.useProxyCheckbox
+ );
+ this._useProxyCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useLocalProxy
+ );
+ this._useProxyCheckbox.addEventListener("command", e => {
+ const checked = this._useProxyCheckbox.checked;
+ gTorPane.onToggleProxy(checked).onUpdateProxySettings();
+ });
+ this._proxyTypeLabel = prefpane.querySelector(
+ selectors.advanced.proxyTypeLabel
+ );
+ this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType);
+
+ let mockProxies = [
+ {
+ value: TorProxyType.SOCKS4,
+ label: TorStrings.settings.proxyTypeSOCKS4,
+ },
+ {
+ value: TorProxyType.SOCKS5,
+ label: TorStrings.settings.proxyTypeSOCKS5,
+ },
+ { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP },
+ ];
+ this._proxyTypeMenulist = prefpane.querySelector(
+ selectors.advanced.proxyTypeList
+ );
+ this._proxyTypeMenulist.addEventListener("command", e => {
+ const value = this._proxyTypeMenulist.value;
+ gTorPane.onSelectProxyType(value).onUpdateProxySettings();
+ });
+ for (let currentProxy of mockProxies) {
+ let menuEntry = document.createXULElement("menuitem");
+ menuEntry.setAttribute("value", currentProxy.value);
+ menuEntry.setAttribute("label", currentProxy.label);
+ this._proxyTypeMenulist
+ .querySelector("menupopup")
+ .appendChild(menuEntry);
+ }
+
+ this._proxyAddressLabel = prefpane.querySelector(
+ selectors.advanced.proxyAddressLabel
+ );
+ this._proxyAddressLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyAddress
+ );
+ this._proxyAddressTextbox = prefpane.querySelector(
+ selectors.advanced.proxyAddressTextbox
+ );
+ this._proxyAddressTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyAddressPlaceholder
+ );
+ this._proxyAddressTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+ this._proxyPortLabel = prefpane.querySelector(
+ selectors.advanced.proxyPortLabel
+ );
+ this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort);
+ this._proxyPortTextbox = prefpane.querySelector(
+ selectors.advanced.proxyPortTextbox
+ );
+ this._proxyPortTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+ this._proxyUsernameLabel = prefpane.querySelector(
+ selectors.advanced.proxyUsernameLabel
+ );
+ this._proxyUsernameLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyUsername
+ );
+ this._proxyUsernameTextbox = prefpane.querySelector(
+ selectors.advanced.proxyUsernameTextbox
+ );
+ this._proxyUsernameTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyUsernamePasswordPlaceholder
+ );
+ this._proxyUsernameTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+ this._proxyPasswordLabel = prefpane.querySelector(
+ selectors.advanced.proxyPasswordLabel
+ );
+ this._proxyPasswordLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyPassword
+ );
+ this._proxyPasswordTextbox = prefpane.querySelector(
+ selectors.advanced.proxyPasswordTextbox
+ );
+ this._proxyPasswordTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyUsernamePasswordPlaceholder
+ );
+ this._proxyPasswordTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+
+ // Local firewall
+ this._useFirewallCheckbox = prefpane.querySelector(
+ selectors.advanced.useFirewallCheckbox
+ );
+ this._useFirewallCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useFirewall
+ );
+ this._useFirewallCheckbox.addEventListener("command", e => {
+ const checked = this._useFirewallCheckbox.checked;
+ gTorPane.onToggleFirewall(checked).onUpdateFirewallSettings();
+ });
+ this._allowedPortsLabel = prefpane.querySelector(
+ selectors.advanced.firewallAllowedPortsLabel
+ );
+ this._allowedPortsLabel.setAttribute(
+ "value",
+ TorStrings.settings.allowedPorts
+ );
+ this._allowedPortsTextbox = prefpane.querySelector(
+ selectors.advanced.firewallAllowedPortsTextbox
+ );
+ this._allowedPortsTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.allowedPortsPlaceholder
+ );
+ this._allowedPortsTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateFirewallSettings();
+ });
+
+ // Tor logs
+ prefpane
+ .querySelector(selectors.advanced.torLogsLabel)
+ .setAttribute("value", TorStrings.settings.showTorDaemonLogs);
+ let torLogsButton = prefpane.querySelector(
+ selectors.advanced.torLogsButton
+ );
+ torLogsButton.setAttribute("label", TorStrings.settings.showLogs);
+ torLogsButton.addEventListener("command", () => {
+ gTorPane.onViewTorLogs();
+ });
+
+ // Disable all relevant elements by default
+ this._setElementsDisabled(
+ [
+ this._builtinBridgeOption,
+ this._builtinBridgeMenulist,
+ this._requestBridgeOption,
+ this._requestBridgeButton,
+ this._requestBridgeTextarea,
+ this._provideBridgeOption,
+ this._provideBridgeTextarea,
+ this._proxyTypeLabel,
+ this._proxyTypeMenulist,
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ this._allowedPortsLabel,
+ this._allowedPortsTextbox,
+ ],
+ true
+ );
+
+ // load bridge settings
+ let torBridgeSettings = new TorBridgeSettings();
+ torBridgeSettings.readSettings();
+
+ // populate the bridge list
+ for (let currentBridge of TorBridgeSettings.defaultBridgeTypes) {
+ let menuEntry = document.createXULElement("menuitem");
+ menuEntry.setAttribute("value", currentBridge);
+ menuEntry.setAttribute("label", currentBridge);
+ this._builtinBridgeMenulist
+ .querySelector("menupopup")
+ .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;
+ }
+
+ this._bridgeSettings = torBridgeSettings;
+
+ // load proxy settings
+ let torProxySettings = new TorProxySettings();
+ torProxySettings.readSettings();
+
+ if (torProxySettings.type != TorProxyType.NONE) {
+ 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._proxySettings = torProxySettings;
+
+ // load firewall settings
+ let torFirewallSettings = new TorFirewallSettings();
+ torFirewallSettings.readSettings();
+
+ if (torFirewallSettings.hasPorts) {
+ this.onToggleFirewall(true);
+ this._allowedPortsTextbox.value =
+ torFirewallSettings.commaSeparatedListString;
+ }
+
+ this._firewallSettings = torFirewallSettings;
+ },
+
+ init() {
+ this._populateXUL();
+ },
+
+ // whether the page should be present in about:preferences
+ get enabled() {
+ return TorProtocolService.ownsTorDaemon;
+ },
+
+ //
+ // Callbacks
+ //
+
+ // callback when using bridges toggled
+ onToggleBridge(enabled) {
+ this._useBridgeCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ // first disable all the bridge related elements
+ this._setElementsDisabled(
+ [
+ this._builtinBridgeOption,
+ this._builtinBridgeMenulist,
+ this._requestBridgeOption,
+ this._requestBridgeButton,
+ this._requestBridgeTextarea,
+ this._provideBridgeOption,
+ this._provideBridgeTextarea,
+ ],
+ disabled
+ );
+
+ // and selectively re-enable based on the radiogroup's current value
+ if (enabled) {
+ this.onSelectBridgeOption(this._bridgeSelectionRadiogroup.value);
+ } else {
+ this.onSelectBridgeOption(TorBridgeSource.NONE);
+ }
+ return this;
+ },
+
+ // callback when a bridge option is selected
+ onSelectBridgeOption(source) {
+ // disable all of the bridge elements under radio buttons
+ this._setElementsDisabled(
+ [
+ this._builtinBridgeMenulist,
+ this._requestBridgeButton,
+ this._requestBridgeTextarea,
+ this._provideBridgeTextarea,
+ ],
+ true
+ );
+
+ if (source != TorBridgeSource.NONE) {
+ this._bridgeSelectionRadiogroup.value = source;
+ }
+
+ switch (source) {
+ case TorBridgeSource.BUILTIN: {
+ this._setElementsDisabled([this._builtinBridgeMenulist], false);
+ break;
+ }
+ case TorBridgeSource.BRIDGEDB: {
+ this._setElementsDisabled(
+ [this._requestBridgeButton, this._requestBridgeTextarea],
+ false
+ );
+ break;
+ }
+ case TorBridgeSource.USERPROVIDED: {
+ this._setElementsDisabled([this._provideBridgeTextarea], false);
+ break;
+ }
+ }
+ return this;
+ },
+
+ // called when the request bridge button is activated
+ onRequestBridge() {
+ let requestBridgeDialog = new RequestBridgeDialog();
+ requestBridgeDialog.openDialog(
+ gSubDialog,
+ this._proxySettings.proxyURI,
+ aBridges => {
+ if (aBridges.length > 0) {
+ let bridgeSettings = makeTorBridgeSettingsBridgeDB(aBridges);
+ bridgeSettings.writeSettings();
+ this._bridgeSettings = bridgeSettings;
+
+ this._requestBridgeTextarea.value = bridgeSettings.bridgeStrings;
+ }
+ }
+ );
+ return this;
+ },
+
+ // pushes bridge settings from UI to tor
+ onUpdateBridgeSettings() {
+ let bridgeSettings = null;
+
+ let source = this._useBridgeCheckbox.checked
+ ? this._bridgeSelectionRadiogroup.value
+ : TorBridgeSource.NONE;
+ switch (source) {
+ case TorBridgeSource.NONE: {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ break;
+ }
+ case TorBridgeSource.BUILTIN: {
+ // if there is a built-in bridge already selected, use that
+ let bridgeType = this._builtinBridgeMenulist.value;
+ if (bridgeType) {
+ bridgeSettings = makeTorBridgeSettingsBuiltin(bridgeType);
+ } else {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ }
+ break;
+ }
+ 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);
+ } else {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ }
+ break;
+ }
+ 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
+ );
+ } else {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ }
+ break;
+ }
+ }
+ bridgeSettings.writeSettings();
+ this._bridgeSettings = bridgeSettings;
+ return this;
+ },
+
+ // callback when proxy is toggled
+ onToggleProxy(enabled) {
+ this._useProxyCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ this._setElementsDisabled(
+ [
+ this._proxyTypeLabel,
+ this._proxyTypeMenulist,
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ disabled
+ );
+ this.onSelectProxyType(this._proxyTypeMenulist.value);
+ return this;
+ },
+
+ // callback when proxy type is changed
+ onSelectProxyType(value) {
+ if (value == "") {
+ value = TorProxyType.NONE;
+ }
+ this._proxyTypeMenulist.value = value;
+ switch (value) {
+ case TorProxyType.NONE: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ true
+ ); // DISABLE
+
+ this._proxyAddressTextbox.value = "";
+ this._proxyPortTextbox.value = "";
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ break;
+ }
+ case TorProxyType.SOCKS4: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ ],
+ false
+ ); // ENABLE
+ this._setElementsDisabled(
+ [
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ true
+ ); // DISABLE
+
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ break;
+ }
+ case TorProxyType.SOCKS5:
+ case TorProxyType.HTTPS: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ false
+ ); // ENABLE
+ break;
+ }
+ }
+ return this;
+ },
+
+ // 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();
+ break;
+ case TorProxyType.SOCKS4:
+ proxySettings = makeTorProxySettingsSocks4(
+ addressString,
+ parsePort(portString)
+ );
+ break;
+ case TorProxyType.SOCKS5:
+ proxySettings = makeTorProxySettingsSocks5(
+ addressString,
+ parsePort(portString),
+ usernameString,
+ passwordString
+ );
+ break;
+ case TorProxyType.HTTPS:
+ proxySettings = makeTorProxySettingsHTTPS(
+ addressString,
+ parsePort(portString),
+ usernameString,
+ passwordString
+ );
+ break;
+ }
+
+ proxySettings.writeSettings();
+ this._proxySettings = proxySettings;
+ return this;
+ },
+
+ // callback when firewall proxy is toggled
+ onToggleFirewall(enabled) {
+ this._useFirewallCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ this._setElementsDisabled(
+ [this._allowedPortsLabel, this._allowedPortsTextbox],
+ disabled
+ );
+
+ return this;
+ },
+
+ // 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)
+ );
+ } else {
+ firewallSettings = makeTorFirewallSettingsNone();
+ }
+
+ firewallSettings.writeSettings();
+ this._firewallSettings = firewallSettings;
+ return this;
+ },
+
+ onViewTorLogs() {
+ let torLogDialog = new TorLogDialog();
+ torLogDialog.openDialog(gSubDialog);
+ },
+ };
+ return retval;
+})(); /* gTorPane */
diff --git a/browser/components/torpreferences/content/torPane.xhtml b/browser/components/torpreferences/content/torPane.xhtml
new file mode 100644
index 0000000000000..3c966b2b3726d
--- /dev/null
+++ b/browser/components/torpreferences/content/torPane.xhtml
@@ -0,0 +1,123 @@
+<!-- Tor panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/torpreferences/torPane.js"/>
+<html:template id="template-paneTor">
+<hbox id="torPreferencesCategory"
+ class="subcategory"
+ data-category="paneTor"
+ hidden="true">
+ <html:h1 id="torPreferences-header"/>
+</hbox>
+
+<groupbox data-category="paneTor"
+ hidden="true">
+ <description flex="1">
+ <html:span id="torPreferences-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+</groupbox>
+
+<!-- Bridges -->
+<groupbox id="torPreferences-bridges-group"
+ data-category="paneTor"
+ hidden="true">
+ <html:h2 id="torPreferences-bridges-header"/>
+ <description flex="1">
+ <html:span id="torPreferences-bridges-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-bridges-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+ <checkbox id="torPreferences-bridges-toggle"/>
+ <radiogroup id="torPreferences-bridges-bridgeSelection">
+ <hbox class="indent">
+ <radio id="torPreferences-bridges-radioBuiltin"/>
+ <spacer flex="1"/>
+ <menulist id="torPreferences-bridges-builtinList" class="torMarginFix">
+ <menupopup/>
+ </menulist>
+ </hbox>
+ <vbox class="indent">
+ <hbox>
+ <radio id="torPreferences-bridges-radioRequestBridge"/>
+ <space flex="1"/>
+ <button id="torPreferences-bridges-buttonRequestBridge" class="torMarginFix"/>
+ </hbox>
+ <html:textarea
+ id="torPreferences-bridges-textareaRequestBridge"
+ class="indent torMarginFix"
+ multiline="true"
+ rows="3"
+ readonly="true"/>
+ </vbox>
+ <hbox class="indent" flex="1">
+ <vbox flex="1">
+ <radio id="torPreferences-bridges-radioProvideBridge"/>
+ <description id="torPreferences-bridges-descriptionProvideBridge" class="indent"/>
+ <html:textarea
+ id="torPreferences-bridges-textareaProvideBridge"
+ class="indent torMarginFix"
+ multiline="true"
+ rows="3"/>
+ </vbox>
+ </hbox>
+ </radiogroup>
+</groupbox>
+
+<!-- Advanced -->
+<groupbox id="torPreferences-advanced-group"
+ data-category="paneTor"
+ hidden="true">
+ <html:h2 id="torPreferences-advanced-header"/>
+ <description flex="1">
+ <html:span id="torPreferences-advanced-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-advanced-learnMore" class="learnMore text-link" is="text-link" style="display:none"/>
+ </description>
+ <box id="torPreferences-advanced-grid">
+ <!-- Local Proxy -->
+ <hbox class="torPreferences-advanced-checkbox-container">
+ <checkbox id="torPreferences-advanced-toggleProxy"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-type"/>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <menulist id="torPreferences-localProxy-builtinList" class="torMarginFix">
+ <menupopup/>
+ </menulist>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-address"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-localProxy-textboxAddress" type="text" class="torMarginFix"/>
+ <label id="torPreferences-localProxy-port"/>
+ <!-- proxy-port-input class style pulled from preferences.css and used in the vanilla proxy setup menu -->
+ <html:input id="torPreferences-localProxy-textboxPort" class="proxy-port-input torMarginFix" hidespinbuttons="true" type="number" min="0" max="65535" maxlength="5"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-username"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-localProxy-textboxUsername" type="text" class="torMarginFix"/>
+ <label id="torPreferences-localProxy-password"/>
+ <html:input id="torPreferences-localProxy-textboxPassword" class="torMarginFix" type="password"/>
+ </hbox>
+ <!-- Firewall -->
+ <hbox class="torPreferences-advanced-checkbox-container">
+ <checkbox id="torPreferences-advanced-toggleFirewall"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-advanced-allowedPorts"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-advanced-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/>
+ </hbox>
+ </box>
+ <hbox id="torPreferences-torDaemon-hbox" align="center">
+ <label id="torPreferences-torLogs"/>
+ <spacer flex="1"/>
+ <button id="torPreferences-buttonTorLogs" class="torMarginFix"/>
+ </hbox>
+</groupbox>
+</html:template>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
new file mode 100644
index 0000000000000..4dac2c4578237
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -0,0 +1,77 @@
+#category-tor > .category-icon {
+ list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg");
+}
+
+#torPreferences-advanced-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+}
+
+.torPreferences-advanced-checkbox-container {
+ grid-column: 1 / 3;
+}
+
+#torPreferences-localProxy-textboxAddress,
+#torPreferences-localProxy-textboxUsername,
+#torPreferences-localProxy-textboxPassword,
+#torPreferences-advanced-textboxAllowedPorts {
+ -moz-box-flex: 1;
+}
+
+hbox#torPreferences-torDaemon-hbox {
+ margin-top: 20px;
+}
+
+description#torPreferences-requestBridge-description {
+ /*margin-bottom: 1em;*/
+ min-height: 2em;
+}
+
+image#torPreferences-requestBridge-captchaImage {
+ margin: 1em;
+ min-height: 125px;
+}
+
+button#torPreferences-requestBridge-refreshCaptchaButton {
+ min-width: initial;
+}
+
+dialog#torPreferences-requestBridge-dialog > hbox {
+ margin-bottom: 1em;
+}
+
+/*
+ Various elements that really should be lining up don't because they have inconsistent margins
+*/
+.torMarginFix {
+ margin-left : 4px;
+ margin-right : 4px;
+}
+
+/*
+ This hbox is hidden by css here by default so that the
+ xul dialog allocates enough screen space for the error message
+ element, otherwise it gets cut off since dialog's overflow is hidden
+*/
+hbox#torPreferences-requestBridge-incorrectCaptchaHbox {
+ visibility: hidden;
+}
+
+image#torPreferences-requestBridge-errorIcon {
+ list-style-image: url("chrome://browser/skin/warning.svg");
+}
+
+groupbox#torPreferences-bridges-group textarea {
+ white-space: pre;
+ overflow: auto;
+}
+
+textarea#torPreferences-torDialog-textarea {
+ -moz-box-flex: 1;
+ font-family: monospace;
+ font-size: 0.8em;
+ white-space: pre;
+ overflow: auto;
+ /* 10 lines */
+ min-height: 20em;
+}
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferencesIcon.svg b/browser/components/torpreferences/content/torPreferencesIcon.svg
new file mode 100644
index 0000000000000..382a061774aaa
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferencesIcon.svg
@@ -0,0 +1,8 @@
+<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
+ <g clip-rule="evenodd" fill-rule="evenodd">
+ <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/>
+ <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/>
+ <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/>
+ </g>
+ <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torProxySettings.jsm b/browser/components/torpreferences/content/torProxySettings.jsm
new file mode 100644
index 0000000000000..98bb5e8d5cbfa
--- /dev/null
+++ b/browser/components/torpreferences/content/torProxySettings.jsm
@@ -0,0 +1,245 @@
+"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
new file mode 100644
index 0000000000000..857bc9ee3eac1
--- /dev/null
+++ b/browser/components/torpreferences/jar.mn
@@ -0,0 +1,14 @@
+browser.jar:
+ content/browser/torpreferences/parseFunctions.jsm (content/parseFunctions.jsm)
+ 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/torLogDialog.jsm (content/torLogDialog.jsm)
+ 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/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/components/torpreferences/moz.build b/browser/components/torpreferences/moz.build
new file mode 100644
index 0000000000000..2661ad7cb9f3d
--- /dev/null
+++ b/browser/components/torpreferences/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/browser/modules/BridgeDB.jsm b/browser/modules/BridgeDB.jsm
new file mode 100644
index 0000000000000..9f76b4f05a690
--- /dev/null
+++ b/browser/modules/BridgeDB.jsm
@@ -0,0 +1,110 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["BridgeDB"];
+
+const { TorLauncherBridgeDB } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-bridgedb.jsm"
+);
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+var BridgeDB = {
+ _moatRequestor: null,
+ _currentCaptchaInfo: null,
+ _bridges: null,
+
+ get currentCaptchaImage() {
+ if (this._currentCaptchaInfo) {
+ return this._currentCaptchaInfo.captchaImage;
+ }
+ return null;
+ },
+
+ get currentBridges() {
+ return this._bridges;
+ },
+
+ async submitCaptchaGuess(aCaptchaSolution) {
+ if (this._moatRequestor && this._currentCaptchaInfo) {
+ return this._moatRequestor
+ .finishFetch(
+ this._currentCaptchaInfo.transport,
+ this._currentCaptchaInfo.challenge,
+ aCaptchaSolution
+ )
+ .then(aBridgeInfo => {
+ this._moatRequestor.close();
+ this._moatRequestor = null;
+ this._currentCaptchaInfo = null;
+ this._bridges = aBridgeInfo.bridges;
+ // array of bridge strings
+ return this._bridges;
+ });
+ }
+
+ return new Promise((aResponse, aReject) => {
+ aReject(new Error("Invalid _moatRequestor or _currentCaptchaInfo"));
+ });
+ },
+
+ async requestNewCaptchaImage(aProxyURI) {
+ // close and clear out existing state on captcha request
+ this.close();
+
+ let transportPlugins = await TorProtocolService.readStringArraySetting(
+ TorStrings.configKeys.clientTransportPlugin
+ );
+
+ let meekClientPath;
+ let meekTransport; // We support both "meek" and "meek_lite".
+ let meekClientArgs;
+ // TODO: shouldn't this early out once meek settings are found?
+ for (const line of transportPlugins) {
+ // Parse each ClientTransportPlugin line and look for the meek or
+ // meek_lite transport. This code works a lot like the Tor daemon's
+ // parse_transport_line() function.
+ let tokens = line.split(" ");
+ if (tokens.length > 2 && tokens[1] == "exec") {
+ let transportArray = tokens[0].split(",").map(aStr => aStr.trim());
+ let transport = transportArray.find(
+ aTransport => aTransport === "meek"
+ );
+ if (!transport) {
+ transport = transportArray.find(
+ aTransport => aTransport === "meek_lite"
+ );
+ }
+ if (transport) {
+ meekTransport = transport;
+ meekClientPath = tokens[2];
+ meekClientArgs = tokens.slice(3);
+ }
+ }
+ }
+
+ this._moatRequestor = TorLauncherBridgeDB.createMoatRequestor();
+
+ return this._moatRequestor
+ .init(aProxyURI, meekTransport, meekClientPath, meekClientArgs)
+ .then(() => {
+ // TODO: get this from TorLauncherUtil
+ let bridgeType = "obfs4";
+ return this._moatRequestor.fetchBridges([bridgeType]);
+ })
+ .then(aCaptchaInfo => {
+ // cache off the current captcha info as the challenge is needed for response
+ this._currentCaptchaInfo = aCaptchaInfo;
+ return aCaptchaInfo.captchaImage;
+ });
+ },
+
+ close() {
+ if (this._moatRequestor) {
+ this._moatRequestor.close();
+ this._moatRequestor = null;
+ }
+ this._currentCaptchaInfo = null;
+ },
+};
diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm
new file mode 100644
index 0000000000000..409d6be60b830
--- /dev/null
+++ b/browser/modules/TorProtocolService.jsm
@@ -0,0 +1,323 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorProtocolService"];
+
+const { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+);
+
+var TorProtocolService = {
+ _tlps: Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject,
+
+ // maintain a map of tor settings set by Tor Browser so that we don't
+ // repeatedly set the same key/values over and over
+ // this map contains string keys to primitive or array values
+ _settingsCache: new Map(),
+
+ _typeof(aValue) {
+ switch (typeof aValue) {
+ case "boolean":
+ return "boolean";
+ case "string":
+ return "string";
+ case "object":
+ if (aValue == null) {
+ return "null";
+ } else if (Array.isArray(aValue)) {
+ return "array";
+ }
+ return "object";
+ }
+ return "unknown";
+ },
+
+ _assertValidSettingKey(aSetting) {
+ // ensure the 'key' is a string
+ if (typeof aSetting != "string") {
+ throw new Error(
+ `Expected setting of type string but received ${typeof aSetting}`
+ );
+ }
+ },
+
+ _assertValidSetting(aSetting, aValue) {
+ this._assertValidSettingKey(aSetting);
+
+ const valueType = this._typeof(aValue);
+ switch (valueType) {
+ case "boolean":
+ case "string":
+ case "null":
+ return;
+ case "array":
+ for (const element of aValue) {
+ if (typeof element != "string") {
+ throw new Error(
+ `Setting '${aSetting}' array contains value of invalid type '${typeof element}'`
+ );
+ }
+ }
+ return;
+ default:
+ throw new Error(
+ `Invalid object type received for setting '${aSetting}'`
+ );
+ }
+ },
+
+ // takes a Map containing tor settings
+ // throws on error
+ async writeSettings(aSettingsObj) {
+ // only write settings that have changed
+ let newSettings = new Map();
+ for (const [setting, value] of aSettingsObj) {
+ let saveSetting = false;
+
+ // make sure we have valid data here
+ this._assertValidSetting(setting, value);
+
+ if (!this._settingsCache.has(setting)) {
+ // no cached setting, so write
+ saveSetting = true;
+ } else {
+ const cachedValue = this._settingsCache.get(setting);
+ if (value != cachedValue) {
+ // compare arrays member-wise
+ if (Array.isArray(value) && Array.isArray(cachedValue)) {
+ if (value.length != cachedValue.length) {
+ saveSetting = true;
+ } else {
+ const arrayLength = value.length;
+ for (let i = 0; i < arrayLength; ++i) {
+ if (value[i] != cachedValue[i]) {
+ saveSetting = true;
+ break;
+ }
+ }
+ }
+ } else {
+ // some other different values
+ saveSetting = true;
+ }
+ }
+ }
+
+ if (saveSetting) {
+ newSettings.set(setting, value);
+ }
+ }
+
+ // only write if new setting to save
+ if (newSettings.size > 0) {
+ // convert settingsObject map to js object for torlauncher-protocol-service
+ let settingsObject = {};
+ for (const [setting, value] of newSettings) {
+ settingsObject[setting] = value;
+ }
+
+ let errorObject = {};
+ if (! await this._TorLauncherProtocolService.TorSetConfWithReply(settingsObject, errorObject)) {
+ throw new Error(errorObject.details);
+ }
+
+ // save settings to cache after successfully writing to Tor
+ for (const [setting, value] of newSettings) {
+ this._settingsCache.set(setting, value);
+ }
+ }
+ },
+
+ async _readSetting(aSetting) {
+ this._assertValidSettingKey(aSetting);
+ let reply = await this._TorLauncherProtocolService.TorGetConf(aSetting);
+ if (this._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
+ return reply.lineArray;
+ }
+ throw new Error(reply.lineArray.join("\n"));
+ },
+
+ async _readBoolSetting(aSetting) {
+ let lineArray = await this._readSetting(aSetting);
+ if (lineArray.length != 1) {
+ throw new Error(
+ `Expected an array with length 1 but received array of length ${
+ lineArray.length
+ }`
+ );
+ }
+
+ let retval = lineArray[0];
+ switch (retval) {
+ case "0":
+ return false;
+ case "1":
+ return true;
+ default:
+ throw new Error(`Expected boolean (1 or 0) but received '${retval}'`);
+ }
+ },
+
+ async _readStringSetting(aSetting) {
+ let lineArray = await this._readSetting(aSetting);
+ if (lineArray.length != 1) {
+ throw new Error(
+ `Expected an array with length 1 but received array of length ${
+ lineArray.length
+ }`
+ );
+ }
+ return lineArray[0];
+ },
+
+ async _readStringArraySetting(aSetting) {
+ let lineArray = await this._readSetting(aSetting);
+ return lineArray;
+ },
+
+ async readBoolSetting(aSetting) {
+ let value = await this._readBoolSetting(aSetting);
+ this._settingsCache.set(aSetting, value);
+ return value;
+ },
+
+ async readStringSetting(aSetting) {
+ let value = await this._readStringSetting(aSetting);
+ this._settingsCache.set(aSetting, value);
+ return value;
+ },
+
+ async readStringArraySetting(aSetting) {
+ let value = await this._readStringArraySetting(aSetting);
+ this._settingsCache.set(aSetting, value);
+ return value;
+ },
+
+ // writes current tor settings to disk
+ async flushSettings() {
+ await this.sendCommand("SAVECONF");
+ },
+
+ getLog() {
+ let countObj = { value: 0 };
+ let torLog = this._tlps.TorGetLog(countObj);
+ return torLog;
+ },
+
+ // true if we launched and control tor, false if using system tor
+ get ownsTorDaemon() {
+ return this._TorLauncherUtil.shouldStartAndOwnTor;
+ },
+
+ // Assumes `ownsTorDaemon` is true
+ isNetworkDisabled() {
+ const reply = TorProtocolService._TorLauncherProtocolService.TorGetConfBool(
+ "DisableNetwork",
+ true
+ );
+ if (TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
+ return reply.retVal;
+ }
+ return true;
+ },
+
+ async enableNetwork() {
+ let settings = {};
+ settings.DisableNetwork = false;
+ let errorObject = {};
+ if (! await this._TorLauncherProtocolService.TorSetConfWithReply(settings, errorObject)) {
+ throw new Error(errorObject.details);
+ }
+ },
+
+ async sendCommand(cmd) {
+ return await this._TorLauncherProtocolService.TorSendCommand(cmd);
+ },
+
+ retrieveBootstrapStatus() {
+ return this._TorLauncherProtocolService.TorRetrieveBootstrapStatus();
+ },
+
+ _GetSaveSettingsErrorMessage(aDetails) {
+ try {
+ return this._TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
+ } catch (e) {
+ console.log("GetSaveSettingsErrorMessage error", e);
+ return "Unexpected Error";
+ }
+ },
+
+ async setConfWithReply(settings) {
+ let result = false;
+ const error = {};
+ try {
+ result = await this._TorLauncherProtocolService.TorSetConfWithReply(settings, error);
+ } catch (e) {
+ console.log("TorSetConfWithReply error", e);
+ error.details = this._GetSaveSettingsErrorMessage(e.message);
+ }
+ return { result, error };
+ },
+
+ isBootstrapDone() {
+ return this._TorProcessService.mIsBootstrapDone;
+ },
+
+ clearBootstrapError() {
+ return this._TorProcessService.TorClearBootstrapError();
+ },
+
+ torBootstrapErrorOccurred() {
+ return this._TorProcessService.TorBootstrapErrorOccurred;
+ },
+
+ // Resolves to null if ok, or an error otherwise
+ async connect() {
+ const kTorConfKeyDisableNetwork = "DisableNetwork";
+ const settings = {};
+ settings[kTorConfKeyDisableNetwork] = false;
+ const { result, error } = await this.setConfWithReply(settings);
+ if (!result) {
+ return error;
+ }
+ try {
+ await this.sendCommand("SAVECONF");
+ this.clearBootstrapError();
+ this.retrieveBootstrapStatus();
+ } catch (e) {
+ return error;
+ }
+ return null;
+ },
+
+ torLogHasWarnOrErr() {
+ return this._TorLauncherProtocolService.TorLogHasWarnOrErr;
+ },
+
+ async torStopBootstrap() {
+ // Tell tor to disable use of the network; this should stop the bootstrap
+ // process.
+ const kErrorPrefix = "Setting DisableNetwork=1 failed: ";
+ try {
+ let settings = {};
+ settings.DisableNetwork = true;
+ const { result, error } = await this.setConfWithReply(settings);
+ if (!result) {
+ console.log(
+ `Error stopping bootstrap ${kErrorPrefix} ${error.details}`
+ );
+ }
+ } catch (e) {
+ console.log(`Error stopping bootstrap ${kErrorPrefix} ${e}`);
+ }
+ this.retrieveBootstrapStatus();
+ },
+
+ get torProcessStatus() {
+ if (this._TorProcessService) {
+ return this._TorProcessService.TorProcessStatus;
+ }
+ return TorProcessStatus.Unknown;
+ },
+};
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
index 3774c12f4ef02..1f7c6bc4c67e3 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -128,6 +128,7 @@ EXTRA_JS_MODULES += [
"AboutNewTab.jsm",
"AppUpdater.jsm",
"AsyncTabSwitcher.jsm",
+ "BridgeDB.jsm",
"BrowserUIUtils.jsm",
"BrowserUsageTelemetry.jsm",
"BrowserWindowTracker.jsm",
@@ -152,6 +153,7 @@ EXTRA_JS_MODULES += [
"TabsList.jsm",
"TabUnloader.jsm",
"ThemeVariableMap.jsm",
+ "TorProtocolService.jsm",
"TorStrings.jsm",
"TransientPrefs.jsm",
"webrtcUI.jsm",
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits