Pier Angelo Vendrame pushed to branch tor-browser-128.4.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits: 41bcfa1b by Pier Angelo Vendrame at 2024-10-29T19:05:48+01:00 fixup! Bug 40283: Workaround for the file upload bug
Lint with android-format.
- - - - - c8428799 by Pier Angelo Vendrame at 2024-10-29T19:05:53+01:00 fixup! Bug 40171: Make WebRequest and GeckoWebExecutor First-Party aware
Lint with android-format.
- - - - - cdcb77db by Pier Angelo Vendrame at 2024-10-29T19:05:53+01:00 fixup! Bug 42247: Android helpers for the TorProvider
Lint with android-format.
- - - - - ad0782df by Pier Angelo Vendrame at 2024-10-29T19:05:54+01:00 fixup! Bug 40597: Implement TorSettings module
Fix a couple of references to Moat in the generic DomainFrontedRequests module.
- - - - - 6b2120f9 by Pier Angelo Vendrame at 2024-10-29T19:05:54+01:00 fixup! Bug 40597: Implement TorSettings module
ch.asyncOpen is not a JS async function, so no need to await it. The async in the name means that the channel will call methods from a listener object that it takes as an argument when it receives data.
- - - - - 464b5a9b by Pier Angelo Vendrame at 2024-10-29T19:05:55+01:00 fixup! Bug 42247: Android helpers for the TorProvider
Bug 43232: Make the Android Meek transport easier to debug.
- - - - -
8 changed files:
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorSettings.java - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/androidlegacysettings/Prefs.java - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/androidlegacysettings/TorLegacyAndroidSettings.java - toolkit/modules/DomainFrontedRequests.sys.mjs - toolkit/modules/Moat.sys.mjs
Changes:
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java ===================================== @@ -6319,10 +6319,10 @@ public class GeckoSession { }
private static String normalizePath(String input) { - // For an unclear reason, Android media picker delivers file paths - // starting with double slash. Firefox performs path validation on - // all paths, and double slash is deemed invalid. - return input.startsWith("//") ? input.substring(1) : input; + // For an unclear reason, Android media picker delivers file paths + // starting with double slash. Firefox performs path validation on + // all paths, and double slash is deemed invalid. + return input.startsWith("//") ? input.substring(1) : input; }
private static String getFile(final @NonNull Context context, final @NonNull Uri uri) {
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java ===================================== @@ -9,671 +9,720 @@ package org.mozilla.geckoview; import android.content.Context; import android.os.AsyncTask; import android.util.Log; - -import androidx.annotation.AnyThread; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.InterruptedIOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Set; - import org.mozilla.gecko.EventDispatcher; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.util.BundleEventListener; import org.mozilla.gecko.util.EventCallback; import org.mozilla.gecko.util.GeckoBundle; - import org.mozilla.geckoview.androidlegacysettings.TorLegacyAndroidSettings;
public class TorIntegrationAndroid implements BundleEventListener { - private static final String TAG = "TorIntegrationAndroid"; - - // Events we listen to - private static final String EVENT_TOR_START = "GeckoView:Tor:StartTor"; - private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor"; - private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek"; - private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek"; - private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged"; - private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError"; - private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress"; - private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete"; - private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs"; - private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady"; - private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged"; - private static final String EVENT_SETTINGS_OPEN = "GeckoView:Tor:OpenSettings"; - - // Events we emit - private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet"; - private static final String EVENT_SETTINGS_SET = "GeckoView:Tor:SettingsSet"; - private static final String EVENT_SETTINGS_APPLY = "GeckoView:Tor:SettingsApply"; - private static final String EVENT_SETTINGS_SAVE = "GeckoView:Tor:SettingsSave"; - private static final String EVENT_BOOTSTRAP_BEGIN = "GeckoView:Tor:BootstrapBegin"; - private static final String EVENT_BOOTSTRAP_BEGIN_AUTO = "GeckoView:Tor:BootstrapBeginAuto"; - private static final String EVENT_BOOTSTRAP_CANCEL = "GeckoView:Tor:BootstrapCancel"; - private static final String EVENT_BOOTSTRAP_GET_STATE = "GeckoView:Tor:BootstrapGetState"; - - private static final String CONTROL_PORT_FILE = "/control-ipc"; - private static final String SOCKS_FILE = "/socks-ipc"; - private static final String COOKIE_AUTH_FILE = "/auth-file"; - - private final String mLibraryDir; - private final String mCacheDir; - private final String mIpcDirectory; - private final File mDataDir; - - private TorProcess mTorProcess = null; - /** - * The first time we run a Tor process in this session, we copy some configuration files to be - * sure we always have the latest version, but if we re-launch a tor process we do not need to - * copy them again. - */ - private boolean mCopiedConfigFiles = false; - /** - * Allow multiple proxies to be started, even though it might not actually happen. - * The key should be positive (also 0 is not allowed). - */ - private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>(); - private int mMeekCounter; - - /** - * mSettings is a Java-side copy of the authoritative settings in the JS code. - * It's useful to maintain as the UI may be fetching these options often and we don't watch each - * fetch to be a passthrough to JS with marshalling/unmarshalling each time. - */ - private TorSettings mSettings = null; - - /* package */ TorIntegrationAndroid(Context context) { - mLibraryDir = context.getApplicationInfo().nativeLibraryDir; - mCacheDir = context.getCacheDir().getAbsolutePath(); - mIpcDirectory = mCacheDir + "/tor-private"; - mDataDir = new File(context.getFilesDir(), "tor"); - registerListener(); + private static final String TAG = "TorIntegrationAndroid"; + + // Events we listen to + private static final String EVENT_TOR_START = "GeckoView:Tor:StartTor"; + private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor"; + private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek"; + private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek"; + private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged"; + private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError"; + private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress"; + private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete"; + private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs"; + private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady"; + private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged"; + private static final String EVENT_SETTINGS_OPEN = "GeckoView:Tor:OpenSettings"; + + // Events we emit + private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet"; + private static final String EVENT_SETTINGS_SET = "GeckoView:Tor:SettingsSet"; + private static final String EVENT_SETTINGS_APPLY = "GeckoView:Tor:SettingsApply"; + private static final String EVENT_SETTINGS_SAVE = "GeckoView:Tor:SettingsSave"; + private static final String EVENT_BOOTSTRAP_BEGIN = "GeckoView:Tor:BootstrapBegin"; + private static final String EVENT_BOOTSTRAP_BEGIN_AUTO = "GeckoView:Tor:BootstrapBeginAuto"; + private static final String EVENT_BOOTSTRAP_CANCEL = "GeckoView:Tor:BootstrapCancel"; + private static final String EVENT_BOOTSTRAP_GET_STATE = "GeckoView:Tor:BootstrapGetState"; + + private static final String CONTROL_PORT_FILE = "/control-ipc"; + private static final String SOCKS_FILE = "/socks-ipc"; + private static final String COOKIE_AUTH_FILE = "/auth-file"; + + private final String mLibraryDir; + private final String mCacheDir; + private final String mIpcDirectory; + private final File mDataDir; + + private TorProcess mTorProcess = null; + + /** + * The first time we run a Tor process in this session, we copy some configuration files to be + * sure we always have the latest version, but if we re-launch a tor process we do not need to + * copy them again. + */ + private boolean mCopiedConfigFiles = false; + + /** + * Allow multiple proxies to be started, even though it might not actually happen. The key should + * be positive (also 0 is not allowed). + */ + private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>(); + + private int mMeekCounter; + + /** + * mSettings is a Java-side copy of the authoritative settings in the JS code. It's useful to + * maintain as the UI may be fetching these options often and we don't watch each fetch to be a + * passthrough to JS with marshalling/unmarshalling each time. + */ + private TorSettings mSettings = null; + + /* package */ TorIntegrationAndroid(Context context) { + mLibraryDir = context.getApplicationInfo().nativeLibraryDir; + mCacheDir = context.getCacheDir().getAbsolutePath(); + mIpcDirectory = mCacheDir + "/tor-private"; + mDataDir = new File(context.getFilesDir(), "tor"); + registerListener(); + } + + /* package */ synchronized void shutdown() { + // FIXME: It seems this never gets called + if (mTorProcess != null) { + mTorProcess.shutdown(); + mTorProcess = null; } - - /* package */ synchronized void shutdown() { - // FIXME: It seems this never gets called - if (mTorProcess != null) { - mTorProcess.shutdown(); - mTorProcess = null; - } + } + + private void registerListener() { + EventDispatcher.getInstance() + .registerUiThreadListener( + this, + EVENT_TOR_START, + EVENT_MEEK_START, + EVENT_MEEK_STOP, + EVENT_SETTINGS_READY, + EVENT_SETTINGS_CHANGED, + EVENT_CONNECT_STATE_CHANGED, + EVENT_CONNECT_ERROR, + EVENT_BOOTSTRAP_PROGRESS, + EVENT_BOOTSTRAP_COMPLETE, + EVENT_TOR_LOGS, + EVENT_SETTINGS_OPEN); + } + + @Override // BundleEventListener + public synchronized void handleMessage( + final String event, final GeckoBundle message, final EventCallback callback) { + if (EVENT_TOR_START.equals(event)) { + startDaemon(message, callback); + } else if (EVENT_TOR_STOP.equals(event)) { + stopDaemon(message, callback); + } else if (EVENT_MEEK_START.equals(event)) { + startMeek(message, callback); + } else if (EVENT_MEEK_STOP.equals(event)) { + stopMeek(message, callback); + } else if (EVENT_SETTINGS_READY.equals(event)) { + try { + new SettingsLoader().execute(message); + } catch (Exception e) { + Log.e(TAG, "SettingsLoader error: " + e.toString()); + } + } else if (EVENT_SETTINGS_CHANGED.equals(event)) { + GeckoBundle newSettings = message.getBundle("settings"); + if (newSettings != null) { + // TODO: Should we notify listeners? + mSettings = new TorSettings(newSettings); + } else { + Log.w(TAG, "Ignoring a settings changed event that did not have the new settings."); + } + } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) { + String state = message.getString("state"); + for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { + listener.onBootstrapStateChange(state); + } + } else if (EVENT_CONNECT_ERROR.equals(event)) { + String code = message.getString("code"); + String msg = message.getString("message"); + String phase = message.getString("phase"); + String reason = message.getString("reason"); + for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { + listener.onBootstrapError(code, msg, phase, reason); + } + } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) { + double progress = message.getDouble("progress"); + boolean hasWarnings = message.getBoolean("hasWarnings"); + for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { + listener.onBootstrapProgress(progress, hasWarnings); + } + } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) { + for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { + listener.onBootstrapComplete(); + } + } else if (EVENT_TOR_LOGS.equals(event)) { + String msg = message.getString("message"); + String type = message.getString("logType"); + for (TorLogListener listener : mLogListeners) { + listener.onLog(type, msg); + } + } else if (EVENT_SETTINGS_OPEN.equals(event)) { + for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { + listener.onSettingsRequested(); + } } - - private void registerListener() { - EventDispatcher.getInstance() - .registerUiThreadListener( - this, - EVENT_TOR_START, - EVENT_MEEK_START, - EVENT_MEEK_STOP, - EVENT_SETTINGS_READY, - EVENT_SETTINGS_CHANGED, - EVENT_CONNECT_STATE_CHANGED, - EVENT_CONNECT_ERROR, - EVENT_BOOTSTRAP_PROGRESS, - EVENT_BOOTSTRAP_COMPLETE, - EVENT_TOR_LOGS, - EVENT_SETTINGS_OPEN); + } + + private class SettingsLoader extends AsyncTask<GeckoBundle, Void, TorSettings> { + protected TorSettings doInBackground(GeckoBundle... messages) { + GeckoBundle message = messages[0]; + TorSettings settings; + if (TorLegacyAndroidSettings.unmigrated()) { + settings = TorLegacyAndroidSettings.loadTorSettings(); + } else { + GeckoBundle bundle = message.getBundle("settings"); + settings = new TorSettings(bundle); + } + return settings; }
- @Override // BundleEventListener - public synchronized void handleMessage( - final String event, final GeckoBundle message, final EventCallback callback) { - if (EVENT_TOR_START.equals(event)) { - startDaemon(message, callback); - } else if (EVENT_TOR_STOP.equals(event)) { - stopDaemon(message, callback); - } else if (EVENT_MEEK_START.equals(event)) { - startMeek(message, callback); - } else if (EVENT_MEEK_STOP.equals(event)) { - stopMeek(message, callback); - } else if (EVENT_SETTINGS_READY.equals(event)) { - try { - new SettingsLoader().execute(message); - } catch(Exception e) { - Log.e(TAG, "SettingsLoader error: "+ e.toString()); - } - } else if (EVENT_SETTINGS_CHANGED.equals(event)) { - GeckoBundle newSettings = message.getBundle("settings"); - if (newSettings != null) { - // TODO: Should we notify listeners? - mSettings = new TorSettings(newSettings); - } else { - Log.w(TAG, "Ignoring a settings changed event that did not have the new settings."); - } - } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) { - String state = message.getString("state"); - for (BootstrapStateChangeListener listener: mBootstrapStateListeners) { - listener.onBootstrapStateChange(state); - } - } else if (EVENT_CONNECT_ERROR.equals(event)) { - String code = message.getString("code"); - String msg = message.getString("message"); - String phase = message.getString("phase"); - String reason = message.getString("reason"); - for (BootstrapStateChangeListener listener: mBootstrapStateListeners) { - listener.onBootstrapError(code, msg, phase, reason); - } - } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) { - double progress = message.getDouble("progress"); - boolean hasWarnings = message.getBoolean("hasWarnings"); - for (BootstrapStateChangeListener listener: mBootstrapStateListeners) { - listener.onBootstrapProgress(progress, hasWarnings); - } - } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) { - for (BootstrapStateChangeListener listener: mBootstrapStateListeners) { - listener.onBootstrapComplete(); - } - } else if (EVENT_TOR_LOGS.equals(event)) { - String msg = message.getString("message"); - String type = message.getString("logType"); - for (TorLogListener listener: mLogListeners) { - listener.onLog(type, msg); - } - } else if (EVENT_SETTINGS_OPEN.equals(event)) { - for (BootstrapStateChangeListener listener: mBootstrapStateListeners) { - listener.onSettingsRequested(); - } - } + @Override + protected void onPostExecute(TorSettings torSettings) { + mSettings = torSettings; + if (TorLegacyAndroidSettings.unmigrated()) { + setSettings(mSettings, true, true); + TorLegacyAndroidSettings.setMigrated(); + } + } + } + + private synchronized void startDaemon(final GeckoBundle message, final EventCallback callback) { + // Let JS generate this to possibly reduce the chance of race conditions. + String handle = message.getString("handle", ""); + if (handle.isEmpty()) { + Log.e(TAG, "Requested to start a tor process without a handle."); + callback.sendError("Expected a handle for the new process."); + return; } + Log.d(TAG, "Starting the a tor process with handle " + handle);
- private class SettingsLoader extends AsyncTask<GeckoBundle, Void, TorSettings> { - protected TorSettings doInBackground(GeckoBundle... messages) { - GeckoBundle message = messages[0]; - TorSettings settings; - if (TorLegacyAndroidSettings.unmigrated()) { - settings = TorLegacyAndroidSettings.loadTorSettings(); - } else { - GeckoBundle bundle = message.getBundle("settings"); - settings = new TorSettings(bundle); - } - return settings; - } + TorProcess previousProcess = mTorProcess; + if (previousProcess != null) { + Log.w(TAG, "We still have a running process: " + previousProcess.getHandle()); + } + mTorProcess = new TorProcess(handle); + + GeckoBundle bundle = new GeckoBundle(3); + bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE); + bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE); + bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE); + callback.sendSuccess(bundle); + } + + private synchronized void stopDaemon(final GeckoBundle message, final EventCallback callback) { + if (mTorProcess == null) { + if (callback != null) { + callback.sendSuccess(null); + } + return; + } + String handle = message.getString("handle", ""); + if (!mTorProcess.getHandle().equals(handle)) { + GeckoBundle bundle = new GeckoBundle(1); + bundle.putString( + "error", "The requested process has not been found. It might have already been stopped."); + callback.sendError(bundle); + return; + } + mTorProcess.shutdown(); + mTorProcess = null; + callback.sendSuccess(null); + } + + class TorProcess extends Thread { + private static final String EVENT_TOR_STARTED = "GeckoView:Tor:TorStarted"; + private static final String EVENT_TOR_START_FAILED = "GeckoView:Tor:TorStartFailed"; + private static final String EVENT_TOR_EXITED = "GeckoView:Tor:TorExited"; + private final String mHandle; + private Process mProcess = null; + + TorProcess(String handle) { + mHandle = handle; + setName("tor-process-" + handle); + start(); + }
- @Override - protected void onPostExecute(TorSettings torSettings) { - mSettings = torSettings; - if (TorLegacyAndroidSettings.unmigrated()) { - setSettings(mSettings, true, true); - TorLegacyAndroidSettings.setMigrated(); - } + @Override + public void run() { + cleanIpcDirectory(); + + final String ipcDir = TorIntegrationAndroid.this.mIpcDirectory; + final ArrayList<String> args = new ArrayList<>(); + args.add(mLibraryDir + "/libTor.so"); + args.add("DisableNetwork"); + args.add("1"); + args.add("+__ControlPort"); + args.add("unix:" + ipcDir + CONTROL_PORT_FILE); + args.add("+__SocksPort"); + args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth"); + args.add("CookieAuthentication"); + args.add("1"); + args.add("CookieAuthFile"); + args.add(ipcDir + COOKIE_AUTH_FILE); + args.add("DataDirectory"); + args.add(mDataDir.getAbsolutePath()); + boolean copied = true; + try { + copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args); + } catch (IOException e) { + Log.w( + TAG, "torrc-default cannot be created, pluggable transports will not be available", e); + copied = false; + } + // tor-browser#42607: For now we do not ship geoip databases, as we + // do not have the circuit display functionality and they allow us + // to save some space in the final APK. + /*try { + copyAndUseConfigFile("GeoIPFile", "geoip", args); + copyAndUseConfigFile("GeoIPv6File", "geoip6", args); + } catch (IOException e) { + Log.w(TAG, "GeoIP files cannot be created, this feature will not be available.", e); + copied = false; + }*/ + mCopiedConfigFiles = copied; + + Log.d(TAG, "Starting tor with the follwing args: " + args.toString()); + final ProcessBuilder builder = new ProcessBuilder(args); + builder.directory(new File(mLibraryDir)); + try { + mProcess = builder.start(); + } catch (IOException e) { + Log.e(TAG, "Cannot start tor " + mHandle, e); + final GeckoBundle data = new GeckoBundle(2); + data.putString("handle", mHandle); + data.putString("error", e.getMessage()); + EventDispatcher.getInstance().dispatch(EVENT_TOR_START_FAILED, data); + return; + } + Log.i(TAG, "Tor process " + mHandle + " started."); + { + final GeckoBundle data = new GeckoBundle(1); + data.putString("handle", mHandle); + EventDispatcher.getInstance().dispatch(EVENT_TOR_STARTED, data); + } + try { + BufferedReader reader = + new BufferedReader(new InputStreamReader(mProcess.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + Log.i(TAG, "[tor-" + mHandle + "] " + line); } + } catch (IOException e) { + Log.e(TAG, "Failed to read stdout of the tor process " + mHandle, e); + } + Log.d(TAG, "Exiting the stdout loop for process " + mHandle); + final GeckoBundle data = new GeckoBundle(2); + data.putString("handle", mHandle); + try { + data.putInt("status", mProcess.waitFor()); + } catch (InterruptedException e) { + Log.e(TAG, "Failed to wait for the tor process " + mHandle, e); + data.putInt("status", 0xdeadbeef); + } + // FIXME: We usually don't reach this when the application is killed! + // So, we don't do our cleanup. + Log.i(TAG, "Tor process " + mHandle + " has exited."); + EventDispatcher.getInstance().dispatch(EVENT_TOR_EXITED, data); }
- private synchronized void startDaemon(final GeckoBundle message, final EventCallback callback) { - // Let JS generate this to possibly reduce the chance of race conditions. - String handle = message.getString("handle", ""); - if (handle.isEmpty()) { - Log.e(TAG, "Requested to start a tor process without a handle."); - callback.sendError("Expected a handle for the new process."); - return; + private void cleanIpcDirectory() { + File directory = new File(TorIntegrationAndroid.this.mIpcDirectory); + if (!directory.isDirectory()) { + if (!directory.mkdirs()) { + Log.e(TAG, "Failed to create the IPC directory."); + return; } - Log.d(TAG, "Starting the a tor process with handle " + handle); - - TorProcess previousProcess = mTorProcess; - if (previousProcess != null) { - Log.w(TAG, "We still have a running process: " + previousProcess.getHandle()); + try { + // First remove the permissions for everybody... + directory.setReadable(false, false); + directory.setWritable(false, false); + directory.setExecutable(false, false); + // ... then add them back, but only for the owner. + directory.setReadable(true, true); + directory.setWritable(true, true); + directory.setExecutable(true, true); + } catch (SecurityException e) { + Log.e(TAG, "Could not set the permissions to the IPC directory.", e); } - mTorProcess = new TorProcess(handle); + return; + } + // We assume we do not have child directories, only files + File[] maybeFiles = directory.listFiles(); + if (maybeFiles != null) { + for (File file : maybeFiles) { + if (!file.delete()) { + Log.d(TAG, "Could not delete " + file); + } + } + } + }
- GeckoBundle bundle = new GeckoBundle(3); - bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE); - bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE); - bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE); - callback.sendSuccess(bundle); + private void copyAndUseConfigFile(String option, String name, ArrayList<String> args) + throws IOException { + File file = copyConfigFile(name); + args.add(option); + args.add(file.getAbsolutePath()); }
- private synchronized void stopDaemon(final GeckoBundle message, final EventCallback callback) { - if (mTorProcess == null) { - if (callback != null) { - callback.sendSuccess(null); - } - return; + private File copyConfigFile(String name) throws IOException { + final File file = new File(mCacheDir, name); + if (mCopiedConfigFiles && file.exists()) { + return file; + } + + final Context context = GeckoAppShell.getApplicationContext(); + final InputStream in = context.getAssets().open("common/" + name); + // Files.copy is API 26+, so use java.io and a loop for now. + FileOutputStream out = null; + try { + out = new FileOutputStream(file); + } catch (IOException e) { + in.close(); + throw e; + } + try { + byte buffer[] = new byte[4096]; + int read; + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); } - String handle = message.getString("handle", ""); - if (!mTorProcess.getHandle().equals(handle)) { - GeckoBundle bundle = new GeckoBundle(1); - bundle.putString("error", "The requested process has not been found. It might have already been stopped."); - callback.sendError(bundle); - return; + } finally { + try { + in.close(); + } catch (IOException e) { + Log.w(TAG, "Cannot close the input stream for " + name); } - mTorProcess.shutdown(); - mTorProcess = null; - callback.sendSuccess(null); + try { + out.close(); + } catch (IOException e) { + Log.w(TAG, "Cannot close the output stream for " + name); + } + } + return file; }
- class TorProcess extends Thread { - private static final String EVENT_TOR_STARTED = "GeckoView:Tor:TorStarted"; - private static final String EVENT_TOR_START_FAILED = "GeckoView:Tor:TorStartFailed"; - private static final String EVENT_TOR_EXITED = "GeckoView:Tor:TorExited"; - private final String mHandle; - private Process mProcess = null; - - TorProcess(String handle) { - mHandle = handle; - setName("tor-process-" + handle); - start(); + public void shutdown() { + if (mProcess != null && mProcess.isAlive()) { + mProcess.destroy(); + } + if (isAlive()) { + try { + join(); + } catch (InterruptedException e) { + Log.e( + TAG, + "Cannot join the thread for tor process " + mHandle + ", possibly already terminated", + e); } + } + }
- @Override - public void run() { - cleanIpcDirectory(); - - final String ipcDir = TorIntegrationAndroid.this.mIpcDirectory; - final ArrayList<String> args = new ArrayList<>(); - args.add(mLibraryDir + "/libTor.so"); - args.add("DisableNetwork"); - args.add("1"); - args.add("+__ControlPort"); - args.add("unix:" + ipcDir + CONTROL_PORT_FILE); - args.add("+__SocksPort"); - args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth"); - args.add("CookieAuthentication"); - args.add("1"); - args.add("CookieAuthFile"); - args.add(ipcDir + COOKIE_AUTH_FILE); - args.add("DataDirectory"); - args.add(mDataDir.getAbsolutePath()); - boolean copied = true; - try { - copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args); - } catch (IOException e) { - Log.w(TAG, "torrc-default cannot be created, pluggable transports will not be available", e); - copied = false; - } - // tor-browser#42607: For now we do not ship geoip databases, as we - // do not have the circuit display functionality and they allow us - // to save some space in the final APK. - /*try { - copyAndUseConfigFile("GeoIPFile", "geoip", args); - copyAndUseConfigFile("GeoIPv6File", "geoip6", args); - } catch (IOException e) { - Log.w(TAG, "GeoIP files cannot be created, this feature will not be available.", e); - copied = false; - }*/ - mCopiedConfigFiles = copied; - - Log.d(TAG, "Starting tor with the follwing args: " + args.toString()); - final ProcessBuilder builder = new ProcessBuilder(args); - builder.directory(new File(mLibraryDir)); - try { - mProcess = builder.start(); - } catch (IOException e) { - Log.e(TAG, "Cannot start tor " + mHandle, e); - final GeckoBundle data = new GeckoBundle(2); - data.putString("handle", mHandle); - data.putString("error", e.getMessage()); - EventDispatcher.getInstance().dispatch(EVENT_TOR_START_FAILED, data); - return; - } - Log.i(TAG, "Tor process " + mHandle + " started."); - { - final GeckoBundle data = new GeckoBundle(1); - data.putString("handle", mHandle); - EventDispatcher.getInstance().dispatch(EVENT_TOR_STARTED, data); - } - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(mProcess.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - Log.i(TAG, "[tor-" + mHandle + "] " + line); - } - } catch (IOException e) { - Log.e(TAG, "Failed to read stdout of the tor process " + mHandle, e); - } - Log.d(TAG, "Exiting the stdout loop for process " + mHandle); - final GeckoBundle data = new GeckoBundle(2); - data.putString("handle", mHandle); - try { - data.putInt("status", mProcess.waitFor()); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to wait for the tor process " + mHandle, e); - data.putInt("status", 0xdeadbeef); - } - // FIXME: We usually don't reach this when the application is killed! - // So, we don't do our cleanup. - Log.i(TAG, "Tor process " + mHandle + " has exited."); - EventDispatcher.getInstance().dispatch(EVENT_TOR_EXITED, data); - } + public String getHandle() { + return mHandle; + } + } + + private synchronized void startMeek(final GeckoBundle message, final EventCallback callback) { + if (callback == null) { + Log.e(TAG, "Tried to start Meek without a callback."); + return; + } + mMeekCounter++; + mMeeks.put( + new Integer(mMeekCounter), + new MeekTransport(callback, mMeekCounter, message.getStringArray("arguments"))); + } + + private synchronized void stopMeek(final GeckoBundle message, final EventCallback callback) { + final Integer key = message.getInteger("id"); + final MeekTransport meek = mMeeks.remove(key); + if (meek != null) { + meek.shutdown(); + } + if (callback != null) { + callback.sendSuccess(null); + } + } + + private class MeekTransport extends Thread { + private static final String TRANSPORT = "meek_lite"; + private Process mProcess; + private final EventCallback mCallback; + private final int mId; + + MeekTransport(final EventCallback callback, int id, String[] args) { + setName("meek-" + id); + + final String command = mLibraryDir + "/libObfs4proxy.so"; + ArrayList<String> argList = new ArrayList<String>(); + argList.add(command); + if (args != null && args.length > 0) { + // Normally not used, but it helps to debug only by editing JS. + Log.d(TAG, "Requested custom arguments for meek: " + String.join(" ", args)); + argList.addAll(Arrays.asList(args)); + } + final ProcessBuilder builder = new ProcessBuilder(argList); + + File ptStateDir = new File(mDataDir, "pt_state"); + Log.d(TAG, "Using " + ptStateDir.getAbsolutePath() + " as a state directory for meek."); + final Map<String, String> env = builder.environment(); + env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1"); + env.put("TOR_PT_STATE_LOCATION", ptStateDir.getAbsolutePath()); + env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1"); + env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT); + + mCallback = callback; + mId = id; + try { + // We expect this process to be short-lived, therefore we do not bother with + // implementing this as a service. + mProcess = builder.start(); + } catch (IOException e) { + Log.e(TAG, "Cannot start the PT", e); + callback.sendError(e.getMessage()); + return; + } + start(); + }
- private void cleanIpcDirectory() { - File directory = new File(TorIntegrationAndroid.this.mIpcDirectory); - if (!directory.isDirectory()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Failed to create the IPC directory."); - return; - } - try { - // First remove the permissions for everybody... - directory.setReadable(false, false); - directory.setWritable(false, false); - directory.setExecutable(false, false); - // ... then add them back, but only for the owner. - directory.setReadable(true, true); - directory.setWritable(true, true); - directory.setExecutable(true, true); - } catch (SecurityException e) { - Log.e(TAG, "Could not set the permissions to the IPC directory.", e); - } - return; + /** + * Parse the standard output of the pluggable transport to find the hostname and port it is + * listening on. + * + * <p>See also the specs for the IPC protocol at https://spec.torproject.org/pt-spec/ipc.html. + */ + @Override + public void run() { + final String PROTOCOL_VERSION = "1"; + String hostname = ""; + boolean valid = false; + int port = 0; + String error = "Did not see a CMETHOD"; + try { + InputStreamReader isr = new InputStreamReader(mProcess.getInputStream()); + BufferedReader reader = new BufferedReader(isr); + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + Log.d(TAG, "Meek line: " + line); + // Split produces always at least one item + String[] tokens = line.split(" "); + if ("VERSION".equals(tokens[0]) + && (tokens.length != 2 || !PROTOCOL_VERSION.equals(tokens[1]))) { + error = "Bad version: " + line; + break; + } + if ("CMETHOD".equals(tokens[0])) { + if (tokens.length != 4) { + error = "Bad number of tokens in CMETHOD: " + line; + break; } - // We assume we do not have child directories, only files - File[] maybeFiles = directory.listFiles(); - if (maybeFiles != null) { - for (File file : maybeFiles) { - if (!file.delete()) { - Log.d(TAG, "Could not delete " + file); - } - } + if (!tokens[1].equals(TRANSPORT)) { + error = "Unexpected transport: " + tokens[1]; + break; } - } - - private void copyAndUseConfigFile(String option, String name, ArrayList<String> args) throws IOException { - File file = copyConfigFile(name); - args.add(option); - args.add(file.getAbsolutePath()); - } - - private File copyConfigFile(String name) throws IOException { - final File file = new File(mCacheDir, name); - if (mCopiedConfigFiles && file.exists()) { - return file; + if (!"socks5".equals(tokens[2])) { + error = "Unexpected proxy type: " + tokens[2]; + break; } - - final Context context = GeckoAppShell.getApplicationContext(); - final InputStream in = context.getAssets().open("common/" + name); - // Files.copy is API 26+, so use java.io and a loop for now. - FileOutputStream out = null; - try { - out = new FileOutputStream(file); - } catch (IOException e) { - in.close(); - throw e; + String[] addr = tokens[3].split(":"); + if (addr.length != 2) { + error = "Invalid address"; + break; } + hostname = addr[0]; try { - byte buffer[] = new byte[4096]; - int read; - while ((read = in.read(buffer)) >= 0) { - out.write(buffer, 0, read); - } - } finally { - try { - in.close(); - } catch (IOException e) { - Log.w(TAG, "Cannot close the input stream for " + name); - } - try { - out.close(); - } catch (IOException e) { - Log.w(TAG, "Cannot close the output stream for " + name); - } - } - return file; - } - - public void shutdown() { - if (mProcess != null && mProcess.isAlive()) { - mProcess.destroy(); + port = Integer.parseInt(addr[1]); + } catch (NumberFormatException e) { + error = "Invalid port: " + e.getMessage(); + break; } - if (isAlive()) { - try { - join(); - } catch (InterruptedException e) { - Log.e(TAG, "Cannot join the thread for tor process " + mHandle + ", possibly already terminated", e); - } + if (port < 1 || port > 65535) { + error = "Invalid port: out of bounds"; + break; } + valid = true; + break; + } + if (tokens[0].endsWith("-ERROR")) { + error = "Seen an error: " + line; + break; + } } - - public String getHandle() { - return mHandle; - } + } catch (Exception e) { + error = e.getMessage(); + } + if (valid) { + Log.d(TAG, "Setup a meek transport " + mId + ": " + hostname + ":" + port); + final GeckoBundle bundle = new GeckoBundle(3); + bundle.putInt("id", mId); + bundle.putString("address", hostname); + bundle.putInt("port", port); + mCallback.sendSuccess(bundle); + } else { + Log.e(TAG, "Failed to get a usable config from the PT: " + error); + mCallback.sendError(error); + return; + } + dumpStdout(); }
- private synchronized void startMeek(final GeckoBundle message, final EventCallback callback) { - if (callback == null) { - Log.e(TAG, "Tried to start Meek without a callback."); - return; - } - mMeekCounter++; - mMeeks.put(new Integer(mMeekCounter), new MeekTransport(callback, mMeekCounter)); + void shutdown() { + if (mProcess != null) { + Log.i(TAG, "Shutting down meek process " + mId); + mProcess.destroy(); + mProcess = null; + } else { + Log.w( + TAG, + "Shutdown request on the meek process " + mId + " that has already been shutdown."); + } + try { + join(); + } catch (InterruptedException e) { + Log.e(TAG, "Could not join the meek thread", e); + } }
- private synchronized void stopMeek(final GeckoBundle message, final EventCallback callback) { - final Integer key = message.getInteger("id"); - final MeekTransport meek = mMeeks.remove(key); - if (meek != null) { - meek.shutdown(); - } - if (callback != null) { - callback.sendSuccess(null); + void dumpStdout() { + try { + BufferedReader reader = + new BufferedReader(new InputStreamReader(mProcess.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + Log.d(TAG, "[meek-" + mId + "] " + line); } + } catch (InterruptedIOException e) { + // This happens normally, do not log it. + } catch (IOException e) { + Log.e(TAG, "Failed to read stdout of the meek process process " + mId, e); + } } + }
- private class MeekTransport extends Thread { - private static final String TRANSPORT = "meek_lite"; - private Process mProcess; - private final EventCallback mCallback; - private final int mId; - - MeekTransport(final EventCallback callback, int id) { - setName("meek-" + id); - final ProcessBuilder builder = new ProcessBuilder(mLibraryDir + "/libObfs4proxy.so"); - { - File ptStateDir = new File(mDataDir, "pt_state"); - final Map<String, String> env = builder.environment(); - env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1"); - env.put("TOR_PT_STATE_LOCATION", ptStateDir.getAbsolutePath()); - env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1"); - env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT); - } - mCallback = callback; - mId = id; - try { - // We expect this process to be short-lived, therefore we do not bother with - // implementing this as a service. - mProcess = builder.start(); - } catch (IOException e) { - Log.e(TAG, "Cannot start the PT", e); - callback.sendError(e.getMessage()); - return; - } - start(); - } + public interface BootstrapStateChangeListener { + void onBootstrapStateChange(String state);
- /** - * Parse the standard output of the pluggable transport to find the hostname and port it is - * listening on. - * <p> - * See also the specs for the IPC protocol at https://spec.torproject.org/pt-spec/ipc.html. - */ - @Override - public void run() { - final String PROTOCOL_VERSION = "1"; - String hostname = ""; - boolean valid = false; - int port = 0; - String error = "Did not see a CMETHOD"; - try { - InputStreamReader isr = new InputStreamReader(mProcess.getInputStream()); - BufferedReader reader = new BufferedReader(isr); - String line; - while ((line = reader.readLine()) != null) { - line = line.trim(); - Log.d(TAG, "Meek line: " + line); - // Split produces always at least one item - String[] tokens = line.split(" "); - if ("VERSION".equals(tokens[0]) && (tokens.length != 2 || !PROTOCOL_VERSION.equals(tokens[1]))) { - error = "Bad version: " + line; - break; - } - if ("CMETHOD".equals(tokens[0])) { - if (tokens.length != 4) { - error = "Bad number of tokens in CMETHOD: " + line; - break; - } - if (!tokens[1].equals(TRANSPORT)) { - error = "Unexpected transport: " + tokens[1]; - break; - } - if (!"socks5".equals(tokens[2])) { - error = "Unexpected proxy type: " + tokens[2]; - break; - } - String[] addr = tokens[3].split(":"); - if (addr.length != 2) { - error = "Invalid address"; - break; - } - hostname = addr[0]; - try { - port = Integer.parseInt(addr[1]); - } catch (NumberFormatException e) { - error = "Invalid port: " + e.getMessage(); - break; - } - if (port < 1 || port > 65535) { - error = "Invalid port: out of bounds"; - break; - } - valid = true; - break; - } - if (tokens[0].endsWith("-ERROR")) { - error = "Seen an error: " + line; - break; - } - } - } catch (Exception e) { - error = e.getMessage(); - } - if (valid) { - Log.d(TAG, "Setup a meek transport " + mId + ": " + hostname + ":" + port); - final GeckoBundle bundle = new GeckoBundle(3); - bundle.putInt("id", mId); - bundle.putString("address", hostname); - bundle.putInt("port", port); - mCallback.sendSuccess(bundle); - } else { - Log.e(TAG, "Failed to get a usable config from the PT: " + error); - mCallback.sendError(error); - } - } + void onBootstrapProgress(double progress, boolean hasWarnings);
- void shutdown() { - if (mProcess != null) { - mProcess.destroy(); - mProcess = null; - } - try { - join(); - } catch (InterruptedException e) { - Log.e(TAG, "Could not join the meek thread", e); - } - } - } + void onBootstrapComplete();
- public interface BootstrapStateChangeListener { - void onBootstrapStateChange(String state); - void onBootstrapProgress(double progress, boolean hasWarnings); - void onBootstrapComplete(); - void onBootstrapError(String code, String message, String phase, String reason); - void onSettingsRequested(); - } + void onBootstrapError(String code, String message, String phase, String reason);
- public interface TorLogListener { - void onLog(String logType, String message); - } + void onSettingsRequested(); + }
- private @NonNull void reloadSettings() { - EventDispatcher.getInstance().queryBundle(EVENT_SETTINGS_GET).then( new GeckoResult.OnValueListener<GeckoBundle, Void>() { - public GeckoResult<Void> onValue(final GeckoBundle bundle) { + public interface TorLogListener { + void onLog(String logType, String message); + } + + private @NonNull void reloadSettings() { + EventDispatcher.getInstance() + .queryBundle(EVENT_SETTINGS_GET) + .then( + new GeckoResult.OnValueListener<GeckoBundle, Void>() { + public GeckoResult<Void> onValue(final GeckoBundle bundle) { mSettings = new TorSettings(bundle); return new GeckoResult<Void>(); - } - }); - } + } + }); + }
- public TorSettings getSettings() { - return mSettings; - } + public TorSettings getSettings() { + return mSettings; + }
- public void setSettings(final TorSettings settings, boolean save, boolean apply) { - mSettings = settings; + public void setSettings(final TorSettings settings, boolean save, boolean apply) { + mSettings = settings;
- emitSetSettings(settings, save, apply).then( + emitSetSettings(settings, save, apply) + .then( new GeckoResult.OnValueListener<Void, Void>() { - public GeckoResult<Void> onValue(Void v) { - return new GeckoResult<Void>(); - } + public GeckoResult<Void> onValue(Void v) { + return new GeckoResult<Void>(); + } }, new GeckoResult.OnExceptionListener<Void>() { - public GeckoResult<Void> onException(final Throwable e) { - Log.e(TAG, "Failed to set settings", e); - reloadSettings(); - return new GeckoResult<Void>(); - } + public GeckoResult<Void> onException(final Throwable e) { + Log.e(TAG, "Failed to set settings", e); + reloadSettings(); + return new GeckoResult<Void>(); + } }); - } - - private @NonNull GeckoResult<Void> emitSetSettings(final TorSettings settings, boolean save, boolean apply) { - GeckoBundle bundle = new GeckoBundle(3); - bundle.putBoolean("save", save); - bundle.putBoolean("apply", apply); - bundle.putBundle("settings", settings.asGeckoBundle()); - return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SET, bundle); - } - - public @NonNull GeckoResult<Void> applySettings() { - return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_APPLY); - } - - public @NonNull GeckoResult<Void> saveSettings() { - return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SAVE); - } - - public @NonNull GeckoResult<Void> beginBootstrap() { - return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN); - } - - public @NonNull GeckoResult<Void> beginAutoBootstrap(final String countryCode) { - final GeckoBundle bundle = new GeckoBundle(1); - bundle.putString("countryCode", countryCode); - return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN_AUTO, bundle); - } - - public @NonNull GeckoResult<Void> beginAutoBootstrap() { - return beginAutoBootstrap(null); - } - - public @NonNull GeckoResult<Void> cancelBootstrap() { - return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_CANCEL); - } - - public void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) { - mBootstrapStateListeners.add(listener); - } - - public void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) { - mBootstrapStateListeners.remove(listener); - } - - private final HashSet<BootstrapStateChangeListener> mBootstrapStateListeners = new HashSet<>(); - - public void registerLogListener(TorLogListener listener) { - mLogListeners.add(listener); - } - - public void unregisterLogListener(TorLogListener listener) { - mLogListeners.remove(listener); - } - - private final HashSet<TorLogListener> mLogListeners = new HashSet<>(); + } + + private @NonNull GeckoResult<Void> emitSetSettings( + final TorSettings settings, boolean save, boolean apply) { + GeckoBundle bundle = new GeckoBundle(3); + bundle.putBoolean("save", save); + bundle.putBoolean("apply", apply); + bundle.putBundle("settings", settings.asGeckoBundle()); + return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SET, bundle); + } + + public @NonNull GeckoResult<Void> applySettings() { + return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_APPLY); + } + + public @NonNull GeckoResult<Void> saveSettings() { + return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SAVE); + } + + public @NonNull GeckoResult<Void> beginBootstrap() { + return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN); + } + + public @NonNull GeckoResult<Void> beginAutoBootstrap(final String countryCode) { + final GeckoBundle bundle = new GeckoBundle(1); + bundle.putString("countryCode", countryCode); + return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN_AUTO, bundle); + } + + public @NonNull GeckoResult<Void> beginAutoBootstrap() { + return beginAutoBootstrap(null); + } + + public @NonNull GeckoResult<Void> cancelBootstrap() { + return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_CANCEL); + } + + public void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) { + mBootstrapStateListeners.add(listener); + } + + public void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) { + mBootstrapStateListeners.remove(listener); + } + + private final HashSet<BootstrapStateChangeListener> mBootstrapStateListeners = new HashSet<>(); + + public void registerLogListener(TorLogListener listener) { + mLogListeners.add(listener); + } + + public void unregisterLogListener(TorLogListener listener) { + mLogListeners.remove(listener); + } + + private final HashSet<TorLogListener> mLogListeners = new HashSet<>(); }
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorSettings.java ===================================== @@ -1,185 +1,192 @@ package org.mozilla.geckoview;
import android.util.Log; - import org.mozilla.gecko.util.GeckoBundle;
public class TorSettings {
- public enum BridgeSource { - Invalid(-1), - BuiltIn(0), - BridgeDB(1), - UserProvided(2); - - private int source; - - BridgeSource(final int source) { - this.source = source; - } - - public static BridgeSource fromInt(int i) { - switch (i) { - case -1: return Invalid; - case 0: return BuiltIn; - case 1: return BridgeDB; - case 2: return UserProvided; - } - return Invalid; - } - - public int toInt() { - return this.source; - } + public enum BridgeSource { + Invalid(-1), + BuiltIn(0), + BridgeDB(1), + UserProvided(2); + + private int source; + + BridgeSource(final int source) { + this.source = source; }
- public enum ProxyType { - Invalid(-1), - Socks4(0), - Socks5(1), - HTTPS(2); - - private int type; - - ProxyType(final int type) { - this.type = type; - } - - public int toInt() { - return type; - } - - public static ProxyType fromInt(int i) { - switch (i) { - case -1: return Invalid; - case 0: return Socks4; - case 1: return Socks5; - case 2: return HTTPS; - } - return Invalid; - } + public static BridgeSource fromInt(int i) { + switch (i) { + case -1: + return Invalid; + case 0: + return BuiltIn; + case 1: + return BridgeDB; + case 2: + return UserProvided; + } + return Invalid; }
- public enum BridgeBuiltinType { - /* TorSettings.sys.mjs ~ln43: string: obfs4|meek-azure|snowflake|etc */ - Invalid("invalid"), - Obfs4("obfs4"), - MeekAzure("meek-azure"), - Snowflake("snowflake"); + public int toInt() { + return this.source; + } + }
+ public enum ProxyType { + Invalid(-1), + Socks4(0), + Socks5(1), + HTTPS(2);
- private String type; + private int type;
- BridgeBuiltinType(String type) { - this.type = type; - } + ProxyType(final int type) { + this.type = type; + }
- public String toString() { - return type; - } + public int toInt() { + return type; + }
- public static BridgeBuiltinType fromString(String s) { - switch (s) { - case "obfs4": return Obfs4; - case "meek-azure": return MeekAzure; - case "snowflake": return Snowflake; - } - return Invalid; - } + public static ProxyType fromInt(int i) { + switch (i) { + case -1: + return Invalid; + case 0: + return Socks4; + case 1: + return Socks5; + case 2: + return HTTPS; + } + return Invalid; + } + } + + public enum BridgeBuiltinType { + /* TorSettings.sys.mjs ~ln43: string: obfs4|meek-azure|snowflake|etc */ + Invalid("invalid"), + Obfs4("obfs4"), + MeekAzure("meek-azure"), + Snowflake("snowflake"); + + private String type;
+ BridgeBuiltinType(String type) { + this.type = type; }
- private boolean loaded = false; + public String toString() { + return type; + }
- public boolean enabled = true; + public static BridgeBuiltinType fromString(String s) { + switch (s) { + case "obfs4": + return Obfs4; + case "meek-azure": + return MeekAzure; + case "snowflake": + return Snowflake; + } + return Invalid; + } + }
- public boolean quickstart = false; + private boolean loaded = false;
- // bridges section - public boolean bridgesEnabled = false; - public BridgeSource bridgesSource = BridgeSource.Invalid; - public BridgeBuiltinType bridgesBuiltinType = BridgeBuiltinType.Invalid; - public String[] bridgeBridgeStrings; + public boolean enabled = true;
- // proxy section - public boolean proxyEnabled = false; - public ProxyType proxyType = ProxyType.Invalid; - public String proxyAddress = ""; - public int proxyPort = 0; - public String proxyUsername = ""; - public String proxyPassword = ""; + public boolean quickstart = false;
- // firewall section - public boolean firewallEnabled = false; - public int[] firewallAllowedPorts; + // bridges section + public boolean bridgesEnabled = false; + public BridgeSource bridgesSource = BridgeSource.Invalid; + public BridgeBuiltinType bridgesBuiltinType = BridgeBuiltinType.Invalid; + public String[] bridgeBridgeStrings;
- public TorSettings() { - } + // proxy section + public boolean proxyEnabled = false; + public ProxyType proxyType = ProxyType.Invalid; + public String proxyAddress = ""; + public int proxyPort = 0; + public String proxyUsername = ""; + public String proxyPassword = ""; + + // firewall section + public boolean firewallEnabled = false; + public int[] firewallAllowedPorts; + + public TorSettings() {} + + public TorSettings(GeckoBundle bundle) { + try { + GeckoBundle qs = bundle.getBundle("quickstart"); + GeckoBundle bridges = bundle.getBundle("bridges"); + GeckoBundle proxy = bundle.getBundle("proxy"); + GeckoBundle firewall = bundle.getBundle("firewall"); + + bridgesEnabled = bridges.getBoolean("enabled"); + bridgesSource = BridgeSource.fromInt(bridges.getInt("source")); + bridgesBuiltinType = BridgeBuiltinType.fromString(bridges.getString("builtin_type")); + bridgeBridgeStrings = bridges.getStringArray("bridge_strings");
- public TorSettings(GeckoBundle bundle) { - try { - GeckoBundle qs = bundle.getBundle("quickstart"); - GeckoBundle bridges = bundle.getBundle("bridges"); - GeckoBundle proxy = bundle.getBundle("proxy"); - GeckoBundle firewall = bundle.getBundle("firewall"); - - bridgesEnabled = bridges.getBoolean("enabled"); - bridgesSource = BridgeSource.fromInt(bridges.getInt("source")); - bridgesBuiltinType = BridgeBuiltinType.fromString(bridges.getString("builtin_type")); - bridgeBridgeStrings = bridges.getStringArray("bridge_strings"); - - quickstart = qs.getBoolean("enabled"); - - firewallEnabled = firewall.getBoolean("enabled"); - firewallAllowedPorts = firewall.getIntArray("allowed_ports"); - - proxyEnabled = proxy.getBoolean("enabled"); - proxyAddress = proxy.getString("address"); - proxyUsername = proxy.getString("username"); - proxyPassword = proxy.getString("password"); - proxyPort = proxy.getInt("port"); - proxyType = ProxyType.fromInt(proxy.getInt("type")); - - loaded = true; - } catch (Exception e) { - Log.e("TorSettings", "bundle access error: " + e.toString(), e); - } + quickstart = qs.getBoolean("enabled"); + + firewallEnabled = firewall.getBoolean("enabled"); + firewallAllowedPorts = firewall.getIntArray("allowed_ports"); + + proxyEnabled = proxy.getBoolean("enabled"); + proxyAddress = proxy.getString("address"); + proxyUsername = proxy.getString("username"); + proxyPassword = proxy.getString("password"); + proxyPort = proxy.getInt("port"); + proxyType = ProxyType.fromInt(proxy.getInt("type")); + + loaded = true; + } catch (Exception e) { + Log.e("TorSettings", "bundle access error: " + e.toString(), e); } + }
- public GeckoBundle asGeckoBundle() { - GeckoBundle bundle = new GeckoBundle(); + public GeckoBundle asGeckoBundle() { + GeckoBundle bundle = new GeckoBundle();
- GeckoBundle qs = new GeckoBundle(); - GeckoBundle bridges = new GeckoBundle(); - GeckoBundle proxy = new GeckoBundle(); - GeckoBundle firewall = new GeckoBundle(); + GeckoBundle qs = new GeckoBundle(); + GeckoBundle bridges = new GeckoBundle(); + GeckoBundle proxy = new GeckoBundle(); + GeckoBundle firewall = new GeckoBundle();
- bridges.putBoolean("enabled", bridgesEnabled); - bridges.putInt("source", bridgesSource.toInt()); - bridges.putString("builtin_type", bridgesBuiltinType.toString()); - bridges.putStringArray("bridge_strings", bridgeBridgeStrings); + bridges.putBoolean("enabled", bridgesEnabled); + bridges.putInt("source", bridgesSource.toInt()); + bridges.putString("builtin_type", bridgesBuiltinType.toString()); + bridges.putStringArray("bridge_strings", bridgeBridgeStrings);
- qs.putBoolean("enabled", quickstart); + qs.putBoolean("enabled", quickstart);
- firewall.putBoolean("enabled", firewallEnabled); - firewall.putIntArray("allowed_ports", firewallAllowedPorts); + firewall.putBoolean("enabled", firewallEnabled); + firewall.putIntArray("allowed_ports", firewallAllowedPorts);
- proxy.putBoolean("enabled", proxyEnabled); - proxy.putString("address", proxyAddress); - proxy.putString("username", proxyUsername); - proxy.putString("password", proxyPassword); - proxy.putInt("port", proxyPort); - proxy.putInt("type", proxyType.toInt()); + proxy.putBoolean("enabled", proxyEnabled); + proxy.putString("address", proxyAddress); + proxy.putString("username", proxyUsername); + proxy.putString("password", proxyPassword); + proxy.putInt("port", proxyPort); + proxy.putInt("type", proxyType.toInt());
- bundle.putBundle("quickstart", qs); - bundle.putBundle("bridges", bridges); - bundle.putBundle("proxy", proxy); - bundle.putBundle("firewall", firewall); + bundle.putBundle("quickstart", qs); + bundle.putBundle("bridges", bridges); + bundle.putBundle("proxy", proxy); + bundle.putBundle("firewall", firewall);
- return bundle; - } + return bundle; + }
- public boolean isLoaded() { - return this.loaded; - } + public boolean isLoaded() { + return this.loaded; + } }
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java ===================================== @@ -49,9 +49,7 @@ public class WebRequest extends WebMessage { /** The value of the Referer header for this request. */ public final @Nullable String referrer;
- /** - * The value of the origin of this request. - */ + /** The value of the origin of this request. */ public final @Nullable String origin;
@Retention(RetentionPolicy.SOURCE) @@ -248,10 +246,10 @@ public class WebRequest extends WebMessage { * @param origin A URI String * @return This Builder instance. */ - public @NonNull Builder origin(final @Nullable String origin) { - mOrigin = origin; - return this; - } + public @NonNull Builder origin(final @Nullable String origin) { + mOrigin = origin; + return this; + }
/** * @return A {@link WebRequest} constructed with the values from this Builder instance.
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/androidlegacysettings/Prefs.java ===================================== @@ -2,71 +2,68 @@ package org.mozilla.geckoview.androidlegacysettings;
import android.content.Context; import android.content.SharedPreferences; -import org.mozilla.gecko.GeckoAppShell; - import java.util.Locale; +import org.mozilla.gecko.GeckoAppShell;
// tor-android-service utils/Prefs.java
/* package */ class Prefs { - private final static String PREF_BRIDGES_ENABLED = "pref_bridges_enabled"; - private final static String PREF_BRIDGES_LIST = "pref_bridges_list"; + private static final String PREF_BRIDGES_ENABLED = "pref_bridges_enabled"; + private static final String PREF_BRIDGES_LIST = "pref_bridges_list";
- private static SharedPreferences prefs; + private static SharedPreferences prefs;
- // OrbotConstants - private final static String PREF_TOR_SHARED_PREFS = "org.torproject.android_preferences"; + // OrbotConstants + private static final String PREF_TOR_SHARED_PREFS = "org.torproject.android_preferences";
+ // tor-android-service utils/TorServiceUtil.java
- // tor-android-service utils/TorServiceUtil.java - - private static void setContext() { - if (prefs == null) { - prefs = GeckoAppShell.getApplicationContext().getSharedPreferences(PREF_TOR_SHARED_PREFS, - Context.MODE_MULTI_PROCESS); - } + private static void setContext() { + if (prefs == null) { + prefs = + GeckoAppShell.getApplicationContext() + .getSharedPreferences(PREF_TOR_SHARED_PREFS, Context.MODE_MULTI_PROCESS); } - - public static boolean getBoolean(String key, boolean def) { - setContext(); - return prefs.getBoolean(key, def); - } - - public static void putBoolean(String key, boolean value) { - setContext(); - prefs.edit().putBoolean(key, value).apply(); - } - - public static void putString(String key, String value) { - setContext(); - prefs.edit().putString(key, value).apply(); + } + + public static boolean getBoolean(String key, boolean def) { + setContext(); + return prefs.getBoolean(key, def); + } + + public static void putBoolean(String key, boolean value) { + setContext(); + prefs.edit().putBoolean(key, value).apply(); + } + + public static void putString(String key, String value) { + setContext(); + prefs.edit().putString(key, value).apply(); + } + + public static String getString(String key, String def) { + setContext(); + return prefs.getString(key, def); + } + + public static boolean bridgesEnabled() { + setContext(); + // for Locale.getDefault().getLanguage().equals("fa"), bridges were enabled by default (and + // it was meek). This was a default set in 2019 code, but it is not a good default anymore, + // so we removed the check. + return prefs.getBoolean(PREF_BRIDGES_ENABLED, false); + } + + public static String getBridgesList() { + setContext(); + String list = prefs.getString(PREF_BRIDGES_LIST, ""); + // list might be empty if the default PT was used, so check also if bridges are enabled. + if (list.isEmpty() && prefs.getBoolean(PREF_BRIDGES_ENABLED, false)) { + // Even though the check on the fa locale is not good to enable bridges by default, we + // still check it here, because if the list was empty, it was likely that it was the + // choice for users with this locale. + return (Locale.getDefault().getLanguage().equals("fa")) ? "meek" : "obfs4"; } - - public static String getString(String key, String def) { - setContext(); - return prefs.getString(key, def); - } - - public static boolean bridgesEnabled() { - setContext(); - // for Locale.getDefault().getLanguage().equals("fa"), bridges were enabled by default (and - // it was meek). This was a default set in 2019 code, but it is not a good default anymore, - // so we removed the check. - return prefs.getBoolean(PREF_BRIDGES_ENABLED, false); - } - - public static String getBridgesList() { - setContext(); - String list = prefs.getString(PREF_BRIDGES_LIST, ""); - // list might be empty if the default PT was used, so check also if bridges are enabled. - if (list.isEmpty() && prefs.getBoolean(PREF_BRIDGES_ENABLED, false)) { - // Even though the check on the fa locale is not good to enable bridges by default, we - // still check it here, because if the list was empty, it was likely that it was the - // choice for users with this locale. - return (Locale.getDefault().getLanguage().equals("fa")) ? "meek": "obfs4"; - } - return list; - } - - + return list; + } }
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/androidlegacysettings/TorLegacyAndroidSettings.java ===================================== @@ -4,70 +4,71 @@ import org.mozilla.geckoview.TorSettings;
public class TorLegacyAndroidSettings {
- private static String PREF_USE_MOZ_PREFS = "tor_use_moz_prefs"; + private static String PREF_USE_MOZ_PREFS = "tor_use_moz_prefs";
- public static boolean unmigrated() { - return !Prefs.getBoolean(PREF_USE_MOZ_PREFS, false); - } + public static boolean unmigrated() { + return !Prefs.getBoolean(PREF_USE_MOZ_PREFS, false); + }
- public static void setUnmigrated() { - Prefs.putBoolean(PREF_USE_MOZ_PREFS, false); - } + public static void setUnmigrated() { + Prefs.putBoolean(PREF_USE_MOZ_PREFS, false); + }
- public static void setMigrated() { - Prefs.putBoolean(PREF_USE_MOZ_PREFS, true); - } + public static void setMigrated() { + Prefs.putBoolean(PREF_USE_MOZ_PREFS, true); + }
- public static TorSettings loadTorSettings() { - TorSettings settings = new TorSettings(); + public static TorSettings loadTorSettings() { + TorSettings settings = new TorSettings();
- // always true, tor is enabled in TB - settings.enabled = true; + // always true, tor is enabled in TB + settings.enabled = true;
- // firefox-android disconnected quick start a while ago so it's untracked - settings.quickstart = false; + // firefox-android disconnected quick start a while ago so it's untracked + settings.quickstart = false;
- settings.bridgesEnabled = Prefs.bridgesEnabled(); + settings.bridgesEnabled = Prefs.bridgesEnabled();
- // tor-android-service CustomTorInstaller.java -/* - BridgesList is an overloaded field, which can cause some confusion. - The list can be: - 1) a filter like obfs4, meek, or snowflake OR - 2) it can be a custom bridge - For (1), we just pass back all bridges, the filter will occur - elsewhere in the library. - For (2) we return the bridge list as a raw stream. - If length is greater than 9, then we know this is a custom bridge - */ - String userDefinedBridgeList = Prefs.getBridgesList(); - boolean userDefinedBridge = userDefinedBridgeList.length() > 9; - // Terrible hack. Must keep in sync with topl::addBridgesFromResources. - if (!userDefinedBridge) { - settings.bridgesSource = TorSettings.BridgeSource.BuiltIn; - switch (userDefinedBridgeList) { - case "obfs4": - case "snowflake": - settings.bridgesBuiltinType = TorSettings.BridgeBuiltinType.fromString(userDefinedBridgeList); - break; - case "meek": - settings.bridgesBuiltinType = TorSettings.BridgeBuiltinType.MeekAzure; - break; - default: - settings.bridgesSource = TorSettings.BridgeSource.Invalid; - break; - } - } else { - settings.bridgesSource = TorSettings.BridgeSource.UserProvided; // user provided - settings.bridgeBridgeStrings = userDefinedBridgeList.split("\r\n"); - } + // tor-android-service CustomTorInstaller.java + /* + BridgesList is an overloaded field, which can cause some confusion. + The list can be: + 1) a filter like obfs4, meek, or snowflake OR + 2) it can be a custom bridge + For (1), we just pass back all bridges, the filter will occur + elsewhere in the library. + For (2) we return the bridge list as a raw stream. + If length is greater than 9, then we know this is a custom bridge + */ + String userDefinedBridgeList = Prefs.getBridgesList(); + boolean userDefinedBridge = userDefinedBridgeList.length() > 9; + // Terrible hack. Must keep in sync with topl::addBridgesFromResources. + if (!userDefinedBridge) { + settings.bridgesSource = TorSettings.BridgeSource.BuiltIn; + switch (userDefinedBridgeList) { + case "obfs4": + case "snowflake": + settings.bridgesBuiltinType = + TorSettings.BridgeBuiltinType.fromString(userDefinedBridgeList); + break; + case "meek": + settings.bridgesBuiltinType = TorSettings.BridgeBuiltinType.MeekAzure; + break; + default: + settings.bridgesSource = TorSettings.BridgeSource.Invalid; + break; + } + } else { + settings.bridgesSource = TorSettings.BridgeSource.UserProvided; // user provided + settings.bridgeBridgeStrings = userDefinedBridgeList.split("\r\n"); + }
- // Tor Browser Android doesn't take proxy and firewall settings - settings.proxyEnabled = false; + // Tor Browser Android doesn't take proxy and firewall settings + settings.proxyEnabled = false;
- settings.firewallEnabled = false; - settings.firewallAllowedPorts = new int[0]; + settings.firewallEnabled = false; + settings.firewallAllowedPorts = new int[0];
- return settings; - } + return settings; + } }
===================================== toolkit/modules/DomainFrontedRequests.sys.mjs ===================================== @@ -444,7 +444,7 @@ export class DomainFrontRequestBuilder {
async init(reflector, front) { if (this.#inited) { - throw new Error("MoatRPC: Already initialized"); + throw new Error("DomainFrontRequestBuilder: Already initialized"); }
const meekTransport = @@ -464,7 +464,7 @@ export class DomainFrontRequestBuilder {
buildHttpHandler(uriString) { if (!this.#inited) { - throw new Error("MoatRPC: Not initialized"); + throw new Error("DomainFrontRequestBuilder: Not initialized"); }
const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
===================================== toolkit/modules/Moat.sys.mjs ===================================== @@ -119,7 +119,7 @@ export class MoatRPC { ch.requestMethod = "HEAD";
const listener = new InternetTestResponseListener(); - await ch.asyncOpen(listener, ch); + ch.asyncOpen(listener, ch); return listener.status; }
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/70283a2...