... |
... |
@@ -9,671 +9,720 @@ package org.mozilla.geckoview; |
9
|
9
|
import android.content.Context;
|
10
|
10
|
import android.os.AsyncTask;
|
11
|
11
|
import android.util.Log;
|
12
|
|
-
|
13
|
|
-import androidx.annotation.AnyThread;
|
14
|
12
|
import androidx.annotation.NonNull;
|
15
|
|
-import androidx.annotation.Nullable;
|
16
|
|
-
|
17
|
13
|
import java.io.BufferedReader;
|
18
|
14
|
import java.io.File;
|
19
|
15
|
import java.io.FileOutputStream;
|
20
|
16
|
import java.io.IOException;
|
21
|
17
|
import java.io.InputStream;
|
22
|
18
|
import java.io.InputStreamReader;
|
|
19
|
+import java.io.InterruptedIOException;
|
23
|
20
|
import java.util.ArrayList;
|
|
21
|
+import java.util.Arrays;
|
24
|
22
|
import java.util.HashMap;
|
25
|
23
|
import java.util.HashSet;
|
26
|
24
|
import java.util.Map;
|
27
|
|
-import java.util.Set;
|
28
|
|
-
|
29
|
25
|
import org.mozilla.gecko.EventDispatcher;
|
30
|
26
|
import org.mozilla.gecko.GeckoAppShell;
|
31
|
27
|
import org.mozilla.gecko.util.BundleEventListener;
|
32
|
28
|
import org.mozilla.gecko.util.EventCallback;
|
33
|
29
|
import org.mozilla.gecko.util.GeckoBundle;
|
34
|
|
-
|
35
|
30
|
import org.mozilla.geckoview.androidlegacysettings.TorLegacyAndroidSettings;
|
36
|
31
|
|
37
|
32
|
public class TorIntegrationAndroid implements BundleEventListener {
|
38
|
|
- private static final String TAG = "TorIntegrationAndroid";
|
39
|
|
-
|
40
|
|
- // Events we listen to
|
41
|
|
- private static final String EVENT_TOR_START = "GeckoView:Tor:StartTor";
|
42
|
|
- private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor";
|
43
|
|
- private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek";
|
44
|
|
- private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek";
|
45
|
|
- private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged";
|
46
|
|
- private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError";
|
47
|
|
- private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress";
|
48
|
|
- private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete";
|
49
|
|
- private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs";
|
50
|
|
- private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
|
51
|
|
- private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
|
52
|
|
- private static final String EVENT_SETTINGS_OPEN = "GeckoView:Tor:OpenSettings";
|
53
|
|
-
|
54
|
|
- // Events we emit
|
55
|
|
- private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet";
|
56
|
|
- private static final String EVENT_SETTINGS_SET = "GeckoView:Tor:SettingsSet";
|
57
|
|
- private static final String EVENT_SETTINGS_APPLY = "GeckoView:Tor:SettingsApply";
|
58
|
|
- private static final String EVENT_SETTINGS_SAVE = "GeckoView:Tor:SettingsSave";
|
59
|
|
- private static final String EVENT_BOOTSTRAP_BEGIN = "GeckoView:Tor:BootstrapBegin";
|
60
|
|
- private static final String EVENT_BOOTSTRAP_BEGIN_AUTO = "GeckoView:Tor:BootstrapBeginAuto";
|
61
|
|
- private static final String EVENT_BOOTSTRAP_CANCEL = "GeckoView:Tor:BootstrapCancel";
|
62
|
|
- private static final String EVENT_BOOTSTRAP_GET_STATE = "GeckoView:Tor:BootstrapGetState";
|
63
|
|
-
|
64
|
|
- private static final String CONTROL_PORT_FILE = "/control-ipc";
|
65
|
|
- private static final String SOCKS_FILE = "/socks-ipc";
|
66
|
|
- private static final String COOKIE_AUTH_FILE = "/auth-file";
|
67
|
|
-
|
68
|
|
- private final String mLibraryDir;
|
69
|
|
- private final String mCacheDir;
|
70
|
|
- private final String mIpcDirectory;
|
71
|
|
- private final File mDataDir;
|
72
|
|
-
|
73
|
|
- private TorProcess mTorProcess = null;
|
74
|
|
- /**
|
75
|
|
- * The first time we run a Tor process in this session, we copy some configuration files to be
|
76
|
|
- * sure we always have the latest version, but if we re-launch a tor process we do not need to
|
77
|
|
- * copy them again.
|
78
|
|
- */
|
79
|
|
- private boolean mCopiedConfigFiles = false;
|
80
|
|
- /**
|
81
|
|
- * Allow multiple proxies to be started, even though it might not actually happen.
|
82
|
|
- * The key should be positive (also 0 is not allowed).
|
83
|
|
- */
|
84
|
|
- private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>();
|
85
|
|
- private int mMeekCounter;
|
86
|
|
-
|
87
|
|
- /**
|
88
|
|
- * mSettings is a Java-side copy of the authoritative settings in the JS code.
|
89
|
|
- * It's useful to maintain as the UI may be fetching these options often and we don't watch each
|
90
|
|
- * fetch to be a passthrough to JS with marshalling/unmarshalling each time.
|
91
|
|
- */
|
92
|
|
- private TorSettings mSettings = null;
|
93
|
|
-
|
94
|
|
- /* package */ TorIntegrationAndroid(Context context) {
|
95
|
|
- mLibraryDir = context.getApplicationInfo().nativeLibraryDir;
|
96
|
|
- mCacheDir = context.getCacheDir().getAbsolutePath();
|
97
|
|
- mIpcDirectory = mCacheDir + "/tor-private";
|
98
|
|
- mDataDir = new File(context.getFilesDir(), "tor");
|
99
|
|
- registerListener();
|
|
33
|
+ private static final String TAG = "TorIntegrationAndroid";
|
|
34
|
+
|
|
35
|
+ // Events we listen to
|
|
36
|
+ private static final String EVENT_TOR_START = "GeckoView:Tor:StartTor";
|
|
37
|
+ private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor";
|
|
38
|
+ private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek";
|
|
39
|
+ private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek";
|
|
40
|
+ private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged";
|
|
41
|
+ private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError";
|
|
42
|
+ private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress";
|
|
43
|
+ private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete";
|
|
44
|
+ private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs";
|
|
45
|
+ private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
|
|
46
|
+ private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
|
|
47
|
+ private static final String EVENT_SETTINGS_OPEN = "GeckoView:Tor:OpenSettings";
|
|
48
|
+
|
|
49
|
+ // Events we emit
|
|
50
|
+ private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet";
|
|
51
|
+ private static final String EVENT_SETTINGS_SET = "GeckoView:Tor:SettingsSet";
|
|
52
|
+ private static final String EVENT_SETTINGS_APPLY = "GeckoView:Tor:SettingsApply";
|
|
53
|
+ private static final String EVENT_SETTINGS_SAVE = "GeckoView:Tor:SettingsSave";
|
|
54
|
+ private static final String EVENT_BOOTSTRAP_BEGIN = "GeckoView:Tor:BootstrapBegin";
|
|
55
|
+ private static final String EVENT_BOOTSTRAP_BEGIN_AUTO = "GeckoView:Tor:BootstrapBeginAuto";
|
|
56
|
+ private static final String EVENT_BOOTSTRAP_CANCEL = "GeckoView:Tor:BootstrapCancel";
|
|
57
|
+ private static final String EVENT_BOOTSTRAP_GET_STATE = "GeckoView:Tor:BootstrapGetState";
|
|
58
|
+
|
|
59
|
+ private static final String CONTROL_PORT_FILE = "/control-ipc";
|
|
60
|
+ private static final String SOCKS_FILE = "/socks-ipc";
|
|
61
|
+ private static final String COOKIE_AUTH_FILE = "/auth-file";
|
|
62
|
+
|
|
63
|
+ private final String mLibraryDir;
|
|
64
|
+ private final String mCacheDir;
|
|
65
|
+ private final String mIpcDirectory;
|
|
66
|
+ private final File mDataDir;
|
|
67
|
+
|
|
68
|
+ private TorProcess mTorProcess = null;
|
|
69
|
+
|
|
70
|
+ /**
|
|
71
|
+ * The first time we run a Tor process in this session, we copy some configuration files to be
|
|
72
|
+ * sure we always have the latest version, but if we re-launch a tor process we do not need to
|
|
73
|
+ * copy them again.
|
|
74
|
+ */
|
|
75
|
+ private boolean mCopiedConfigFiles = false;
|
|
76
|
+
|
|
77
|
+ /**
|
|
78
|
+ * Allow multiple proxies to be started, even though it might not actually happen. The key should
|
|
79
|
+ * be positive (also 0 is not allowed).
|
|
80
|
+ */
|
|
81
|
+ private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>();
|
|
82
|
+
|
|
83
|
+ private int mMeekCounter;
|
|
84
|
+
|
|
85
|
+ /**
|
|
86
|
+ * mSettings is a Java-side copy of the authoritative settings in the JS code. It's useful to
|
|
87
|
+ * maintain as the UI may be fetching these options often and we don't watch each fetch to be a
|
|
88
|
+ * passthrough to JS with marshalling/unmarshalling each time.
|
|
89
|
+ */
|
|
90
|
+ private TorSettings mSettings = null;
|
|
91
|
+
|
|
92
|
+ /* package */ TorIntegrationAndroid(Context context) {
|
|
93
|
+ mLibraryDir = context.getApplicationInfo().nativeLibraryDir;
|
|
94
|
+ mCacheDir = context.getCacheDir().getAbsolutePath();
|
|
95
|
+ mIpcDirectory = mCacheDir + "/tor-private";
|
|
96
|
+ mDataDir = new File(context.getFilesDir(), "tor");
|
|
97
|
+ registerListener();
|
|
98
|
+ }
|
|
99
|
+
|
|
100
|
+ /* package */ synchronized void shutdown() {
|
|
101
|
+ // FIXME: It seems this never gets called
|
|
102
|
+ if (mTorProcess != null) {
|
|
103
|
+ mTorProcess.shutdown();
|
|
104
|
+ mTorProcess = null;
|
100
|
105
|
}
|
101
|
|
-
|
102
|
|
- /* package */ synchronized void shutdown() {
|
103
|
|
- // FIXME: It seems this never gets called
|
104
|
|
- if (mTorProcess != null) {
|
105
|
|
- mTorProcess.shutdown();
|
106
|
|
- mTorProcess = null;
|
107
|
|
- }
|
|
106
|
+ }
|
|
107
|
+
|
|
108
|
+ private void registerListener() {
|
|
109
|
+ EventDispatcher.getInstance()
|
|
110
|
+ .registerUiThreadListener(
|
|
111
|
+ this,
|
|
112
|
+ EVENT_TOR_START,
|
|
113
|
+ EVENT_MEEK_START,
|
|
114
|
+ EVENT_MEEK_STOP,
|
|
115
|
+ EVENT_SETTINGS_READY,
|
|
116
|
+ EVENT_SETTINGS_CHANGED,
|
|
117
|
+ EVENT_CONNECT_STATE_CHANGED,
|
|
118
|
+ EVENT_CONNECT_ERROR,
|
|
119
|
+ EVENT_BOOTSTRAP_PROGRESS,
|
|
120
|
+ EVENT_BOOTSTRAP_COMPLETE,
|
|
121
|
+ EVENT_TOR_LOGS,
|
|
122
|
+ EVENT_SETTINGS_OPEN);
|
|
123
|
+ }
|
|
124
|
+
|
|
125
|
+ @Override // BundleEventListener
|
|
126
|
+ public synchronized void handleMessage(
|
|
127
|
+ final String event, final GeckoBundle message, final EventCallback callback) {
|
|
128
|
+ if (EVENT_TOR_START.equals(event)) {
|
|
129
|
+ startDaemon(message, callback);
|
|
130
|
+ } else if (EVENT_TOR_STOP.equals(event)) {
|
|
131
|
+ stopDaemon(message, callback);
|
|
132
|
+ } else if (EVENT_MEEK_START.equals(event)) {
|
|
133
|
+ startMeek(message, callback);
|
|
134
|
+ } else if (EVENT_MEEK_STOP.equals(event)) {
|
|
135
|
+ stopMeek(message, callback);
|
|
136
|
+ } else if (EVENT_SETTINGS_READY.equals(event)) {
|
|
137
|
+ try {
|
|
138
|
+ new SettingsLoader().execute(message);
|
|
139
|
+ } catch (Exception e) {
|
|
140
|
+ Log.e(TAG, "SettingsLoader error: " + e.toString());
|
|
141
|
+ }
|
|
142
|
+ } else if (EVENT_SETTINGS_CHANGED.equals(event)) {
|
|
143
|
+ GeckoBundle newSettings = message.getBundle("settings");
|
|
144
|
+ if (newSettings != null) {
|
|
145
|
+ // TODO: Should we notify listeners?
|
|
146
|
+ mSettings = new TorSettings(newSettings);
|
|
147
|
+ } else {
|
|
148
|
+ Log.w(TAG, "Ignoring a settings changed event that did not have the new settings.");
|
|
149
|
+ }
|
|
150
|
+ } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) {
|
|
151
|
+ String state = message.getString("state");
|
|
152
|
+ for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
|
|
153
|
+ listener.onBootstrapStateChange(state);
|
|
154
|
+ }
|
|
155
|
+ } else if (EVENT_CONNECT_ERROR.equals(event)) {
|
|
156
|
+ String code = message.getString("code");
|
|
157
|
+ String msg = message.getString("message");
|
|
158
|
+ String phase = message.getString("phase");
|
|
159
|
+ String reason = message.getString("reason");
|
|
160
|
+ for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
|
|
161
|
+ listener.onBootstrapError(code, msg, phase, reason);
|
|
162
|
+ }
|
|
163
|
+ } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) {
|
|
164
|
+ double progress = message.getDouble("progress");
|
|
165
|
+ boolean hasWarnings = message.getBoolean("hasWarnings");
|
|
166
|
+ for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
|
|
167
|
+ listener.onBootstrapProgress(progress, hasWarnings);
|
|
168
|
+ }
|
|
169
|
+ } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) {
|
|
170
|
+ for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
|
|
171
|
+ listener.onBootstrapComplete();
|
|
172
|
+ }
|
|
173
|
+ } else if (EVENT_TOR_LOGS.equals(event)) {
|
|
174
|
+ String msg = message.getString("message");
|
|
175
|
+ String type = message.getString("logType");
|
|
176
|
+ for (TorLogListener listener : mLogListeners) {
|
|
177
|
+ listener.onLog(type, msg);
|
|
178
|
+ }
|
|
179
|
+ } else if (EVENT_SETTINGS_OPEN.equals(event)) {
|
|
180
|
+ for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
|
|
181
|
+ listener.onSettingsRequested();
|
|
182
|
+ }
|
108
|
183
|
}
|
109
|
|
-
|
110
|
|
- private void registerListener() {
|
111
|
|
- EventDispatcher.getInstance()
|
112
|
|
- .registerUiThreadListener(
|
113
|
|
- this,
|
114
|
|
- EVENT_TOR_START,
|
115
|
|
- EVENT_MEEK_START,
|
116
|
|
- EVENT_MEEK_STOP,
|
117
|
|
- EVENT_SETTINGS_READY,
|
118
|
|
- EVENT_SETTINGS_CHANGED,
|
119
|
|
- EVENT_CONNECT_STATE_CHANGED,
|
120
|
|
- EVENT_CONNECT_ERROR,
|
121
|
|
- EVENT_BOOTSTRAP_PROGRESS,
|
122
|
|
- EVENT_BOOTSTRAP_COMPLETE,
|
123
|
|
- EVENT_TOR_LOGS,
|
124
|
|
- EVENT_SETTINGS_OPEN);
|
|
184
|
+ }
|
|
185
|
+
|
|
186
|
+ private class SettingsLoader extends AsyncTask<GeckoBundle, Void, TorSettings> {
|
|
187
|
+ protected TorSettings doInBackground(GeckoBundle... messages) {
|
|
188
|
+ GeckoBundle message = messages[0];
|
|
189
|
+ TorSettings settings;
|
|
190
|
+ if (TorLegacyAndroidSettings.unmigrated()) {
|
|
191
|
+ settings = TorLegacyAndroidSettings.loadTorSettings();
|
|
192
|
+ } else {
|
|
193
|
+ GeckoBundle bundle = message.getBundle("settings");
|
|
194
|
+ settings = new TorSettings(bundle);
|
|
195
|
+ }
|
|
196
|
+ return settings;
|
125
|
197
|
}
|
126
|
198
|
|
127
|
|
- @Override // BundleEventListener
|
128
|
|
- public synchronized void handleMessage(
|
129
|
|
- final String event, final GeckoBundle message, final EventCallback callback) {
|
130
|
|
- if (EVENT_TOR_START.equals(event)) {
|
131
|
|
- startDaemon(message, callback);
|
132
|
|
- } else if (EVENT_TOR_STOP.equals(event)) {
|
133
|
|
- stopDaemon(message, callback);
|
134
|
|
- } else if (EVENT_MEEK_START.equals(event)) {
|
135
|
|
- startMeek(message, callback);
|
136
|
|
- } else if (EVENT_MEEK_STOP.equals(event)) {
|
137
|
|
- stopMeek(message, callback);
|
138
|
|
- } else if (EVENT_SETTINGS_READY.equals(event)) {
|
139
|
|
- try {
|
140
|
|
- new SettingsLoader().execute(message);
|
141
|
|
- } catch(Exception e) {
|
142
|
|
- Log.e(TAG, "SettingsLoader error: "+ e.toString());
|
143
|
|
- }
|
144
|
|
- } else if (EVENT_SETTINGS_CHANGED.equals(event)) {
|
145
|
|
- GeckoBundle newSettings = message.getBundle("settings");
|
146
|
|
- if (newSettings != null) {
|
147
|
|
- // TODO: Should we notify listeners?
|
148
|
|
- mSettings = new TorSettings(newSettings);
|
149
|
|
- } else {
|
150
|
|
- Log.w(TAG, "Ignoring a settings changed event that did not have the new settings.");
|
151
|
|
- }
|
152
|
|
- } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) {
|
153
|
|
- String state = message.getString("state");
|
154
|
|
- for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
|
155
|
|
- listener.onBootstrapStateChange(state);
|
156
|
|
- }
|
157
|
|
- } else if (EVENT_CONNECT_ERROR.equals(event)) {
|
158
|
|
- String code = message.getString("code");
|
159
|
|
- String msg = message.getString("message");
|
160
|
|
- String phase = message.getString("phase");
|
161
|
|
- String reason = message.getString("reason");
|
162
|
|
- for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
|
163
|
|
- listener.onBootstrapError(code, msg, phase, reason);
|
164
|
|
- }
|
165
|
|
- } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) {
|
166
|
|
- double progress = message.getDouble("progress");
|
167
|
|
- boolean hasWarnings = message.getBoolean("hasWarnings");
|
168
|
|
- for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
|
169
|
|
- listener.onBootstrapProgress(progress, hasWarnings);
|
170
|
|
- }
|
171
|
|
- } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) {
|
172
|
|
- for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
|
173
|
|
- listener.onBootstrapComplete();
|
174
|
|
- }
|
175
|
|
- } else if (EVENT_TOR_LOGS.equals(event)) {
|
176
|
|
- String msg = message.getString("message");
|
177
|
|
- String type = message.getString("logType");
|
178
|
|
- for (TorLogListener listener: mLogListeners) {
|
179
|
|
- listener.onLog(type, msg);
|
180
|
|
- }
|
181
|
|
- } else if (EVENT_SETTINGS_OPEN.equals(event)) {
|
182
|
|
- for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
|
183
|
|
- listener.onSettingsRequested();
|
184
|
|
- }
|
185
|
|
- }
|
|
199
|
+ @Override
|
|
200
|
+ protected void onPostExecute(TorSettings torSettings) {
|
|
201
|
+ mSettings = torSettings;
|
|
202
|
+ if (TorLegacyAndroidSettings.unmigrated()) {
|
|
203
|
+ setSettings(mSettings, true, true);
|
|
204
|
+ TorLegacyAndroidSettings.setMigrated();
|
|
205
|
+ }
|
|
206
|
+ }
|
|
207
|
+ }
|
|
208
|
+
|
|
209
|
+ private synchronized void startDaemon(final GeckoBundle message, final EventCallback callback) {
|
|
210
|
+ // Let JS generate this to possibly reduce the chance of race conditions.
|
|
211
|
+ String handle = message.getString("handle", "");
|
|
212
|
+ if (handle.isEmpty()) {
|
|
213
|
+ Log.e(TAG, "Requested to start a tor process without a handle.");
|
|
214
|
+ callback.sendError("Expected a handle for the new process.");
|
|
215
|
+ return;
|
186
|
216
|
}
|
|
217
|
+ Log.d(TAG, "Starting the a tor process with handle " + handle);
|
187
|
218
|
|
188
|
|
- private class SettingsLoader extends AsyncTask<GeckoBundle, Void, TorSettings> {
|
189
|
|
- protected TorSettings doInBackground(GeckoBundle... messages) {
|
190
|
|
- GeckoBundle message = messages[0];
|
191
|
|
- TorSettings settings;
|
192
|
|
- if (TorLegacyAndroidSettings.unmigrated()) {
|
193
|
|
- settings = TorLegacyAndroidSettings.loadTorSettings();
|
194
|
|
- } else {
|
195
|
|
- GeckoBundle bundle = message.getBundle("settings");
|
196
|
|
- settings = new TorSettings(bundle);
|
197
|
|
- }
|
198
|
|
- return settings;
|
199
|
|
- }
|
|
219
|
+ TorProcess previousProcess = mTorProcess;
|
|
220
|
+ if (previousProcess != null) {
|
|
221
|
+ Log.w(TAG, "We still have a running process: " + previousProcess.getHandle());
|
|
222
|
+ }
|
|
223
|
+ mTorProcess = new TorProcess(handle);
|
|
224
|
+
|
|
225
|
+ GeckoBundle bundle = new GeckoBundle(3);
|
|
226
|
+ bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE);
|
|
227
|
+ bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE);
|
|
228
|
+ bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE);
|
|
229
|
+ callback.sendSuccess(bundle);
|
|
230
|
+ }
|
|
231
|
+
|
|
232
|
+ private synchronized void stopDaemon(final GeckoBundle message, final EventCallback callback) {
|
|
233
|
+ if (mTorProcess == null) {
|
|
234
|
+ if (callback != null) {
|
|
235
|
+ callback.sendSuccess(null);
|
|
236
|
+ }
|
|
237
|
+ return;
|
|
238
|
+ }
|
|
239
|
+ String handle = message.getString("handle", "");
|
|
240
|
+ if (!mTorProcess.getHandle().equals(handle)) {
|
|
241
|
+ GeckoBundle bundle = new GeckoBundle(1);
|
|
242
|
+ bundle.putString(
|
|
243
|
+ "error", "The requested process has not been found. It might have already been stopped.");
|
|
244
|
+ callback.sendError(bundle);
|
|
245
|
+ return;
|
|
246
|
+ }
|
|
247
|
+ mTorProcess.shutdown();
|
|
248
|
+ mTorProcess = null;
|
|
249
|
+ callback.sendSuccess(null);
|
|
250
|
+ }
|
|
251
|
+
|
|
252
|
+ class TorProcess extends Thread {
|
|
253
|
+ private static final String EVENT_TOR_STARTED = "GeckoView:Tor:TorStarted";
|
|
254
|
+ private static final String EVENT_TOR_START_FAILED = "GeckoView:Tor:TorStartFailed";
|
|
255
|
+ private static final String EVENT_TOR_EXITED = "GeckoView:Tor:TorExited";
|
|
256
|
+ private final String mHandle;
|
|
257
|
+ private Process mProcess = null;
|
|
258
|
+
|
|
259
|
+ TorProcess(String handle) {
|
|
260
|
+ mHandle = handle;
|
|
261
|
+ setName("tor-process-" + handle);
|
|
262
|
+ start();
|
|
263
|
+ }
|
200
|
264
|
|
201
|
|
- @Override
|
202
|
|
- protected void onPostExecute(TorSettings torSettings) {
|
203
|
|
- mSettings = torSettings;
|
204
|
|
- if (TorLegacyAndroidSettings.unmigrated()) {
|
205
|
|
- setSettings(mSettings, true, true);
|
206
|
|
- TorLegacyAndroidSettings.setMigrated();
|
207
|
|
- }
|
|
265
|
+ @Override
|
|
266
|
+ public void run() {
|
|
267
|
+ cleanIpcDirectory();
|
|
268
|
+
|
|
269
|
+ final String ipcDir = TorIntegrationAndroid.this.mIpcDirectory;
|
|
270
|
+ final ArrayList<String> args = new ArrayList<>();
|
|
271
|
+ args.add(mLibraryDir + "/libTor.so");
|
|
272
|
+ args.add("DisableNetwork");
|
|
273
|
+ args.add("1");
|
|
274
|
+ args.add("+__ControlPort");
|
|
275
|
+ args.add("unix:" + ipcDir + CONTROL_PORT_FILE);
|
|
276
|
+ args.add("+__SocksPort");
|
|
277
|
+ args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth");
|
|
278
|
+ args.add("CookieAuthentication");
|
|
279
|
+ args.add("1");
|
|
280
|
+ args.add("CookieAuthFile");
|
|
281
|
+ args.add(ipcDir + COOKIE_AUTH_FILE);
|
|
282
|
+ args.add("DataDirectory");
|
|
283
|
+ args.add(mDataDir.getAbsolutePath());
|
|
284
|
+ boolean copied = true;
|
|
285
|
+ try {
|
|
286
|
+ copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args);
|
|
287
|
+ } catch (IOException e) {
|
|
288
|
+ Log.w(
|
|
289
|
+ TAG, "torrc-default cannot be created, pluggable transports will not be available", e);
|
|
290
|
+ copied = false;
|
|
291
|
+ }
|
|
292
|
+ // tor-browser#42607: For now we do not ship geoip databases, as we
|
|
293
|
+ // do not have the circuit display functionality and they allow us
|
|
294
|
+ // to save some space in the final APK.
|
|
295
|
+ /*try {
|
|
296
|
+ copyAndUseConfigFile("GeoIPFile", "geoip", args);
|
|
297
|
+ copyAndUseConfigFile("GeoIPv6File", "geoip6", args);
|
|
298
|
+ } catch (IOException e) {
|
|
299
|
+ Log.w(TAG, "GeoIP files cannot be created, this feature will not be available.", e);
|
|
300
|
+ copied = false;
|
|
301
|
+ }*/
|
|
302
|
+ mCopiedConfigFiles = copied;
|
|
303
|
+
|
|
304
|
+ Log.d(TAG, "Starting tor with the follwing args: " + args.toString());
|
|
305
|
+ final ProcessBuilder builder = new ProcessBuilder(args);
|
|
306
|
+ builder.directory(new File(mLibraryDir));
|
|
307
|
+ try {
|
|
308
|
+ mProcess = builder.start();
|
|
309
|
+ } catch (IOException e) {
|
|
310
|
+ Log.e(TAG, "Cannot start tor " + mHandle, e);
|
|
311
|
+ final GeckoBundle data = new GeckoBundle(2);
|
|
312
|
+ data.putString("handle", mHandle);
|
|
313
|
+ data.putString("error", e.getMessage());
|
|
314
|
+ EventDispatcher.getInstance().dispatch(EVENT_TOR_START_FAILED, data);
|
|
315
|
+ return;
|
|
316
|
+ }
|
|
317
|
+ Log.i(TAG, "Tor process " + mHandle + " started.");
|
|
318
|
+ {
|
|
319
|
+ final GeckoBundle data = new GeckoBundle(1);
|
|
320
|
+ data.putString("handle", mHandle);
|
|
321
|
+ EventDispatcher.getInstance().dispatch(EVENT_TOR_STARTED, data);
|
|
322
|
+ }
|
|
323
|
+ try {
|
|
324
|
+ BufferedReader reader =
|
|
325
|
+ new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
|
326
|
+ String line;
|
|
327
|
+ while ((line = reader.readLine()) != null) {
|
|
328
|
+ Log.i(TAG, "[tor-" + mHandle + "] " + line);
|
208
|
329
|
}
|
|
330
|
+ } catch (IOException e) {
|
|
331
|
+ Log.e(TAG, "Failed to read stdout of the tor process " + mHandle, e);
|
|
332
|
+ }
|
|
333
|
+ Log.d(TAG, "Exiting the stdout loop for process " + mHandle);
|
|
334
|
+ final GeckoBundle data = new GeckoBundle(2);
|
|
335
|
+ data.putString("handle", mHandle);
|
|
336
|
+ try {
|
|
337
|
+ data.putInt("status", mProcess.waitFor());
|
|
338
|
+ } catch (InterruptedException e) {
|
|
339
|
+ Log.e(TAG, "Failed to wait for the tor process " + mHandle, e);
|
|
340
|
+ data.putInt("status", 0xdeadbeef);
|
|
341
|
+ }
|
|
342
|
+ // FIXME: We usually don't reach this when the application is killed!
|
|
343
|
+ // So, we don't do our cleanup.
|
|
344
|
+ Log.i(TAG, "Tor process " + mHandle + " has exited.");
|
|
345
|
+ EventDispatcher.getInstance().dispatch(EVENT_TOR_EXITED, data);
|
209
|
346
|
}
|
210
|
347
|
|
211
|
|
- private synchronized void startDaemon(final GeckoBundle message, final EventCallback callback) {
|
212
|
|
- // Let JS generate this to possibly reduce the chance of race conditions.
|
213
|
|
- String handle = message.getString("handle", "");
|
214
|
|
- if (handle.isEmpty()) {
|
215
|
|
- Log.e(TAG, "Requested to start a tor process without a handle.");
|
216
|
|
- callback.sendError("Expected a handle for the new process.");
|
217
|
|
- return;
|
|
348
|
+ private void cleanIpcDirectory() {
|
|
349
|
+ File directory = new File(TorIntegrationAndroid.this.mIpcDirectory);
|
|
350
|
+ if (!directory.isDirectory()) {
|
|
351
|
+ if (!directory.mkdirs()) {
|
|
352
|
+ Log.e(TAG, "Failed to create the IPC directory.");
|
|
353
|
+ return;
|
218
|
354
|
}
|
219
|
|
- Log.d(TAG, "Starting the a tor process with handle " + handle);
|
220
|
|
-
|
221
|
|
- TorProcess previousProcess = mTorProcess;
|
222
|
|
- if (previousProcess != null) {
|
223
|
|
- Log.w(TAG, "We still have a running process: " + previousProcess.getHandle());
|
|
355
|
+ try {
|
|
356
|
+ // First remove the permissions for everybody...
|
|
357
|
+ directory.setReadable(false, false);
|
|
358
|
+ directory.setWritable(false, false);
|
|
359
|
+ directory.setExecutable(false, false);
|
|
360
|
+ // ... then add them back, but only for the owner.
|
|
361
|
+ directory.setReadable(true, true);
|
|
362
|
+ directory.setWritable(true, true);
|
|
363
|
+ directory.setExecutable(true, true);
|
|
364
|
+ } catch (SecurityException e) {
|
|
365
|
+ Log.e(TAG, "Could not set the permissions to the IPC directory.", e);
|
224
|
366
|
}
|
225
|
|
- mTorProcess = new TorProcess(handle);
|
|
367
|
+ return;
|
|
368
|
+ }
|
|
369
|
+ // We assume we do not have child directories, only files
|
|
370
|
+ File[] maybeFiles = directory.listFiles();
|
|
371
|
+ if (maybeFiles != null) {
|
|
372
|
+ for (File file : maybeFiles) {
|
|
373
|
+ if (!file.delete()) {
|
|
374
|
+ Log.d(TAG, "Could not delete " + file);
|
|
375
|
+ }
|
|
376
|
+ }
|
|
377
|
+ }
|
|
378
|
+ }
|
226
|
379
|
|
227
|
|
- GeckoBundle bundle = new GeckoBundle(3);
|
228
|
|
- bundle.putString("controlPortPath", mIpcDirectory + CONTROL_PORT_FILE);
|
229
|
|
- bundle.putString("socksPath", mIpcDirectory + SOCKS_FILE);
|
230
|
|
- bundle.putString("cookieFilePath", mIpcDirectory + COOKIE_AUTH_FILE);
|
231
|
|
- callback.sendSuccess(bundle);
|
|
380
|
+ private void copyAndUseConfigFile(String option, String name, ArrayList<String> args)
|
|
381
|
+ throws IOException {
|
|
382
|
+ File file = copyConfigFile(name);
|
|
383
|
+ args.add(option);
|
|
384
|
+ args.add(file.getAbsolutePath());
|
232
|
385
|
}
|
233
|
386
|
|
234
|
|
- private synchronized void stopDaemon(final GeckoBundle message, final EventCallback callback) {
|
235
|
|
- if (mTorProcess == null) {
|
236
|
|
- if (callback != null) {
|
237
|
|
- callback.sendSuccess(null);
|
238
|
|
- }
|
239
|
|
- return;
|
|
387
|
+ private File copyConfigFile(String name) throws IOException {
|
|
388
|
+ final File file = new File(mCacheDir, name);
|
|
389
|
+ if (mCopiedConfigFiles && file.exists()) {
|
|
390
|
+ return file;
|
|
391
|
+ }
|
|
392
|
+
|
|
393
|
+ final Context context = GeckoAppShell.getApplicationContext();
|
|
394
|
+ final InputStream in = context.getAssets().open("common/" + name);
|
|
395
|
+ // Files.copy is API 26+, so use java.io and a loop for now.
|
|
396
|
+ FileOutputStream out = null;
|
|
397
|
+ try {
|
|
398
|
+ out = new FileOutputStream(file);
|
|
399
|
+ } catch (IOException e) {
|
|
400
|
+ in.close();
|
|
401
|
+ throw e;
|
|
402
|
+ }
|
|
403
|
+ try {
|
|
404
|
+ byte buffer[] = new byte[4096];
|
|
405
|
+ int read;
|
|
406
|
+ while ((read = in.read(buffer)) >= 0) {
|
|
407
|
+ out.write(buffer, 0, read);
|
240
|
408
|
}
|
241
|
|
- String handle = message.getString("handle", "");
|
242
|
|
- if (!mTorProcess.getHandle().equals(handle)) {
|
243
|
|
- GeckoBundle bundle = new GeckoBundle(1);
|
244
|
|
- bundle.putString("error", "The requested process has not been found. It might have already been stopped.");
|
245
|
|
- callback.sendError(bundle);
|
246
|
|
- return;
|
|
409
|
+ } finally {
|
|
410
|
+ try {
|
|
411
|
+ in.close();
|
|
412
|
+ } catch (IOException e) {
|
|
413
|
+ Log.w(TAG, "Cannot close the input stream for " + name);
|
247
|
414
|
}
|
248
|
|
- mTorProcess.shutdown();
|
249
|
|
- mTorProcess = null;
|
250
|
|
- callback.sendSuccess(null);
|
|
415
|
+ try {
|
|
416
|
+ out.close();
|
|
417
|
+ } catch (IOException e) {
|
|
418
|
+ Log.w(TAG, "Cannot close the output stream for " + name);
|
|
419
|
+ }
|
|
420
|
+ }
|
|
421
|
+ return file;
|
251
|
422
|
}
|
252
|
423
|
|
253
|
|
- class TorProcess extends Thread {
|
254
|
|
- private static final String EVENT_TOR_STARTED = "GeckoView:Tor:TorStarted";
|
255
|
|
- private static final String EVENT_TOR_START_FAILED = "GeckoView:Tor:TorStartFailed";
|
256
|
|
- private static final String EVENT_TOR_EXITED = "GeckoView:Tor:TorExited";
|
257
|
|
- private final String mHandle;
|
258
|
|
- private Process mProcess = null;
|
259
|
|
-
|
260
|
|
- TorProcess(String handle) {
|
261
|
|
- mHandle = handle;
|
262
|
|
- setName("tor-process-" + handle);
|
263
|
|
- start();
|
|
424
|
+ public void shutdown() {
|
|
425
|
+ if (mProcess != null && mProcess.isAlive()) {
|
|
426
|
+ mProcess.destroy();
|
|
427
|
+ }
|
|
428
|
+ if (isAlive()) {
|
|
429
|
+ try {
|
|
430
|
+ join();
|
|
431
|
+ } catch (InterruptedException e) {
|
|
432
|
+ Log.e(
|
|
433
|
+ TAG,
|
|
434
|
+ "Cannot join the thread for tor process " + mHandle + ", possibly already terminated",
|
|
435
|
+ e);
|
264
|
436
|
}
|
|
437
|
+ }
|
|
438
|
+ }
|
265
|
439
|
|
266
|
|
- @Override
|
267
|
|
- public void run() {
|
268
|
|
- cleanIpcDirectory();
|
269
|
|
-
|
270
|
|
- final String ipcDir = TorIntegrationAndroid.this.mIpcDirectory;
|
271
|
|
- final ArrayList<String> args = new ArrayList<>();
|
272
|
|
- args.add(mLibraryDir + "/libTor.so");
|
273
|
|
- args.add("DisableNetwork");
|
274
|
|
- args.add("1");
|
275
|
|
- args.add("+__ControlPort");
|
276
|
|
- args.add("unix:" + ipcDir + CONTROL_PORT_FILE);
|
277
|
|
- args.add("+__SocksPort");
|
278
|
|
- args.add("unix:" + ipcDir + SOCKS_FILE + " IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth");
|
279
|
|
- args.add("CookieAuthentication");
|
280
|
|
- args.add("1");
|
281
|
|
- args.add("CookieAuthFile");
|
282
|
|
- args.add(ipcDir + COOKIE_AUTH_FILE);
|
283
|
|
- args.add("DataDirectory");
|
284
|
|
- args.add(mDataDir.getAbsolutePath());
|
285
|
|
- boolean copied = true;
|
286
|
|
- try {
|
287
|
|
- copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args);
|
288
|
|
- } catch (IOException e) {
|
289
|
|
- Log.w(TAG, "torrc-default cannot be created, pluggable transports will not be available", e);
|
290
|
|
- copied = false;
|
291
|
|
- }
|
292
|
|
- // tor-browser#42607: For now we do not ship geoip databases, as we
|
293
|
|
- // do not have the circuit display functionality and they allow us
|
294
|
|
- // to save some space in the final APK.
|
295
|
|
- /*try {
|
296
|
|
- copyAndUseConfigFile("GeoIPFile", "geoip", args);
|
297
|
|
- copyAndUseConfigFile("GeoIPv6File", "geoip6", args);
|
298
|
|
- } catch (IOException e) {
|
299
|
|
- Log.w(TAG, "GeoIP files cannot be created, this feature will not be available.", e);
|
300
|
|
- copied = false;
|
301
|
|
- }*/
|
302
|
|
- mCopiedConfigFiles = copied;
|
303
|
|
-
|
304
|
|
- Log.d(TAG, "Starting tor with the follwing args: " + args.toString());
|
305
|
|
- final ProcessBuilder builder = new ProcessBuilder(args);
|
306
|
|
- builder.directory(new File(mLibraryDir));
|
307
|
|
- try {
|
308
|
|
- mProcess = builder.start();
|
309
|
|
- } catch (IOException e) {
|
310
|
|
- Log.e(TAG, "Cannot start tor " + mHandle, e);
|
311
|
|
- final GeckoBundle data = new GeckoBundle(2);
|
312
|
|
- data.putString("handle", mHandle);
|
313
|
|
- data.putString("error", e.getMessage());
|
314
|
|
- EventDispatcher.getInstance().dispatch(EVENT_TOR_START_FAILED, data);
|
315
|
|
- return;
|
316
|
|
- }
|
317
|
|
- Log.i(TAG, "Tor process " + mHandle + " started.");
|
318
|
|
- {
|
319
|
|
- final GeckoBundle data = new GeckoBundle(1);
|
320
|
|
- data.putString("handle", mHandle);
|
321
|
|
- EventDispatcher.getInstance().dispatch(EVENT_TOR_STARTED, data);
|
322
|
|
- }
|
323
|
|
- try {
|
324
|
|
- BufferedReader reader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
325
|
|
- String line;
|
326
|
|
- while ((line = reader.readLine()) != null) {
|
327
|
|
- Log.i(TAG, "[tor-" + mHandle + "] " + line);
|
328
|
|
- }
|
329
|
|
- } catch (IOException e) {
|
330
|
|
- Log.e(TAG, "Failed to read stdout of the tor process " + mHandle, e);
|
331
|
|
- }
|
332
|
|
- Log.d(TAG, "Exiting the stdout loop for process " + mHandle);
|
333
|
|
- final GeckoBundle data = new GeckoBundle(2);
|
334
|
|
- data.putString("handle", mHandle);
|
335
|
|
- try {
|
336
|
|
- data.putInt("status", mProcess.waitFor());
|
337
|
|
- } catch (InterruptedException e) {
|
338
|
|
- Log.e(TAG, "Failed to wait for the tor process " + mHandle, e);
|
339
|
|
- data.putInt("status", 0xdeadbeef);
|
340
|
|
- }
|
341
|
|
- // FIXME: We usually don't reach this when the application is killed!
|
342
|
|
- // So, we don't do our cleanup.
|
343
|
|
- Log.i(TAG, "Tor process " + mHandle + " has exited.");
|
344
|
|
- EventDispatcher.getInstance().dispatch(EVENT_TOR_EXITED, data);
|
345
|
|
- }
|
|
440
|
+ public String getHandle() {
|
|
441
|
+ return mHandle;
|
|
442
|
+ }
|
|
443
|
+ }
|
|
444
|
+
|
|
445
|
+ private synchronized void startMeek(final GeckoBundle message, final EventCallback callback) {
|
|
446
|
+ if (callback == null) {
|
|
447
|
+ Log.e(TAG, "Tried to start Meek without a callback.");
|
|
448
|
+ return;
|
|
449
|
+ }
|
|
450
|
+ mMeekCounter++;
|
|
451
|
+ mMeeks.put(
|
|
452
|
+ new Integer(mMeekCounter),
|
|
453
|
+ new MeekTransport(callback, mMeekCounter, message.getStringArray("arguments")));
|
|
454
|
+ }
|
|
455
|
+
|
|
456
|
+ private synchronized void stopMeek(final GeckoBundle message, final EventCallback callback) {
|
|
457
|
+ final Integer key = message.getInteger("id");
|
|
458
|
+ final MeekTransport meek = mMeeks.remove(key);
|
|
459
|
+ if (meek != null) {
|
|
460
|
+ meek.shutdown();
|
|
461
|
+ }
|
|
462
|
+ if (callback != null) {
|
|
463
|
+ callback.sendSuccess(null);
|
|
464
|
+ }
|
|
465
|
+ }
|
|
466
|
+
|
|
467
|
+ private class MeekTransport extends Thread {
|
|
468
|
+ private static final String TRANSPORT = "meek_lite";
|
|
469
|
+ private Process mProcess;
|
|
470
|
+ private final EventCallback mCallback;
|
|
471
|
+ private final int mId;
|
|
472
|
+
|
|
473
|
+ MeekTransport(final EventCallback callback, int id, String[] args) {
|
|
474
|
+ setName("meek-" + id);
|
|
475
|
+
|
|
476
|
+ final String command = mLibraryDir + "/libObfs4proxy.so";
|
|
477
|
+ ArrayList<String> argList = new ArrayList<String>();
|
|
478
|
+ argList.add(command);
|
|
479
|
+ if (args != null && args.length > 0) {
|
|
480
|
+ // Normally not used, but it helps to debug only by editing JS.
|
|
481
|
+ Log.d(TAG, "Requested custom arguments for meek: " + String.join(" ", args));
|
|
482
|
+ argList.addAll(Arrays.asList(args));
|
|
483
|
+ }
|
|
484
|
+ final ProcessBuilder builder = new ProcessBuilder(argList);
|
|
485
|
+
|
|
486
|
+ File ptStateDir = new File(mDataDir, "pt_state");
|
|
487
|
+ Log.d(TAG, "Using " + ptStateDir.getAbsolutePath() + " as a state directory for meek.");
|
|
488
|
+ final Map<String, String> env = builder.environment();
|
|
489
|
+ env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1");
|
|
490
|
+ env.put("TOR_PT_STATE_LOCATION", ptStateDir.getAbsolutePath());
|
|
491
|
+ env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1");
|
|
492
|
+ env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT);
|
|
493
|
+
|
|
494
|
+ mCallback = callback;
|
|
495
|
+ mId = id;
|
|
496
|
+ try {
|
|
497
|
+ // We expect this process to be short-lived, therefore we do not bother with
|
|
498
|
+ // implementing this as a service.
|
|
499
|
+ mProcess = builder.start();
|
|
500
|
+ } catch (IOException e) {
|
|
501
|
+ Log.e(TAG, "Cannot start the PT", e);
|
|
502
|
+ callback.sendError(e.getMessage());
|
|
503
|
+ return;
|
|
504
|
+ }
|
|
505
|
+ start();
|
|
506
|
+ }
|
346
|
507
|
|
347
|
|
- private void cleanIpcDirectory() {
|
348
|
|
- File directory = new File(TorIntegrationAndroid.this.mIpcDirectory);
|
349
|
|
- if (!directory.isDirectory()) {
|
350
|
|
- if (!directory.mkdirs()) {
|
351
|
|
- Log.e(TAG, "Failed to create the IPC directory.");
|
352
|
|
- return;
|
353
|
|
- }
|
354
|
|
- try {
|
355
|
|
- // First remove the permissions for everybody...
|
356
|
|
- directory.setReadable(false, false);
|
357
|
|
- directory.setWritable(false, false);
|
358
|
|
- directory.setExecutable(false, false);
|
359
|
|
- // ... then add them back, but only for the owner.
|
360
|
|
- directory.setReadable(true, true);
|
361
|
|
- directory.setWritable(true, true);
|
362
|
|
- directory.setExecutable(true, true);
|
363
|
|
- } catch (SecurityException e) {
|
364
|
|
- Log.e(TAG, "Could not set the permissions to the IPC directory.", e);
|
365
|
|
- }
|
366
|
|
- return;
|
|
508
|
+ /**
|
|
509
|
+ * Parse the standard output of the pluggable transport to find the hostname and port it is
|
|
510
|
+ * listening on.
|
|
511
|
+ *
|
|
512
|
+ * <p>See also the specs for the IPC protocol at https://spec.torproject.org/pt-spec/ipc.html.
|
|
513
|
+ */
|
|
514
|
+ @Override
|
|
515
|
+ public void run() {
|
|
516
|
+ final String PROTOCOL_VERSION = "1";
|
|
517
|
+ String hostname = "";
|
|
518
|
+ boolean valid = false;
|
|
519
|
+ int port = 0;
|
|
520
|
+ String error = "Did not see a CMETHOD";
|
|
521
|
+ try {
|
|
522
|
+ InputStreamReader isr = new InputStreamReader(mProcess.getInputStream());
|
|
523
|
+ BufferedReader reader = new BufferedReader(isr);
|
|
524
|
+ String line;
|
|
525
|
+ while ((line = reader.readLine()) != null) {
|
|
526
|
+ line = line.trim();
|
|
527
|
+ Log.d(TAG, "Meek line: " + line);
|
|
528
|
+ // Split produces always at least one item
|
|
529
|
+ String[] tokens = line.split(" ");
|
|
530
|
+ if ("VERSION".equals(tokens[0])
|
|
531
|
+ && (tokens.length != 2 || !PROTOCOL_VERSION.equals(tokens[1]))) {
|
|
532
|
+ error = "Bad version: " + line;
|
|
533
|
+ break;
|
|
534
|
+ }
|
|
535
|
+ if ("CMETHOD".equals(tokens[0])) {
|
|
536
|
+ if (tokens.length != 4) {
|
|
537
|
+ error = "Bad number of tokens in CMETHOD: " + line;
|
|
538
|
+ break;
|
367
|
539
|
}
|
368
|
|
- // We assume we do not have child directories, only files
|
369
|
|
- File[] maybeFiles = directory.listFiles();
|
370
|
|
- if (maybeFiles != null) {
|
371
|
|
- for (File file : maybeFiles) {
|
372
|
|
- if (!file.delete()) {
|
373
|
|
- Log.d(TAG, "Could not delete " + file);
|
374
|
|
- }
|
375
|
|
- }
|
|
540
|
+ if (!tokens[1].equals(TRANSPORT)) {
|
|
541
|
+ error = "Unexpected transport: " + tokens[1];
|
|
542
|
+ break;
|
376
|
543
|
}
|
377
|
|
- }
|
378
|
|
-
|
379
|
|
- private void copyAndUseConfigFile(String option, String name, ArrayList<String> args) throws IOException {
|
380
|
|
- File file = copyConfigFile(name);
|
381
|
|
- args.add(option);
|
382
|
|
- args.add(file.getAbsolutePath());
|
383
|
|
- }
|
384
|
|
-
|
385
|
|
- private File copyConfigFile(String name) throws IOException {
|
386
|
|
- final File file = new File(mCacheDir, name);
|
387
|
|
- if (mCopiedConfigFiles && file.exists()) {
|
388
|
|
- return file;
|
|
544
|
+ if (!"socks5".equals(tokens[2])) {
|
|
545
|
+ error = "Unexpected proxy type: " + tokens[2];
|
|
546
|
+ break;
|
389
|
547
|
}
|
390
|
|
-
|
391
|
|
- final Context context = GeckoAppShell.getApplicationContext();
|
392
|
|
- final InputStream in = context.getAssets().open("common/" + name);
|
393
|
|
- // Files.copy is API 26+, so use java.io and a loop for now.
|
394
|
|
- FileOutputStream out = null;
|
395
|
|
- try {
|
396
|
|
- out = new FileOutputStream(file);
|
397
|
|
- } catch (IOException e) {
|
398
|
|
- in.close();
|
399
|
|
- throw e;
|
|
548
|
+ String[] addr = tokens[3].split(":");
|
|
549
|
+ if (addr.length != 2) {
|
|
550
|
+ error = "Invalid address";
|
|
551
|
+ break;
|
400
|
552
|
}
|
|
553
|
+ hostname = addr[0];
|
401
|
554
|
try {
|
402
|
|
- byte buffer[] = new byte[4096];
|
403
|
|
- int read;
|
404
|
|
- while ((read = in.read(buffer)) >= 0) {
|
405
|
|
- out.write(buffer, 0, read);
|
406
|
|
- }
|
407
|
|
- } finally {
|
408
|
|
- try {
|
409
|
|
- in.close();
|
410
|
|
- } catch (IOException e) {
|
411
|
|
- Log.w(TAG, "Cannot close the input stream for " + name);
|
412
|
|
- }
|
413
|
|
- try {
|
414
|
|
- out.close();
|
415
|
|
- } catch (IOException e) {
|
416
|
|
- Log.w(TAG, "Cannot close the output stream for " + name);
|
417
|
|
- }
|
418
|
|
- }
|
419
|
|
- return file;
|
420
|
|
- }
|
421
|
|
-
|
422
|
|
- public void shutdown() {
|
423
|
|
- if (mProcess != null && mProcess.isAlive()) {
|
424
|
|
- mProcess.destroy();
|
|
555
|
+ port = Integer.parseInt(addr[1]);
|
|
556
|
+ } catch (NumberFormatException e) {
|
|
557
|
+ error = "Invalid port: " + e.getMessage();
|
|
558
|
+ break;
|
425
|
559
|
}
|
426
|
|
- if (isAlive()) {
|
427
|
|
- try {
|
428
|
|
- join();
|
429
|
|
- } catch (InterruptedException e) {
|
430
|
|
- Log.e(TAG, "Cannot join the thread for tor process " + mHandle + ", possibly already terminated", e);
|
431
|
|
- }
|
|
560
|
+ if (port < 1 || port > 65535) {
|
|
561
|
+ error = "Invalid port: out of bounds";
|
|
562
|
+ break;
|
432
|
563
|
}
|
|
564
|
+ valid = true;
|
|
565
|
+ break;
|
|
566
|
+ }
|
|
567
|
+ if (tokens[0].endsWith("-ERROR")) {
|
|
568
|
+ error = "Seen an error: " + line;
|
|
569
|
+ break;
|
|
570
|
+ }
|
433
|
571
|
}
|
434
|
|
-
|
435
|
|
- public String getHandle() {
|
436
|
|
- return mHandle;
|
437
|
|
- }
|
|
572
|
+ } catch (Exception e) {
|
|
573
|
+ error = e.getMessage();
|
|
574
|
+ }
|
|
575
|
+ if (valid) {
|
|
576
|
+ Log.d(TAG, "Setup a meek transport " + mId + ": " + hostname + ":" + port);
|
|
577
|
+ final GeckoBundle bundle = new GeckoBundle(3);
|
|
578
|
+ bundle.putInt("id", mId);
|
|
579
|
+ bundle.putString("address", hostname);
|
|
580
|
+ bundle.putInt("port", port);
|
|
581
|
+ mCallback.sendSuccess(bundle);
|
|
582
|
+ } else {
|
|
583
|
+ Log.e(TAG, "Failed to get a usable config from the PT: " + error);
|
|
584
|
+ mCallback.sendError(error);
|
|
585
|
+ return;
|
|
586
|
+ }
|
|
587
|
+ dumpStdout();
|
438
|
588
|
}
|
439
|
589
|
|
440
|
|
- private synchronized void startMeek(final GeckoBundle message, final EventCallback callback) {
|
441
|
|
- if (callback == null) {
|
442
|
|
- Log.e(TAG, "Tried to start Meek without a callback.");
|
443
|
|
- return;
|
444
|
|
- }
|
445
|
|
- mMeekCounter++;
|
446
|
|
- mMeeks.put(new Integer(mMeekCounter), new MeekTransport(callback, mMeekCounter));
|
|
590
|
+ void shutdown() {
|
|
591
|
+ if (mProcess != null) {
|
|
592
|
+ Log.i(TAG, "Shutting down meek process " + mId);
|
|
593
|
+ mProcess.destroy();
|
|
594
|
+ mProcess = null;
|
|
595
|
+ } else {
|
|
596
|
+ Log.w(
|
|
597
|
+ TAG,
|
|
598
|
+ "Shutdown request on the meek process " + mId + " that has already been shutdown.");
|
|
599
|
+ }
|
|
600
|
+ try {
|
|
601
|
+ join();
|
|
602
|
+ } catch (InterruptedException e) {
|
|
603
|
+ Log.e(TAG, "Could not join the meek thread", e);
|
|
604
|
+ }
|
447
|
605
|
}
|
448
|
606
|
|
449
|
|
- private synchronized void stopMeek(final GeckoBundle message, final EventCallback callback) {
|
450
|
|
- final Integer key = message.getInteger("id");
|
451
|
|
- final MeekTransport meek = mMeeks.remove(key);
|
452
|
|
- if (meek != null) {
|
453
|
|
- meek.shutdown();
|
454
|
|
- }
|
455
|
|
- if (callback != null) {
|
456
|
|
- callback.sendSuccess(null);
|
|
607
|
+ void dumpStdout() {
|
|
608
|
+ try {
|
|
609
|
+ BufferedReader reader =
|
|
610
|
+ new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
|
611
|
+ String line;
|
|
612
|
+ while ((line = reader.readLine()) != null) {
|
|
613
|
+ Log.d(TAG, "[meek-" + mId + "] " + line);
|
457
|
614
|
}
|
|
615
|
+ } catch (InterruptedIOException e) {
|
|
616
|
+ // This happens normally, do not log it.
|
|
617
|
+ } catch (IOException e) {
|
|
618
|
+ Log.e(TAG, "Failed to read stdout of the meek process process " + mId, e);
|
|
619
|
+ }
|
458
|
620
|
}
|
|
621
|
+ }
|
459
|
622
|
|
460
|
|
- private class MeekTransport extends Thread {
|
461
|
|
- private static final String TRANSPORT = "meek_lite";
|
462
|
|
- private Process mProcess;
|
463
|
|
- private final EventCallback mCallback;
|
464
|
|
- private final int mId;
|
465
|
|
-
|
466
|
|
- MeekTransport(final EventCallback callback, int id) {
|
467
|
|
- setName("meek-" + id);
|
468
|
|
- final ProcessBuilder builder = new ProcessBuilder(mLibraryDir + "/libObfs4proxy.so");
|
469
|
|
- {
|
470
|
|
- File ptStateDir = new File(mDataDir, "pt_state");
|
471
|
|
- final Map<String, String> env = builder.environment();
|
472
|
|
- env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1");
|
473
|
|
- env.put("TOR_PT_STATE_LOCATION", ptStateDir.getAbsolutePath());
|
474
|
|
- env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1");
|
475
|
|
- env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT);
|
476
|
|
- }
|
477
|
|
- mCallback = callback;
|
478
|
|
- mId = id;
|
479
|
|
- try {
|
480
|
|
- // We expect this process to be short-lived, therefore we do not bother with
|
481
|
|
- // implementing this as a service.
|
482
|
|
- mProcess = builder.start();
|
483
|
|
- } catch (IOException e) {
|
484
|
|
- Log.e(TAG, "Cannot start the PT", e);
|
485
|
|
- callback.sendError(e.getMessage());
|
486
|
|
- return;
|
487
|
|
- }
|
488
|
|
- start();
|
489
|
|
- }
|
|
623
|
+ public interface BootstrapStateChangeListener {
|
|
624
|
+ void onBootstrapStateChange(String state);
|
490
|
625
|
|
491
|
|
- /**
|
492
|
|
- * Parse the standard output of the pluggable transport to find the hostname and port it is
|
493
|
|
- * listening on.
|
494
|
|
- * <p>
|
495
|
|
- * See also the specs for the IPC protocol at https://spec.torproject.org/pt-spec/ipc.html.
|
496
|
|
- */
|
497
|
|
- @Override
|
498
|
|
- public void run() {
|
499
|
|
- final String PROTOCOL_VERSION = "1";
|
500
|
|
- String hostname = "";
|
501
|
|
- boolean valid = false;
|
502
|
|
- int port = 0;
|
503
|
|
- String error = "Did not see a CMETHOD";
|
504
|
|
- try {
|
505
|
|
- InputStreamReader isr = new InputStreamReader(mProcess.getInputStream());
|
506
|
|
- BufferedReader reader = new BufferedReader(isr);
|
507
|
|
- String line;
|
508
|
|
- while ((line = reader.readLine()) != null) {
|
509
|
|
- line = line.trim();
|
510
|
|
- Log.d(TAG, "Meek line: " + line);
|
511
|
|
- // Split produces always at least one item
|
512
|
|
- String[] tokens = line.split(" ");
|
513
|
|
- if ("VERSION".equals(tokens[0]) && (tokens.length != 2 || !PROTOCOL_VERSION.equals(tokens[1]))) {
|
514
|
|
- error = "Bad version: " + line;
|
515
|
|
- break;
|
516
|
|
- }
|
517
|
|
- if ("CMETHOD".equals(tokens[0])) {
|
518
|
|
- if (tokens.length != 4) {
|
519
|
|
- error = "Bad number of tokens in CMETHOD: " + line;
|
520
|
|
- break;
|
521
|
|
- }
|
522
|
|
- if (!tokens[1].equals(TRANSPORT)) {
|
523
|
|
- error = "Unexpected transport: " + tokens[1];
|
524
|
|
- break;
|
525
|
|
- }
|
526
|
|
- if (!"socks5".equals(tokens[2])) {
|
527
|
|
- error = "Unexpected proxy type: " + tokens[2];
|
528
|
|
- break;
|
529
|
|
- }
|
530
|
|
- String[] addr = tokens[3].split(":");
|
531
|
|
- if (addr.length != 2) {
|
532
|
|
- error = "Invalid address";
|
533
|
|
- break;
|
534
|
|
- }
|
535
|
|
- hostname = addr[0];
|
536
|
|
- try {
|
537
|
|
- port = Integer.parseInt(addr[1]);
|
538
|
|
- } catch (NumberFormatException e) {
|
539
|
|
- error = "Invalid port: " + e.getMessage();
|
540
|
|
- break;
|
541
|
|
- }
|
542
|
|
- if (port < 1 || port > 65535) {
|
543
|
|
- error = "Invalid port: out of bounds";
|
544
|
|
- break;
|
545
|
|
- }
|
546
|
|
- valid = true;
|
547
|
|
- break;
|
548
|
|
- }
|
549
|
|
- if (tokens[0].endsWith("-ERROR")) {
|
550
|
|
- error = "Seen an error: " + line;
|
551
|
|
- break;
|
552
|
|
- }
|
553
|
|
- }
|
554
|
|
- } catch (Exception e) {
|
555
|
|
- error = e.getMessage();
|
556
|
|
- }
|
557
|
|
- if (valid) {
|
558
|
|
- Log.d(TAG, "Setup a meek transport " + mId + ": " + hostname + ":" + port);
|
559
|
|
- final GeckoBundle bundle = new GeckoBundle(3);
|
560
|
|
- bundle.putInt("id", mId);
|
561
|
|
- bundle.putString("address", hostname);
|
562
|
|
- bundle.putInt("port", port);
|
563
|
|
- mCallback.sendSuccess(bundle);
|
564
|
|
- } else {
|
565
|
|
- Log.e(TAG, "Failed to get a usable config from the PT: " + error);
|
566
|
|
- mCallback.sendError(error);
|
567
|
|
- }
|
568
|
|
- }
|
|
626
|
+ void onBootstrapProgress(double progress, boolean hasWarnings);
|
569
|
627
|
|
570
|
|
- void shutdown() {
|
571
|
|
- if (mProcess != null) {
|
572
|
|
- mProcess.destroy();
|
573
|
|
- mProcess = null;
|
574
|
|
- }
|
575
|
|
- try {
|
576
|
|
- join();
|
577
|
|
- } catch (InterruptedException e) {
|
578
|
|
- Log.e(TAG, "Could not join the meek thread", e);
|
579
|
|
- }
|
580
|
|
- }
|
581
|
|
- }
|
|
628
|
+ void onBootstrapComplete();
|
582
|
629
|
|
583
|
|
- public interface BootstrapStateChangeListener {
|
584
|
|
- void onBootstrapStateChange(String state);
|
585
|
|
- void onBootstrapProgress(double progress, boolean hasWarnings);
|
586
|
|
- void onBootstrapComplete();
|
587
|
|
- void onBootstrapError(String code, String message, String phase, String reason);
|
588
|
|
- void onSettingsRequested();
|
589
|
|
- }
|
|
630
|
+ void onBootstrapError(String code, String message, String phase, String reason);
|
590
|
631
|
|
591
|
|
- public interface TorLogListener {
|
592
|
|
- void onLog(String logType, String message);
|
593
|
|
- }
|
|
632
|
+ void onSettingsRequested();
|
|
633
|
+ }
|
594
|
634
|
|
595
|
|
- private @NonNull void reloadSettings() {
|
596
|
|
- EventDispatcher.getInstance().queryBundle(EVENT_SETTINGS_GET).then( new GeckoResult.OnValueListener<GeckoBundle, Void>() {
|
597
|
|
- public GeckoResult<Void> onValue(final GeckoBundle bundle) {
|
|
635
|
+ public interface TorLogListener {
|
|
636
|
+ void onLog(String logType, String message);
|
|
637
|
+ }
|
|
638
|
+
|
|
639
|
+ private @NonNull void reloadSettings() {
|
|
640
|
+ EventDispatcher.getInstance()
|
|
641
|
+ .queryBundle(EVENT_SETTINGS_GET)
|
|
642
|
+ .then(
|
|
643
|
+ new GeckoResult.OnValueListener<GeckoBundle, Void>() {
|
|
644
|
+ public GeckoResult<Void> onValue(final GeckoBundle bundle) {
|
598
|
645
|
mSettings = new TorSettings(bundle);
|
599
|
646
|
return new GeckoResult<Void>();
|
600
|
|
- }
|
601
|
|
- });
|
602
|
|
- }
|
|
647
|
+ }
|
|
648
|
+ });
|
|
649
|
+ }
|
603
|
650
|
|
604
|
|
- public TorSettings getSettings() {
|
605
|
|
- return mSettings;
|
606
|
|
- }
|
|
651
|
+ public TorSettings getSettings() {
|
|
652
|
+ return mSettings;
|
|
653
|
+ }
|
607
|
654
|
|
608
|
|
- public void setSettings(final TorSettings settings, boolean save, boolean apply) {
|
609
|
|
- mSettings = settings;
|
|
655
|
+ public void setSettings(final TorSettings settings, boolean save, boolean apply) {
|
|
656
|
+ mSettings = settings;
|
610
|
657
|
|
611
|
|
- emitSetSettings(settings, save, apply).then(
|
|
658
|
+ emitSetSettings(settings, save, apply)
|
|
659
|
+ .then(
|
612
|
660
|
new GeckoResult.OnValueListener<Void, Void>() {
|
613
|
|
- public GeckoResult<Void> onValue(Void v) {
|
614
|
|
- return new GeckoResult<Void>();
|
615
|
|
- }
|
|
661
|
+ public GeckoResult<Void> onValue(Void v) {
|
|
662
|
+ return new GeckoResult<Void>();
|
|
663
|
+ }
|
616
|
664
|
},
|
617
|
665
|
new GeckoResult.OnExceptionListener<Void>() {
|
618
|
|
- public GeckoResult<Void> onException(final Throwable e) {
|
619
|
|
- Log.e(TAG, "Failed to set settings", e);
|
620
|
|
- reloadSettings();
|
621
|
|
- return new GeckoResult<Void>();
|
622
|
|
- }
|
|
666
|
+ public GeckoResult<Void> onException(final Throwable e) {
|
|
667
|
+ Log.e(TAG, "Failed to set settings", e);
|
|
668
|
+ reloadSettings();
|
|
669
|
+ return new GeckoResult<Void>();
|
|
670
|
+ }
|
623
|
671
|
});
|
624
|
|
- }
|
625
|
|
-
|
626
|
|
- private @NonNull GeckoResult<Void> emitSetSettings(final TorSettings settings, boolean save, boolean apply) {
|
627
|
|
- GeckoBundle bundle = new GeckoBundle(3);
|
628
|
|
- bundle.putBoolean("save", save);
|
629
|
|
- bundle.putBoolean("apply", apply);
|
630
|
|
- bundle.putBundle("settings", settings.asGeckoBundle());
|
631
|
|
- return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SET, bundle);
|
632
|
|
- }
|
633
|
|
-
|
634
|
|
- public @NonNull GeckoResult<Void> applySettings() {
|
635
|
|
- return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_APPLY);
|
636
|
|
- }
|
637
|
|
-
|
638
|
|
- public @NonNull GeckoResult<Void> saveSettings() {
|
639
|
|
- return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SAVE);
|
640
|
|
- }
|
641
|
|
-
|
642
|
|
- public @NonNull GeckoResult<Void> beginBootstrap() {
|
643
|
|
- return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN);
|
644
|
|
- }
|
645
|
|
-
|
646
|
|
- public @NonNull GeckoResult<Void> beginAutoBootstrap(final String countryCode) {
|
647
|
|
- final GeckoBundle bundle = new GeckoBundle(1);
|
648
|
|
- bundle.putString("countryCode", countryCode);
|
649
|
|
- return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN_AUTO, bundle);
|
650
|
|
- }
|
651
|
|
-
|
652
|
|
- public @NonNull GeckoResult<Void> beginAutoBootstrap() {
|
653
|
|
- return beginAutoBootstrap(null);
|
654
|
|
- }
|
655
|
|
-
|
656
|
|
- public @NonNull GeckoResult<Void> cancelBootstrap() {
|
657
|
|
- return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_CANCEL);
|
658
|
|
- }
|
659
|
|
-
|
660
|
|
- public void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
|
661
|
|
- mBootstrapStateListeners.add(listener);
|
662
|
|
- }
|
663
|
|
-
|
664
|
|
- public void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
|
665
|
|
- mBootstrapStateListeners.remove(listener);
|
666
|
|
- }
|
667
|
|
-
|
668
|
|
- private final HashSet<BootstrapStateChangeListener> mBootstrapStateListeners = new HashSet<>();
|
669
|
|
-
|
670
|
|
- public void registerLogListener(TorLogListener listener) {
|
671
|
|
- mLogListeners.add(listener);
|
672
|
|
- }
|
673
|
|
-
|
674
|
|
- public void unregisterLogListener(TorLogListener listener) {
|
675
|
|
- mLogListeners.remove(listener);
|
676
|
|
- }
|
677
|
|
-
|
678
|
|
- private final HashSet<TorLogListener> mLogListeners = new HashSet<>();
|
|
672
|
+ }
|
|
673
|
+
|
|
674
|
+ private @NonNull GeckoResult<Void> emitSetSettings(
|
|
675
|
+ final TorSettings settings, boolean save, boolean apply) {
|
|
676
|
+ GeckoBundle bundle = new GeckoBundle(3);
|
|
677
|
+ bundle.putBoolean("save", save);
|
|
678
|
+ bundle.putBoolean("apply", apply);
|
|
679
|
+ bundle.putBundle("settings", settings.asGeckoBundle());
|
|
680
|
+ return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SET, bundle);
|
|
681
|
+ }
|
|
682
|
+
|
|
683
|
+ public @NonNull GeckoResult<Void> applySettings() {
|
|
684
|
+ return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_APPLY);
|
|
685
|
+ }
|
|
686
|
+
|
|
687
|
+ public @NonNull GeckoResult<Void> saveSettings() {
|
|
688
|
+ return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SAVE);
|
|
689
|
+ }
|
|
690
|
+
|
|
691
|
+ public @NonNull GeckoResult<Void> beginBootstrap() {
|
|
692
|
+ return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN);
|
|
693
|
+ }
|
|
694
|
+
|
|
695
|
+ public @NonNull GeckoResult<Void> beginAutoBootstrap(final String countryCode) {
|
|
696
|
+ final GeckoBundle bundle = new GeckoBundle(1);
|
|
697
|
+ bundle.putString("countryCode", countryCode);
|
|
698
|
+ return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN_AUTO, bundle);
|
|
699
|
+ }
|
|
700
|
+
|
|
701
|
+ public @NonNull GeckoResult<Void> beginAutoBootstrap() {
|
|
702
|
+ return beginAutoBootstrap(null);
|
|
703
|
+ }
|
|
704
|
+
|
|
705
|
+ public @NonNull GeckoResult<Void> cancelBootstrap() {
|
|
706
|
+ return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_CANCEL);
|
|
707
|
+ }
|
|
708
|
+
|
|
709
|
+ public void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
|
|
710
|
+ mBootstrapStateListeners.add(listener);
|
|
711
|
+ }
|
|
712
|
+
|
|
713
|
+ public void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
|
|
714
|
+ mBootstrapStateListeners.remove(listener);
|
|
715
|
+ }
|
|
716
|
+
|
|
717
|
+ private final HashSet<BootstrapStateChangeListener> mBootstrapStateListeners = new HashSet<>();
|
|
718
|
+
|
|
719
|
+ public void registerLogListener(TorLogListener listener) {
|
|
720
|
+ mLogListeners.add(listener);
|
|
721
|
+ }
|
|
722
|
+
|
|
723
|
+ public void unregisterLogListener(TorLogListener listener) {
|
|
724
|
+ mLogListeners.remove(listener);
|
|
725
|
+ }
|
|
726
|
+
|
|
727
|
+ private final HashSet<TorLogListener> mLogListeners = new HashSet<>();
|
679
|
728
|
} |