Pier Angelo Vendrame pushed to branch tor-browser-115.5.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
5ad20208
by Pier Angelo Vendrame at 2023-12-06T18:31:01+01:00
-
8c95910c
by Pier Angelo Vendrame at 2023-12-06T18:31:02+01:00
-
fb609914
by Pier Angelo Vendrame at 2023-12-07T19:35:29+01:00
-
1b7630ec
by Pier Angelo Vendrame at 2023-12-07T19:35:34+01:00
-
d0ae1f7e
by Pier Angelo Vendrame at 2023-12-07T19:35:35+01:00
-
51d44491
by Pier Angelo Vendrame at 2023-12-07T19:35:35+01:00
-
be3afbb8
by Pier Angelo Vendrame at 2023-12-07T19:35:55+01:00
-
766abfe6
by Pier Angelo Vendrame at 2023-12-07T19:35:58+01:00
13 changed files:
- mobile/android/components/geckoview/GeckoViewStartup.jsm
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
- + mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
- mobile/android/modules/geckoview/GeckoViewContent.sys.mjs
- + toolkit/components/tor-launcher/TorProcessAndroid.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/tor-launcher/moz.build
- toolkit/modules/Moat.sys.mjs
- + toolkit/modules/TorAndroidIntegration.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
- toolkit/modules/moz.build
Changes:
... | ... | @@ -5,6 +5,10 @@ |
5 | 5 | |
6 | 6 | var EXPORTED_SYMBOLS = ["GeckoViewStartup"];
|
7 | 7 | |
8 | +const { AppConstants } = ChromeUtils.importESModule(
|
|
9 | + "resource://gre/modules/AppConstants.sys.mjs"
|
|
10 | +);
|
|
11 | + |
|
8 | 12 | const { GeckoViewUtils } = ChromeUtils.importESModule(
|
9 | 13 | "resource://gre/modules/GeckoViewUtils.sys.mjs"
|
10 | 14 | );
|
... | ... | @@ -17,6 +21,7 @@ ChromeUtils.defineESModuleGetters(lazy, { |
17 | 21 | PdfJs: "resource://pdf.js/PdfJs.sys.mjs",
|
18 | 22 | Preferences: "resource://gre/modules/Preferences.sys.mjs",
|
19 | 23 | RFPHelper: "resource://gre/modules/RFPHelper.sys.mjs",
|
24 | + TorAndroidIntegration: "resource://gre/modules/TorAndroidIntegration.sys.mjs",
|
|
20 | 25 | TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
|
21 | 26 | });
|
22 | 27 | |
... | ... | @@ -259,6 +264,7 @@ class GeckoViewStartup { |
259 | 264 | "GeckoView:SetLocale",
|
260 | 265 | ]);
|
261 | 266 | |
267 | + lazy.TorAndroidIntegration.init();
|
|
262 | 268 | lazy.TorDomainIsolator.init();
|
263 | 269 | |
264 | 270 | Services.obs.addObserver(this, "browser-idle-startup-tasks-finished");
|
... | ... | @@ -167,7 +167,7 @@ public final class GeckoRuntime implements Parcelable { |
167 | 167 | if (!BuildConfig.TOR_BROWSER) {
|
168 | 168 | GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext());
|
169 | 169 | } else {
|
170 | - Log.d(LOGTAG, "Tor Browser: skip GeckoNetworkManager startup");
|
|
170 | + Log.d(LOGTAG, "Tor Browser: skip GeckoNetworkManager startup");
|
|
171 | 171 | }
|
172 | 172 | |
173 | 173 | // Set settings that may have changed between last app opening
|
... | ... | @@ -230,6 +230,8 @@ public final class GeckoRuntime implements Parcelable { |
230 | 230 | private final ProfilerController mProfilerController;
|
231 | 231 | private final GeckoScreenChangeListener mScreenChangeListener;
|
232 | 232 | |
233 | + private TorIntegrationAndroid mTorIntegration;
|
|
234 | + |
|
233 | 235 | private GeckoRuntime() {
|
234 | 236 | mWebExtensionController = new WebExtensionController(this);
|
235 | 237 | mContentBlockingController = new ContentBlockingController();
|
... | ... | @@ -484,6 +486,8 @@ public final class GeckoRuntime implements Parcelable { |
484 | 486 | mScreenChangeListener.enable();
|
485 | 487 | }
|
486 | 488 | |
489 | + mTorIntegration = new TorIntegrationAndroid(context);
|
|
490 | + |
|
487 | 491 | mProfilerController.addMarker(
|
488 | 492 | "GeckoView Initialization START", mProfilerController.getProfilerTime());
|
489 | 493 | return true;
|
... | ... | @@ -600,6 +604,10 @@ public final class GeckoRuntime implements Parcelable { |
600 | 604 | mScreenChangeListener.disable();
|
601 | 605 | }
|
602 | 606 | |
607 | + if (mTorIntegration != null) {
|
|
608 | + mTorIntegration.shutdown();
|
|
609 | + }
|
|
610 | + |
|
603 | 611 | GeckoThread.forceQuit();
|
604 | 612 | }
|
605 | 613 |
... | ... | @@ -487,6 +487,11 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { |
487 | 487 | getSettings().mPrioritizeOnions.set(flag);
|
488 | 488 | return this;
|
489 | 489 | }
|
490 | + |
|
491 | + public @NonNull Builder useNewBootstrap(final boolean flag) {
|
|
492 | + getSettings().mUseNewBootstrap.set(flag);
|
|
493 | + return this;
|
|
494 | + }
|
|
490 | 495 | }
|
491 | 496 | |
492 | 497 | private GeckoRuntime mRuntime;
|
... | ... | @@ -540,6 +545,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { |
540 | 545 | new Pref<>("browser.security_level.security_slider", 4);
|
541 | 546 | /* package */ final Pref<Boolean> mPrioritizeOnions =
|
542 | 547 | new Pref<>("privacy.prioritizeonions.enabled", false);
|
548 | + /* package */ final Pref<Boolean> mUseNewBootstrap =
|
|
549 | + new Pref<>("browser.tor_android.use_new_bootstrap", false);
|
|
543 | 550 | |
544 | 551 | /* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
|
545 | 552 | |
... | ... | @@ -1352,6 +1359,15 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { |
1352 | 1359 | return this;
|
1353 | 1360 | }
|
1354 | 1361 | |
1362 | + public boolean getUseNewBootstrap() {
|
|
1363 | + return mUseNewBootstrap.get();
|
|
1364 | + }
|
|
1365 | + |
|
1366 | + public @NonNull GeckoRuntimeSettings setUseNewBootstrap(final boolean flag) {
|
|
1367 | + mUseNewBootstrap.commit(flag);
|
|
1368 | + return this;
|
|
1369 | + }
|
|
1370 | + |
|
1355 | 1371 | @Override // Parcelable
|
1356 | 1372 | public void writeToParcel(final Parcel out, final int flags) {
|
1357 | 1373 | super.writeToParcel(out, flags);
|
... | ... | @@ -2493,6 +2493,16 @@ public class GeckoSession { |
2493 | 2493 | return mEventDispatcher.queryBoolean("GeckoView:IsPdfJs");
|
2494 | 2494 | }
|
2495 | 2495 | |
2496 | + /**
|
|
2497 | + * Try to get last circuit used in this session, if possible.
|
|
2498 | + *
|
|
2499 | + * @return The circuit information as a {@link GeckoResult} object.
|
|
2500 | + */
|
|
2501 | + @AnyThread
|
|
2502 | + public @NonNull GeckoResult<GeckoBundle> getTorCircuit() {
|
|
2503 | + return mEventDispatcher.queryBundle("GeckoView:GetTorCircuit");
|
|
2504 | + }
|
|
2505 | + |
|
2496 | 2506 | /**
|
2497 | 2507 | * Set this GeckoSession as active or inactive, which represents if the session is currently
|
2498 | 2508 | * visible or not. Setting a GeckoSession to inactive will significantly reduce its memory
|
1 | +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
2 | + * vim: ts=4 sw=4 expandtab:
|
|
3 | + * This Source Code Form is subject to the terms of the Mozilla Public
|
|
4 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
6 | + |
|
7 | +package org.mozilla.geckoview;
|
|
8 | + |
|
9 | +import android.content.Context;
|
|
10 | +import android.util.Log;
|
|
11 | + |
|
12 | +import java.io.BufferedReader;
|
|
13 | +import java.io.File;
|
|
14 | +import java.io.IOException;
|
|
15 | +import java.io.InputStream;
|
|
16 | +import java.io.InputStreamReader;
|
|
17 | +import java.nio.file.Files;
|
|
18 | +import java.nio.file.Path;
|
|
19 | +import java.nio.file.Paths;
|
|
20 | +import java.nio.file.StandardCopyOption;
|
|
21 | +import java.nio.file.attribute.PosixFilePermission;
|
|
22 | +import java.nio.file.attribute.PosixFilePermissions;
|
|
23 | +import java.util.ArrayList;
|
|
24 | +import java.util.HashMap;
|
|
25 | +import java.util.Map;
|
|
26 | +import java.util.Set;
|
|
27 | +import java.util.UUID;
|
|
28 | + |
|
29 | +import org.mozilla.gecko.EventDispatcher;
|
|
30 | +import org.mozilla.gecko.GeckoAppShell;
|
|
31 | +import org.mozilla.gecko.util.BundleEventListener;
|
|
32 | +import org.mozilla.gecko.util.EventCallback;
|
|
33 | +import org.mozilla.gecko.util.GeckoBundle;
|
|
34 | + |
|
35 | +/* package */ class TorIntegrationAndroid implements BundleEventListener {
|
|
36 | + private static final String TAG = "TorIntegrationAndroid";
|
|
37 | + |
|
38 | + private static final String TOR_EVENT_START = "GeckoView:Tor:StartTor";
|
|
39 | + private static final String TOR_EVENT_STOP = "GeckoView:Tor:StopTor";
|
|
40 | + private static final String MEEK_EVENT_START = "GeckoView:Tor:StartMeek";
|
|
41 | + private static final String MEEK_EVENT_STOP = "GeckoView:Tor:StopMeek";
|
|
42 | + |
|
43 | + private static final String CONTROL_PORT_FILE = "/control-ipc";
|
|
44 | + private static final String SOCKS_FILE = "/socks-ipc";
|
|
45 | + private static final String COOKIE_AUTH_FILE = "/auth-file";
|
|
46 | + |
|
47 | + private final String mLibraryDir;
|
|
48 | + private final Path mCacheDir;
|
|
49 | + private final String mIpcDirectory;
|
|
50 | + private final String mDataDir;
|
|
51 | + |
|
52 | + private TorProcess mTorProcess = null;
|
|
53 | + /**
|
|
54 | + * The first time we run a Tor process in this session, we copy some configuration files to be
|
|
55 | + * sure we always have the latest version, but if we re-launch a tor process we do not need to
|
|
56 | + * copy them again.
|
|
57 | + */
|
|
58 | + private boolean mCopiedConfigFiles = false;
|
|
59 | + /**
|
|
60 | + * Allow multiple proxies to be started, even though it might not actually happen.
|
|
61 | + * The key should be positive (also 0 is not allowed).
|
|
62 | + */
|
|
63 | + private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>();
|
|
64 | + private int mMeekCounter;
|
|
65 | + |
|
66 | + public TorIntegrationAndroid(Context context) {
|
|
67 | + mLibraryDir = context.getApplicationInfo().nativeLibraryDir;
|
|
68 | + mCacheDir = context.getCacheDir().toPath();
|
|
69 | + mIpcDirectory = mCacheDir + "/tor-private";
|
|
70 | + mDataDir = context.getDataDir().getAbsolutePath() + "/tor";
|
|
71 | + registerListener();
|
|
72 | + }
|
|
73 | + |
|
74 | + public synchronized void shutdown() {
|
|
75 | + // FIXME: It seems this never gets called
|
|
76 | + if (mTorProcess != null) {
|
|
77 | + mTorProcess.shutdown();
|
|
78 | + mTorProcess = null;
|
|
79 | + }
|
|
80 | + }
|
|
81 | + |
|
82 | + private void registerListener() {
|
|
83 | + EventDispatcher.getInstance()
|
|
84 | + .registerUiThreadListener(
|
|
85 | + this,
|
|
86 | + TOR_EVENT_START,
|
|
87 | + MEEK_EVENT_START,
|
|
88 | + MEEK_EVENT_STOP);
|
|
89 | + }
|
|
90 | + |
|
91 | + @Override // BundleEventListener
|
|
92 | + public synchronized void handleMessage(
|
|
93 | + final String event, final GeckoBundle message, final EventCallback callback) {
|
|
94 | + if (TOR_EVENT_START.equals(event)) {
|
|
95 | + startDaemon(message, callback);
|
|
96 | + } else if (TOR_EVENT_STOP.equals(event)) {
|
|
97 | + stopDaemon(message, callback);
|
|
98 | + } else if (MEEK_EVENT_START.equals(event)) {
|
|
99 | + startMeek(message, callback);
|
|
100 | + } else if (MEEK_EVENT_STOP.equals(event)) {
|
|
101 | + stopMeek(message, callback);
|
|
102 | + }
|
|
103 | + }
|
|
104 | + |
|
105 | + private synchronized void startDaemon(final GeckoBundle message, final EventCallback callback) {
|
|
106 | + // Let JS generate this to possibly reduce the chance of race conditions.
|
|
107 | + String handle = message.getString("handle", "");
|
|
108 | + if (handle.isEmpty()) {
|
|
109 | + Log.e(TAG, "Requested to start a tor process without a handle.");
|
|
110 | + callback.sendError("Expected a handle for the new process.");
|
|
111 | + return;
|
|
112 | + }
|
|
113 | + Log.d(TAG, "Starting the a tor process with handle " + handle);
|
|
114 | + |
|
115 | + TorProcess previousProcess = mTorProcess;
|
|
116 | + if (previousProcess != null) {
|
|
117 | + Log.w(TAG, "We still have a running process: " + previousProcess.getHandle());
|
|
118 | + }
|
|
119 | + mTorProcess = new TorProcess(handle);
|
|
120 | + |
|
121 | + GeckoBundle bundle = new GeckoBundle(3);
|
|
122 | + bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE);
|
|
123 | + bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE);
|
|
124 | + bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE);
|
|
125 | + callback.sendSuccess(bundle);
|
|
126 | + }
|
|
127 | + |
|
128 | + private synchronized void stopDaemon(final GeckoBundle message, final EventCallback callback) {
|
|
129 | + if (mTorProcess == null) {
|
|
130 | + if (callback != null) {
|
|
131 | + callback.sendSuccess(null);
|
|
132 | + }
|
|
133 | + return;
|
|
134 | + }
|
|
135 | + String handle = message.getString("handle", "");
|
|
136 | + if (!mTorProcess.getHandle().equals(handle)) {
|
|
137 | + GeckoBundle bundle = new GeckoBundle(1);
|
|
138 | + bundle.putString("error", "The requested process has not been found. It might have already been stopped.");
|
|
139 | + callback.sendError(bundle);
|
|
140 | + return;
|
|
141 | + }
|
|
142 | + mTorProcess.shutdown();
|
|
143 | + mTorProcess = null;
|
|
144 | + callback.sendSuccess(null);
|
|
145 | + }
|
|
146 | + |
|
147 | + class TorProcess extends Thread {
|
|
148 | + private static final String TOR_EVENT_STARTED = "GeckoView:Tor:TorStarted";
|
|
149 | + private static final String TOR_EVENT_START_FAILED = "GeckoView:Tor:TorStartFailed";
|
|
150 | + private static final String TOR_EVENT_EXITED = "GeckoView:Tor:TorExited";
|
|
151 | + private final String mHandle;
|
|
152 | + private Process mProcess = null;
|
|
153 | + |
|
154 | + TorProcess(String handle) {
|
|
155 | + mHandle = handle;
|
|
156 | + setName("tor-process-" + handle);
|
|
157 | + start();
|
|
158 | + }
|
|
159 | + |
|
160 | + @Override
|
|
161 | + public void run() {
|
|
162 | + cleanIpcDirectory();
|
|
163 | + |
|
164 | + final String ipcDir = TorIntegrationAndroid.this.mIpcDirectory;
|
|
165 | + final ArrayList<String> args = new ArrayList<>();
|
|
166 | + args.add(mLibraryDir + "/libTor.so");
|
|
167 | + args.add("DisableNetwork");
|
|
168 | + args.add("1");
|
|
169 | + args.add("+__ControlPort");
|
|
170 | + args.add("unix:" + ipcDir + CONTROL_PORT_FILE);
|
|
171 | + args.add("+__SocksPort");
|
|
172 | + args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth");
|
|
173 | + args.add("CookieAuthentication");
|
|
174 | + args.add("1");
|
|
175 | + args.add("CookieAuthFile");
|
|
176 | + args.add(ipcDir + COOKIE_AUTH_FILE);
|
|
177 | + args.add("DataDirectory");
|
|
178 | + args.add(mDataDir);
|
|
179 | + boolean copied = true;
|
|
180 | + try {
|
|
181 | + copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args);
|
|
182 | + } catch (IOException e) {
|
|
183 | + Log.w(TAG, "torrc-default cannot be created, pluggable transports will not be available", e);
|
|
184 | + copied = false;
|
|
185 | + }
|
|
186 | + try {
|
|
187 | + copyAndUseConfigFile("GeoIPFile", "geoip", args);
|
|
188 | + copyAndUseConfigFile("GeoIPv6File", "geoip6", args);
|
|
189 | + } catch (IOException e) {
|
|
190 | + Log.w(TAG, "GeoIP files cannot be created, this feature will not be available.", e);
|
|
191 | + copied = false;
|
|
192 | + }
|
|
193 | + mCopiedConfigFiles = copied;
|
|
194 | + |
|
195 | + Log.d(TAG, "Starting tor with the follwing args: " + args.toString());
|
|
196 | + final ProcessBuilder builder = new ProcessBuilder(args);
|
|
197 | + builder.directory(new File(mLibraryDir));
|
|
198 | + try {
|
|
199 | + mProcess = builder.start();
|
|
200 | + } catch (IOException e) {
|
|
201 | + Log.e(TAG, "Cannot start tor " + mHandle, e);
|
|
202 | + final GeckoBundle data = new GeckoBundle(2);
|
|
203 | + data.putString("handle", mHandle);
|
|
204 | + data.putString("error", e.getMessage());
|
|
205 | + EventDispatcher.getInstance().dispatch(TOR_EVENT_START_FAILED, data);
|
|
206 | + return;
|
|
207 | + }
|
|
208 | + Log.i(TAG, "Tor process " + mHandle + " started.");
|
|
209 | + {
|
|
210 | + final GeckoBundle data = new GeckoBundle(1);
|
|
211 | + data.putString("handle", mHandle);
|
|
212 | + EventDispatcher.getInstance().dispatch(TOR_EVENT_STARTED, data);
|
|
213 | + }
|
|
214 | + try {
|
|
215 | + BufferedReader reader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
|
216 | + String line;
|
|
217 | + while ((line = reader.readLine()) != null) {
|
|
218 | + Log.i(TAG, "[tor-" + mHandle + "] " + line);
|
|
219 | + }
|
|
220 | + } catch (IOException e) {
|
|
221 | + Log.e(TAG, "Failed to read stdout of the tor process " + mHandle, e);
|
|
222 | + }
|
|
223 | + Log.d(TAG, "Exiting the stdout loop for process " + mHandle);
|
|
224 | + final GeckoBundle data = new GeckoBundle(2);
|
|
225 | + data.putString("handle", mHandle);
|
|
226 | + try {
|
|
227 | + data.putInt("status", mProcess.waitFor());
|
|
228 | + } catch (InterruptedException e) {
|
|
229 | + Log.e(TAG, "Failed to wait for the tor process " + mHandle, e);
|
|
230 | + data.putInt("status", 0xdeadbeef);
|
|
231 | + }
|
|
232 | + // FIXME: We usually don't reach this when the application is killed!
|
|
233 | + // So, we don't do our cleanup.
|
|
234 | + Log.i(TAG, "Tor process " + mHandle + " has exited.");
|
|
235 | + EventDispatcher.getInstance().dispatch(TOR_EVENT_EXITED, data);
|
|
236 | + }
|
|
237 | + |
|
238 | + private void cleanIpcDirectory() {
|
|
239 | + File directory = new File(TorIntegrationAndroid.this.mIpcDirectory);
|
|
240 | + if (!Files.isDirectory(directory.toPath())) {
|
|
241 | + if (!directory.mkdirs()) {
|
|
242 | + Log.e(TAG, "Failed to create the IPC directory.");
|
|
243 | + return;
|
|
244 | + }
|
|
245 | + try {
|
|
246 | + Set<PosixFilePermission> chmod = PosixFilePermissions.fromString("rwx------");
|
|
247 | + Files.setPosixFilePermissions(directory.toPath(), chmod);
|
|
248 | + } catch (IOException e) {
|
|
249 | + Log.e(TAG, "Could not set the permissions to the IPC directory.", e);
|
|
250 | + }
|
|
251 | + return;
|
|
252 | + }
|
|
253 | + // We assume we do not have child directories, only files
|
|
254 | + File[] maybeFiles = directory.listFiles();
|
|
255 | + if (maybeFiles != null) {
|
|
256 | + for (File file : maybeFiles) {
|
|
257 | + if (!file.delete()) {
|
|
258 | + Log.d(TAG, "Could not delete " + file);
|
|
259 | + }
|
|
260 | + }
|
|
261 | + }
|
|
262 | + }
|
|
263 | + |
|
264 | + private void copyAndUseConfigFile(String option, String name, ArrayList<String> args) throws IOException {
|
|
265 | + final Path path = Paths.get(mCacheDir.toFile().getAbsolutePath(), name);
|
|
266 | + if (!mCopiedConfigFiles || !path.toFile().exists()) {
|
|
267 | + final Context context = GeckoAppShell.getApplicationContext();
|
|
268 | + final InputStream in = context.getAssets().open("common/" + name);
|
|
269 | + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
|
|
270 | + in.close();
|
|
271 | + }
|
|
272 | + args.add(option);
|
|
273 | + args.add(path.toString());
|
|
274 | + }
|
|
275 | + |
|
276 | + public void shutdown() {
|
|
277 | + if (mProcess != null && mProcess.isAlive()) {
|
|
278 | + mProcess.destroy();
|
|
279 | + }
|
|
280 | + if (isAlive()) {
|
|
281 | + try {
|
|
282 | + join();
|
|
283 | + } catch (InterruptedException e) {
|
|
284 | + Log.e(TAG, "Cannot join the thread for tor process " + mHandle + ", possibly already terminated", e);
|
|
285 | + }
|
|
286 | + }
|
|
287 | + }
|
|
288 | + |
|
289 | + public String getHandle() {
|
|
290 | + return mHandle;
|
|
291 | + }
|
|
292 | + }
|
|
293 | + |
|
294 | + private synchronized void startMeek(final GeckoBundle message, final EventCallback callback) {
|
|
295 | + if (callback == null) {
|
|
296 | + Log.e(TAG, "Tried to start Meek without a callback.");
|
|
297 | + return;
|
|
298 | + }
|
|
299 | + mMeekCounter++;
|
|
300 | + mMeeks.put(new Integer(mMeekCounter), new MeekTransport(callback, mMeekCounter));
|
|
301 | + }
|
|
302 | + |
|
303 | + private synchronized void stopMeek(final GeckoBundle message, final EventCallback callback) {
|
|
304 | + final Integer key = message.getInteger("id");
|
|
305 | + final MeekTransport meek = mMeeks.remove(key);
|
|
306 | + if (meek != null) {
|
|
307 | + meek.shutdown();
|
|
308 | + }
|
|
309 | + if (callback != null) {
|
|
310 | + callback.sendSuccess(null);
|
|
311 | + }
|
|
312 | + }
|
|
313 | + |
|
314 | + private class MeekTransport extends Thread {
|
|
315 | + private static final String TRANSPORT = "meek_lite";
|
|
316 | + private Process mProcess;
|
|
317 | + private final EventCallback mCallback;
|
|
318 | + private final int mId;
|
|
319 | + |
|
320 | + MeekTransport(final EventCallback callback, int id) {
|
|
321 | + setName("meek-" + id);
|
|
322 | + final ProcessBuilder builder = new ProcessBuilder(mLibraryDir + "/libObfs4proxy.so");
|
|
323 | + {
|
|
324 | + final Map<String, String> env = builder.environment();
|
|
325 | + env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1");
|
|
326 | + env.put("TOR_PT_STATE_LOCATION", mDataDir + "/pt_state");
|
|
327 | + env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1");
|
|
328 | + env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT);
|
|
329 | + }
|
|
330 | + mCallback = callback;
|
|
331 | + mId = id;
|
|
332 | + try {
|
|
333 | + // We expect this process to be short-lived, therefore we do not bother with
|
|
334 | + // implementing this as a service.
|
|
335 | + mProcess = builder.start();
|
|
336 | + } catch (IOException e) {
|
|
337 | + Log.e(TAG, "Cannot start the PT", e);
|
|
338 | + callback.sendError(e.getMessage());
|
|
339 | + return;
|
|
340 | + }
|
|
341 | + start();
|
|
342 | + }
|
|
343 | + |
|
344 | + /**
|
|
345 | + * Parse the standard output of the pluggable transport to find the hostname and port it is
|
|
346 | + * listening on.
|
|
347 | + * <p>
|
|
348 | + * See also the specs for the IPC protocol at https://spec.torproject.org/pt-spec/ipc.html.
|
|
349 | + */
|
|
350 | + @Override
|
|
351 | + public void run() {
|
|
352 | + final String PROTOCOL_VERSION = "1";
|
|
353 | + String hostname = "";
|
|
354 | + boolean valid = false;
|
|
355 | + int port = 0;
|
|
356 | + String error = "Did not see a CMETHOD";
|
|
357 | + try {
|
|
358 | + InputStreamReader isr = new InputStreamReader(mProcess.getInputStream());
|
|
359 | + BufferedReader reader = new BufferedReader(isr);
|
|
360 | + String line;
|
|
361 | + while ((line = reader.readLine()) != null) {
|
|
362 | + line = line.trim();
|
|
363 | + Log.d(TAG, "Meek line: " + line);
|
|
364 | + // Split produces always at least one item
|
|
365 | + String[] tokens = line.split(" ");
|
|
366 | + if ("VERSION".equals(tokens[0]) && (tokens.length != 2 || !PROTOCOL_VERSION.equals(tokens[1]))) {
|
|
367 | + error = "Bad version: " + line;
|
|
368 | + break;
|
|
369 | + }
|
|
370 | + if ("CMETHOD".equals(tokens[0])) {
|
|
371 | + if (tokens.length != 4) {
|
|
372 | + error = "Bad number of tokens in CMETHOD: " + line;
|
|
373 | + break;
|
|
374 | + }
|
|
375 | + if (!tokens[1].equals(TRANSPORT)) {
|
|
376 | + error = "Unexpected transport: " + tokens[1];
|
|
377 | + break;
|
|
378 | + }
|
|
379 | + if (!"socks5".equals(tokens[2])) {
|
|
380 | + error = "Unexpected proxy type: " + tokens[2];
|
|
381 | + break;
|
|
382 | + }
|
|
383 | + String[] addr = tokens[3].split(":");
|
|
384 | + if (addr.length != 2) {
|
|
385 | + error = "Invalid address";
|
|
386 | + break;
|
|
387 | + }
|
|
388 | + hostname = addr[0];
|
|
389 | + try {
|
|
390 | + port = Integer.parseInt(addr[1]);
|
|
391 | + } catch (NumberFormatException e) {
|
|
392 | + error = "Invalid port: " + e.getMessage();
|
|
393 | + break;
|
|
394 | + }
|
|
395 | + if (port < 1 || port > 65535) {
|
|
396 | + error = "Invalid port: out of bounds";
|
|
397 | + break;
|
|
398 | + }
|
|
399 | + valid = true;
|
|
400 | + break;
|
|
401 | + }
|
|
402 | + if (tokens[0].endsWith("-ERROR")) {
|
|
403 | + error = "Seen an error: " + line;
|
|
404 | + break;
|
|
405 | + }
|
|
406 | + }
|
|
407 | + } catch (Exception e) {
|
|
408 | + error = e.getMessage();
|
|
409 | + }
|
|
410 | + if (valid) {
|
|
411 | + Log.d(TAG, "Setup a meek transport " + mId + ": " + hostname + ":" + port);
|
|
412 | + final GeckoBundle bundle = new GeckoBundle(3);
|
|
413 | + bundle.putInt("id", mId);
|
|
414 | + bundle.putString("address", hostname);
|
|
415 | + bundle.putInt("port", port);
|
|
416 | + mCallback.sendSuccess(bundle);
|
|
417 | + } else {
|
|
418 | + Log.e(TAG, "Failed to get a usable config from the PT: " + error);
|
|
419 | + mCallback.sendError(error);
|
|
420 | + }
|
|
421 | + }
|
|
422 | + |
|
423 | + void shutdown() {
|
|
424 | + if (mProcess != null) {
|
|
425 | + mProcess.destroy();
|
|
426 | + mProcess = null;
|
|
427 | + }
|
|
428 | + try {
|
|
429 | + join();
|
|
430 | + } catch (InterruptedException e) {
|
|
431 | + Log.e(TAG, "Could not join the meek thread", e);
|
|
432 | + }
|
|
433 | + }
|
|
434 | + }
|
|
435 | +} |
... | ... | @@ -4,6 +4,12 @@ |
4 | 4 | |
5 | 5 | import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
|
6 | 6 | |
7 | +const lazy = {};
|
|
8 | + |
|
9 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
10 | + TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
|
|
11 | +});
|
|
12 | + |
|
7 | 13 | export class GeckoViewContent extends GeckoViewModule {
|
8 | 14 | onInit() {
|
9 | 15 | this.registerListener([
|
... | ... | @@ -22,6 +28,7 @@ export class GeckoViewContent extends GeckoViewModule { |
22 | 28 | "GeckoView:UpdateInitData",
|
23 | 29 | "GeckoView:ZoomToInput",
|
24 | 30 | "GeckoView:IsPdfJs",
|
31 | + "GeckoView:GetTorCircuit",
|
|
25 | 32 | ]);
|
26 | 33 | }
|
27 | 34 | |
... | ... | @@ -190,6 +197,21 @@ export class GeckoViewContent extends GeckoViewModule { |
190 | 197 | case "GeckoView:HasCookieBannerRuleForBrowsingContextTree":
|
191 | 198 | this._hasCookieBannerRuleForBrowsingContextTree(aCallback);
|
192 | 199 | break;
|
200 | + case "GeckoView:GetTorCircuit":
|
|
201 | + if (this.browser && aCallback) {
|
|
202 | + const domain = lazy.TorDomainIsolator.getDomainForBrowser(
|
|
203 | + this.browser
|
|
204 | + );
|
|
205 | + const nodes = lazy.TorDomainIsolator.getCircuit(
|
|
206 | + this.browser,
|
|
207 | + domain,
|
|
208 | + this.browser.contentPrincipal.originAttributes.userContextId
|
|
209 | + );
|
|
210 | + aCallback?.onSuccess({ domain, nodes });
|
|
211 | + } else {
|
|
212 | + aCallback?.onSuccess(null);
|
|
213 | + }
|
|
214 | + break;
|
|
193 | 215 | }
|
194 | 216 | }
|
195 | 217 |
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/. */
|
|
4 | + |
|
5 | +import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
|
|
6 | + |
|
7 | +const lazy = {};
|
|
8 | + |
|
9 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
10 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
11 | +});
|
|
12 | + |
|
13 | +const logger = new ConsoleAPI({
|
|
14 | + maxLogLevel: "info",
|
|
15 | + prefix: "TorProcessAndroid",
|
|
16 | +});
|
|
17 | + |
|
18 | +const TorOutgoingEvents = Object.freeze({
|
|
19 | + start: "GeckoView:Tor:StartTor",
|
|
20 | + stop: "GeckoView:Tor:StopTor",
|
|
21 | +});
|
|
22 | + |
|
23 | +// The events we will listen to
|
|
24 | +const TorIncomingEvents = Object.freeze({
|
|
25 | + started: "GeckoView:Tor:TorStarted",
|
|
26 | + startFailed: "GeckoView:Tor:TorStartFailed",
|
|
27 | + exited: "GeckoView:Tor:TorExited",
|
|
28 | +});
|
|
29 | + |
|
30 | +export class TorProcessAndroid {
|
|
31 | + /**
|
|
32 | + * The handle the Java counterpart uses to refer to the process we started.
|
|
33 | + * We use it to filter the exit events and make sure they refer to the daemon
|
|
34 | + * we are interested in.
|
|
35 | + */
|
|
36 | + #processHandle = null;
|
|
37 | + /**
|
|
38 | + * The promise resolver we call when the Java counterpart sends the event that
|
|
39 | + * tor has started.
|
|
40 | + */
|
|
41 | + #startResolve = null;
|
|
42 | + /**
|
|
43 | + * The promise resolver we call when the Java counterpart sends the event that
|
|
44 | + * it failed to start tor.
|
|
45 | + */
|
|
46 | + #startReject = null;
|
|
47 | + |
|
48 | + onExit = () => {};
|
|
49 | + |
|
50 | + get isRunning() {
|
|
51 | + return !!this.#processHandle;
|
|
52 | + }
|
|
53 | + |
|
54 | + async start() {
|
|
55 | + // Generate the handle on the JS side so that it's ready in case it takes
|
|
56 | + // less to start the process than to propagate the success.
|
|
57 | + this.#processHandle = crypto.randomUUID();
|
|
58 | + logger.info(`Starting new process with handle ${this.#processHandle}`);
|
|
59 | + // Let's declare it immediately, so that the Java side can do its stuff in
|
|
60 | + // an async manner and we avoid possible race conditions (at most we await
|
|
61 | + // an already resolved/rejected promise.
|
|
62 | + const startEventPromise = new Promise((resolve, reject) => {
|
|
63 | + this.#startResolve = resolve;
|
|
64 | + this.#startReject = reject;
|
|
65 | + });
|
|
66 | + lazy.EventDispatcher.instance.registerListener(
|
|
67 | + this,
|
|
68 | + Object.values(TorIncomingEvents)
|
|
69 | + );
|
|
70 | + let config;
|
|
71 | + try {
|
|
72 | + config = await lazy.EventDispatcher.instance.sendRequestForResult({
|
|
73 | + type: TorOutgoingEvents.start,
|
|
74 | + handle: this.#processHandle,
|
|
75 | + });
|
|
76 | + logger.debug("Sent the start event.");
|
|
77 | + } catch (e) {
|
|
78 | + this.forget();
|
|
79 | + throw e;
|
|
80 | + }
|
|
81 | + await startEventPromise;
|
|
82 | + return config;
|
|
83 | + }
|
|
84 | + |
|
85 | + forget() {
|
|
86 | + // Processes usually exit when we close the control port connection to them.
|
|
87 | + logger.trace(`Forgetting process ${this.#processHandle}`);
|
|
88 | + lazy.EventDispatcher.instance.sendRequestForResult({
|
|
89 | + type: TorOutgoingEvents.stop,
|
|
90 | + handle: this.#processHandle,
|
|
91 | + });
|
|
92 | + logger.debug("Sent the start event.");
|
|
93 | + this.#processHandle = null;
|
|
94 | + lazy.EventDispatcher.instance.unregisterListener(
|
|
95 | + this,
|
|
96 | + Object.values(TorIncomingEvents)
|
|
97 | + );
|
|
98 | + }
|
|
99 | + |
|
100 | + onEvent(event, data, callback) {
|
|
101 | + if (data?.handle !== this.#processHandle) {
|
|
102 | + logger.debug(`Ignoring event ${event} with another handle`, data);
|
|
103 | + return;
|
|
104 | + }
|
|
105 | + logger.info(`Received an event ${event}`, data);
|
|
106 | + switch (event) {
|
|
107 | + case TorIncomingEvents.started:
|
|
108 | + this.#startResolve();
|
|
109 | + break;
|
|
110 | + case TorIncomingEvents.startFailed:
|
|
111 | + this.#startReject(new Error(data.error));
|
|
112 | + break;
|
|
113 | + case TorIncomingEvents.exited:
|
|
114 | + this.forget();
|
|
115 | + if (this.#startReject !== null) {
|
|
116 | + this.#startReject();
|
|
117 | + }
|
|
118 | + this.onExit(data.status);
|
|
119 | + break;
|
|
120 | + }
|
|
121 | + }
|
|
122 | +} |
... | ... | @@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, { |
14 | 14 | FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
|
15 | 15 | TorController: "resource://gre/modules/TorControlPort.sys.mjs",
|
16 | 16 | TorProcess: "resource://gre/modules/TorProcess.sys.mjs",
|
17 | + TorProcessAndroid: "resource://gre/modules/TorProcessAndroid.sys.mjs",
|
|
17 | 18 | });
|
18 | 19 | |
19 | 20 | const logger = new ConsoleAPI({
|
... | ... | @@ -182,8 +183,12 @@ export class TorProvider { |
182 | 183 | logger.debug("Initializing the Tor provider.");
|
183 | 184 | |
184 | 185 | // These settings might be customized in the following steps.
|
185 | - this.#socksSettings = TorLauncherUtil.getPreferredSocksConfiguration();
|
|
186 | - logger.debug("Requested SOCKS configuration", this.#socksSettings);
|
|
186 | + if (TorLauncherUtil.isAndroid) {
|
|
187 | + this.#socksSettings = { transproxy: false };
|
|
188 | + } else {
|
|
189 | + this.#socksSettings = TorLauncherUtil.getPreferredSocksConfiguration();
|
|
190 | + logger.debug("Requested SOCKS configuration", this.#socksSettings);
|
|
191 | + }
|
|
187 | 192 | |
188 | 193 | try {
|
189 | 194 | await this.#setControlPortConfiguration();
|
... | ... | @@ -490,10 +495,14 @@ export class TorProvider { |
490 | 495 | return;
|
491 | 496 | }
|
492 | 497 | |
493 | - this.#torProcess = new lazy.TorProcess(
|
|
494 | - this.#controlPortSettings,
|
|
495 | - this.#socksSettings
|
|
496 | - );
|
|
498 | + if (TorLauncherUtil.isAndroid) {
|
|
499 | + this.#torProcess = new lazy.TorProcessAndroid();
|
|
500 | + } else {
|
|
501 | + this.#torProcess = new lazy.TorProcess(
|
|
502 | + this.#controlPortSettings,
|
|
503 | + this.#socksSettings
|
|
504 | + );
|
|
505 | + }
|
|
497 | 506 | // Use a closure instead of bind because we reassign #cancelConnection.
|
498 | 507 | // Also, we now assign an exit handler that cancels the first connection,
|
499 | 508 | // so that a sudden exit before the first connection is completed might
|
... | ... | @@ -507,7 +516,17 @@ export class TorProvider { |
507 | 516 | };
|
508 | 517 | |
509 | 518 | logger.debug("Trying to start the tor process.");
|
510 | - await this.#torProcess.start();
|
|
519 | + const res = await this.#torProcess.start();
|
|
520 | + if (TorLauncherUtil.isAndroid) {
|
|
521 | + this.#controlPortSettings = {
|
|
522 | + ipcFile: new lazy.FileUtils.File(res.controlPortPath),
|
|
523 | + cookieFilePath: res.cookieFilePath,
|
|
524 | + };
|
|
525 | + this.#socksSettings = {
|
|
526 | + transproxy: false,
|
|
527 | + ipcFile: new lazy.FileUtils.File(res.socksPath),
|
|
528 | + };
|
|
529 | + }
|
|
511 | 530 | logger.info("Started a tor process");
|
512 | 531 | }
|
513 | 532 | |
... | ... | @@ -521,6 +540,11 @@ export class TorProvider { |
521 | 540 | logger.debug("Reading the control port configuration");
|
522 | 541 | const settings = {};
|
523 | 542 | |
543 | + if (TorLauncherUtil.isAndroid) {
|
|
544 | + // We will populate the settings after having started the daemon.
|
|
545 | + return;
|
|
546 | + }
|
|
547 | + |
|
524 | 548 | const isWindows = Services.appinfo.OS === "WINNT";
|
525 | 549 | // Determine how Tor Launcher will connect to the Tor control port.
|
526 | 550 | // Environment variables get top priority followed by preferences.
|
... | ... | @@ -5,6 +5,7 @@ EXTRA_JS_MODULES += [ |
5 | 5 | "TorLauncherUtil.sys.mjs",
|
6 | 6 | "TorParsers.sys.mjs",
|
7 | 7 | "TorProcess.sys.mjs",
|
8 | + "TorProcessAndroid.sys.mjs",
|
|
8 | 9 | "TorProvider.sys.mjs",
|
9 | 10 | "TorProviderBuilder.sys.mjs",
|
10 | 11 | "TorStartupService.sys.mjs",
|
... | ... | @@ -10,6 +10,7 @@ import { |
10 | 10 | const lazy = {};
|
11 | 11 | |
12 | 12 | ChromeUtils.defineESModuleGetters(lazy, {
|
13 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
13 | 14 | Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
14 | 15 | TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
15 | 16 | TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
... | ... | @@ -290,6 +291,48 @@ class MeekTransport { |
290 | 291 | }
|
291 | 292 | }
|
292 | 293 | |
294 | +class MeekTransportAndroid {
|
|
295 | + // These members are used by consumers to setup the proxy to do requests over
|
|
296 | + // meek. They are passed to newProxyInfoWithAuth.
|
|
297 | + proxyType = null;
|
|
298 | + proxyAddress = null;
|
|
299 | + proxyPort = 0;
|
|
300 | + proxyUsername = null;
|
|
301 | + proxyPassword = null;
|
|
302 | + |
|
303 | + #id = 0;
|
|
304 | + |
|
305 | + async init() {
|
|
306 | + // ensure we haven't already init'd
|
|
307 | + if (this.#id) {
|
|
308 | + throw new Error("MeekTransport: Already initialized");
|
|
309 | + }
|
|
310 | + const details = await lazy.EventDispatcher.instance.sendRequestForResult({
|
|
311 | + type: "GeckoView:Tor:StartMeek",
|
|
312 | + });
|
|
313 | + this.#id = details.id;
|
|
314 | + this.proxyType = "socks";
|
|
315 | + this.proxyAddress = details.address;
|
|
316 | + this.proxyPort = details.port;
|
|
317 | + [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
|
|
318 | + this.proxyType
|
|
319 | + );
|
|
320 | + }
|
|
321 | + |
|
322 | + async uninit() {
|
|
323 | + lazy.EventDispatcher.instance.sendRequest({
|
|
324 | + type: "GeckoView:Tor:StopMeek",
|
|
325 | + id: this.#id,
|
|
326 | + });
|
|
327 | + this.#id = 0;
|
|
328 | + this.proxyType = null;
|
|
329 | + this.proxyAddress = null;
|
|
330 | + this.proxyPort = 0;
|
|
331 | + this.proxyUsername = null;
|
|
332 | + this.proxyPassword = null;
|
|
333 | + }
|
|
334 | +}
|
|
335 | + |
|
293 | 336 | //
|
294 | 337 | // Callback object with a cached promise for the returned Moat data
|
295 | 338 | //
|
... | ... | @@ -407,7 +450,10 @@ export class MoatRPC { |
407 | 450 | throw new Error("MoatRPC: Already initialized");
|
408 | 451 | }
|
409 | 452 | |
410 | - const meekTransport = new MeekTransport();
|
|
453 | + const meekTransport =
|
|
454 | + Services.appinfo.OS === "Android"
|
|
455 | + ? new MeekTransportAndroid()
|
|
456 | + : new MeekTransport();
|
|
411 | 457 | await meekTransport.init();
|
412 | 458 | this.#meekTransport = meekTransport;
|
413 | 459 | this.#inited = true;
|
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/. */
|
|
4 | + |
|
5 | +import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
|
|
6 | + |
|
7 | +const lazy = {};
|
|
8 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
9 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
10 | + TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
|
|
11 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
12 | + TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
|
|
13 | +});
|
|
14 | + |
|
15 | +const Prefs = Object.freeze({
|
|
16 | + useNewBootstrap: "browser.tor_android.use_new_bootstrap",
|
|
17 | + logLevel: "browser.tor_android.log_level",
|
|
18 | +});
|
|
19 | + |
|
20 | +const logger = new ConsoleAPI({
|
|
21 | + maxLogLevel: "info",
|
|
22 | + maxLogLevelPref: Prefs.logLevel,
|
|
23 | + prefix: "TorAndroidIntegration",
|
|
24 | +});
|
|
25 | + |
|
26 | +const ListenedEvents = Object.freeze({
|
|
27 | + settingsGet: "GeckoView:Tor:SettingsGet",
|
|
28 | + settingsSet: "GeckoView:Tor:SettingsSet",
|
|
29 | + settingsApply: "GeckoView:Tor:SettingsApply",
|
|
30 | + settingsSave: "GeckoView:Tor:SettingsSave",
|
|
31 | +});
|
|
32 | + |
|
33 | +class TorAndroidIntegrationImpl {
|
|
34 | + #initialized = false;
|
|
35 | + |
|
36 | + init() {
|
|
37 | + lazy.EventDispatcher.instance.registerListener(
|
|
38 | + this,
|
|
39 | + Object.values(ListenedEvents)
|
|
40 | + );
|
|
41 | + |
|
42 | + this.#bootstrapMethodReset();
|
|
43 | + Services.prefs.addObserver(Prefs.useNewBootstrap, this);
|
|
44 | + }
|
|
45 | + |
|
46 | + async #initNewBootstrap() {
|
|
47 | + if (this.#initialized) {
|
|
48 | + return;
|
|
49 | + }
|
|
50 | + this.#initialized = true;
|
|
51 | + |
|
52 | + lazy.TorProviderBuilder.init().finally(() => {
|
|
53 | + lazy.TorProviderBuilder.firstWindowLoaded();
|
|
54 | + });
|
|
55 | + try {
|
|
56 | + await lazy.TorSettings.init();
|
|
57 | + await lazy.TorConnect.init();
|
|
58 | + } catch (e) {
|
|
59 | + logger.error("Cannot initialize TorSettings or TorConnect", e);
|
|
60 | + }
|
|
61 | + }
|
|
62 | + |
|
63 | + observe(subj, topic, data) {
|
|
64 | + switch (topic) {
|
|
65 | + case "nsPref:changed":
|
|
66 | + if (data === Prefs.useNewBootstrap) {
|
|
67 | + this.#bootstrapMethodReset();
|
|
68 | + }
|
|
69 | + break;
|
|
70 | + }
|
|
71 | + }
|
|
72 | + |
|
73 | + async onEvent(event, data, callback) {
|
|
74 | + logger.debug(`Received event ${event}`, data);
|
|
75 | + try {
|
|
76 | + switch (event) {
|
|
77 | + case settingsGet:
|
|
78 | + callback?.onSuccess(lazy.TorSettings.getSettings());
|
|
79 | + return;
|
|
80 | + case settingsSet:
|
|
81 | + // This does not throw, so we do not have any way to report the error!
|
|
82 | + lazy.TorSettings.setSettings(data);
|
|
83 | + break;
|
|
84 | + case settingsApply:
|
|
85 | + await lazy.TorSettings.applySettings();
|
|
86 | + break;
|
|
87 | + case settingsSave:
|
|
88 | + await lazy.TorSettings.saveSettings();
|
|
89 | + break;
|
|
90 | + }
|
|
91 | + callback?.onSuccess();
|
|
92 | + } catch (e) {
|
|
93 | + logger.error();
|
|
94 | + callback?.sendError(e);
|
|
95 | + }
|
|
96 | + }
|
|
97 | + |
|
98 | + #bootstrapMethodReset() {
|
|
99 | + if (Services.prefs.getBoolPref(Prefs.useNewBootstrap, false)) {
|
|
100 | + this.#initNewBootstrap();
|
|
101 | + } else {
|
|
102 | + Services.prefs.clearUserPref("network.proxy.socks");
|
|
103 | + Services.prefs.clearUserPref("network.proxy.socks_port");
|
|
104 | + }
|
|
105 | + }
|
|
106 | +}
|
|
107 | + |
|
108 | +export const TorAndroidIntegration = new TorAndroidIntegrationImpl(); |
... | ... | @@ -793,6 +793,9 @@ export const TorConnect = (() => { |
793 | 793 | |
794 | 794 | TorConnect._errorMessage = errorMessage;
|
795 | 795 | TorConnect._errorDetails = errorDetails;
|
796 | + console.error(
|
|
797 | + `[TorConnect] Entering error state (${errorMessage}, ${errorDetails})`
|
|
798 | + );
|
|
796 | 799 | |
797 | 800 | Services.obs.notifyObservers(
|
798 | 801 | { message: errorMessage, details: errorDetails },
|
... | ... | @@ -215,6 +215,7 @@ EXTRA_JS_MODULES += [ |
215 | 215 | "Sqlite.sys.mjs",
|
216 | 216 | "SubDialog.sys.mjs",
|
217 | 217 | "Timer.sys.mjs",
|
218 | + "TorAndroidIntegration.sys.mjs",
|
|
218 | 219 | "TorConnect.sys.mjs",
|
219 | 220 | "TorSettings.sys.mjs",
|
220 | 221 | "TorStrings.sys.mjs",
|