richard pushed to branch tor-browser-115.1.0esr-13.0-1 at The Tor Project / Applications / Tor Browser
Commits:
-
d339153b
by Pier Angelo Vendrame at 2023-08-07T18:36:05+02:00
-
e8d3c0b8
by Pier Angelo Vendrame at 2023-08-07T18:36:14+02:00
-
c1b05a65
by Pier Angelo Vendrame at 2023-08-07T18:36:15+02:00
-
d23cb300
by Pier Angelo Vendrame at 2023-08-07T18:36:15+02:00
-
c801d620
by Pier Angelo Vendrame at 2023-08-07T18:36:16+02:00
-
fa105e4f
by Pier Angelo Vendrame at 2023-08-07T18:36:16+02:00
-
cd92f30e
by Pier Angelo Vendrame at 2023-08-07T18:36:17+02:00
-
384dff97
by Pier Angelo Vendrame at 2023-08-07T18:36:17+02:00
24 changed files:
- browser/components/abouttor/TorCheckService.sys.mjs
- browser/components/onionservices/content/authPrompt.js
- browser/components/onionservices/content/savedKeysDialog.js
- browser/components/torpreferences/content/builtinBridgeDialog.jsm
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionSettingsDialog.jsm
- browser/components/torpreferences/content/provideBridgeDialog.jsm
- browser/components/torpreferences/content/requestBridgeDialog.jsm
- browser/components/torpreferences/content/torLogDialog.jsm
- browser/modules/BridgeDB.jsm → browser/modules/BridgeDB.sys.mjs
- browser/modules/Moat.jsm → browser/modules/Moat.sys.mjs
- browser/modules/TorConnect.jsm → browser/modules/TorConnect.sys.mjs
- browser/modules/TorSettings.jsm → browser/modules/TorSettings.sys.mjs
- browser/modules/moz.build
- toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
- toolkit/components/tor-launcher/TorControlPort.sys.mjs
- toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs
- − toolkit/components/tor-launcher/TorMonitorService.sys.mjs
- toolkit/components/tor-launcher/TorParsers.sys.mjs
- toolkit/components/tor-launcher/TorProtocolService.sys.mjs → toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
- toolkit/components/tor-launcher/TorStartupService.sys.mjs
- toolkit/components/tor-launcher/moz.build
- toolkit/mozapps/update/UpdateService.sys.mjs
Changes:
... | ... | @@ -11,14 +11,9 @@ const lazy = {}; |
11 | 11 | |
12 | 12 | ChromeUtils.defineESModuleGetters(lazy, {
|
13 | 13 | ConsoleAPI: "resource://gre/modules/Console.sys.mjs",
|
14 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
14 | 15 | });
|
15 | 16 | |
16 | -ChromeUtils.defineModuleGetter(
|
|
17 | - lazy,
|
|
18 | - "TorProtocolService",
|
|
19 | - "resource://gre/modules/TorProtocolService.jsm"
|
|
20 | -);
|
|
21 | - |
|
22 | 17 | export const TorCheckService = {
|
23 | 18 | kCheckNotInitiated: 0, // Possible values for status.
|
24 | 19 | kCheckSuccessful: 1,
|
... | ... | @@ -109,7 +104,7 @@ export const TorCheckService = { |
109 | 104 | |
110 | 105 | let listeners;
|
111 | 106 | try {
|
112 | - listeners = await lazy.TorProtocolService.getSocksListeners();
|
|
107 | + listeners = await lazy.TorProviderBuilder.build().getSocksListeners();
|
|
113 | 108 | } catch (e) {
|
114 | 109 | this._logger.error("Failed to get the SOCKS listerner addresses.", e);
|
115 | 110 | return false;
|
... | ... | @@ -4,10 +4,13 @@ |
4 | 4 | |
5 | 5 | /* globals gBrowser, PopupNotifications, Services, XPCOMUtils */
|
6 | 6 | |
7 | +ChromeUtils.defineESModuleGetters(this, {
|
|
8 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
9 | +});
|
|
10 | + |
|
7 | 11 | XPCOMUtils.defineLazyModuleGetters(this, {
|
8 | 12 | OnionAuthUtil: "chrome://browser/content/onionservices/authUtil.jsm",
|
9 | 13 | CommonUtils: "resource://services-common/utils.js",
|
10 | - TorProtocolService: "resource://gre/modules/TorProtocolService.jsm",
|
|
11 | 14 | TorStrings: "resource:///modules/TorStrings.jsm",
|
12 | 15 | });
|
13 | 16 | |
... | ... | @@ -203,7 +206,8 @@ const OnionAuthPrompt = (function () { |
203 | 206 | |
204 | 207 | let checkboxElem = this._getCheckboxElement();
|
205 | 208 | let isPermanent = checkboxElem && checkboxElem.checked;
|
206 | - TorProtocolService.onionAuthAdd(onionServiceId, base64key, isPermanent)
|
|
209 | + TorProviderBuilder.build()
|
|
210 | + .onionAuthAdd(onionServiceId, base64key, isPermanent)
|
|
207 | 211 | .then(aResponse => {
|
208 | 212 | // Success! Reload the page.
|
209 | 213 | this._browser.sendMessageToActor(
|
... | ... | @@ -8,11 +8,9 @@ ChromeUtils.defineModuleGetter( |
8 | 8 | "resource:///modules/TorStrings.jsm"
|
9 | 9 | );
|
10 | 10 | |
11 | -ChromeUtils.defineModuleGetter(
|
|
12 | - this,
|
|
13 | - "TorProtocolService",
|
|
14 | - "resource://gre/modules/TorProtocolService.jsm"
|
|
15 | -);
|
|
11 | +ChromeUtils.defineESModuleGetters(this, {
|
|
12 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
13 | +});
|
|
16 | 14 | |
17 | 15 | var gOnionServicesSavedKeysDialog = {
|
18 | 16 | selector: {
|
... | ... | @@ -54,6 +52,7 @@ var gOnionServicesSavedKeysDialog = { |
54 | 52 | await this._deleteOneKey(indexesToDelete[i]);
|
55 | 53 | }
|
56 | 54 | } catch (e) {
|
55 | + console.error("Removing a saved key failed", e);
|
|
57 | 56 | if (e.torMessage) {
|
58 | 57 | this._showError(e.torMessage);
|
59 | 58 | } else {
|
... | ... | @@ -125,22 +124,16 @@ var gOnionServicesSavedKeysDialog = { |
125 | 124 | try {
|
126 | 125 | this._tree.view = this;
|
127 | 126 | |
128 | - const keyInfoList = await TorProtocolService.onionAuthViewKeys();
|
|
127 | + const keyInfoList = await TorProviderBuilder.build().onionAuthViewKeys();
|
|
129 | 128 | if (keyInfoList) {
|
130 | 129 | // Filter out temporary keys.
|
131 | - this._keyInfoList = keyInfoList.filter(aKeyInfo => {
|
|
132 | - if (!aKeyInfo.Flags) {
|
|
133 | - return false;
|
|
134 | - }
|
|
135 | - |
|
136 | - const flags = aKeyInfo.Flags.split(",");
|
|
137 | - return flags.includes("Permanent");
|
|
138 | - });
|
|
139 | - |
|
130 | + this._keyInfoList = keyInfoList.filter(aKeyInfo =>
|
|
131 | + aKeyInfo.flags?.includes("Permanent")
|
|
132 | + );
|
|
140 | 133 | // Sort by the .onion address.
|
141 | 134 | this._keyInfoList.sort((aObj1, aObj2) => {
|
142 | - const hsAddr1 = aObj1.hsAddress.toLowerCase();
|
|
143 | - const hsAddr2 = aObj2.hsAddress.toLowerCase();
|
|
135 | + const hsAddr1 = aObj1.address.toLowerCase();
|
|
136 | + const hsAddr2 = aObj2.address.toLowerCase();
|
|
144 | 137 | if (hsAddr1 < hsAddr2) {
|
145 | 138 | return -1;
|
146 | 139 | }
|
... | ... | @@ -164,7 +157,7 @@ var gOnionServicesSavedKeysDialog = { |
164 | 157 | // This method may throw; callers should catch errors.
|
165 | 158 | async _deleteOneKey(aIndex) {
|
166 | 159 | const keyInfoObj = this._keyInfoList[aIndex];
|
167 | - await TorProtocolService.onionAuthRemove(keyInfoObj.hsAddress);
|
|
160 | + await TorProviderBuilder.build().onionAuthRemove(keyInfoObj.address);
|
|
168 | 161 | this._tree.view.selection.clearRange(aIndex, aIndex);
|
169 | 162 | this._keyInfoList.splice(aIndex, 1);
|
170 | 163 | this._tree.rowCountChanged(aIndex + 1, -1);
|
... | ... | @@ -193,26 +186,20 @@ var gOnionServicesSavedKeysDialog = { |
193 | 186 | |
194 | 187 | // XUL tree widget view implementation.
|
195 | 188 | get rowCount() {
|
196 | - return this._keyInfoList ? this._keyInfoList.length : 0;
|
|
189 | + return this._keyInfoList?.length ?? 0;
|
|
197 | 190 | },
|
198 | 191 | |
199 | 192 | getCellText(aRow, aCol) {
|
200 | - let val = "";
|
|
201 | 193 | if (this._keyInfoList && aRow < this._keyInfoList.length) {
|
202 | 194 | const keyInfo = this._keyInfoList[aRow];
|
203 | 195 | if (aCol.id.endsWith("-siteCol")) {
|
204 | - val = keyInfo.hsAddress;
|
|
196 | + return keyInfo.address;
|
|
205 | 197 | } else if (aCol.id.endsWith("-keyCol")) {
|
206 | - val = keyInfo.typeAndKey;
|
|
207 | - // Omit keyType because it is always "x25519".
|
|
208 | - const idx = val.indexOf(":");
|
|
209 | - if (idx > 0) {
|
|
210 | - val = val.substring(idx + 1);
|
|
211 | - }
|
|
198 | + // keyType is always "x25519", so do not show it.
|
|
199 | + return keyInfo.keyBlob;
|
|
212 | 200 | }
|
213 | 201 | }
|
214 | - |
|
215 | - return val;
|
|
202 | + return "";
|
|
216 | 203 | },
|
217 | 204 | |
218 | 205 | isSeparator(index) {
|
... | ... | @@ -7,10 +7,10 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); |
7 | 7 | const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
|
8 | 8 | |
9 | 9 | const { TorSettings, TorBridgeSource, TorBuiltinBridgeTypes } =
|
10 | - ChromeUtils.import("resource:///modules/TorSettings.jsm");
|
|
10 | + ChromeUtils.importESModule("resource:///modules/TorSettings.sys.mjs");
|
|
11 | 11 | |
12 | -const { TorConnect, TorConnectTopics } = ChromeUtils.import(
|
|
13 | - "resource:///modules/TorConnect.jsm"
|
|
12 | +const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
|
|
13 | + "resource:///modules/TorConnect.sys.mjs"
|
|
14 | 14 | );
|
15 | 15 | |
16 | 16 | class BuiltinBridgeDialog {
|
... | ... | @@ -12,20 +12,17 @@ const { setTimeout, clearTimeout } = ChromeUtils.import( |
12 | 12 | );
|
13 | 13 | |
14 | 14 | const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource } =
|
15 | - ChromeUtils.import("resource:///modules/TorSettings.jsm");
|
|
15 | + ChromeUtils.importESModule("resource:///modules/TorSettings.sys.mjs");
|
|
16 | 16 | |
17 | 17 | const { TorParsers } = ChromeUtils.importESModule(
|
18 | 18 | "resource://gre/modules/TorParsers.sys.mjs"
|
19 | 19 | );
|
20 | -const { TorProtocolService } = ChromeUtils.importESModule(
|
|
21 | - "resource://gre/modules/TorProtocolService.sys.mjs"
|
|
22 | -);
|
|
23 | -const { TorMonitorService, TorMonitorTopics } = ChromeUtils.import(
|
|
24 | - "resource://gre/modules/TorMonitorService.jsm"
|
|
20 | +const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule(
|
|
21 | + "resource://gre/modules/TorProviderBuilder.sys.mjs"
|
|
25 | 22 | );
|
26 | 23 | |
27 | 24 | const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } =
|
28 | - ChromeUtils.import("resource:///modules/TorConnect.jsm");
|
|
25 | + ChromeUtils.importESModule("resource:///modules/TorConnect.sys.mjs");
|
|
29 | 26 | |
30 | 27 | const { TorLogDialog } = ChromeUtils.import(
|
31 | 28 | "chrome://browser/content/torpreferences/torLogDialog.jsm"
|
... | ... | @@ -51,7 +48,9 @@ const { ProvideBridgeDialog } = ChromeUtils.import( |
51 | 48 | "chrome://browser/content/torpreferences/provideBridgeDialog.jsm"
|
52 | 49 | );
|
53 | 50 | |
54 | -const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
|
|
51 | +const { MoatRPC } = ChromeUtils.importESModule(
|
|
52 | + "resource:///modules/Moat.sys.mjs"
|
|
53 | +);
|
|
55 | 54 | |
56 | 55 | const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm");
|
57 | 56 | |
... | ... | @@ -156,7 +155,7 @@ const gConnectionPane = (function () { |
156 | 155 | _populateXUL() {
|
157 | 156 | // saves tor settings to disk when navigate away from about:preferences
|
158 | 157 | window.addEventListener("blur", val => {
|
159 | - TorProtocolService.flushSettings();
|
|
158 | + TorProviderBuilder.build().flushSettings();
|
|
160 | 159 | });
|
161 | 160 | |
162 | 161 | document
|
... | ... | @@ -751,7 +750,7 @@ const gConnectionPane = (function () { |
751 | 750 | // TODO: We could make sure TorSettings is in sync by monitoring also
|
752 | 751 | // changes of settings. At that point, we could query it, instead of
|
753 | 752 | // doing a query over the control port.
|
754 | - const bridge = TorMonitorService.currentBridge;
|
|
753 | + const bridge = TorProviderBuilder.build().currentBridge;
|
|
755 | 754 | if (bridge?.fingerprint !== this._currentBridgeId) {
|
756 | 755 | this._currentBridgeId = bridge?.fingerprint ?? null;
|
757 | 756 | this._updateConnectedBridges();
|
... | ... | @@ -850,7 +849,7 @@ const gConnectionPane = (function () { |
850 | 849 | });
|
851 | 850 | |
852 | 851 | Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
853 | - Services.obs.addObserver(this, TorMonitorTopics.BridgeChanged);
|
|
852 | + Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
|
|
854 | 853 | Services.obs.addObserver(this, "intl:app-locales-changed");
|
855 | 854 | },
|
856 | 855 | |
... | ... | @@ -875,7 +874,7 @@ const gConnectionPane = (function () { |
875 | 874 | // unregister our observer topics
|
876 | 875 | Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged);
|
877 | 876 | Services.obs.removeObserver(this, TorConnectTopics.StateChange);
|
878 | - Services.obs.removeObserver(this, TorMonitorTopics.BridgeChanged);
|
|
877 | + Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
|
|
879 | 878 | Services.obs.removeObserver(this, "intl:app-locales-changed");
|
880 | 879 | },
|
881 | 880 | |
... | ... | @@ -907,7 +906,7 @@ const gConnectionPane = (function () { |
907 | 906 | this.onStateChange();
|
908 | 907 | break;
|
909 | 908 | }
|
910 | - case TorMonitorTopics.BridgeChanged: {
|
|
909 | + case TorProviderTopics.BridgeChanged: {
|
|
911 | 910 | if (data?.fingerprint !== this._currentBridgeId) {
|
912 | 911 | this._checkConnectedBridge();
|
913 | 912 | }
|
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | |
3 | 3 | var EXPORTED_SYMBOLS = ["ConnectionSettingsDialog"];
|
4 | 4 | |
5 | -const { TorSettings, TorProxyType } = ChromeUtils.import(
|
|
6 | - "resource:///modules/TorSettings.jsm"
|
|
5 | +const { TorSettings, TorProxyType } = ChromeUtils.importESModule(
|
|
6 | + "resource:///modules/TorSettings.sys.mjs"
|
|
7 | 7 | );
|
8 | 8 | |
9 | 9 | const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
|
... | ... | @@ -6,12 +6,12 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); |
6 | 6 | |
7 | 7 | const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
|
8 | 8 | |
9 | -const { TorSettings, TorBridgeSource } = ChromeUtils.import(
|
|
10 | - "resource:///modules/TorSettings.jsm"
|
|
9 | +const { TorSettings, TorBridgeSource } = ChromeUtils.importESModule(
|
|
10 | + "resource:///modules/TorSettings.sys.mjs"
|
|
11 | 11 | );
|
12 | 12 | |
13 | -const { TorConnect, TorConnectTopics } = ChromeUtils.import(
|
|
14 | - "resource:///modules/TorConnect.jsm"
|
|
13 | +const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
|
|
14 | + "resource:///modules/TorConnect.sys.mjs"
|
|
15 | 15 | );
|
16 | 16 | |
17 | 17 | class ProvideBridgeDialog {
|
... | ... | @@ -4,11 +4,13 @@ var EXPORTED_SYMBOLS = ["RequestBridgeDialog"]; |
4 | 4 | |
5 | 5 | const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
6 | 6 | |
7 | -const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm");
|
|
7 | +const { BridgeDB } = ChromeUtils.importESModule(
|
|
8 | + "resource:///modules/BridgeDB.sys.mjs"
|
|
9 | +);
|
|
8 | 10 | const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
|
9 | 11 | |
10 | -const { TorConnect, TorConnectTopics } = ChromeUtils.import(
|
|
11 | - "resource:///modules/TorConnect.jsm"
|
|
12 | +const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
|
|
13 | + "resource:///modules/TorConnect.sys.mjs"
|
|
12 | 14 | );
|
13 | 15 | |
14 | 16 | class RequestBridgeDialog {
|
... | ... | @@ -2,12 +2,12 @@ |
2 | 2 | |
3 | 3 | var EXPORTED_SYMBOLS = ["TorLogDialog"];
|
4 | 4 | |
5 | -const { setTimeout, clearTimeout } = ChromeUtils.import(
|
|
6 | - "resource://gre/modules/Timer.jsm"
|
|
5 | +const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
|
|
6 | + "resource://gre/modules/Timer.sys.mjs"
|
|
7 | 7 | );
|
8 | 8 | |
9 | -const { TorMonitorService } = ChromeUtils.import(
|
|
10 | - "resource://gre/modules/TorMonitorService.jsm"
|
|
9 | +const { TorProviderBuilder } = ChromeUtils.importESModule(
|
|
10 | + "resource://gre/modules/TorProviderBuilder.sys.mjs"
|
|
11 | 11 | );
|
12 | 12 | const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
|
13 | 13 | |
... | ... | @@ -56,7 +56,7 @@ class TorLogDialog { |
56 | 56 | }, RESTORE_TIME);
|
57 | 57 | });
|
58 | 58 | |
59 | - this._logTextarea.value = TorMonitorService.getLog();
|
|
59 | + this._logTextarea.value = TorProviderBuilder.build().getLog();
|
|
60 | 60 | }
|
61 | 61 | |
62 | 62 | init(window, aDialog) {
|
1 | -"use strict";
|
|
1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
2 | 4 | |
3 | -var EXPORTED_SYMBOLS = ["BridgeDB"];
|
|
5 | +const lazy = {};
|
|
4 | 6 | |
5 | -const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
|
|
7 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
8 | + MoatRPC: "resource:///modules/Moat.sys.mjs",
|
|
9 | +});
|
|
6 | 10 | |
7 | -var BridgeDB = {
|
|
11 | +export var BridgeDB = {
|
|
8 | 12 | _moatRPC: null,
|
9 | 13 | _challenge: null,
|
10 | 14 | _image: null,
|
... | ... | @@ -20,7 +24,7 @@ var BridgeDB = { |
20 | 24 | |
21 | 25 | async submitCaptchaGuess(solution) {
|
22 | 26 | if (!this._moatRPC) {
|
23 | - this._moatRPC = new MoatRPC();
|
|
27 | + this._moatRPC = new lazy.MoatRPC();
|
|
24 | 28 | await this._moatRPC.init();
|
25 | 29 | }
|
26 | 30 | |
... | ... | @@ -37,7 +41,7 @@ var BridgeDB = { |
37 | 41 | async requestNewCaptchaImage() {
|
38 | 42 | try {
|
39 | 43 | if (!this._moatRPC) {
|
40 | - this._moatRPC = new MoatRPC();
|
|
44 | + this._moatRPC = new lazy.MoatRPC();
|
|
41 | 45 | await this._moatRPC.init();
|
42 | 46 | }
|
43 | 47 |
1 | -"use strict";
|
|
1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
2 | 4 | |
3 | -var EXPORTED_SYMBOLS = ["MoatRPC"];
|
|
5 | +import {
|
|
6 | + TorSettings,
|
|
7 | + TorBridgeSource,
|
|
8 | +} from "resource:///modules/TorSettings.sys.mjs";
|
|
4 | 9 | |
5 | -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
10 | +const lazy = {};
|
|
6 | 11 | |
7 | -const { Subprocess } = ChromeUtils.import(
|
|
8 | - "resource://gre/modules/Subprocess.jsm"
|
|
9 | -);
|
|
10 | - |
|
11 | -const { TorLauncherUtil } = ChromeUtils.import(
|
|
12 | - "resource://gre/modules/TorLauncherUtil.jsm"
|
|
13 | -);
|
|
14 | - |
|
15 | -const { TorProtocolService } = ChromeUtils.import(
|
|
16 | - "resource://gre/modules/TorProtocolService.jsm"
|
|
17 | -);
|
|
18 | - |
|
19 | -const { TorSettings, TorBridgeSource } = ChromeUtils.import(
|
|
20 | - "resource:///modules/TorSettings.jsm"
|
|
21 | -);
|
|
12 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
13 | + Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
|
14 | + TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
|
15 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
16 | +});
|
|
22 | 17 | |
23 | 18 | const TorLauncherPrefs = Object.freeze({
|
24 | 19 | bridgedb_front: "extensions.torlauncher.bridgedb_front",
|
... | ... | @@ -26,73 +21,54 @@ const TorLauncherPrefs = Object.freeze({ |
26 | 21 | moat_service: "extensions.torlauncher.moat_service",
|
27 | 22 | });
|
28 | 23 | |
29 | -// Config keys used to query tor daemon properties
|
|
30 | -const TorConfigKeys = Object.freeze({
|
|
31 | - clientTransportPlugin: "ClientTransportPlugin",
|
|
32 | -});
|
|
33 | - |
|
34 | 24 | //
|
35 | 25 | // Launches and controls the PT process lifetime
|
36 | 26 | //
|
37 | 27 | class MeekTransport {
|
38 | - constructor() {
|
|
39 | - this._inited = false;
|
|
40 | - this._meekClientProcess = null;
|
|
41 | - this._meekProxyType = null;
|
|
42 | - this._meekProxyAddress = null;
|
|
43 | - this._meekProxyPort = 0;
|
|
44 | - this._meekProxyUsername = null;
|
|
45 | - this._meekProxyPassword = null;
|
|
46 | - }
|
|
28 | + // These members are used by consumers to setup the proxy to do requests over
|
|
29 | + // meek. They are passed to newProxyInfoWithAuth.
|
|
30 | + proxyType = null;
|
|
31 | + proxyAddress = null;
|
|
32 | + proxyPort = 0;
|
|
33 | + proxyUsername = null;
|
|
34 | + proxyPassword = null;
|
|
35 | + |
|
36 | + #inited = false;
|
|
37 | + #meekClientProcess = null;
|
|
47 | 38 | |
48 | 39 | // launches the meekprocess
|
49 | 40 | async init() {
|
50 | 41 | // ensure we haven't already init'd
|
51 | - if (this._inited) {
|
|
42 | + if (this.#inited) {
|
|
52 | 43 | throw new Error("MeekTransport: Already initialized");
|
53 | 44 | }
|
54 | 45 | |
55 | - // cleanup function for killing orphaned pt process
|
|
56 | - let onException = () => {};
|
|
57 | 46 | try {
|
58 | 47 | // figure out which pluggable transport to use
|
59 | 48 | const supportedTransports = ["meek", "meek_lite"];
|
60 | - let transportPlugins = await TorProtocolService.readStringArraySetting(
|
|
61 | - TorConfigKeys.clientTransportPlugin
|
|
49 | + const proxy = (
|
|
50 | + await lazy.TorProviderBuilder.build().getPluggableTransports()
|
|
51 | + ).find(
|
|
52 | + pt =>
|
|
53 | + pt.type === "exec" &&
|
|
54 | + supportedTransports.some(t => pt.transports.includes(t))
|
|
62 | 55 | );
|
56 | + if (!proxy) {
|
|
57 | + throw new Error("No supported transport found.");
|
|
58 | + }
|
|
63 | 59 | |
64 | - let { meekTransport, meekClientPath, meekClientArgs } = (() => {
|
|
65 | - for (const line of transportPlugins) {
|
|
66 | - let tokens = line.split(" ");
|
|
67 | - if (tokens.length > 2 && tokens[1] == "exec") {
|
|
68 | - let transportArray = tokens[0].split(",").map(aStr => aStr.trim());
|
|
69 | - let transport = transportArray.find(aTransport =>
|
|
70 | - supportedTransports.includes(aTransport)
|
|
71 | - );
|
|
72 | - |
|
73 | - if (transport != undefined) {
|
|
74 | - return {
|
|
75 | - meekTransport: transport,
|
|
76 | - meekClientPath: tokens[2],
|
|
77 | - meekClientArgs: tokens.slice(3),
|
|
78 | - };
|
|
79 | - }
|
|
80 | - }
|
|
81 | - }
|
|
82 | - |
|
83 | - return {
|
|
84 | - meekTransport: null,
|
|
85 | - meekClientPath: null,
|
|
86 | - meekClientArgs: null,
|
|
87 | - };
|
|
88 | - })();
|
|
89 | - |
|
60 | + const meekTransport = proxy.transports.find(t =>
|
|
61 | + supportedTransports.includes(t)
|
|
62 | + );
|
|
90 | 63 | // Convert meek client path to absolute path if necessary
|
91 | - let meekWorkDir = TorLauncherUtil.getTorFile("pt-startup-dir", false);
|
|
92 | - if (TorLauncherUtil.isPathRelative(meekClientPath)) {
|
|
93 | - let meekPath = meekWorkDir.clone();
|
|
94 | - meekPath.appendRelativePath(meekClientPath);
|
|
95 | - meekClientPath = meekPath.path;
|
|
64 | + const meekWorkDir = lazy.TorLauncherUtil.getTorFile(
|
|
65 | + "pt-startup-dir",
|
|
66 | + false
|
|
67 | + );
|
|
68 | + if (lazy.TorLauncherUtil.isPathRelative(proxy.pathToBinary)) {
|
|
69 | + const meekPath = meekWorkDir.clone();
|
|
70 | + meekPath.appendRelativePath(proxy.pathToBinary);
|
|
71 | + proxy.pathToBinary = meekPath.path;
|
|
96 | 72 | }
|
97 | 73 | |
98 | 74 | // Construct the per-connection arguments.
|
... | ... | @@ -105,16 +81,13 @@ class MeekTransport { |
105 | 81 | // First the "<Key>=<Value>" formatted arguments MUST be escaped,
|
106 | 82 | // such that all backslash, equal sign, and semicolon characters
|
107 | 83 | // are escaped with a backslash.
|
108 | - let escapeArgValue = aValue => {
|
|
109 | - if (!aValue) {
|
|
110 | - return "";
|
|
111 | - }
|
|
112 | - |
|
113 | - let rv = aValue.replace(/\\/g, "\\\\");
|
|
114 | - rv = rv.replace(/=/g, "\\=");
|
|
115 | - rv = rv.replace(/;/g, "\\;");
|
|
116 | - return rv;
|
|
117 | - };
|
|
84 | + const escapeArgValue = aValue =>
|
|
85 | + aValue
|
|
86 | + ? aValue
|
|
87 | + .replaceAll("\\", "\\\\")
|
|
88 | + .replaceAll("=", "\\=")
|
|
89 | + .replaceAll(";", "\\;")
|
|
90 | + : "";
|
|
118 | 91 | |
119 | 92 | if (meekReflector) {
|
120 | 93 | meekClientEscapedArgs += "url=";
|
... | ... | @@ -132,10 +105,10 @@ class MeekTransport { |
132 | 105 | }
|
133 | 106 | |
134 | 107 | // Setup env and start meek process
|
135 | - let ptStateDir = TorLauncherUtil.getTorFile("tordatadir", false);
|
|
108 | + const ptStateDir = lazy.TorLauncherUtil.getTorFile("tordatadir", false);
|
|
136 | 109 | ptStateDir.append("pt_state"); // Match what tor uses.
|
137 | 110 | |
138 | - let envAdditions = {
|
|
111 | + const envAdditions = {
|
|
139 | 112 | TOR_PT_MANAGED_TRANSPORT_VER: "1",
|
140 | 113 | TOR_PT_STATE_LOCATION: ptStateDir.path,
|
141 | 114 | TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
|
... | ... | @@ -145,9 +118,9 @@ class MeekTransport { |
145 | 118 | envAdditions.TOR_PT_PROXY = TorSettings.proxy.uri;
|
146 | 119 | }
|
147 | 120 | |
148 | - let opts = {
|
|
149 | - command: meekClientPath,
|
|
150 | - arguments: meekClientArgs,
|
|
121 | + const opts = {
|
|
122 | + command: proxy.pathToBinary,
|
|
123 | + arguments: proxy.options.split(/s+/),
|
|
151 | 124 | workdir: meekWorkDir.path,
|
152 | 125 | environmentAppend: true,
|
153 | 126 | environment: envAdditions,
|
... | ... | @@ -155,27 +128,23 @@ class MeekTransport { |
155 | 128 | };
|
156 | 129 | |
157 | 130 | // Launch meek client
|
158 | - let meekClientProcess = await Subprocess.call(opts);
|
|
159 | - // kill our process if exception is thrown
|
|
160 | - onException = () => {
|
|
161 | - meekClientProcess.kill();
|
|
162 | - };
|
|
131 | + this.#meekClientProcess = await lazy.Subprocess.call(opts);
|
|
163 | 132 | |
164 | 133 | // Callback chain for reading stderr
|
165 | - let stderrLogger = async () => {
|
|
166 | - if (this._meekClientProcess) {
|
|
167 | - let errString = await this._meekClientProcess.stderr.readString();
|
|
168 | - console.log(`MeekTransport: stderr => ${errString}`);
|
|
169 | - await stderrLogger();
|
|
134 | + const stderrLogger = async () => {
|
|
135 | + while (this.#meekClientProcess) {
|
|
136 | + const errString = await this.#meekClientProcess.stderr.readString();
|
|
137 | + if (errString) {
|
|
138 | + console.log(`MeekTransport: stderr => ${errString}`);
|
|
139 | + }
|
|
170 | 140 | }
|
171 | 141 | };
|
172 | 142 | stderrLogger();
|
173 | 143 | |
174 | 144 | // Read pt's stdout until terminal (CMETHODS DONE) is reached
|
175 | 145 | // returns array of lines for parsing
|
176 | - let getInitLines = async (stdout = "") => {
|
|
177 | - let string = await meekClientProcess.stdout.readString();
|
|
178 | - stdout += string;
|
|
146 | + const getInitLines = async (stdout = "") => {
|
|
147 | + stdout += await this.#meekClientProcess.stdout.readString();
|
|
179 | 148 | |
180 | 149 | // look for the final message
|
181 | 150 | const CMETHODS_DONE = "CMETHODS DONE";
|
... | ... | @@ -188,20 +157,16 @@ class MeekTransport { |
188 | 157 | };
|
189 | 158 | |
190 | 159 | // read our lines from pt's stdout
|
191 | - let meekInitLines = await getInitLines();
|
|
160 | + const meekInitLines = await getInitLines();
|
|
192 | 161 | // tokenize our pt lines
|
193 | - let meekInitTokens = meekInitLines.map(line => {
|
|
194 | - let tokens = line.split(" ");
|
|
162 | + const meekInitTokens = meekInitLines.map(line => {
|
|
163 | + const tokens = line.split(" ");
|
|
195 | 164 | return {
|
196 | 165 | keyword: tokens[0],
|
197 | 166 | args: tokens.slice(1),
|
198 | 167 | };
|
199 | 168 | });
|
200 | 169 | |
201 | - let meekProxyType = null;
|
|
202 | - let meekProxyAddr = null;
|
|
203 | - let meekProxyPort = 0;
|
|
204 | - |
|
205 | 170 | // parse our pt tokens
|
206 | 171 | for (const { keyword, args } of meekInitTokens) {
|
207 | 172 | const argsJoined = args.join(" ");
|
... | ... | @@ -251,9 +216,9 @@ class MeekTransport { |
251 | 216 | }
|
252 | 217 | |
253 | 218 | // convert proxy type to strings used by protocol-proxy-servce
|
254 | - meekProxyType = proxyType === "socks5" ? "socks" : "socks4";
|
|
255 | - meekProxyAddr = addr;
|
|
256 | - meekProxyPort = port;
|
|
219 | + this.proxyType = proxyType === "socks5" ? "socks" : "socks4";
|
|
220 | + this.proxyAddress = addr;
|
|
221 | + this.proxyPort = port;
|
|
257 | 222 | |
258 | 223 | break;
|
259 | 224 | }
|
... | ... | @@ -278,49 +243,47 @@ class MeekTransport { |
278 | 243 | }
|
279 | 244 | }
|
280 | 245 | |
281 | - this._meekClientProcess = meekClientProcess;
|
|
282 | 246 | // register callback to cleanup on process exit
|
283 | - this._meekClientProcess.wait().then(exitObj => {
|
|
284 | - this._meekClientProcess = null;
|
|
247 | + this.#meekClientProcess.wait().then(exitObj => {
|
|
248 | + this.#meekClientProcess = null;
|
|
285 | 249 | this.uninit();
|
286 | 250 | });
|
287 | 251 | |
288 | - this._meekProxyType = meekProxyType;
|
|
289 | - this._meekProxyAddress = meekProxyAddr;
|
|
290 | - this._meekProxyPort = meekProxyPort;
|
|
291 | - |
|
292 | 252 | // socks5
|
293 | - if (meekProxyType === "socks") {
|
|
253 | + if (this.proxyType === "socks") {
|
|
294 | 254 | if (meekClientEscapedArgs.length <= 255) {
|
295 | - this._meekProxyUsername = meekClientEscapedArgs;
|
|
296 | - this._meekProxyPassword = "\x00";
|
|
255 | + this.proxyUsername = meekClientEscapedArgs;
|
|
256 | + this.proxyPassword = "\x00";
|
|
297 | 257 | } else {
|
298 | - this._meekProxyUsername = meekClientEscapedArgs.substring(0, 255);
|
|
299 | - this._meekProxyPassword = meekClientEscapedArgs.substring(255);
|
|
258 | + this.proxyUsername = meekClientEscapedArgs.substring(0, 255);
|
|
259 | + this.proxyPassword = meekClientEscapedArgs.substring(255);
|
|
300 | 260 | }
|
301 | 261 | // socks4
|
302 | 262 | } else {
|
303 | - this._meekProxyUsername = meekClientEscapedArgs;
|
|
304 | - this._meekProxyPassword = undefined;
|
|
263 | + this.proxyUsername = meekClientEscapedArgs;
|
|
264 | + this.proxyPassword = undefined;
|
|
305 | 265 | }
|
306 | 266 | |
307 | - this._inited = true;
|
|
267 | + this.#inited = true;
|
|
308 | 268 | } catch (ex) {
|
309 | - onException();
|
|
269 | + if (this.#meekClientProcess) {
|
|
270 | + this.#meekClientProcess.kill();
|
|
271 | + this.#meekClientProcess = null;
|
|
272 | + }
|
|
310 | 273 | throw ex;
|
311 | 274 | }
|
312 | 275 | }
|
313 | 276 | |
314 | 277 | async uninit() {
|
315 | - this._inited = false;
|
|
316 | - |
|
317 | - await this._meekClientProcess?.kill();
|
|
318 | - this._meekClientProcess = null;
|
|
319 | - this._meekProxyType = null;
|
|
320 | - this._meekProxyAddress = null;
|
|
321 | - this._meekProxyPort = 0;
|
|
322 | - this._meekProxyUsername = null;
|
|
323 | - this._meekProxyPassword = null;
|
|
278 | + this.#inited = false;
|
|
279 | + |
|
280 | + await this.#meekClientProcess?.kill();
|
|
281 | + this.#meekClientProcess = null;
|
|
282 | + this.proxyType = null;
|
|
283 | + this.proxyAddress = null;
|
|
284 | + this.proxyPort = 0;
|
|
285 | + this.proxyUsername = null;
|
|
286 | + this.proxyPassword = null;
|
|
324 | 287 | }
|
325 | 288 | }
|
326 | 289 | |
... | ... | @@ -328,21 +291,25 @@ class MeekTransport { |
328 | 291 | // Callback object with a cached promise for the returned Moat data
|
329 | 292 | //
|
330 | 293 | class MoatResponseListener {
|
294 | + #response = "";
|
|
295 | + #responsePromise;
|
|
296 | + #resolve;
|
|
297 | + #reject;
|
|
331 | 298 | constructor() {
|
332 | - this._response = "";
|
|
299 | + this.#response = "";
|
|
333 | 300 | // we need this promise here because await nsIHttpChannel::asyncOpen does
|
334 | 301 | // not return only once the request is complete, it seems to return
|
335 | 302 | // after it begins, so we have to get the result from this listener object.
|
336 | 303 | // This promise is only resolved once onStopRequest is called
|
337 | - this._responsePromise = new Promise((resolve, reject) => {
|
|
338 | - this._resolve = resolve;
|
|
339 | - this._reject = reject;
|
|
304 | + this.#responsePromise = new Promise((resolve, reject) => {
|
|
305 | + this.#resolve = resolve;
|
|
306 | + this.#reject = reject;
|
|
340 | 307 | });
|
341 | 308 | }
|
342 | 309 | |
343 | 310 | // callers wait on this for final response
|
344 | 311 | response() {
|
345 | - return this._responsePromise;
|
|
312 | + return this.#responsePromise;
|
|
346 | 313 | }
|
347 | 314 | |
348 | 315 | // noop
|
... | ... | @@ -352,16 +319,17 @@ class MoatResponseListener { |
352 | 319 | onStopRequest(request, status) {
|
353 | 320 | try {
|
354 | 321 | if (!Components.isSuccessCode(status)) {
|
355 | - const errorMessage = TorLauncherUtil.getLocalizedStringForError(status);
|
|
356 | - this._reject(new Error(errorMessage));
|
|
322 | + const errorMessage =
|
|
323 | + lazy.TorLauncherUtil.getLocalizedStringForError(status);
|
|
324 | + this.#reject(new Error(errorMessage));
|
|
357 | 325 | }
|
358 | 326 | if (request.responseStatus != 200) {
|
359 | - this._reject(new Error(request.responseStatusText));
|
|
327 | + this.#reject(new Error(request.responseStatusText));
|
|
360 | 328 | }
|
361 | 329 | } catch (err) {
|
362 | - this._reject(err);
|
|
330 | + this.#reject(err);
|
|
363 | 331 | }
|
364 | - this._resolve(this._response);
|
|
332 | + this.#resolve(this.#response);
|
|
365 | 333 | }
|
366 | 334 | |
367 | 335 | // read response data
|
... | ... | @@ -370,30 +338,32 @@ class MoatResponseListener { |
370 | 338 | "@mozilla.org/scriptableinputstream;1"
|
371 | 339 | ].createInstance(Ci.nsIScriptableInputStream);
|
372 | 340 | scriptableStream.init(stream);
|
373 | - this._response += scriptableStream.read(length);
|
|
341 | + this.#response += scriptableStream.read(length);
|
|
374 | 342 | }
|
375 | 343 | }
|
376 | 344 | |
377 | 345 | class InternetTestResponseListener {
|
346 | + #promise;
|
|
347 | + #resolve;
|
|
348 | + #reject;
|
|
378 | 349 | constructor() {
|
379 | - this._promise = new Promise((resolve, reject) => {
|
|
380 | - this._resolve = resolve;
|
|
381 | - this._reject = reject;
|
|
350 | + this.#promise = new Promise((resolve, reject) => {
|
|
351 | + this.#resolve = resolve;
|
|
352 | + this.#reject = reject;
|
|
382 | 353 | });
|
383 | 354 | }
|
384 | 355 | |
385 | 356 | // callers wait on this for final response
|
386 | 357 | get status() {
|
387 | - return this._promise;
|
|
358 | + return this.#promise;
|
|
388 | 359 | }
|
389 | 360 | |
390 | 361 | onStartRequest(request) {}
|
391 | 362 | |
392 | 363 | // resolve or reject our Promise
|
393 | 364 | onStopRequest(request, status) {
|
394 | - let statuses = {};
|
|
395 | 365 | try {
|
396 | - statuses = {
|
|
366 | + const statuses = {
|
|
397 | 367 | components: status,
|
398 | 368 | successful: Components.isSuccessCode(status),
|
399 | 369 | };
|
... | ... | @@ -408,56 +378,51 @@ class InternetTestResponseListener { |
408 | 378 | err
|
409 | 379 | );
|
410 | 380 | }
|
381 | + this.#resolve(statuses);
|
|
411 | 382 | } catch (err) {
|
412 | - this._reject(err);
|
|
383 | + this.#reject(err);
|
|
413 | 384 | }
|
414 | - this._resolve(statuses);
|
|
415 | 385 | }
|
416 | 386 | |
417 | 387 | onDataAvailable(request, stream, offset, length) {
|
418 | - // We do not care of the actual data, as long as we have a successful
|
|
388 | + // We do not care of the actual data, as long as we have a successful
|
|
419 | 389 | // connection
|
420 | 390 | }
|
421 | 391 | }
|
422 | 392 | |
423 | 393 | // constructs the json objects and sends the request over moat
|
424 | -class MoatRPC {
|
|
425 | - constructor() {
|
|
426 | - this._meekTransport = null;
|
|
427 | - this._inited = false;
|
|
428 | - }
|
|
394 | +export class MoatRPC {
|
|
395 | + #inited = false;
|
|
396 | + #meekTransport = null;
|
|
429 | 397 | |
430 | 398 | get inited() {
|
431 | - return this._inited;
|
|
399 | + return this.#inited;
|
|
432 | 400 | }
|
433 | 401 | |
434 | 402 | async init() {
|
435 | - if (this._inited) {
|
|
403 | + if (this.#inited) {
|
|
436 | 404 | throw new Error("MoatRPC: Already initialized");
|
437 | 405 | }
|
438 | 406 | |
439 | 407 | let meekTransport = new MeekTransport();
|
440 | 408 | await meekTransport.init();
|
441 | - this._meekTransport = meekTransport;
|
|
442 | - this._inited = true;
|
|
409 | + this.#meekTransport = meekTransport;
|
|
410 | + this.#inited = true;
|
|
443 | 411 | }
|
444 | 412 | |
445 | 413 | async uninit() {
|
446 | - await this._meekTransport?.uninit();
|
|
447 | - this._meekTransport = null;
|
|
448 | - this._inited = false;
|
|
414 | + await this.#meekTransport?.uninit();
|
|
415 | + this.#meekTransport = null;
|
|
416 | + this.#inited = false;
|
|
449 | 417 | }
|
450 | 418 | |
451 | - _makeHttpHandler(uriString) {
|
|
452 | - if (!this._inited) {
|
|
419 | + #makeHttpHandler(uriString) {
|
|
420 | + if (!this.#inited) {
|
|
453 | 421 | throw new Error("MoatRPC: Not initialized");
|
454 | 422 | }
|
455 | 423 | |
456 | - const proxyType = this._meekTransport._meekProxyType;
|
|
457 | - const proxyAddress = this._meekTransport._meekProxyAddress;
|
|
458 | - const proxyPort = this._meekTransport._meekProxyPort;
|
|
459 | - const proxyUsername = this._meekTransport._meekProxyUsername;
|
|
460 | - const proxyPassword = this._meekTransport._meekProxyPassword;
|
|
424 | + const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
|
|
425 | + this.#meekTransport;
|
|
461 | 426 | |
462 | 427 | const proxyPS = Cc[
|
463 | 428 | "@mozilla.org/network/protocol-proxy-service;1"
|
... | ... | @@ -511,11 +476,11 @@ class MoatRPC { |
511 | 476 | return ch;
|
512 | 477 | }
|
513 | 478 | |
514 | - async _makeRequest(procedure, args) {
|
|
479 | + async #makeRequest(procedure, args) {
|
|
515 | 480 | const procedureURIString = `${Services.prefs.getStringPref(
|
516 | 481 | TorLauncherPrefs.moat_service
|
517 | 482 | )}/${procedure}`;
|
518 | - const ch = this._makeHttpHandler(procedureURIString);
|
|
483 | + const ch = this.#makeHttpHandler(procedureURIString);
|
|
519 | 484 | |
520 | 485 | // Arrange for the POST data to be sent.
|
521 | 486 | const argsJson = JSON.stringify(args);
|
... | ... | @@ -544,7 +509,7 @@ class MoatRPC { |
544 | 509 | const uri = `${Services.prefs.getStringPref(
|
545 | 510 | TorLauncherPrefs.moat_service
|
546 | 511 | )}/circumvention/countries`;
|
547 | - const ch = this._makeHttpHandler(uri);
|
|
512 | + const ch = this.#makeHttpHandler(uri);
|
|
548 | 513 | ch.requestMethod = "HEAD";
|
549 | 514 | |
550 | 515 | const listener = new InternetTestResponseListener();
|
... | ... | @@ -582,7 +547,7 @@ class MoatRPC { |
582 | 547 | },
|
583 | 548 | ],
|
584 | 549 | };
|
585 | - const response = await this._makeRequest("fetch", args);
|
|
550 | + const response = await this.#makeRequest("fetch", args);
|
|
586 | 551 | if ("errors" in response) {
|
587 | 552 | const code = response.errors[0].code;
|
588 | 553 | const detail = response.errors[0].detail;
|
... | ... | @@ -623,7 +588,7 @@ class MoatRPC { |
623 | 588 | },
|
624 | 589 | ],
|
625 | 590 | };
|
626 | - const response = await this._makeRequest("check", args);
|
|
591 | + const response = await this.#makeRequest("check", args);
|
|
627 | 592 | if ("errors" in response) {
|
628 | 593 | const code = response.errors[0].code;
|
629 | 594 | const detail = response.errors[0].detail;
|
... | ... | @@ -642,7 +607,7 @@ class MoatRPC { |
642 | 607 | |
643 | 608 | // Convert received settings object to format used by TorSettings module
|
644 | 609 | // In the event of error, just return null
|
645 | - _fixupSettings(settings) {
|
|
610 | + #fixupSettings(settings) {
|
|
646 | 611 | try {
|
647 | 612 | let retval = TorSettings.defaultSettings();
|
648 | 613 | if ("bridges" in settings) {
|
... | ... | @@ -691,11 +656,11 @@ class MoatRPC { |
691 | 656 | // Converts a list of settings objects received from BridgeDB to a list of settings objects
|
692 | 657 | // understood by the TorSettings module
|
693 | 658 | // In the event of error, returns and empty list
|
694 | - _fixupSettingsList(settingsList) {
|
|
659 | + #fixupSettingsList(settingsList) {
|
|
695 | 660 | try {
|
696 | 661 | let retval = [];
|
697 | 662 | for (let settings of settingsList) {
|
698 | - settings = this._fixupSettings(settings);
|
|
663 | + settings = this.#fixupSettings(settings);
|
|
699 | 664 | if (settings != null) {
|
700 | 665 | retval.push(settings);
|
701 | 666 | }
|
... | ... | @@ -724,7 +689,7 @@ class MoatRPC { |
724 | 689 | transports: transports ? transports : [],
|
725 | 690 | country,
|
726 | 691 | };
|
727 | - const response = await this._makeRequest("circumvention/settings", args);
|
|
692 | + const response = await this.#makeRequest("circumvention/settings", args);
|
|
728 | 693 | let settings = {};
|
729 | 694 | if ("errors" in response) {
|
730 | 695 | const code = response.errors[0].code;
|
... | ... | @@ -739,7 +704,7 @@ class MoatRPC { |
739 | 704 | |
740 | 705 | throw new Error(`MoatRPC: ${detail} (${code})`);
|
741 | 706 | } else if ("settings" in response) {
|
742 | - settings.settings = this._fixupSettingsList(response.settings);
|
|
707 | + settings.settings = this.#fixupSettingsList(response.settings);
|
|
743 | 708 | }
|
744 | 709 | if ("country" in response) {
|
745 | 710 | settings.country = response.country;
|
... | ... | @@ -753,7 +718,7 @@ class MoatRPC { |
753 | 718 | // for
|
754 | 719 | async circumvention_countries() {
|
755 | 720 | const args = {};
|
756 | - return this._makeRequest("circumvention/countries", args);
|
|
721 | + return this.#makeRequest("circumvention/countries", args);
|
|
757 | 722 | }
|
758 | 723 | |
759 | 724 | // Request a copy of the builtin bridges, takes the following parameters:
|
... | ... | @@ -766,7 +731,7 @@ class MoatRPC { |
766 | 731 | const args = {
|
767 | 732 | transports: transports ? transports : [],
|
768 | 733 | };
|
769 | - const response = await this._makeRequest("circumvention/builtin", args);
|
|
734 | + const response = await this.#makeRequest("circumvention/builtin", args);
|
|
770 | 735 | if ("errors" in response) {
|
771 | 736 | const code = response.errors[0].code;
|
772 | 737 | const detail = response.errors[0].detail;
|
... | ... | @@ -791,13 +756,13 @@ class MoatRPC { |
791 | 756 | const args = {
|
792 | 757 | transports: transports ? transports : [],
|
793 | 758 | };
|
794 | - const response = await this._makeRequest("circumvention/defaults", args);
|
|
759 | + const response = await this.#makeRequest("circumvention/defaults", args);
|
|
795 | 760 | if ("errors" in response) {
|
796 | 761 | const code = response.errors[0].code;
|
797 | 762 | const detail = response.errors[0].detail;
|
798 | 763 | throw new Error(`MoatRPC: ${detail} (${code})`);
|
799 | 764 | } else if ("settings" in response) {
|
800 | - return this._fixupSettingsList(response.settings);
|
|
765 | + return this.#fixupSettingsList(response.settings);
|
|
801 | 766 | }
|
802 | 767 | return [];
|
803 | 768 | }
|
1 | -"use strict";
|
|
1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
2 | 4 | |
3 | -var EXPORTED_SYMBOLS = [
|
|
4 | - "InternetStatus",
|
|
5 | - "TorConnect",
|
|
6 | - "TorConnectTopics",
|
|
7 | - "TorConnectState",
|
|
8 | -];
|
|
5 | +import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
|
|
9 | 6 | |
10 | -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
7 | +const lazy = {};
|
|
11 | 8 | |
12 | -const { setTimeout, clearTimeout } = ChromeUtils.import(
|
|
13 | - "resource://gre/modules/Timer.jsm"
|
|
14 | -);
|
|
9 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
10 | + MoatRPC: "resource:///modules/Moat.sys.mjs",
|
|
11 | + TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
|
|
12 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
13 | +});
|
|
15 | 14 | |
16 | -const { BrowserWindowTracker } = ChromeUtils.import(
|
|
15 | +// TODO: Should we move this to the about:torconnect actor?
|
|
16 | +ChromeUtils.defineModuleGetter(
|
|
17 | + lazy,
|
|
18 | + "BrowserWindowTracker",
|
|
17 | 19 | "resource:///modules/BrowserWindowTracker.jsm"
|
18 | 20 | );
|
19 | 21 | |
20 | -const { TorMonitorService } = ChromeUtils.import(
|
|
21 | - "resource://gre/modules/TorMonitorService.jsm"
|
|
22 | -);
|
|
23 | -const { TorBootstrapRequest } = ChromeUtils.import(
|
|
24 | - "resource://gre/modules/TorBootstrapRequest.jsm"
|
|
25 | -);
|
|
26 | - |
|
27 | -const { TorSettings, TorSettingsTopics, TorBuiltinBridgeTypes } =
|
|
28 | - ChromeUtils.import("resource:///modules/TorSettings.jsm");
|
|
22 | +import {
|
|
23 | + TorSettings,
|
|
24 | + TorSettingsTopics,
|
|
25 | + TorBuiltinBridgeTypes,
|
|
26 | +} from "resource:///modules/TorSettings.sys.mjs";
|
|
29 | 27 | |
30 | 28 | const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
|
31 | 29 | |
32 | -const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
|
|
33 | - |
|
34 | 30 | const TorTopics = Object.freeze({
|
35 | 31 | LogHasWarnOrErr: "TorLogHasWarnOrErr",
|
36 | 32 | ProcessExited: "TorProcessExited",
|
... | ... | @@ -46,7 +42,7 @@ const TorConnectPrefs = Object.freeze({ |
46 | 42 | allow_internet_test: "torbrowser.bootstrap.allow_internet_test",
|
47 | 43 | });
|
48 | 44 | |
49 | -const TorConnectState = Object.freeze({
|
|
45 | +export const TorConnectState = Object.freeze({
|
|
50 | 46 | /* Our initial state */
|
51 | 47 | Initial: "Initial",
|
52 | 48 | /* In-between initial boot and bootstrapping, users can change tor network settings during this state */
|
... | ... | @@ -156,7 +152,7 @@ const TorConnectStateTransitions = Object.freeze( |
156 | 152 | );
|
157 | 153 | |
158 | 154 | /* Topics Notified by the TorConnect module */
|
159 | -const TorConnectTopics = Object.freeze({
|
|
155 | +export const TorConnectTopics = Object.freeze({
|
|
160 | 156 | StateChange: "torconnect:state-change",
|
161 | 157 | BootstrapProgress: "torconnect:bootstrap-progress",
|
162 | 158 | BootstrapComplete: "torconnect:bootstrap-complete",
|
... | ... | @@ -238,7 +234,7 @@ const debug_sleep = async ms => { |
238 | 234 | });
|
239 | 235 | };
|
240 | 236 | |
241 | -const InternetStatus = Object.freeze({
|
|
237 | +export const InternetStatus = Object.freeze({
|
|
242 | 238 | Unknown: -1,
|
243 | 239 | Offline: 0,
|
244 | 240 | Online: 1,
|
... | ... | @@ -302,7 +298,7 @@ class InternetTest { |
302 | 298 | // waiting both for the bootstrap, and for the Internet test.
|
303 | 299 | // However, managing Moat with async/await is much easier as it avoids a
|
304 | 300 | // callback hell, and it makes extra explicit that we are uniniting it.
|
305 | - const mrpc = new MoatRPC();
|
|
301 | + const mrpc = new lazy.MoatRPC();
|
|
306 | 302 | let status = null;
|
307 | 303 | let error = null;
|
308 | 304 | try {
|
... | ... | @@ -340,7 +336,7 @@ class InternetTest { |
340 | 336 | }
|
341 | 337 | }
|
342 | 338 | |
343 | -const TorConnect = (() => {
|
|
339 | +export const TorConnect = (() => {
|
|
344 | 340 | let retval = {
|
345 | 341 | _state: TorConnectState.Initial,
|
346 | 342 | _bootstrapProgress: 0,
|
... | ... | @@ -459,7 +455,7 @@ const TorConnect = (() => { |
459 | 455 | return;
|
460 | 456 | }
|
461 | 457 | |
462 | - const tbr = new TorBootstrapRequest();
|
|
458 | + const tbr = new lazy.TorBootstrapRequest();
|
|
463 | 459 | const internetTest = new InternetTest();
|
464 | 460 | let cancelled = false;
|
465 | 461 | |
... | ... | @@ -604,7 +600,7 @@ const TorConnect = (() => { |
604 | 600 | |
605 | 601 | // lookup user's potential censorship circumvention settings from Moat service
|
606 | 602 | try {
|
607 | - this.mrpc = new MoatRPC();
|
|
603 | + this.mrpc = new lazy.MoatRPC();
|
|
608 | 604 | await this.mrpc.init();
|
609 | 605 | |
610 | 606 | if (this.transitioning) {
|
... | ... | @@ -678,7 +674,7 @@ const TorConnect = (() => { |
678 | 674 | await TorSettings.applySettings();
|
679 | 675 | |
680 | 676 | // build out our bootstrap request
|
681 | - const tbr = new TorBootstrapRequest();
|
|
677 | + const tbr = new lazy.TorBootstrapRequest();
|
|
682 | 678 | tbr.onbootstrapstatus = (progress, status) => {
|
683 | 679 | TorConnect._updateBootstrapStatus(progress, status);
|
684 | 680 | };
|
... | ... | @@ -915,7 +911,7 @@ const TorConnect = (() => { |
915 | 911 | * @type {boolean}
|
916 | 912 | */
|
917 | 913 | get enabled() {
|
918 | - return TorMonitorService.ownsTorDaemon;
|
|
914 | + return lazy.TorProviderBuilder.build().ownsTorDaemon;
|
|
919 | 915 | },
|
920 | 916 | |
921 | 917 | get shouldShowTorConnect() {
|
... | ... | @@ -1053,7 +1049,7 @@ const TorConnect = (() => { |
1053 | 1049 | Further external commands and helper methods
|
1054 | 1050 | */
|
1055 | 1051 | openTorPreferences() {
|
1056 | - const win = BrowserWindowTracker.getTopWindow();
|
|
1052 | + const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
1057 | 1053 | win.switchToTabHavingURI("about:preferences#connection", true);
|
1058 | 1054 | },
|
1059 | 1055 | |
... | ... | @@ -1073,7 +1069,7 @@ const TorConnect = (() => { |
1073 | 1069 | * begin AutoBootstrapping, if possible.
|
1074 | 1070 | */
|
1075 | 1071 | openTorConnect(options) {
|
1076 | - const win = BrowserWindowTracker.getTopWindow();
|
|
1072 | + const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
1077 | 1073 | win.switchToTabHavingURI("about:torconnect", true, {
|
1078 | 1074 | ignoreQueryString: true,
|
1079 | 1075 | });
|
... | ... | @@ -1094,7 +1090,7 @@ const TorConnect = (() => { |
1094 | 1090 | },
|
1095 | 1091 | |
1096 | 1092 | viewTorLogs() {
|
1097 | - const win = BrowserWindowTracker.getTopWindow();
|
|
1093 | + const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
1098 | 1094 | win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
|
1099 | 1095 | },
|
1100 | 1096 | |
... | ... | @@ -1104,7 +1100,7 @@ const TorConnect = (() => { |
1104 | 1100 | if (this._countryCodes.length) {
|
1105 | 1101 | return this._countryCodes;
|
1106 | 1102 | }
|
1107 | - const mrpc = new MoatRPC();
|
|
1103 | + const mrpc = new lazy.MoatRPC();
|
|
1108 | 1104 | try {
|
1109 | 1105 | await mrpc.init();
|
1110 | 1106 | this._countryCodes = await mrpc.circumvention_countries();
|
1 | -"use strict";
|
|
1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
2 | 4 | |
3 | -var EXPORTED_SYMBOLS = [
|
|
4 | - "TorSettings",
|
|
5 | - "TorSettingsTopics",
|
|
6 | - "TorSettingsData",
|
|
7 | - "TorBridgeSource",
|
|
8 | - "TorBuiltinBridgeTypes",
|
|
9 | - "TorProxyType",
|
|
10 | -];
|
|
5 | +const lazy = {};
|
|
11 | 6 | |
12 | -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
13 | - |
|
14 | -const { TorMonitorService } = ChromeUtils.import(
|
|
15 | - "resource://gre/modules/TorMonitorService.jsm"
|
|
16 | -);
|
|
17 | -const { TorProtocolService } = ChromeUtils.import(
|
|
18 | - "resource://gre/modules/TorProtocolService.jsm"
|
|
19 | -);
|
|
20 | - |
|
21 | -/* tor-launcher observer topics */
|
|
22 | -const TorTopics = Object.freeze({
|
|
23 | - ProcessIsReady: "TorProcessIsReady",
|
|
7 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
8 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
9 | + TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
24 | 10 | });
|
25 | 11 | |
26 | 12 | /* TorSettings observer topics */
|
27 | -const TorSettingsTopics = Object.freeze({
|
|
13 | +export const TorSettingsTopics = Object.freeze({
|
|
28 | 14 | Ready: "torsettings:ready",
|
29 | 15 | SettingChanged: "torsettings:setting-changed",
|
30 | 16 | });
|
31 | 17 | |
32 | 18 | /* TorSettings observer data (for SettingChanged topic) */
|
33 | -const TorSettingsData = Object.freeze({
|
|
19 | +export const TorSettingsData = Object.freeze({
|
|
34 | 20 | QuickStartEnabled: "torsettings:quickstart_enabled",
|
35 | 21 | });
|
36 | 22 | |
... | ... | @@ -98,21 +84,21 @@ const TorConfigKeys = Object.freeze({ |
98 | 84 | clientTransportPlugin: "ClientTransportPlugin",
|
99 | 85 | });
|
100 | 86 | |
101 | -const TorBridgeSource = Object.freeze({
|
|
87 | +export const TorBridgeSource = Object.freeze({
|
|
102 | 88 | Invalid: -1,
|
103 | 89 | BuiltIn: 0,
|
104 | 90 | BridgeDB: 1,
|
105 | 91 | UserProvided: 2,
|
106 | 92 | });
|
107 | 93 | |
108 | -const TorProxyType = Object.freeze({
|
|
94 | +export const TorProxyType = Object.freeze({
|
|
109 | 95 | Invalid: -1,
|
110 | 96 | Socks4: 0,
|
111 | 97 | Socks5: 1,
|
112 | 98 | HTTPS: 2,
|
113 | 99 | });
|
114 | 100 | |
115 | -const TorBuiltinBridgeTypes = Object.freeze(
|
|
101 | +export const TorBuiltinBridgeTypes = Object.freeze(
|
|
116 | 102 | (() => {
|
117 | 103 | const bridgeListBranch = Services.prefs.getBranch(
|
118 | 104 | TorLauncherPrefs.default_bridge
|
... | ... | @@ -254,7 +240,7 @@ const arrayCopy = function (array) { |
254 | 240 | |
255 | 241 | /* TorSettings module */
|
256 | 242 | |
257 | -const TorSettings = (() => {
|
|
243 | +export const TorSettings = (() => {
|
|
258 | 244 | const self = {
|
259 | 245 | _settings: null,
|
260 | 246 | |
... | ... | @@ -288,7 +274,8 @@ const TorSettings = (() => { |
288 | 274 | |
289 | 275 | /* load or init our settings, and register observers */
|
290 | 276 | init() {
|
291 | - if (TorMonitorService.ownsTorDaemon) {
|
|
277 | + const provider = lazy.TorProviderBuilder.build();
|
|
278 | + if (provider.ownsTorDaemon) {
|
|
292 | 279 | // if the settings branch exists, load settings from prefs
|
293 | 280 | if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
|
294 | 281 | this.loadFromPrefs();
|
... | ... | @@ -296,9 +283,9 @@ const TorSettings = (() => { |
296 | 283 | // otherwise load defaults
|
297 | 284 | this._settings = this.defaultSettings();
|
298 | 285 | }
|
299 | - Services.obs.addObserver(this, TorTopics.ProcessIsReady);
|
|
286 | + Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
|
|
300 | 287 | |
301 | - if (TorMonitorService.isRunning) {
|
|
288 | + if (provider.isRunning) {
|
|
302 | 289 | this.handleProcessReady();
|
303 | 290 | }
|
304 | 291 | }
|
... | ... | @@ -309,8 +296,11 @@ const TorSettings = (() => { |
309 | 296 | console.log(`TorSettings: Observed ${topic}`);
|
310 | 297 | |
311 | 298 | switch (topic) {
|
312 | - case TorTopics.ProcessIsReady:
|
|
313 | - Services.obs.removeObserver(this, TorTopics.ProcessIsReady);
|
|
299 | + case lazy.TorProviderTopics.ProcessIsReady:
|
|
300 | + Services.obs.removeObserver(
|
|
301 | + this,
|
|
302 | + lazy.TorProviderTopics.ProcessIsReady
|
|
303 | + );
|
|
314 | 304 | await this.handleProcessReady();
|
315 | 305 | break;
|
316 | 306 | }
|
... | ... | @@ -569,7 +559,7 @@ const TorSettings = (() => { |
569 | 559 | }
|
570 | 560 | |
571 | 561 | /* Push to Tor */
|
572 | - await TorProtocolService.writeSettings(settingsMap);
|
|
562 | + await lazy.TorProviderBuilder.build().writeSettings(settingsMap);
|
|
573 | 563 | |
574 | 564 | return this;
|
575 | 565 | },
|
... | ... | @@ -123,7 +123,7 @@ XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"] |
123 | 123 | EXTRA_JS_MODULES += [
|
124 | 124 | "AboutNewTab.jsm",
|
125 | 125 | "AsyncTabSwitcher.jsm",
|
126 | - "BridgeDB.jsm",
|
|
126 | + "BridgeDB.sys.mjs",
|
|
127 | 127 | "BrowserUIUtils.jsm",
|
128 | 128 | "BrowserUsageTelemetry.jsm",
|
129 | 129 | "BrowserWindowTracker.jsm",
|
... | ... | @@ -135,7 +135,7 @@ EXTRA_JS_MODULES += [ |
135 | 135 | "FeatureCallout.sys.mjs",
|
136 | 136 | "HomePage.jsm",
|
137 | 137 | "LaterRun.jsm",
|
138 | - 'Moat.jsm',
|
|
138 | + "Moat.sys.mjs",
|
|
139 | 139 | "NewTabPagePreloading.jsm",
|
140 | 140 | "OpenInTabsUtils.jsm",
|
141 | 141 | "PageActions.jsm",
|
... | ... | @@ -149,8 +149,8 @@ EXTRA_JS_MODULES += [ |
149 | 149 | "SitePermissions.sys.mjs",
|
150 | 150 | "TabsList.jsm",
|
151 | 151 | "TabUnloader.jsm",
|
152 | - "TorConnect.jsm",
|
|
153 | - "TorSettings.jsm",
|
|
152 | + "TorConnect.sys.mjs",
|
|
153 | + "TorSettings.sys.mjs",
|
|
154 | 154 | "TorStrings.jsm",
|
155 | 155 | "TransientPrefs.jsm",
|
156 | 156 | "URILoadingHelper.sys.mjs",
|
1 | 1 | import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
|
2 | 2 | |
3 | -import { TorProtocolService } from "resource://gre/modules/TorProtocolService.sys.mjs";
|
|
3 | +import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
|
|
4 | 4 | import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
|
5 | 5 | |
6 | 6 | /* tor-launcher observer topics */
|
... | ... | @@ -13,19 +13,23 @@ export const TorTopics = Object.freeze({ |
13 | 13 | // modeled after XMLHttpRequest
|
14 | 14 | // nicely encapsulates the observer register/unregister logic
|
15 | 15 | export class TorBootstrapRequest {
|
16 | + // number of ms to wait before we abandon the bootstrap attempt
|
|
17 | + // a value of 0 implies we never wait
|
|
18 | + timeout = 0;
|
|
19 | + |
|
20 | + // callbacks for bootstrap process status updates
|
|
21 | + onbootstrapstatus = (progress, status) => {};
|
|
22 | + onbootstrapcomplete = () => {};
|
|
23 | + onbootstraperror = (message, details) => {};
|
|
24 | + |
|
25 | + // internal resolve() method for bootstrap
|
|
26 | + #bootstrapPromiseResolve = null;
|
|
27 | + #bootstrapPromise = null;
|
|
28 | + #timeoutID = null;
|
|
29 | + #provider = null;
|
|
30 | + |
|
16 | 31 | constructor() {
|
17 | - // number of ms to wait before we abandon the bootstrap attempt
|
|
18 | - // a value of 0 implies we never wait
|
|
19 | - this.timeout = 0;
|
|
20 | - // callbacks for bootstrap process status updates
|
|
21 | - this.onbootstrapstatus = (progress, status) => {};
|
|
22 | - this.onbootstrapcomplete = () => {};
|
|
23 | - this.onbootstraperror = (message, details) => {};
|
|
24 | - |
|
25 | - // internal resolve() method for bootstrap
|
|
26 | - this._bootstrapPromiseResolve = null;
|
|
27 | - this._bootstrapPromise = null;
|
|
28 | - this._timeoutID = null;
|
|
32 | + this.#provider = TorProviderBuilder.build();
|
|
29 | 33 | }
|
30 | 34 | |
31 | 35 | observe(subject, topic, data) {
|
... | ... | @@ -41,15 +45,16 @@ export class TorBootstrapRequest { |
41 | 45 | if (this.onbootstrapcomplete) {
|
42 | 46 | this.onbootstrapcomplete();
|
43 | 47 | }
|
44 | - this._bootstrapPromiseResolve(true);
|
|
45 | - clearTimeout(this._timeoutID);
|
|
48 | + this.#bootstrapPromiseResolve(true);
|
|
49 | + clearTimeout(this.#timeoutID);
|
|
50 | + this.#timeoutID = null;
|
|
46 | 51 | }
|
47 | 52 | |
48 | 53 | break;
|
49 | 54 | }
|
50 | 55 | case TorTopics.BootstrapError: {
|
51 | 56 | console.info("TorBootstrapRequest: observerd TorBootstrapError", obj);
|
52 | - this._stop(obj?.message, obj?.details);
|
|
57 | + this.#stop(obj?.message, obj?.details);
|
|
53 | 58 | break;
|
54 | 59 | }
|
55 | 60 | }
|
... | ... | @@ -57,12 +62,12 @@ export class TorBootstrapRequest { |
57 | 62 | |
58 | 63 | // resolves 'true' if bootstrap succeeds, false otherwise
|
59 | 64 | bootstrap() {
|
60 | - if (this._bootstrapPromise) {
|
|
61 | - return this._bootstrapPromise;
|
|
65 | + if (this.#bootstrapPromise) {
|
|
66 | + return this.#bootstrapPromise;
|
|
62 | 67 | }
|
63 | 68 | |
64 | - this._bootstrapPromise = new Promise((resolve, reject) => {
|
|
65 | - this._bootstrapPromiseResolve = resolve;
|
|
69 | + this.#bootstrapPromise = new Promise((resolve, reject) => {
|
|
70 | + this.#bootstrapPromiseResolve = resolve;
|
|
66 | 71 | |
67 | 72 | // register ourselves to listen for bootstrap events
|
68 | 73 | Services.obs.addObserver(this, TorTopics.BootstrapStatus);
|
... | ... | @@ -70,10 +75,10 @@ export class TorBootstrapRequest { |
70 | 75 | |
71 | 76 | // optionally cancel bootstrap after a given timeout
|
72 | 77 | if (this.timeout > 0) {
|
73 | - this._timeoutID = setTimeout(async () => {
|
|
74 | - this._timeoutID = null;
|
|
78 | + this.#timeoutID = setTimeout(async () => {
|
|
79 | + this.#timeoutID = null;
|
|
75 | 80 | // TODO: Translate, if really used
|
76 | - await this._stop(
|
|
81 | + await this.#stop(
|
|
77 | 82 | "Tor Bootstrap process timed out",
|
78 | 83 | `Bootstrap attempt abandoned after waiting ${this.timeout} ms`
|
79 | 84 | );
|
... | ... | @@ -81,38 +86,45 @@ export class TorBootstrapRequest { |
81 | 86 | }
|
82 | 87 | |
83 | 88 | // wait for bootstrapping to begin and maybe handle error
|
84 | - TorProtocolService.connect().catch(err => {
|
|
85 | - this._stop(err.message, "");
|
|
89 | + this.#provider.connect().catch(err => {
|
|
90 | + this.#stop(err.message, "");
|
|
86 | 91 | });
|
87 | 92 | }).finally(() => {
|
88 | 93 | // and remove ourselves once bootstrap is resolved
|
89 | 94 | Services.obs.removeObserver(this, TorTopics.BootstrapStatus);
|
90 | 95 | Services.obs.removeObserver(this, TorTopics.BootstrapError);
|
91 | - this._bootstrapPromise = null;
|
|
96 | + this.#bootstrapPromise = null;
|
|
92 | 97 | });
|
93 | 98 | |
94 | - return this._bootstrapPromise;
|
|
99 | + return this.#bootstrapPromise;
|
|
95 | 100 | }
|
96 | 101 | |
97 | 102 | async cancel() {
|
98 | - await this._stop();
|
|
103 | + await this.#stop();
|
|
99 | 104 | }
|
100 | 105 | |
101 | 106 | // Internal implementation. Do not use directly, but call cancel, instead.
|
102 | - async _stop(message, details) {
|
|
107 | + async #stop(message, details) {
|
|
103 | 108 | // first stop our bootstrap timeout before handling the error
|
104 | - if (this._timeoutID !== null) {
|
|
105 | - clearTimeout(this._timeoutID);
|
|
106 | - this._timeoutID = null;
|
|
109 | + if (this.#timeoutID !== null) {
|
|
110 | + clearTimeout(this.#timeoutID);
|
|
111 | + this.#timeoutID = null;
|
|
107 | 112 | }
|
108 | 113 | |
109 | - // stopBootstrap never throws
|
|
110 | - await TorProtocolService.stopBootstrap();
|
|
114 | + try {
|
|
115 | + await this.#provider.stopBootstrap();
|
|
116 | + } catch (e) {
|
|
117 | + console.error("Failed to stop the bootstrap.", e);
|
|
118 | + if (!message) {
|
|
119 | + message = e.message;
|
|
120 | + details = "";
|
|
121 | + }
|
|
122 | + }
|
|
111 | 123 | |
112 | 124 | if (this.onbootstraperror && message) {
|
113 | 125 | this.onbootstraperror(message, details);
|
114 | 126 | }
|
115 | 127 | |
116 | - this._bootstrapPromiseResolve(false);
|
|
128 | + this.#bootstrapPromiseResolve(false);
|
|
117 | 129 | }
|
118 | 130 | } |
... | ... | @@ -274,6 +274,44 @@ class AsyncSocket { |
274 | 274 | * the command
|
275 | 275 | */
|
276 | 276 | |
277 | +/**
|
|
278 | + * @typedef {object} Bridge
|
|
279 | + * @property {string} transport The transport of the bridge, or vanilla if not
|
|
280 | + * specified.
|
|
281 | + * @property {string} addr The IP address and port of the bridge
|
|
282 | + * @property {string} id The fingerprint of the bridge
|
|
283 | + * @property {string} args Optional arguments passed to the bridge
|
|
284 | + */
|
|
285 | +/**
|
|
286 | + * @typedef {object} PTInfo The information about a pluggable transport
|
|
287 | + * @property {string[]} transports An array with all the transports supported by
|
|
288 | + * this configuration.
|
|
289 | + * @property {string} type Either socks4, socks5 or exec
|
|
290 | + * @property {string} [ip] The IP address of the proxy (only for socks4 and
|
|
291 | + * socks5)
|
|
292 | + * @property {integer} [port] The port of the proxy (only for socks4 and socks5)
|
|
293 | + * @property {string} [pathToBinary] Path to the binary that is run (only for
|
|
294 | + * exec)
|
|
295 | + * @property {string} [options] Optional options passed to the binary (only for
|
|
296 | + * exec)
|
|
297 | + */
|
|
298 | +/**
|
|
299 | + * @typedef {object} OnionAuthKeyInfo
|
|
300 | + * @property {string} address The address of the onion service
|
|
301 | + * @property {string} typeAndKey Onion service key and type of key, as
|
|
302 | + * `type:base64-private-key`
|
|
303 | + * @property {string} Flags Additional flags, such as Permanent
|
|
304 | + */
|
|
305 | +/**
|
|
306 | + * @callback EventFilterCallback
|
|
307 | + * @param {any} data Either a raw string, or already parsed data
|
|
308 | + * @returns {boolean}
|
|
309 | + */
|
|
310 | +/**
|
|
311 | + * @callback EventCallback
|
|
312 | + * @param {any} data Either a raw string, or already parsed data
|
|
313 | + */
|
|
314 | + |
|
277 | 315 | class TorError extends Error {
|
278 | 316 | constructor(command, reply) {
|
279 | 317 | super(`${command} -> ${reply}`);
|
... | ... | @@ -584,319 +622,6 @@ class ControlSocket { |
584 | 622 | }
|
585 | 623 | }
|
586 | 624 | |
587 | -// ## utils
|
|
588 | -// A namespace for utility functions
|
|
589 | -let utils = {};
|
|
590 | - |
|
591 | -// __utils.identity(x)__.
|
|
592 | -// Returns its argument unchanged.
|
|
593 | -utils.identity = function (x) {
|
|
594 | - return x;
|
|
595 | -};
|
|
596 | - |
|
597 | -// __utils.capture(string, regex)__.
|
|
598 | -// Takes a string and returns an array of capture items, where regex must have a single
|
|
599 | -// capturing group and use the suffix /.../g to specify a global search.
|
|
600 | -utils.capture = function (string, regex) {
|
|
601 | - let matches = [];
|
|
602 | - // Special trick to use string.replace for capturing multiple matches.
|
|
603 | - string.replace(regex, function (a, captured) {
|
|
604 | - matches.push(captured);
|
|
605 | - });
|
|
606 | - return matches;
|
|
607 | -};
|
|
608 | - |
|
609 | -// __utils.extractor(regex)__.
|
|
610 | -// Returns a function that takes a string and returns an array of regex matches. The
|
|
611 | -// regex must use the suffix /.../g to specify a global search.
|
|
612 | -utils.extractor = function (regex) {
|
|
613 | - return function (text) {
|
|
614 | - return utils.capture(text, regex);
|
|
615 | - };
|
|
616 | -};
|
|
617 | - |
|
618 | -// __utils.splitLines(string)__.
|
|
619 | -// Splits a string into an array of strings, each corresponding to a line.
|
|
620 | -utils.splitLines = function (string) {
|
|
621 | - return string.split(/\r?\n/);
|
|
622 | -};
|
|
623 | - |
|
624 | -// __utils.splitAtSpaces(string)__.
|
|
625 | -// Splits a string into chunks between spaces. Does not split at spaces
|
|
626 | -// inside pairs of quotation marks.
|
|
627 | -utils.splitAtSpaces = utils.extractor(/((\S*?"(.*?)")+\S*|\S+)/g);
|
|
628 | - |
|
629 | -// __utils.splitAtFirst(string, regex)__.
|
|
630 | -// Splits a string at the first instance of regex match. If no match is
|
|
631 | -// found, returns the whole string.
|
|
632 | -utils.splitAtFirst = function (string, regex) {
|
|
633 | - let match = string.match(regex);
|
|
634 | - return match
|
|
635 | - ? [
|
|
636 | - string.substring(0, match.index),
|
|
637 | - string.substring(match.index + match[0].length),
|
|
638 | - ]
|
|
639 | - : string;
|
|
640 | -};
|
|
641 | - |
|
642 | -// __utils.splitAtEquals(string)__.
|
|
643 | -// Splits a string into chunks between equals. Does not split at equals
|
|
644 | -// inside pairs of quotation marks.
|
|
645 | -utils.splitAtEquals = utils.extractor(/(([^=]*?"(.*?)")+[^=]*|[^=]+)/g);
|
|
646 | - |
|
647 | -// __utils.mergeObjects(arrayOfObjects)__.
|
|
648 | -// Takes an array of objects like [{"a":"b"},{"c":"d"}] and merges to a single object.
|
|
649 | -// Pure function.
|
|
650 | -utils.mergeObjects = function (arrayOfObjects) {
|
|
651 | - let result = {};
|
|
652 | - for (let obj of arrayOfObjects) {
|
|
653 | - for (let key in obj) {
|
|
654 | - result[key] = obj[key];
|
|
655 | - }
|
|
656 | - }
|
|
657 | - return result;
|
|
658 | -};
|
|
659 | - |
|
660 | -// __utils.listMapData(parameterString, listNames)__.
|
|
661 | -// Takes a list of parameters separated by spaces, of which the first several are
|
|
662 | -// unnamed, and the remainder are named, in the form `NAME=VALUE`. Apply listNames
|
|
663 | -// to the unnamed parameters, and combine them in a map with the named parameters.
|
|
664 | -// Example: `40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH`
|
|
665 | -//
|
|
666 | -// utils.listMapData("40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH",
|
|
667 | -// ["streamID", "event", "circuitID", "IP"])
|
|
668 | -// // --> {"streamID" : "40", "event" : "FAILED", "circuitID" : "0",
|
|
669 | -// // "address" : "95.78.59.36:80", "REASON" : "CANT_ATTACH"}"
|
|
670 | -utils.listMapData = function (parameterString, listNames) {
|
|
671 | - // Split out the space-delimited parameters.
|
|
672 | - let parameters = utils.splitAtSpaces(parameterString),
|
|
673 | - dataMap = {};
|
|
674 | - // Assign listNames to the first n = listNames.length parameters.
|
|
675 | - for (let i = 0; i < listNames.length; ++i) {
|
|
676 | - dataMap[listNames[i]] = parameters[i];
|
|
677 | - }
|
|
678 | - // Read key-value pairs and copy these to the dataMap.
|
|
679 | - for (let i = listNames.length; i < parameters.length; ++i) {
|
|
680 | - let [key, value] = utils.splitAtEquals(parameters[i]);
|
|
681 | - if (key && value) {
|
|
682 | - dataMap[key] = value;
|
|
683 | - }
|
|
684 | - }
|
|
685 | - return dataMap;
|
|
686 | -};
|
|
687 | - |
|
688 | -// ## info
|
|
689 | -// A namespace for functions related to tor's GETINFO and GETCONF command.
|
|
690 | -let info = {};
|
|
691 | - |
|
692 | -// __info.keyValueStringsFromMessage(messageText)__.
|
|
693 | -// Takes a message (text) response to GETINFO or GETCONF and provides
|
|
694 | -// a series of key-value strings, which are either multiline (with a `250+` prefix):
|
|
695 | -//
|
|
696 | -// 250+config/defaults=
|
|
697 | -// AccountingMax "0 bytes"
|
|
698 | -// AllowDotExit "0"
|
|
699 | -// .
|
|
700 | -//
|
|
701 | -// or single-line (with a `250-` or `250 ` prefix):
|
|
702 | -//
|
|
703 | -// 250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
|
|
704 | -info.keyValueStringsFromMessage = utils.extractor(
|
|
705 | - /^(250\+[\s\S]+?^\.|250[- ].+?)$/gim
|
|
706 | -);
|
|
707 | - |
|
708 | -// __info.applyPerLine(transformFunction)__.
|
|
709 | -// Returns a function that splits text into lines,
|
|
710 | -// and applies transformFunction to each line.
|
|
711 | -info.applyPerLine = function (transformFunction) {
|
|
712 | - return function (text) {
|
|
713 | - return utils.splitLines(text.trim()).map(transformFunction);
|
|
714 | - };
|
|
715 | -};
|
|
716 | - |
|
717 | -// __info.routerStatusParser(valueString)__.
|
|
718 | -// Parses a router status entry as, described in
|
|
719 | -// https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt
|
|
720 | -// (search for "router status entry")
|
|
721 | -info.routerStatusParser = function (valueString) {
|
|
722 | - let lines = utils.splitLines(valueString),
|
|
723 | - objects = [];
|
|
724 | - for (let line of lines) {
|
|
725 | - // Drop first character and grab data following it.
|
|
726 | - let myData = line.substring(2),
|
|
727 | - // Accumulate more maps with data, depending on the first character in the line.
|
|
728 | - dataFun = {
|
|
729 | - r: data =>
|
|
730 | - utils.listMapData(data, [
|
|
731 | - "nickname",
|
|
732 | - "identity",
|
|
733 | - "digest",
|
|
734 | - "publicationDate",
|
|
735 | - "publicationTime",
|
|
736 | - "IP",
|
|
737 | - "ORPort",
|
|
738 | - "DirPort",
|
|
739 | - ]),
|
|
740 | - a: data => ({ IPv6: data }),
|
|
741 | - s: data => ({ statusFlags: utils.splitAtSpaces(data) }),
|
|
742 | - v: data => ({ version: data }),
|
|
743 | - w: data => utils.listMapData(data, []),
|
|
744 | - p: data => ({ portList: data.split(",") }),
|
|
745 | - }[line.charAt(0)];
|
|
746 | - if (dataFun !== undefined) {
|
|
747 | - objects.push(dataFun(myData));
|
|
748 | - }
|
|
749 | - }
|
|
750 | - return utils.mergeObjects(objects);
|
|
751 | -};
|
|
752 | - |
|
753 | -// __info.circuitStatusParser(line)__.
|
|
754 | -// Parse the output of a circuit status line.
|
|
755 | -info.circuitStatusParser = function (line) {
|
|
756 | - let data = utils.listMapData(line, ["id", "status", "circuit"]),
|
|
757 | - circuit = data.circuit;
|
|
758 | - // Parse out the individual circuit IDs and names.
|
|
759 | - if (circuit) {
|
|
760 | - data.circuit = circuit.split(",").map(function (x) {
|
|
761 | - return x.split(/~|=/);
|
|
762 | - });
|
|
763 | - }
|
|
764 | - return data;
|
|
765 | -};
|
|
766 | - |
|
767 | -// __info.streamStatusParser(line)__.
|
|
768 | -// Parse the output of a stream status line.
|
|
769 | -info.streamStatusParser = function (text) {
|
|
770 | - return utils.listMapData(text, [
|
|
771 | - "StreamID",
|
|
772 | - "StreamStatus",
|
|
773 | - "CircuitID",
|
|
774 | - "Target",
|
|
775 | - ]);
|
|
776 | -};
|
|
777 | - |
|
778 | -// TODO: fix this parsing logic to handle bridgeLine correctly
|
|
779 | -// fingerprint/id is an optional parameter
|
|
780 | -// __info.bridgeParser(bridgeLine)__.
|
|
781 | -// Takes a single line from a `getconf bridge` result and returns
|
|
782 | -// a map containing the bridge's type, address, and ID.
|
|
783 | -info.bridgeParser = function (bridgeLine) {
|
|
784 | - let result = {},
|
|
785 | - tokens = bridgeLine.split(/\s+/);
|
|
786 | - // First check if we have a "vanilla" bridge:
|
|
787 | - if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
|
|
788 | - result.type = "vanilla";
|
|
789 | - [result.address, result.ID] = tokens;
|
|
790 | - // Several bridge types have a similar format:
|
|
791 | - } else {
|
|
792 | - result.type = tokens[0];
|
|
793 | - if (
|
|
794 | - [
|
|
795 | - "flashproxy",
|
|
796 | - "fte",
|
|
797 | - "meek",
|
|
798 | - "meek_lite",
|
|
799 | - "obfs3",
|
|
800 | - "obfs4",
|
|
801 | - "scramblesuit",
|
|
802 | - "snowflake",
|
|
803 | - ].includes(result.type)
|
|
804 | - ) {
|
|
805 | - [result.address, result.ID] = tokens.slice(1);
|
|
806 | - }
|
|
807 | - }
|
|
808 | - return result.type ? result : null;
|
|
809 | -};
|
|
810 | - |
|
811 | -// __info.parsers__.
|
|
812 | -// A map of GETINFO and GETCONF keys to parsing function, which convert
|
|
813 | -// result strings to JavaScript data.
|
|
814 | -info.parsers = {
|
|
815 | - "ns/id/": info.routerStatusParser,
|
|
816 | - "ip-to-country/": utils.identity,
|
|
817 | - "circuit-status": info.applyPerLine(info.circuitStatusParser),
|
|
818 | - bridge: info.bridgeParser,
|
|
819 | - // Currently unused parsers:
|
|
820 | - // "ns/name/" : info.routerStatusParser,
|
|
821 | - // "stream-status" : info.applyPerLine(info.streamStatusParser),
|
|
822 | - // "version" : utils.identity,
|
|
823 | - // "config-file" : utils.identity,
|
|
824 | -};
|
|
825 | - |
|
826 | -// __info.getParser(key)__.
|
|
827 | -// Takes a key and determines the parser function that should be used to
|
|
828 | -// convert its corresponding valueString to JavaScript data.
|
|
829 | -info.getParser = function (key) {
|
|
830 | - return (
|
|
831 | - info.parsers[key] ||
|
|
832 | - info.parsers[key.substring(0, key.lastIndexOf("/") + 1)]
|
|
833 | - );
|
|
834 | -};
|
|
835 | - |
|
836 | -// __info.stringToValue(string)__.
|
|
837 | -// Converts a key-value string as from GETINFO or GETCONF to a value.
|
|
838 | -info.stringToValue = function (string) {
|
|
839 | - // key should look something like `250+circuit-status=` or `250-circuit-status=...`
|
|
840 | - // or `250 circuit-status=...`
|
|
841 | - let matchForKey = string.match(/^250[ +-](.+?)=/),
|
|
842 | - key = matchForKey ? matchForKey[1] : null;
|
|
843 | - if (key === null) {
|
|
844 | - return null;
|
|
845 | - }
|
|
846 | - // matchResult finds a single-line result for `250-` or `250 `,
|
|
847 | - // or a multi-line one for `250+`.
|
|
848 | - let matchResult =
|
|
849 | - string.match(/^250[ -].+?=(.*)$/) ||
|
|
850 | - string.match(/^250\+.+?=([\s\S]*?)^\.$/m),
|
|
851 | - // Retrieve the captured group (the text of the value in the key-value pair)
|
|
852 | - valueString = matchResult ? matchResult[1] : null,
|
|
853 | - // Get the parser function for the key found.
|
|
854 | - parse = info.getParser(key.toLowerCase());
|
|
855 | - if (parse === undefined) {
|
|
856 | - throw new Error("No parser found for '" + key + "'");
|
|
857 | - }
|
|
858 | - // Return value produced by the parser.
|
|
859 | - return parse(valueString);
|
|
860 | -};
|
|
861 | - |
|
862 | -/**
|
|
863 | - * @typedef {object} Bridge
|
|
864 | - * @property {string} transport The transport of the bridge, or vanilla if not
|
|
865 | - * specified.
|
|
866 | - * @property {string} addr The IP address and port of the bridge
|
|
867 | - * @property {string} id The fingerprint of the bridge
|
|
868 | - * @property {string} args Optional arguments passed to the bridge
|
|
869 | - */
|
|
870 | -/**
|
|
871 | - * @typedef {object} PTInfo The information about a pluggable transport
|
|
872 | - * @property {string[]} transports An array with all the transports supported by
|
|
873 | - * this configuration.
|
|
874 | - * @property {string} type Either socks4, socks5 or exec
|
|
875 | - * @property {string} [ip] The IP address of the proxy (only for socks4 and
|
|
876 | - * socks5)
|
|
877 | - * @property {integer} [port] The port of the proxy (only for socks4 and socks5)
|
|
878 | - * @property {string} [pathToBinary] Path to the binary that is run (only for
|
|
879 | - * exec)
|
|
880 | - * @property {string} [options] Optional options passed to the binary (only for
|
|
881 | - * exec)
|
|
882 | - */
|
|
883 | -/**
|
|
884 | - * @typedef {object} OnionAuthKeyInfo
|
|
885 | - * @property {string} address The address of the onion service
|
|
886 | - * @property {string} typeAndKey Onion service key and type of key, as
|
|
887 | - * `type:base64-private-key`
|
|
888 | - * @property {string} Flags Additional flags, such as Permanent
|
|
889 | - */
|
|
890 | -/**
|
|
891 | - * @callback EventFilterCallback
|
|
892 | - * @param {any} data Either a raw string, or already parsed data
|
|
893 | - * @returns {boolean}
|
|
894 | - */
|
|
895 | -/**
|
|
896 | - * @callback EventCallback
|
|
897 | - * @param {any} data Either a raw string, or already parsed data
|
|
898 | - */
|
|
899 | - |
|
900 | 625 | class TorController {
|
901 | 626 | /**
|
902 | 627 | * The control socket
|
... | ... | @@ -905,16 +630,6 @@ class TorController { |
905 | 630 | */
|
906 | 631 | #socket;
|
907 | 632 | |
908 | - /**
|
|
909 | - * A map of EVENT keys to parsing functions, which convert result strings to
|
|
910 | - * JavaScript data.
|
|
911 | - */
|
|
912 | - #eventParsers = {
|
|
913 | - stream: info.streamStatusParser,
|
|
914 | - // Currently unused:
|
|
915 | - // "circ" : info.circuitStatusParser,
|
|
916 | - };
|
|
917 | - |
|
918 | 633 | /**
|
919 | 634 | * Builds a new TorController.
|
920 | 635 | *
|
... | ... | @@ -981,18 +696,6 @@ class TorController { |
981 | 696 | await this.#sendCommandSimple(`authenticate ${password || ""}`);
|
982 | 697 | }
|
983 | 698 | |
984 | - /**
|
|
985 | - * Sends a GETINFO for a single key.
|
|
986 | - *
|
|
987 | - * @param {string} key The key to get value for
|
|
988 | - * @returns {any} The return value depends on the requested key
|
|
989 | - */
|
|
990 | - async getInfo(key) {
|
|
991 | - this.#expectString(key, "key");
|
|
992 | - const response = await this.sendCommand(`getinfo ${key}`);
|
|
993 | - return this.#getMultipleResponseValues(response)[0];
|
|
994 | - }
|
|
995 | - |
|
996 | 699 | /**
|
997 | 700 | * Sends a GETINFO for a single key.
|
998 | 701 | * control-spec.txt says "one ReplyLine is sent for each requested value", so,
|
... | ... | @@ -1054,9 +757,7 @@ class TorController { |
1054 | 757 | const addresses = [v4[5]];
|
1055 | 758 | // a address:port
|
1056 | 759 | // dir-spec.txt also states only the first one should be taken
|
1057 | - // TODO: The consumers do not care about the port or the square brackets
|
|
1058 | - // either. Remove them when integrating this function with the rest
|
|
1059 | - const v6 = reply.match(/^a\s+(\[[0-9a-fA-F:]+\]:[0-9]{1,5})$/m);
|
|
760 | + const v6 = reply.match(/^a\s+\[([0-9a-fA-F:]+)\]:\d{1,5}$/m);
|
|
1060 | 761 | if (v6) {
|
1061 | 762 | addresses.push(v6[1]);
|
1062 | 763 | }
|
... | ... | @@ -1091,23 +792,6 @@ class TorController { |
1091 | 792 | |
1092 | 793 | // Configuration
|
1093 | 794 | |
1094 | - /**
|
|
1095 | - * Sends a GETCONF for a single key.
|
|
1096 | - * GETCONF with a single argument returns results with one or more lines that
|
|
1097 | - * look like `250[- ]key=value`.
|
|
1098 | - * Any GETCONF lines that contain a single keyword only are currently dropped.
|
|
1099 | - * So we can use similar parsing to that for getInfo.
|
|
1100 | - *
|
|
1101 | - * @param {string} key The key to get value for
|
|
1102 | - * @returns {any} A parsed config value (it depends if a parser is known)
|
|
1103 | - */
|
|
1104 | - async getConf(key) {
|
|
1105 | - this.#expectString(key, "key");
|
|
1106 | - return this.#getMultipleResponseValues(
|
|
1107 | - await this.sendCommand(`getconf ${key}`)
|
|
1108 | - );
|
|
1109 | - }
|
|
1110 | - |
|
1111 | 795 | /**
|
1112 | 796 | * Sends a GETCONF for a single key.
|
1113 | 797 | * The function could be easily generalized to get multiple keys at once, but
|
... | ... | @@ -1264,12 +948,14 @@ class TorController { |
1264 | 948 | // TODO: Change the consumer and make the fields more consistent with what
|
1265 | 949 | // we get (e.g., separate key and type, and use a boolen for permanent).
|
1266 | 950 | const info = {
|
1267 | - hsAddress: match.groups.HSAddress,
|
|
1268 | - typeAndKey: `${match.groups.KeyType}:${match.groups.PrivateKeyBlob}`,
|
|
951 | + address: match.groups.HSAddress,
|
|
952 | + keyType: match.groups.KeyType,
|
|
953 | + keyBlob: match.groups.PrivateKeyBlob,
|
|
954 | + flags: [],
|
|
1269 | 955 | };
|
1270 | 956 | const maybeFlags = match.groups.other?.match(/Flags=(\S+)/);
|
1271 | 957 | if (maybeFlags) {
|
1272 | - info.Flags = maybeFlags[1];
|
|
958 | + info.flags = maybeFlags[1].split(",");
|
|
1273 | 959 | }
|
1274 | 960 | return info;
|
1275 | 961 | });
|
... | ... | @@ -1369,28 +1055,12 @@ class TorController { |
1369 | 1055 | * first.
|
1370 | 1056 | *
|
1371 | 1057 | * @param {string} type The event type to catch
|
1372 | - * @param {EventFilterCallback?} filter An optional callback to filter
|
|
1373 | - * events for which the callback will be called. If null, all events will be
|
|
1374 | - * passed.
|
|
1375 | 1058 | * @param {EventCallback} callback The callback that will handle the event
|
1376 | - * @param {boolean} raw Tell whether to ignore the data parser, even if
|
|
1377 | - * supported
|
|
1378 | 1059 | */
|
1379 | - watchEvent(type, filter, callback, raw = false) {
|
|
1060 | + watchEvent(type, callback) {
|
|
1380 | 1061 | this.#expectString(type, "type");
|
1381 | 1062 | const start = `650 ${type}`;
|
1382 | - this.#socket.addNotificationCallback(new RegExp(`^${start}`), message => {
|
|
1383 | - // Remove also the initial text
|
|
1384 | - const dataText = message.substring(start.length + 1);
|
|
1385 | - const parser = this.#eventParsers[type.toLowerCase()];
|
|
1386 | - const data = dataText && parser ? parser(dataText) : null;
|
|
1387 | - // FIXME: This is the original code, but we risk of not filtering on the
|
|
1388 | - // data, if we ask for raw data (which we always do at the moment, but we
|
|
1389 | - // do not use a filter either...)
|
|
1390 | - if (filter === null || filter(data)) {
|
|
1391 | - callback(data && !raw ? data : message);
|
|
1392 | - }
|
|
1393 | - });
|
|
1063 | + this.#socket.addNotificationCallback(new RegExp(`^${start}`), callback);
|
|
1394 | 1064 | }
|
1395 | 1065 | |
1396 | 1066 | // Other helpers
|
... | ... | @@ -1453,19 +1123,6 @@ class TorController { |
1453 | 1123 | )
|
1454 | 1124 | );
|
1455 | 1125 | }
|
1456 | - |
|
1457 | - /**
|
|
1458 | - * Process multiple responses to a GETINFO or GETCONF request.
|
|
1459 | - *
|
|
1460 | - * @param {string} message The message to process
|
|
1461 | - * @returns {object[]} The keys depend on the message
|
|
1462 | - */
|
|
1463 | - #getMultipleResponseValues(message) {
|
|
1464 | - return info
|
|
1465 | - .keyValueStringsFromMessage(message)
|
|
1466 | - .map(info.stringToValue)
|
|
1467 | - .filter(x => x);
|
|
1468 | - }
|
|
1469 | 1126 | }
|
1470 | 1127 | |
1471 | 1128 | const controlPortInfo = {};
|
... | ... | @@ -12,6 +12,11 @@ import { |
12 | 12 | |
13 | 13 | const lazy = {};
|
14 | 14 | |
15 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
16 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
17 | + TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
18 | +});
|
|
19 | + |
|
15 | 20 | XPCOMUtils.defineLazyServiceGetters(lazy, {
|
16 | 21 | ProtocolProxyService: [
|
17 | 22 | "@mozilla.org/network/protocol-proxy-service;1",
|
... | ... | @@ -19,11 +24,6 @@ XPCOMUtils.defineLazyServiceGetters(lazy, { |
19 | 24 | ],
|
20 | 25 | });
|
21 | 26 | |
22 | -ChromeUtils.defineESModuleGetters(lazy, {
|
|
23 | - TorMonitorTopics: "resource://gre/modules/TorMonitorService.sys.mjs",
|
|
24 | - TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
|
|
25 | -});
|
|
26 | - |
|
27 | 27 | const logger = new ConsoleAPI({
|
28 | 28 | prefix: "TorDomainIsolator",
|
29 | 29 | maxLogLevel: "warn",
|
... | ... | @@ -143,7 +143,7 @@ class TorDomainIsolatorImpl { |
143 | 143 | |
144 | 144 | Services.prefs.addObserver(NON_TOR_PROXY_PREF, this);
|
145 | 145 | Services.obs.addObserver(this, NEW_IDENTITY_TOPIC);
|
146 | - Services.obs.addObserver(this, lazy.TorMonitorTopics.StreamSucceeded);
|
|
146 | + Services.obs.addObserver(this, lazy.TorProviderTopics.StreamSucceeded);
|
|
147 | 147 | |
148 | 148 | this.#cleanupIntervalId = setInterval(
|
149 | 149 | this.#clearKnownCircuits.bind(this),
|
... | ... | @@ -158,7 +158,7 @@ class TorDomainIsolatorImpl { |
158 | 158 | uninit() {
|
159 | 159 | Services.prefs.removeObserver(NON_TOR_PROXY_PREF, this);
|
160 | 160 | Services.obs.removeObserver(this, NEW_IDENTITY_TOPIC);
|
161 | - Services.obs.removeObserver(this, lazy.TorMonitorTopics.StreamSucceeded);
|
|
161 | + Services.obs.removeObserver(this, lazy.TorProviderTopics.StreamSucceeded);
|
|
162 | 162 | clearInterval(this.#cleanupIntervalId);
|
163 | 163 | this.#cleanupIntervalId = null;
|
164 | 164 | this.clearIsolation();
|
... | ... | @@ -257,12 +257,12 @@ class TorDomainIsolatorImpl { |
257 | 257 | );
|
258 | 258 | this.clearIsolation();
|
259 | 259 | try {
|
260 | - await lazy.TorProtocolService.newnym();
|
|
260 | + await lazy.TorProviderBuilder.build().newnym();
|
|
261 | 261 | } catch (e) {
|
262 | 262 | logger.error("Could not send the newnym command", e);
|
263 | 263 | // TODO: What UX to use here? See tor-browser#41708
|
264 | 264 | }
|
265 | - } else if (topic === lazy.TorMonitorTopics.StreamSucceeded) {
|
|
265 | + } else if (topic === lazy.TorProviderTopics.StreamSucceeded) {
|
|
266 | 266 | const { username, password, circuit } = subject.wrappedJSObject;
|
267 | 267 | this.#updateCircuit(username, password, circuit);
|
268 | 268 | }
|
... | ... | @@ -553,7 +553,7 @@ class TorDomainIsolatorImpl { |
553 | 553 | |
554 | 554 | data = await Promise.all(
|
555 | 555 | circuit.map(fingerprint =>
|
556 | - lazy.TorProtocolService.getNodeInfo(fingerprint)
|
|
556 | + lazy.TorProviderBuilder.build().getNodeInfo(fingerprint)
|
|
557 | 557 | )
|
558 | 558 | );
|
559 | 559 | this.#knownCircuits.set(id, data);
|
1 | -// Copyright (c) 2022, The Tor Project, Inc.
|
|
2 | - |
|
3 | -import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
|
|
4 | - |
|
5 | -const lazy = {};
|
|
6 | -ChromeUtils.defineESModuleGetters(lazy, {
|
|
7 | - TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
|
|
8 | -});
|
|
9 | - |
|
10 | -export const TorMonitorTopics = Object.freeze({
|
|
11 | - BridgeChanged: TorProviderTopics.BridgeChanged,
|
|
12 | - StreamSucceeded: TorProviderTopics.StreamSucceeded,
|
|
13 | -});
|
|
14 | - |
|
15 | -/**
|
|
16 | - * This service monitors an existing Tor instance, or starts one, if needed, and
|
|
17 | - * then starts monitoring it.
|
|
18 | - *
|
|
19 | - * This is the service which should be queried to know information about the
|
|
20 | - * status of the bootstrap, the logs, etc...
|
|
21 | - */
|
|
22 | -export const TorMonitorService = {
|
|
23 | - get currentBridge() {
|
|
24 | - return lazy.TorProtocolService.currentBridge;
|
|
25 | - },
|
|
26 | - |
|
27 | - get ownsTorDaemon() {
|
|
28 | - return lazy.TorProtocolService.ownsTorDaemon;
|
|
29 | - },
|
|
30 | - |
|
31 | - get isRunning() {
|
|
32 | - return lazy.TorProtocolService.isRunning;
|
|
33 | - },
|
|
34 | - |
|
35 | - get isBootstrapDone() {
|
|
36 | - return lazy.TorProtocolService.isBootstrapDone;
|
|
37 | - },
|
|
38 | - |
|
39 | - getLog() {
|
|
40 | - return lazy.TorProtocolService.getLog();
|
|
41 | - },
|
|
42 | -}; |
... | ... | @@ -269,11 +269,14 @@ export const TorParsers = Object.freeze({ |
269 | 269 | },
|
270 | 270 | |
271 | 271 | parseBridgeLine(line) {
|
272 | + if (!line) {
|
|
273 | + return null;
|
|
274 | + }
|
|
272 | 275 | const re =
|
273 | 276 | /\s*(?:(?<transport>\S+)\s+)?(?<addr>[0-9a-fA-F\.\[\]\:]+:\d{1,5})(?:\s+(?<id>[0-9a-fA-F]{40}))?(?:\s+(?<args>.+))?/;
|
274 | 277 | const match = re.exec(line);
|
275 | 278 | if (!match) {
|
276 | - throw new Error("Invalid bridge line.");
|
|
279 | + throw new Error(`Invalid bridge line: ${line}.`);
|
|
277 | 280 | }
|
278 | 281 | const bridge = match.groups;
|
279 | 282 | if (!bridge.transport) {
|
1 | -// Copyright (c) 2021, The Tor Project, Inc.
|
|
1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
2 | 4 | |
3 | 5 | import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
|
4 | 6 | import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
|
... | ... | @@ -11,7 +13,6 @@ import { |
11 | 13 | import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
|
12 | 14 | |
13 | 15 | const lazy = {};
|
14 | - |
|
15 | 16 | ChromeUtils.defineESModuleGetters(lazy, {
|
16 | 17 | controller: "resource://gre/modules/TorControlPort.sys.mjs",
|
17 | 18 | configureControlPortModule: "resource://gre/modules/TorControlPort.sys.mjs",
|
... | ... | @@ -21,7 +22,8 @@ ChromeUtils.defineESModuleGetters(lazy, { |
21 | 22 | |
22 | 23 | const logger = new ConsoleAPI({
|
23 | 24 | maxLogLevel: "warn",
|
24 | - prefix: "TorProtocolService",
|
|
25 | + maxLogLevelPref: "browser.tor_provider.log_level",
|
|
26 | + prefix: "TorProvider",
|
|
25 | 27 | });
|
26 | 28 | |
27 | 29 | /**
|
... | ... | @@ -70,7 +72,7 @@ const ControlConnTimings = Object.freeze({ |
70 | 72 | * It can start a new tor instance, or connect to an existing one.
|
71 | 73 | * In the former case, it also takes its ownership by default.
|
72 | 74 | */
|
73 | -class TorProvider {
|
|
75 | +export class TorProvider {
|
|
74 | 76 | #inited = false;
|
75 | 77 | |
76 | 78 | // Maintain a map of tor settings set by Tor Browser so that we don't
|
... | ... | @@ -85,7 +87,6 @@ class TorProvider { |
85 | 87 | #SOCKSPortInfo = null; // An object that contains ipcFile, host, port.
|
86 | 88 | |
87 | 89 | #controlConnection = null; // This is cached and reused.
|
88 | - #connectionQueue = [];
|
|
89 | 90 | |
90 | 91 | // Public methods
|
91 | 92 | |
... | ... | @@ -123,39 +124,34 @@ class TorProvider { |
123 | 124 | // takes a Map containing tor settings
|
124 | 125 | // throws on error
|
125 | 126 | async writeSettings(aSettingsObj) {
|
127 | + const entries =
|
|
128 | + aSettingsObj instanceof Map
|
|
129 | + ? Array.from(aSettingsObj.entries())
|
|
130 | + : Object.entries(aSettingsObj);
|
|
126 | 131 | // only write settings that have changed
|
127 | - const newSettings = Array.from(aSettingsObj).filter(([setting, value]) => {
|
|
128 | - // make sure we have valid data here
|
|
129 | - this.#assertValidSetting(setting, value);
|
|
130 | - |
|
132 | + const newSettings = entries.filter(([setting, value]) => {
|
|
131 | 133 | if (!this.#settingsCache.has(setting)) {
|
132 | 134 | // no cached setting, so write
|
133 | 135 | return true;
|
134 | 136 | }
|
135 | 137 | |
136 | 138 | const cachedValue = this.#settingsCache.get(setting);
|
137 | - if (value === cachedValue) {
|
|
138 | - return false;
|
|
139 | - } else if (Array.isArray(value) && Array.isArray(cachedValue)) {
|
|
140 | - // compare arrays member-wise
|
|
141 | - if (value.length !== cachedValue.length) {
|
|
142 | - return true;
|
|
143 | - }
|
|
144 | - for (let i = 0; i < value.length; i++) {
|
|
145 | - if (value[i] !== cachedValue[i]) {
|
|
146 | - return true;
|
|
147 | - }
|
|
148 | - }
|
|
149 | - return false;
|
|
139 | + // Arrays are the only special case for which === could fail.
|
|
140 | + // The other values we accept (strings, booleans, numbers, null and
|
|
141 | + // undefined) work correctly with ===.
|
|
142 | + if (Array.isArray(value) && Array.isArray(cachedValue)) {
|
|
143 | + return (
|
|
144 | + value.length !== cachedValue.length ||
|
|
145 | + value.some((val, idx) => val !== cachedValue[idx])
|
|
146 | + );
|
|
150 | 147 | }
|
151 | - // some other different values
|
|
152 | - return true;
|
|
148 | + return value !== cachedValue;
|
|
153 | 149 | });
|
154 | 150 | |
155 | 151 | // only write if new setting to save
|
156 | 152 | if (newSettings.length) {
|
157 | - const settingsObject = Object.fromEntries(newSettings);
|
|
158 | - await this.setConfWithReply(settingsObject);
|
|
153 | + const conn = await this.#getConnection();
|
|
154 | + await conn.setConf(Object.fromEntries(newSettings));
|
|
159 | 155 | |
160 | 156 | // save settings to cache after successfully writing to Tor
|
161 | 157 | for (const [setting, value] of newSettings) {
|
... | ... | @@ -164,23 +160,15 @@ class TorProvider { |
164 | 160 | }
|
165 | 161 | }
|
166 | 162 | |
167 | - async readStringArraySetting(aSetting) {
|
|
168 | - const value = await this.#readSetting(aSetting);
|
|
169 | - this.#settingsCache.set(aSetting, value);
|
|
170 | - return value;
|
|
171 | - }
|
|
172 | - |
|
173 | 163 | // writes current tor settings to disk
|
174 | 164 | async flushSettings() {
|
175 | - await this.sendCommand("SAVECONF");
|
|
165 | + const conn = await this.#getConnection();
|
|
166 | + await conn.flushSettings();
|
|
176 | 167 | }
|
177 | 168 | |
178 | 169 | async connect() {
|
179 | - const kTorConfKeyDisableNetwork = "DisableNetwork";
|
|
180 | - const settings = {};
|
|
181 | - settings[kTorConfKeyDisableNetwork] = false;
|
|
182 | - await this.setConfWithReply(settings);
|
|
183 | - await this.sendCommand("SAVECONF");
|
|
170 | + const conn = await this.#getConnection();
|
|
171 | + await conn.setNetworkEnabled(true);
|
|
184 | 172 | this.clearBootstrapError();
|
185 | 173 | this.retrieveBootstrapStatus();
|
186 | 174 | }
|
... | ... | @@ -188,12 +176,8 @@ class TorProvider { |
188 | 176 | async stopBootstrap() {
|
189 | 177 | // Tell tor to disable use of the network; this should stop the bootstrap
|
190 | 178 | // process.
|
191 | - try {
|
|
192 | - const settings = { DisableNetwork: true };
|
|
193 | - await this.setConfWithReply(settings);
|
|
194 | - } catch (e) {
|
|
195 | - logger.error("Error stopping bootstrap", e);
|
|
196 | - }
|
|
179 | + const conn = await this.#getConnection();
|
|
180 | + await conn.setNetworkEnabled(false);
|
|
197 | 181 | // We are not interested in waiting for this, nor in **catching its error**,
|
198 | 182 | // so we do not await this. We just want to be notified when the bootstrap
|
199 | 183 | // status is actually updated through observers.
|
... | ... | @@ -201,28 +185,31 @@ class TorProvider { |
201 | 185 | }
|
202 | 186 | |
203 | 187 | async newnym() {
|
204 | - return this.sendCommand("SIGNAL NEWNYM");
|
|
188 | + const conn = await this.#getConnection();
|
|
189 | + await conn.newnym();
|
|
205 | 190 | }
|
206 | 191 | |
207 | 192 | // Ask tor which ports it is listening to for SOCKS connections.
|
208 | 193 | // At the moment this is used only in TorCheckService.
|
209 | 194 | async getSocksListeners() {
|
210 | - const cmd = "GETINFO";
|
|
211 | - const keyword = "net/listeners/socks";
|
|
212 | - const response = await this.sendCommand(cmd, keyword);
|
|
213 | - return TorParsers.parseReply(cmd, keyword, response);
|
|
195 | + const conn = await this.#getConnection();
|
|
196 | + return conn.getSocksListeners();
|
|
214 | 197 | }
|
215 | 198 | |
216 | 199 | async getBridges() {
|
200 | + const conn = await this.#getConnection();
|
|
217 | 201 | // Ideally, we would not need this function, because we should be the one
|
218 | 202 | // setting them with TorSettings. However, TorSettings is not notified of
|
219 | 203 | // change of settings. So, asking tor directly with the control connection
|
220 | 204 | // is the most reliable way of getting the configured bridges, at the
|
221 | 205 | // moment. Also, we are using this for the circuit display, which should
|
222 | 206 | // work also when we are not configuring the tor daemon, but just using it.
|
223 | - return this.#withConnection(conn => {
|
|
224 | - return conn.getConf("bridge");
|
|
225 | - });
|
|
207 | + return conn.getBridges();
|
|
208 | + }
|
|
209 | + |
|
210 | + async getPluggableTransports() {
|
|
211 | + const conn = await this.#getConnection();
|
|
212 | + return conn.getPluggableTransports();
|
|
226 | 213 | }
|
227 | 214 | |
228 | 215 | /**
|
... | ... | @@ -232,68 +219,55 @@ class TorProvider { |
232 | 219 | * @returns {Promise<NodeData>}
|
233 | 220 | */
|
234 | 221 | async getNodeInfo(id) {
|
235 | - return this.#withConnection(async conn => {
|
|
236 | - const node = {
|
|
237 | - fingerprint: id,
|
|
238 | - ipAddrs: [],
|
|
239 | - bridgeType: null,
|
|
240 | - regionCode: null,
|
|
241 | - };
|
|
242 | - const bridge = (await conn.getConf("bridge"))?.find(
|
|
243 | - foundBridge => foundBridge.ID?.toUpperCase() === id.toUpperCase()
|
|
244 | - );
|
|
245 | - const addrRe = /^\[?([^\]]+)\]?:\d+$/;
|
|
246 | - if (bridge) {
|
|
247 | - node.bridgeType = bridge.type ?? "";
|
|
248 | - // Attempt to get an IP address from bridge address string.
|
|
249 | - const ip = bridge.address.match(addrRe)?.[1];
|
|
250 | - if (ip && !ip.startsWith("0.")) {
|
|
251 | - node.ipAddrs.push(ip);
|
|
252 | - }
|
|
253 | - } else {
|
|
254 | - // Either dealing with a relay, or a bridge whose fingerprint is not
|
|
255 | - // saved in torrc.
|
|
256 | - const info = await conn.getInfo(`ns/id/${id}`);
|
|
257 | - if (info.IP && !info.IP.startsWith("0.")) {
|
|
258 | - node.ipAddrs.push(info.IP);
|
|
259 | - }
|
|
260 | - const ip6 = info.IPv6?.match(addrRe)?.[1];
|
|
261 | - if (ip6) {
|
|
262 | - node.ipAddrs.push(ip6);
|
|
263 | - }
|
|
222 | + const conn = await this.#getConnection();
|
|
223 | + const node = {
|
|
224 | + fingerprint: id,
|
|
225 | + ipAddrs: [],
|
|
226 | + bridgeType: null,
|
|
227 | + regionCode: null,
|
|
228 | + };
|
|
229 | + const bridge = (await conn.getBridges())?.find(
|
|
230 | + foundBridge => foundBridge.id?.toUpperCase() === id.toUpperCase()
|
|
231 | + );
|
|
232 | + if (bridge) {
|
|
233 | + node.bridgeType = bridge.transport ?? "";
|
|
234 | + // Attempt to get an IP address from bridge address string.
|
|
235 | + const ip = bridge.addr.match(/^\[?([^\]]+)\]?:\d+$/)?.[1];
|
|
236 | + if (ip && !ip.startsWith("0.")) {
|
|
237 | + node.ipAddrs.push(ip);
|
|
264 | 238 | }
|
265 | - if (node.ipAddrs.length) {
|
|
266 | - // Get the country code for the node's IP address.
|
|
267 | - let regionCode;
|
|
268 | - try {
|
|
269 | - // Expect a 2-letter ISO3166-1 code, which should also be a valid
|
|
270 | - // BCP47 Region subtag.
|
|
271 | - regionCode = await conn.getInfo("ip-to-country/" + node.ipAddrs[0]);
|
|
272 | - } catch {}
|
|
239 | + } else {
|
|
240 | + node.ipAddrs = await conn.getNodeAddresses(id);
|
|
241 | + }
|
|
242 | + if (node.ipAddrs.length) {
|
|
243 | + // Get the country code for the node's IP address.
|
|
244 | + try {
|
|
245 | + // Expect a 2-letter ISO3166-1 code, which should also be a valid
|
|
246 | + // BCP47 Region subtag.
|
|
247 | + const regionCode = await conn.getIPCountry(node.ipAddrs[0]);
|
|
273 | 248 | if (regionCode && regionCode !== "??") {
|
274 | 249 | node.regionCode = regionCode.toUpperCase();
|
275 | 250 | }
|
251 | + } catch (e) {
|
|
252 | + logger.warn(`Cannot get a country for IP ${node.ipAddrs[0]}`, e);
|
|
276 | 253 | }
|
277 | - return node;
|
|
278 | - });
|
|
254 | + }
|
|
255 | + return node;
|
|
279 | 256 | }
|
280 | 257 | |
281 | - async onionAuthAdd(hsAddress, b64PrivateKey, isPermanent) {
|
|
282 | - return this.#withConnection(conn => {
|
|
283 | - return conn.onionAuthAdd(hsAddress, b64PrivateKey, isPermanent);
|
|
284 | - });
|
|
258 | + async onionAuthAdd(address, b64PrivateKey, isPermanent) {
|
|
259 | + const conn = await this.#getConnection();
|
|
260 | + return conn.onionAuthAdd(address, b64PrivateKey, isPermanent);
|
|
285 | 261 | }
|
286 | 262 | |
287 | - async onionAuthRemove(hsAddress) {
|
|
288 | - return this.#withConnection(conn => {
|
|
289 | - return conn.onionAuthRemove(hsAddress);
|
|
290 | - });
|
|
263 | + async onionAuthRemove(address) {
|
|
264 | + const conn = await this.#getConnection();
|
|
265 | + return conn.onionAuthRemove(address);
|
|
291 | 266 | }
|
292 | 267 | |
293 | 268 | async onionAuthViewKeys() {
|
294 | - return this.#withConnection(conn => {
|
|
295 | - return conn.onionAuthViewKeys();
|
|
296 | - });
|
|
269 | + const conn = await this.#getConnection();
|
|
270 | + return conn.onionAuthViewKeys();
|
|
297 | 271 | }
|
298 | 272 | |
299 | 273 | // TODO: transform the following 4 functions in getters.
|
... | ... | @@ -333,106 +307,6 @@ class TorProvider { |
333 | 307 | return this.#SOCKSPortInfo;
|
334 | 308 | }
|
335 | 309 | |
336 | - // Public, but called only internally
|
|
337 | - |
|
338 | - // Executes a command on the control port.
|
|
339 | - // Return a reply object or null if a fatal error occurs.
|
|
340 | - async sendCommand(cmd, args) {
|
|
341 | - const maxTimeout = 1000;
|
|
342 | - let leftConnAttempts = 5;
|
|
343 | - let timeout = 250;
|
|
344 | - let reply;
|
|
345 | - while (leftConnAttempts-- > 0) {
|
|
346 | - const response = await this.#trySend(cmd, args, leftConnAttempts === 0);
|
|
347 | - if (response.connected) {
|
|
348 | - reply = response.reply;
|
|
349 | - break;
|
|
350 | - }
|
|
351 | - // We failed to acquire the controller after multiple attempts.
|
|
352 | - // Try again after some time.
|
|
353 | - logger.warn(
|
|
354 | - "sendCommand: Acquiring control connection failed, trying again later.",
|
|
355 | - cmd,
|
|
356 | - args
|
|
357 | - );
|
|
358 | - await new Promise(resolve => setTimeout(() => resolve(), timeout));
|
|
359 | - timeout = Math.min(2 * timeout, maxTimeout);
|
|
360 | - }
|
|
361 | - |
|
362 | - // We sent the command, but we still got an empty response.
|
|
363 | - // Something must be busted elsewhere.
|
|
364 | - if (!reply) {
|
|
365 | - throw new Error(`${cmd} sent an empty response`);
|
|
366 | - }
|
|
367 | - |
|
368 | - // TODO: Move the parsing of the reply to the controller, because anyone
|
|
369 | - // calling sendCommand on it actually wants a parsed reply.
|
|
370 | - |
|
371 | - reply = TorParsers.parseCommandResponse(reply);
|
|
372 | - if (!TorParsers.commandSucceeded(reply)) {
|
|
373 | - if (reply?.lineArray) {
|
|
374 | - throw new Error(reply.lineArray.join("\n"));
|
|
375 | - }
|
|
376 | - throw new Error(`${cmd} failed with code ${reply.statusCode}`);
|
|
377 | - }
|
|
378 | - |
|
379 | - return reply;
|
|
380 | - }
|
|
381 | - |
|
382 | - // Perform a SETCONF command.
|
|
383 | - // aSettingsObj should be a JavaScript object with keys (property values)
|
|
384 | - // that correspond to tor config. keys. The value associated with each
|
|
385 | - // key should be a simple string, a string array, or a Boolean value.
|
|
386 | - // If an associated value is undefined or null, a key with no value is
|
|
387 | - // passed in the SETCONF command.
|
|
388 | - // Throws in case of error, or returns a reply object.
|
|
389 | - async setConfWithReply(settings) {
|
|
390 | - if (!settings) {
|
|
391 | - throw new Error("Empty settings object");
|
|
392 | - }
|
|
393 | - const args = Object.entries(settings)
|
|
394 | - .map(([key, val]) => {
|
|
395 | - if (val === undefined || val === null) {
|
|
396 | - return key;
|
|
397 | - }
|
|
398 | - const valType = typeof val;
|
|
399 | - let rv = `${key}=`;
|
|
400 | - if (valType === "boolean") {
|
|
401 | - rv += val ? "1" : "0";
|
|
402 | - } else if (Array.isArray(val)) {
|
|
403 | - rv += val.map(TorParsers.escapeString).join(` ${key}=`);
|
|
404 | - } else if (valType === "string") {
|
|
405 | - rv += TorParsers.escapeString(val);
|
|
406 | - } else {
|
|
407 | - logger.error(`Got unsupported type for ${key}`, val);
|
|
408 | - throw new Error(`Unsupported type ${valType} (key ${key})`);
|
|
409 | - }
|
|
410 | - return rv;
|
|
411 | - })
|
|
412 | - .filter(arg => arg);
|
|
413 | - if (!args.length) {
|
|
414 | - throw new Error("No settings to set");
|
|
415 | - }
|
|
416 | - |
|
417 | - await this.sendCommand("SETCONF", args.join(" "));
|
|
418 | - }
|
|
419 | - |
|
420 | - // Public, never called?
|
|
421 | - |
|
422 | - async readBoolSetting(aSetting) {
|
|
423 | - let value = await this.#readBoolSetting(aSetting);
|
|
424 | - this.#settingsCache.set(aSetting, value);
|
|
425 | - return value;
|
|
426 | - }
|
|
427 | - |
|
428 | - async readStringSetting(aSetting) {
|
|
429 | - let value = await this.#readStringSetting(aSetting);
|
|
430 | - this.#settingsCache.set(aSetting, value);
|
|
431 | - return value;
|
|
432 | - }
|
|
433 | - |
|
434 | - // Private
|
|
435 | - |
|
436 | 310 | async #setSockets() {
|
437 | 311 | try {
|
438 | 312 | const isWindows = TorLauncherUtil.isWindows;
|
... | ... | @@ -511,167 +385,24 @@ class TorProvider { |
511 | 385 | }
|
512 | 386 | }
|
513 | 387 | |
514 | - #assertValidSettingKey(aSetting) {
|
|
515 | - // ensure the 'key' is a string
|
|
516 | - if (typeof aSetting !== "string") {
|
|
517 | - throw new Error(
|
|
518 | - `Expected setting of type string but received ${typeof aSetting}`
|
|
519 | - );
|
|
520 | - }
|
|
521 | - }
|
|
522 | - |
|
523 | - #assertValidSetting(aSetting, aValue) {
|
|
524 | - this.#assertValidSettingKey(aSetting);
|
|
525 | - switch (typeof aValue) {
|
|
526 | - case "boolean":
|
|
527 | - case "string":
|
|
528 | - return;
|
|
529 | - case "object":
|
|
530 | - if (aValue === null) {
|
|
531 | - return;
|
|
532 | - } else if (Array.isArray(aValue)) {
|
|
533 | - for (const element of aValue) {
|
|
534 | - if (typeof element !== "string") {
|
|
535 | - throw new Error(
|
|
536 | - `Setting '${aSetting}' array contains value of invalid type '${typeof element}'`
|
|
537 | - );
|
|
538 | - }
|
|
539 | - }
|
|
540 | - return;
|
|
541 | - }
|
|
542 | - // fall through
|
|
543 | - default:
|
|
544 | - throw new Error(
|
|
545 | - `Invalid object type received for setting '${aSetting}'`
|
|
546 | - );
|
|
547 | - }
|
|
548 | - }
|
|
549 | - |
|
550 | - // Perform a GETCONF command.
|
|
551 | - async #readSetting(aSetting) {
|
|
552 | - this.#assertValidSettingKey(aSetting);
|
|
553 | - |
|
554 | - const cmd = "GETCONF";
|
|
555 | - let reply = await this.sendCommand(cmd, aSetting);
|
|
556 | - return TorParsers.parseReply(cmd, aSetting, reply);
|
|
557 | - }
|
|
558 | - |
|
559 | - async #readStringSetting(aSetting) {
|
|
560 | - let lineArray = await this.#readSetting(aSetting);
|
|
561 | - if (lineArray.length !== 1) {
|
|
562 | - throw new Error(
|
|
563 | - `Expected an array with length 1 but received array of length ${lineArray.length}`
|
|
564 | - );
|
|
565 | - }
|
|
566 | - return lineArray[0];
|
|
567 | - }
|
|
568 | - |
|
569 | - async #readBoolSetting(aSetting) {
|
|
570 | - const value = this.#readStringSetting(aSetting);
|
|
571 | - switch (value) {
|
|
572 | - case "0":
|
|
573 | - return false;
|
|
574 | - case "1":
|
|
575 | - return true;
|
|
576 | - default:
|
|
577 | - throw new Error(`Expected boolean (1 or 0) but received '${value}'`);
|
|
578 | - }
|
|
579 | - }
|
|
580 | - |
|
581 | - async #trySend(cmd, args, rethrow) {
|
|
582 | - let connected = false;
|
|
583 | - let reply;
|
|
584 | - let leftAttempts = 2;
|
|
585 | - while (leftAttempts-- > 0) {
|
|
586 | - let conn;
|
|
587 | - try {
|
|
588 | - conn = await this.#getConnection();
|
|
589 | - } catch (e) {
|
|
590 | - logger.error("Cannot get a connection to the control port", e);
|
|
591 | - if (leftAttempts == 0 && rethrow) {
|
|
592 | - throw e;
|
|
593 | - }
|
|
594 | - }
|
|
595 | - if (!conn) {
|
|
596 | - continue;
|
|
597 | - }
|
|
598 | - // If we _ever_ got a connection, the caller should not try again
|
|
599 | - connected = true;
|
|
600 | - try {
|
|
601 | - reply = await conn.sendCommand(cmd + (args ? " " + args : ""));
|
|
602 | - if (reply) {
|
|
603 | - // Return for reuse.
|
|
604 | - this.#returnConnection();
|
|
605 | - } else {
|
|
606 | - // Connection is bad.
|
|
607 | - logger.warn(
|
|
608 | - "sendCommand returned an empty response, taking the connection as broken and closing it."
|
|
609 | - );
|
|
610 | - this.#closeConnection();
|
|
611 | - }
|
|
612 | - } catch (e) {
|
|
613 | - logger.error(`Cannot send the command ${cmd}`, e);
|
|
614 | - this.#closeConnection();
|
|
615 | - if (leftAttempts == 0 && rethrow) {
|
|
616 | - throw e;
|
|
617 | - }
|
|
618 | - }
|
|
619 | - }
|
|
620 | - return { connected, reply };
|
|
621 | - }
|
|
622 | - |
|
623 | - // Opens an authenticated connection, sets it to this.#controlConnection, and
|
|
624 | - // return it.
|
|
625 | 388 | async #getConnection() {
|
626 | - if (!this.#controlConnection) {
|
|
389 | + if (!this.#controlConnection?.isOpen) {
|
|
627 | 390 | this.#controlConnection = await lazy.controller();
|
628 | 391 | }
|
629 | - if (this.#controlConnection.inUse) {
|
|
630 | - await new Promise((resolve, reject) =>
|
|
631 | - this.#connectionQueue.push({ resolve, reject })
|
|
632 | - );
|
|
633 | - } else {
|
|
634 | - this.#controlConnection.inUse = true;
|
|
635 | - }
|
|
636 | 392 | return this.#controlConnection;
|
637 | 393 | }
|
638 | 394 | |
639 | - #returnConnection() {
|
|
640 | - if (this.#connectionQueue.length) {
|
|
641 | - this.#connectionQueue.shift().resolve();
|
|
642 | - } else {
|
|
643 | - this.#controlConnection.inUse = false;
|
|
644 | - }
|
|
645 | - }
|
|
646 | - |
|
647 | - async #withConnection(func) {
|
|
648 | - // TODO: Make more robust?
|
|
649 | - const conn = await this.#getConnection();
|
|
650 | - try {
|
|
651 | - return await func(conn);
|
|
652 | - } finally {
|
|
653 | - this.#returnConnection();
|
|
654 | - }
|
|
655 | - }
|
|
656 | - |
|
657 | - // If aConn is omitted, the cached connection is closed.
|
|
658 | 395 | #closeConnection() {
|
659 | 396 | if (this.#controlConnection) {
|
660 | 397 | logger.info("Closing the control connection");
|
661 | 398 | this.#controlConnection.close();
|
662 | 399 | this.#controlConnection = null;
|
663 | 400 | }
|
664 | - for (const promise of this.#connectionQueue) {
|
|
665 | - promise.reject("Connection closed");
|
|
666 | - }
|
|
667 | - this.#connectionQueue = [];
|
|
668 | 401 | }
|
669 | 402 | |
670 | 403 | async #reconnect() {
|
671 | 404 | this.#closeConnection();
|
672 | - const conn = await this.#getConnection();
|
|
673 | - logger.debug("Reconnected to the control port.");
|
|
674 | - this.#returnConnection(conn);
|
|
405 | + await this.#getConnection();
|
|
675 | 406 | }
|
676 | 407 | |
677 | 408 | async #readAuthenticationCookie(aPath) {
|
... | ... | @@ -777,8 +508,9 @@ class TorProvider { |
777 | 508 | if (this.ownsTorDaemon) {
|
778 | 509 | // When we own the tor daemon, we listen to more events, that are used
|
779 | 510 | // for about:torconnect or for showing the logs in the settings page.
|
780 | - this._eventHandlers.set("STATUS_CLIENT", (_eventType, lines) =>
|
|
781 | - this._processBootstrapStatus(lines[0], false)
|
|
511 | + this._eventHandlers.set(
|
|
512 | + "STATUS_CLIENT",
|
|
513 | + this._processStatusClient.bind(this)
|
|
782 | 514 | );
|
783 | 515 | this._eventHandlers.set("NOTICE", this._processLog.bind(this));
|
784 | 516 | this._eventHandlers.set("WARN", this._processLog.bind(this));
|
... | ... | @@ -809,23 +541,10 @@ class TorProvider { |
809 | 541 | throw new Error("Event monitor connection not available");
|
810 | 542 | }
|
811 | 543 | |
812 | - // TODO: Unify with TorProtocolService.sendCommand and put everything in the
|
|
813 | - // reviewed torbutton replacement.
|
|
814 | - const cmd = "GETINFO";
|
|
815 | - const key = "status/bootstrap-phase";
|
|
816 | - let reply = await this._connection.sendCommand(`${cmd} ${key}`);
|
|
817 | - |
|
818 | - // A typical reply looks like:
|
|
819 | - // 250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"
|
|
820 | - // 250 OK
|
|
821 | - reply = TorParsers.parseCommandResponse(reply);
|
|
822 | - if (!TorParsers.commandSucceeded(reply)) {
|
|
823 | - throw new Error(`${cmd} failed`);
|
|
824 | - }
|
|
825 | - reply = TorParsers.parseReply(cmd, key, reply);
|
|
826 | - if (reply.length) {
|
|
827 | - this._processBootstrapStatus(reply[0], true);
|
|
828 | - }
|
|
544 | + this._processBootstrapStatus(
|
|
545 | + await this._connection.getBootstrapPhase(),
|
|
546 | + true
|
|
547 | + );
|
|
829 | 548 | }
|
830 | 549 | |
831 | 550 | // Returns captured log message as a text string (one message per line).
|
... | ... | @@ -1058,37 +777,32 @@ class TorProvider { |
1058 | 777 | _monitorEvent(type, callback) {
|
1059 | 778 | logger.info(`Watching events of type ${type}.`);
|
1060 | 779 | let replyObj = {};
|
1061 | - this._connection.watchEvent(
|
|
1062 | - type,
|
|
1063 | - null,
|
|
1064 | - line => {
|
|
1065 | - if (!line) {
|
|
1066 | - return;
|
|
1067 | - }
|
|
1068 | - logger.debug("Event response: ", line);
|
|
1069 | - const isComplete = TorParsers.parseReplyLine(line, replyObj);
|
|
1070 | - if (!isComplete || replyObj._parseError || !replyObj.lineArray.length) {
|
|
1071 | - return;
|
|
1072 | - }
|
|
1073 | - const reply = replyObj;
|
|
1074 | - replyObj = {};
|
|
1075 | - if (reply.statusCode !== TorStatuses.EventNotification) {
|
|
1076 | - logger.error("Unexpected event status code:", reply.statusCode);
|
|
1077 | - return;
|
|
1078 | - }
|
|
1079 | - if (!reply.lineArray[0].startsWith(`${type} `)) {
|
|
1080 | - logger.error("Wrong format for the first line:", reply.lineArray[0]);
|
|
1081 | - return;
|
|
1082 | - }
|
|
1083 | - reply.lineArray[0] = reply.lineArray[0].substring(type.length + 1);
|
|
1084 | - try {
|
|
1085 | - callback(type, reply.lineArray);
|
|
1086 | - } catch (e) {
|
|
1087 | - logger.error("Exception while handling an event", reply, e);
|
|
1088 | - }
|
|
1089 | - },
|
|
1090 | - true
|
|
1091 | - );
|
|
780 | + this._connection.watchEvent(type, line => {
|
|
781 | + if (!line) {
|
|
782 | + return;
|
|
783 | + }
|
|
784 | + logger.debug("Event response: ", line);
|
|
785 | + const isComplete = TorParsers.parseReplyLine(line, replyObj);
|
|
786 | + if (!isComplete || replyObj._parseError || !replyObj.lineArray.length) {
|
|
787 | + return;
|
|
788 | + }
|
|
789 | + const reply = replyObj;
|
|
790 | + replyObj = {};
|
|
791 | + if (reply.statusCode !== TorStatuses.EventNotification) {
|
|
792 | + logger.error("Unexpected event status code:", reply.statusCode);
|
|
793 | + return;
|
|
794 | + }
|
|
795 | + if (!reply.lineArray[0].startsWith(`${type} `)) {
|
|
796 | + logger.error("Wrong format for the first line:", reply.lineArray[0]);
|
|
797 | + return;
|
|
798 | + }
|
|
799 | + reply.lineArray[0] = reply.lineArray[0].substring(type.length + 1);
|
|
800 | + try {
|
|
801 | + callback(type, reply.lineArray);
|
|
802 | + } catch (e) {
|
|
803 | + logger.error("Exception while handling an event", reply, e);
|
|
804 | + }
|
|
805 | + });
|
|
1092 | 806 | }
|
1093 | 807 | |
1094 | 808 | _processLog(type, lines) {
|
... | ... | @@ -1116,15 +830,12 @@ class TorProvider { |
1116 | 830 | // to TorBootstrapStatus observers.
|
1117 | 831 | // If aSuppressErrors is true, errors are ignored. This is used when we
|
1118 | 832 | // are handling the response to a "GETINFO status/bootstrap-phase" command.
|
1119 | - _processBootstrapStatus(aStatusMsg, aSuppressErrors) {
|
|
1120 | - const statusObj = TorParsers.parseBootstrapStatus(aStatusMsg);
|
|
1121 | - if (!statusObj) {
|
|
1122 | - return;
|
|
1123 | - }
|
|
1124 | - |
|
833 | + _processBootstrapStatus(statusObj, suppressErrors) {
|
|
1125 | 834 | // Notify observers
|
1126 | - statusObj.wrappedJSObject = statusObj;
|
|
1127 | - Services.obs.notifyObservers(statusObj, "TorBootstrapStatus");
|
|
835 | + Services.obs.notifyObservers(
|
|
836 | + { wrappedJSObject: statusObj },
|
|
837 | + "TorBootstrapStatus"
|
|
838 | + );
|
|
1128 | 839 | |
1129 | 840 | if (statusObj.PROGRESS === 100) {
|
1130 | 841 | this._isBootstrapDone = true;
|
... | ... | @@ -1141,7 +852,7 @@ class TorProvider { |
1141 | 852 | if (
|
1142 | 853 | statusObj.TYPE === "WARN" &&
|
1143 | 854 | statusObj.RECOMMENDATION !== "ignore" &&
|
1144 | - !aSuppressErrors
|
|
855 | + !suppressErrors
|
|
1145 | 856 | ) {
|
1146 | 857 | this._notifyBootstrapError(statusObj);
|
1147 | 858 | }
|
... | ... | @@ -1184,6 +895,15 @@ class TorProvider { |
1184 | 895 | }
|
1185 | 896 | }
|
1186 | 897 | |
898 | + _processStatusClient(_type, lines) {
|
|
899 | + const statusObj = TorParsers.parseBootstrapStatus(lines[0]);
|
|
900 | + if (!statusObj) {
|
|
901 | + // No `BOOTSTRAP` in the line
|
|
902 | + return;
|
|
903 | + }
|
|
904 | + this._processBootstrapStatus(statusObj, false);
|
|
905 | + }
|
|
906 | + |
|
1187 | 907 | async _processCircEvent(_type, lines) {
|
1188 | 908 | const builtEvent =
|
1189 | 909 | /^(?<CircuitID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(?:,?\$[0-9a-fA-F]{40}(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
|
... | ... | @@ -1295,7 +1015,3 @@ class TorProvider { |
1295 | 1015 | this.clearBootstrapError();
|
1296 | 1016 | }
|
1297 | 1017 | } |
1298 | - |
|
1299 | -// TODO: Stop defining TorProtocolService, make the builder instance the
|
|
1300 | -// TorProvider.
|
|
1301 | -export const TorProtocolService = new TorProvider(); |
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | |
5 | 5 | const lazy = {};
|
6 | 6 | ChromeUtils.defineESModuleGetters(lazy, {
|
7 | - TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
|
|
7 | + TorProvider: "resource://gre/modules/TorProvider.sys.mjs",
|
|
8 | 8 | });
|
9 | 9 | |
10 | 10 | export const TorProviderTopics = Object.freeze({
|
... | ... | @@ -19,16 +19,25 @@ export const TorProviderTopics = Object.freeze({ |
19 | 19 | });
|
20 | 20 | |
21 | 21 | export class TorProviderBuilder {
|
22 | + static #provider = null;
|
|
23 | + |
|
22 | 24 | static async init() {
|
23 | - await lazy.TorProtocolService.init();
|
|
25 | + const provider = new lazy.TorProvider();
|
|
26 | + await provider.init();
|
|
27 | + // Assign it only when initialization succeeds.
|
|
28 | + TorProviderBuilder.#provider = provider;
|
|
24 | 29 | }
|
25 | 30 | |
26 | 31 | static uninit() {
|
27 | - lazy.TorProtocolService.uninit();
|
|
32 | + TorProviderBuilder.#provider.uninit();
|
|
33 | + TorProviderBuilder.#provider = null;
|
|
28 | 34 | }
|
29 | 35 | |
30 | 36 | // TODO: Switch to an async build?
|
31 | 37 | static build() {
|
32 | - return lazy.TorProtocolService;
|
|
38 | + if (!TorProviderBuilder.#provider) {
|
|
39 | + throw new Error("TorProviderBuilder has not been initialized yet.");
|
|
40 | + }
|
|
41 | + return TorProviderBuilder.#provider;
|
|
33 | 42 | }
|
34 | 43 | } |
... | ... | @@ -3,22 +3,13 @@ const lazy = {}; |
3 | 3 | // We will use the modules only when the profile is loaded, so prefer lazy
|
4 | 4 | // loading
|
5 | 5 | ChromeUtils.defineESModuleGetters(lazy, {
|
6 | + TorConnect: "resource:///modules/TorConnect.sys.mjs",
|
|
6 | 7 | TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
|
7 | 8 | TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
8 | 9 | TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
10 | + TorSettings: "resource:///modules/TorSettings.sys.mjs",
|
|
9 | 11 | });
|
10 | 12 | |
11 | -ChromeUtils.defineModuleGetter(
|
|
12 | - lazy,
|
|
13 | - "TorConnect",
|
|
14 | - "resource:///modules/TorConnect.jsm"
|
|
15 | -);
|
|
16 | -ChromeUtils.defineModuleGetter(
|
|
17 | - lazy,
|
|
18 | - "TorSettings",
|
|
19 | - "resource:///modules/TorSettings.jsm"
|
|
20 | -);
|
|
21 | - |
|
22 | 13 | /* Browser observer topis */
|
23 | 14 | const BrowserTopics = Object.freeze({
|
24 | 15 | ProfileAfterChange: "profile-after-change",
|
... | ... | @@ -3,10 +3,9 @@ EXTRA_JS_MODULES += [ |
3 | 3 | "TorControlPort.sys.mjs",
|
4 | 4 | "TorDomainIsolator.sys.mjs",
|
5 | 5 | "TorLauncherUtil.sys.mjs",
|
6 | - "TorMonitorService.sys.mjs",
|
|
7 | 6 | "TorParsers.sys.mjs",
|
8 | 7 | "TorProcess.sys.mjs",
|
9 | - "TorProtocolService.sys.mjs",
|
|
8 | + "TorProvider.sys.mjs",
|
|
10 | 9 | "TorProviderBuilder.sys.mjs",
|
11 | 10 | "TorStartupService.sys.mjs",
|
12 | 11 | ]
|
... | ... | @@ -23,18 +23,13 @@ ChromeUtils.defineESModuleGetters(lazy, { |
23 | 23 | AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
|
24 | 24 | CertUtils: "resource://gre/modules/CertUtils.sys.mjs",
|
25 | 25 | DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
|
26 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
26 | 27 | UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
|
27 | 28 | WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
|
28 | 29 | ctypes: "resource://gre/modules/ctypes.sys.mjs",
|
29 | 30 | setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
30 | 31 | });
|
31 | 32 | |
32 | -ChromeUtils.defineModuleGetter(
|
|
33 | - lazy,
|
|
34 | - "TorMonitorService",
|
|
35 | - "resource://gre/modules/TorMonitorService.jsm"
|
|
36 | -);
|
|
37 | - |
|
38 | 33 | XPCOMUtils.defineLazyServiceGetter(
|
39 | 34 | lazy,
|
40 | 35 | "AUS",
|
... | ... | @@ -394,10 +389,11 @@ XPCOMUtils.defineLazyGetter( |
394 | 389 | );
|
395 | 390 | |
396 | 391 | function _shouldRegisterBootstrapObserver(errorCode) {
|
392 | + const provider = lazy.TorProviderBuilder.build();
|
|
397 | 393 | return (
|
398 | 394 | errorCode == PROXY_SERVER_CONNECTION_REFUSED &&
|
399 | - !lazy.TorMonitorService.isBootstrapDone &&
|
|
400 | - lazy.TorMonitorService.ownsTorDaemon
|
|
395 | + !provider.isBootstrapDone &&
|
|
396 | + provider.ownsTorDaemon
|
|
401 | 397 | );
|
402 | 398 | }
|
403 | 399 | |
... | ... | @@ -5833,10 +5829,7 @@ Downloader.prototype = { |
5833 | 5829 | // we choose to compute these hashes.
|
5834 | 5830 | hash = hash.finish(false);
|
5835 | 5831 | digest = Array.from(hash, (c, i) =>
|
5836 | - hash
|
|
5837 | - .charCodeAt(i)
|
|
5838 | - .toString(16)
|
|
5839 | - .padStart(2, "0")
|
|
5832 | + hash.charCodeAt(i).toString(16).padStart(2, "0")
|
|
5840 | 5833 | ).join("");
|
5841 | 5834 | } catch (e) {
|
5842 | 5835 | LOG(
|