Pier Angelo Vendrame pushed to branch tor-browser-115.6.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
da0f3108
by Pier Angelo Vendrame at 2024-01-09T18:38:42+01:00
3 changed files:
- + toolkit/modules/DomainFrontedRequests.sys.mjs
- toolkit/modules/Moat.sys.mjs
- toolkit/modules/moz.build
Changes:
1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4 | + |
|
5 | +const lazy = {};
|
|
6 | + |
|
7 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
8 | + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
9 | + Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
|
10 | + TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
|
11 | + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
12 | + TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
|
|
13 | +});
|
|
14 | + |
|
15 | +/**
|
|
16 | + * The meek pluggable transport takes the reflector URL and front domain as
|
|
17 | + * proxy credentials, which can be prepared with this function.
|
|
18 | + *
|
|
19 | + * @param {string} proxyType The proxy type (socks for socks5 or socks4)
|
|
20 | + * @param {string} reflector The URL of the service hosted by the CDN
|
|
21 | + * @param {string} front The domain to use as a front
|
|
22 | + * @returns {string[]} An array containing [username, password]
|
|
23 | + */
|
|
24 | +function makeMeekCredentials(proxyType, reflector, front) {
|
|
25 | + // Construct the per-connection arguments.
|
|
26 | + let meekClientEscapedArgs = "";
|
|
27 | + |
|
28 | + // Escape aValue per section 3.5 of the PT specification:
|
|
29 | + // First the "<Key>=<Value>" formatted arguments MUST be escaped,
|
|
30 | + // such that all backslash, equal sign, and semicolon characters
|
|
31 | + // are escaped with a backslash.
|
|
32 | + const escapeArgValue = aValue =>
|
|
33 | + aValue
|
|
34 | + ? aValue
|
|
35 | + .replaceAll("\\", "\\\\")
|
|
36 | + .replaceAll("=", "\\=")
|
|
37 | + .replaceAll(";", "\\;")
|
|
38 | + : "";
|
|
39 | + |
|
40 | + if (reflector) {
|
|
41 | + meekClientEscapedArgs += "url=";
|
|
42 | + meekClientEscapedArgs += escapeArgValue(reflector);
|
|
43 | + }
|
|
44 | + |
|
45 | + if (front) {
|
|
46 | + if (meekClientEscapedArgs.length) {
|
|
47 | + meekClientEscapedArgs += ";";
|
|
48 | + }
|
|
49 | + meekClientEscapedArgs += "front=";
|
|
50 | + meekClientEscapedArgs += escapeArgValue(front);
|
|
51 | + }
|
|
52 | + |
|
53 | + // socks5
|
|
54 | + if (proxyType === "socks") {
|
|
55 | + if (meekClientEscapedArgs.length <= 255) {
|
|
56 | + return [meekClientEscapedArgs, "\x00"];
|
|
57 | + }
|
|
58 | + return [
|
|
59 | + meekClientEscapedArgs.substring(0, 255),
|
|
60 | + meekClientEscapedArgs.substring(255),
|
|
61 | + ];
|
|
62 | + } else if (proxyType === "socks4") {
|
|
63 | + return [meekClientEscapedArgs, undefined];
|
|
64 | + }
|
|
65 | + throw new Error(`Unsupported proxy type ${proxyType}.`);
|
|
66 | +}
|
|
67 | + |
|
68 | +/**
|
|
69 | + * Subprocess-based implementation to launch and control a PT process.
|
|
70 | + */
|
|
71 | +class MeekTransport {
|
|
72 | + // These members are used by consumers to setup the proxy to do requests over
|
|
73 | + // meek. They are passed to newProxyInfoWithAuth.
|
|
74 | + proxyType = null;
|
|
75 | + proxyAddress = null;
|
|
76 | + proxyPort = 0;
|
|
77 | + proxyUsername = null;
|
|
78 | + proxyPassword = null;
|
|
79 | + |
|
80 | + #inited = false;
|
|
81 | + #meekClientProcess = null;
|
|
82 | + |
|
83 | + // launches the meekprocess
|
|
84 | + async init(reflector, front) {
|
|
85 | + // ensure we haven't already init'd
|
|
86 | + if (this.#inited) {
|
|
87 | + throw new Error("MeekTransport: Already initialized");
|
|
88 | + }
|
|
89 | + |
|
90 | + try {
|
|
91 | + // figure out which pluggable transport to use
|
|
92 | + const supportedTransports = ["meek", "meek_lite"];
|
|
93 | + const provider = await lazy.TorProviderBuilder.build();
|
|
94 | + const proxy = (await provider.getPluggableTransports()).find(
|
|
95 | + pt =>
|
|
96 | + pt.type === "exec" &&
|
|
97 | + supportedTransports.some(t => pt.transports.includes(t))
|
|
98 | + );
|
|
99 | + if (!proxy) {
|
|
100 | + throw new Error("No supported transport found.");
|
|
101 | + }
|
|
102 | + |
|
103 | + const meekTransport = proxy.transports.find(t =>
|
|
104 | + supportedTransports.includes(t)
|
|
105 | + );
|
|
106 | + // Convert meek client path to absolute path if necessary
|
|
107 | + const meekWorkDir = lazy.TorLauncherUtil.getTorFile(
|
|
108 | + "pt-startup-dir",
|
|
109 | + false
|
|
110 | + );
|
|
111 | + if (lazy.TorLauncherUtil.isPathRelative(proxy.pathToBinary)) {
|
|
112 | + const meekPath = meekWorkDir.clone();
|
|
113 | + meekPath.appendRelativePath(proxy.pathToBinary);
|
|
114 | + proxy.pathToBinary = meekPath.path;
|
|
115 | + }
|
|
116 | + |
|
117 | + // Setup env and start meek process
|
|
118 | + const ptStateDir = lazy.TorLauncherUtil.getTorFile("tordatadir", false);
|
|
119 | + ptStateDir.append("pt_state"); // Match what tor uses.
|
|
120 | + |
|
121 | + const envAdditions = {
|
|
122 | + TOR_PT_MANAGED_TRANSPORT_VER: "1",
|
|
123 | + TOR_PT_STATE_LOCATION: ptStateDir.path,
|
|
124 | + TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
|
|
125 | + TOR_PT_CLIENT_TRANSPORTS: meekTransport,
|
|
126 | + };
|
|
127 | + if (lazy.TorSettings.proxy.enabled) {
|
|
128 | + envAdditions.TOR_PT_PROXY = lazy.TorSettings.proxy.uri;
|
|
129 | + }
|
|
130 | + |
|
131 | + const opts = {
|
|
132 | + command: proxy.pathToBinary,
|
|
133 | + arguments: proxy.options.split(/s+/),
|
|
134 | + workdir: meekWorkDir.path,
|
|
135 | + environmentAppend: true,
|
|
136 | + environment: envAdditions,
|
|
137 | + stderr: "pipe",
|
|
138 | + };
|
|
139 | + |
|
140 | + // Launch meek client
|
|
141 | + this.#meekClientProcess = await lazy.Subprocess.call(opts);
|
|
142 | + |
|
143 | + // Callback chain for reading stderr
|
|
144 | + const stderrLogger = async () => {
|
|
145 | + while (this.#meekClientProcess) {
|
|
146 | + const errString = await this.#meekClientProcess.stderr.readString();
|
|
147 | + if (errString) {
|
|
148 | + console.log(`MeekTransport: stderr => ${errString}`);
|
|
149 | + }
|
|
150 | + }
|
|
151 | + };
|
|
152 | + stderrLogger();
|
|
153 | + |
|
154 | + // Read pt's stdout until terminal (CMETHODS DONE) is reached
|
|
155 | + // returns array of lines for parsing
|
|
156 | + const getInitLines = async (stdout = "") => {
|
|
157 | + stdout += await this.#meekClientProcess.stdout.readString();
|
|
158 | + |
|
159 | + // look for the final message
|
|
160 | + const CMETHODS_DONE = "CMETHODS DONE";
|
|
161 | + let endIndex = stdout.lastIndexOf(CMETHODS_DONE);
|
|
162 | + if (endIndex !== -1) {
|
|
163 | + endIndex += CMETHODS_DONE.length;
|
|
164 | + return stdout.substring(0, endIndex).split("\n");
|
|
165 | + }
|
|
166 | + return getInitLines(stdout);
|
|
167 | + };
|
|
168 | + |
|
169 | + // read our lines from pt's stdout
|
|
170 | + const meekInitLines = await getInitLines();
|
|
171 | + // tokenize our pt lines
|
|
172 | + const meekInitTokens = meekInitLines.map(line => {
|
|
173 | + const tokens = line.split(" ");
|
|
174 | + return {
|
|
175 | + keyword: tokens[0],
|
|
176 | + args: tokens.slice(1),
|
|
177 | + };
|
|
178 | + });
|
|
179 | + |
|
180 | + // parse our pt tokens
|
|
181 | + for (const { keyword, args } of meekInitTokens) {
|
|
182 | + const argsJoined = args.join(" ");
|
|
183 | + let keywordError = false;
|
|
184 | + switch (keyword) {
|
|
185 | + case "VERSION": {
|
|
186 | + if (args.length !== 1 || args[0] !== "1") {
|
|
187 | + keywordError = true;
|
|
188 | + }
|
|
189 | + break;
|
|
190 | + }
|
|
191 | + case "PROXY": {
|
|
192 | + if (args.length !== 1 || args[0] !== "DONE") {
|
|
193 | + keywordError = true;
|
|
194 | + }
|
|
195 | + break;
|
|
196 | + }
|
|
197 | + case "CMETHOD": {
|
|
198 | + if (args.length !== 3) {
|
|
199 | + keywordError = true;
|
|
200 | + break;
|
|
201 | + }
|
|
202 | + const transport = args[0];
|
|
203 | + const proxyType = args[1];
|
|
204 | + const addrPortString = args[2];
|
|
205 | + const addrPort = addrPortString.split(":");
|
|
206 | + |
|
207 | + if (transport !== meekTransport) {
|
|
208 | + throw new Error(
|
|
209 | + `MeekTransport: Expected ${meekTransport} but found ${transport}`
|
|
210 | + );
|
|
211 | + }
|
|
212 | + if (!["socks4", "socks4a", "socks5"].includes(proxyType)) {
|
|
213 | + throw new Error(
|
|
214 | + `MeekTransport: Invalid proxy type => ${proxyType}`
|
|
215 | + );
|
|
216 | + }
|
|
217 | + if (addrPort.length !== 2) {
|
|
218 | + throw new Error(
|
|
219 | + `MeekTransport: Invalid proxy address => ${addrPortString}`
|
|
220 | + );
|
|
221 | + }
|
|
222 | + const addr = addrPort[0];
|
|
223 | + const port = parseInt(addrPort[1]);
|
|
224 | + if (port < 1 || port > 65535) {
|
|
225 | + throw new Error(`MeekTransport: Invalid proxy port => ${port}`);
|
|
226 | + }
|
|
227 | + |
|
228 | + // convert proxy type to strings used by protocol-proxy-servce
|
|
229 | + this.proxyType = proxyType === "socks5" ? "socks" : "socks4";
|
|
230 | + this.proxyAddress = addr;
|
|
231 | + this.proxyPort = port;
|
|
232 | + |
|
233 | + break;
|
|
234 | + }
|
|
235 | + // terminal
|
|
236 | + case "CMETHODS": {
|
|
237 | + if (args.length !== 1 || args[0] !== "DONE") {
|
|
238 | + keywordError = true;
|
|
239 | + }
|
|
240 | + break;
|
|
241 | + }
|
|
242 | + // errors (all fall through):
|
|
243 | + case "VERSION-ERROR":
|
|
244 | + case "ENV-ERROR":
|
|
245 | + case "PROXY-ERROR":
|
|
246 | + case "CMETHOD-ERROR":
|
|
247 | + throw new Error(`MeekTransport: ${keyword} => '${argsJoined}'`);
|
|
248 | + }
|
|
249 | + if (keywordError) {
|
|
250 | + throw new Error(
|
|
251 | + `MeekTransport: Invalid ${keyword} keyword args => '${argsJoined}'`
|
|
252 | + );
|
|
253 | + }
|
|
254 | + }
|
|
255 | + |
|
256 | + // register callback to cleanup on process exit
|
|
257 | + this.#meekClientProcess.wait().then(exitObj => {
|
|
258 | + this.#meekClientProcess = null;
|
|
259 | + this.uninit();
|
|
260 | + });
|
|
261 | + [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
|
|
262 | + this.proxyType,
|
|
263 | + reflector,
|
|
264 | + front
|
|
265 | + );
|
|
266 | + this.#inited = true;
|
|
267 | + } catch (ex) {
|
|
268 | + if (this.#meekClientProcess) {
|
|
269 | + this.#meekClientProcess.kill();
|
|
270 | + this.#meekClientProcess = null;
|
|
271 | + }
|
|
272 | + throw ex;
|
|
273 | + }
|
|
274 | + }
|
|
275 | + |
|
276 | + async uninit() {
|
|
277 | + this.#inited = false;
|
|
278 | + |
|
279 | + await this.#meekClientProcess?.kill();
|
|
280 | + this.#meekClientProcess = null;
|
|
281 | + this.proxyType = null;
|
|
282 | + this.proxyAddress = null;
|
|
283 | + this.proxyPort = 0;
|
|
284 | + this.proxyUsername = null;
|
|
285 | + this.proxyPassword = null;
|
|
286 | + }
|
|
287 | +}
|
|
288 | + |
|
289 | +/**
|
|
290 | + * Android implementation of the Meek process.
|
|
291 | + *
|
|
292 | + * GeckoView does not provide the subprocess module, so we have to use the
|
|
293 | + * EventDispatcher, and have a Java handler start and stop the proxy process.
|
|
294 | + */
|
|
295 | +class MeekTransportAndroid {
|
|
296 | + // These members are used by consumers to setup the proxy to do requests over
|
|
297 | + // meek. They are passed to newProxyInfoWithAuth.
|
|
298 | + proxyType = null;
|
|
299 | + proxyAddress = null;
|
|
300 | + proxyPort = 0;
|
|
301 | + proxyUsername = null;
|
|
302 | + proxyPassword = null;
|
|
303 | + |
|
304 | + /**
|
|
305 | + * An id for process this instance is linked to.
|
|
306 | + *
|
|
307 | + * Since we do not restrict the transport to be a singleton, we need a handle to
|
|
308 | + * identify the process we want to stop when the transport owner is done.
|
|
309 | + * We use a counter incremented on the Java side for now.
|
|
310 | + *
|
|
311 | + * This number must be a positive integer (i.e., 0 is an invalid handler).
|
|
312 | + *
|
|
313 | + * @type {number}
|
|
314 | + */
|
|
315 | + #id = 0;
|
|
316 | + |
|
317 | + async init(reflector, front) {
|
|
318 | + // ensure we haven't already init'd
|
|
319 | + if (this.#id) {
|
|
320 | + throw new Error("MeekTransport: Already initialized");
|
|
321 | + }
|
|
322 | + const details = await lazy.EventDispatcher.instance.sendRequestForResult({
|
|
323 | + type: "GeckoView:Tor:StartMeek",
|
|
324 | + });
|
|
325 | + this.#id = details.id;
|
|
326 | + this.proxyType = "socks";
|
|
327 | + this.proxyAddress = details.address;
|
|
328 | + this.proxyPort = details.port;
|
|
329 | + [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
|
|
330 | + this.proxyType,
|
|
331 | + reflector,
|
|
332 | + front
|
|
333 | + );
|
|
334 | + }
|
|
335 | + |
|
336 | + async uninit() {
|
|
337 | + lazy.EventDispatcher.instance.sendRequest({
|
|
338 | + type: "GeckoView:Tor:StopMeek",
|
|
339 | + id: this.#id,
|
|
340 | + });
|
|
341 | + this.#id = 0;
|
|
342 | + this.proxyType = null;
|
|
343 | + this.proxyAddress = null;
|
|
344 | + this.proxyPort = 0;
|
|
345 | + this.proxyUsername = null;
|
|
346 | + this.proxyPassword = null;
|
|
347 | + }
|
|
348 | +}
|
|
349 | + |
|
350 | +/**
|
|
351 | + * Callback object to promisify the XPCOM request.
|
|
352 | + */
|
|
353 | +class ResponseListener {
|
|
354 | + #response = "";
|
|
355 | + #responsePromise;
|
|
356 | + #resolve;
|
|
357 | + #reject;
|
|
358 | + constructor() {
|
|
359 | + this.#response = "";
|
|
360 | + // we need this promise here because await nsIHttpChannel::asyncOpen does
|
|
361 | + // not return only once the request is complete, it seems to return
|
|
362 | + // after it begins, so we have to get the result from this listener object.
|
|
363 | + // This promise is only resolved once onStopRequest is called
|
|
364 | + this.#responsePromise = new Promise((resolve, reject) => {
|
|
365 | + this.#resolve = resolve;
|
|
366 | + this.#reject = reject;
|
|
367 | + });
|
|
368 | + }
|
|
369 | + |
|
370 | + // callers wait on this for final response
|
|
371 | + response() {
|
|
372 | + return this.#responsePromise;
|
|
373 | + }
|
|
374 | + |
|
375 | + // noop
|
|
376 | + onStartRequest(request) {}
|
|
377 | + |
|
378 | + // resolve or reject our Promise
|
|
379 | + onStopRequest(request, status) {
|
|
380 | + try {
|
|
381 | + if (!Components.isSuccessCode(status)) {
|
|
382 | + const errorMessage =
|
|
383 | + lazy.TorLauncherUtil.getLocalizedStringForError(status);
|
|
384 | + this.#reject(new Error(errorMessage));
|
|
385 | + }
|
|
386 | + if (request.responseStatus !== 200) {
|
|
387 | + this.#reject(new Error(request.responseStatusText));
|
|
388 | + }
|
|
389 | + } catch (err) {
|
|
390 | + this.#reject(err);
|
|
391 | + }
|
|
392 | + this.#resolve(this.#response);
|
|
393 | + }
|
|
394 | + |
|
395 | + // read response data
|
|
396 | + onDataAvailable(request, stream, offset, length) {
|
|
397 | + const scriptableStream = Cc[
|
|
398 | + "@mozilla.org/scriptableinputstream;1"
|
|
399 | + ].createInstance(Ci.nsIScriptableInputStream);
|
|
400 | + scriptableStream.init(stream);
|
|
401 | + this.#response += scriptableStream.read(length);
|
|
402 | + }
|
|
403 | +}
|
|
404 | + |
|
405 | +// constructs the json objects and sends the request over moat
|
|
406 | +export class DomainFrontRequestBuilder {
|
|
407 | + #inited = false;
|
|
408 | + #meekTransport = null;
|
|
409 | + |
|
410 | + get inited() {
|
|
411 | + return this.#inited;
|
|
412 | + }
|
|
413 | + |
|
414 | + async init(reflector, front) {
|
|
415 | + if (this.#inited) {
|
|
416 | + throw new Error("MoatRPC: Already initialized");
|
|
417 | + }
|
|
418 | + |
|
419 | + const meekTransport =
|
|
420 | + Services.appinfo.OS === "Android"
|
|
421 | + ? new MeekTransportAndroid()
|
|
422 | + : new MeekTransport();
|
|
423 | + await meekTransport.init(reflector, front);
|
|
424 | + this.#meekTransport = meekTransport;
|
|
425 | + this.#inited = true;
|
|
426 | + }
|
|
427 | + |
|
428 | + async uninit() {
|
|
429 | + await this.#meekTransport?.uninit();
|
|
430 | + this.#meekTransport = null;
|
|
431 | + this.#inited = false;
|
|
432 | + }
|
|
433 | + |
|
434 | + buildHttpHandler(uriString) {
|
|
435 | + if (!this.#inited) {
|
|
436 | + throw new Error("MoatRPC: Not initialized");
|
|
437 | + }
|
|
438 | + |
|
439 | + const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
|
|
440 | + this.#meekTransport;
|
|
441 | + |
|
442 | + const proxyPS = Cc[
|
|
443 | + "@mozilla.org/network/protocol-proxy-service;1"
|
|
444 | + ].getService(Ci.nsIProtocolProxyService);
|
|
445 | + const flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
|
|
446 | + const noTimeout = 0xffffffff; // UINT32_MAX
|
|
447 | + const proxyInfo = proxyPS.newProxyInfoWithAuth(
|
|
448 | + proxyType,
|
|
449 | + proxyAddress,
|
|
450 | + proxyPort,
|
|
451 | + proxyUsername,
|
|
452 | + proxyPassword,
|
|
453 | + undefined,
|
|
454 | + undefined,
|
|
455 | + flags,
|
|
456 | + noTimeout,
|
|
457 | + undefined
|
|
458 | + );
|
|
459 | + |
|
460 | + const uri = Services.io.newURI(uriString);
|
|
461 | + // There does not seem to be a way to directly create an nsILoadInfo from
|
|
462 | + // JavaScript, so we create a throw away non-proxied channel to get one.
|
|
463 | + const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
|
|
464 | + const loadInfo = Services.io.newChannelFromURI(
|
|
465 | + uri,
|
|
466 | + undefined,
|
|
467 | + Services.scriptSecurityManager.getSystemPrincipal(),
|
|
468 | + undefined,
|
|
469 | + secFlags,
|
|
470 | + Ci.nsIContentPolicy.TYPE_OTHER
|
|
471 | + ).loadInfo;
|
|
472 | + |
|
473 | + const httpHandler = Services.io
|
|
474 | + .getProtocolHandler("http")
|
|
475 | + .QueryInterface(Ci.nsIHttpProtocolHandler);
|
|
476 | + const ch = httpHandler
|
|
477 | + .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo)
|
|
478 | + .QueryInterface(Ci.nsIHttpChannel);
|
|
479 | + |
|
480 | + // remove all headers except for 'Host"
|
|
481 | + const headers = [];
|
|
482 | + ch.visitRequestHeaders({
|
|
483 | + visitHeader: (key, val) => {
|
|
484 | + if (key !== "Host") {
|
|
485 | + headers.push(key);
|
|
486 | + }
|
|
487 | + },
|
|
488 | + });
|
|
489 | + headers.forEach(key => ch.setRequestHeader(key, "", false));
|
|
490 | + |
|
491 | + return ch;
|
|
492 | + }
|
|
493 | + |
|
494 | + /**
|
|
495 | + * Make a POST request with a JSON body.
|
|
496 | + *
|
|
497 | + * @param {string} url The URL to load
|
|
498 | + * @param {object} args The arguments to send to the procedure. It will be
|
|
499 | + * serialized to JSON by this function and then set as POST body
|
|
500 | + * @returns {Promise<object>} A promise with the parsed response
|
|
501 | + */
|
|
502 | + async buildPostRequest(url, args) {
|
|
503 | + const ch = this.buildHttpHandler(url);
|
|
504 | + |
|
505 | + const argsJson = JSON.stringify(args);
|
|
506 | + const inStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
|
507 | + Ci.nsIStringInputStream
|
|
508 | + );
|
|
509 | + inStream.setData(argsJson, argsJson.length);
|
|
510 | + const upChannel = ch.QueryInterface(Ci.nsIUploadChannel);
|
|
511 | + const contentType = "application/vnd.api+json";
|
|
512 | + upChannel.setUploadStream(inStream, contentType, argsJson.length);
|
|
513 | + ch.requestMethod = "POST";
|
|
514 | + |
|
515 | + // Make request
|
|
516 | + const listener = new ResponseListener();
|
|
517 | + await ch.asyncOpen(listener, ch);
|
|
518 | + |
|
519 | + // wait for response
|
|
520 | + const responseJSON = await listener.response();
|
|
521 | + |
|
522 | + // parse that JSON
|
|
523 | + return JSON.parse(responseJSON);
|
|
524 | + }
|
|
525 | +} |
... | ... | @@ -10,10 +10,8 @@ import { |
10 | 10 | const lazy = {};
|
11 | 11 | |
12 | 12 | ChromeUtils.defineESModuleGetters(lazy, {
|
13 | - EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
14 | - Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
|
|
15 | - TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
|
16 | - TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
13 | + DomainFrontRequestBuilder:
|
|
14 | + "resource://gre/modules/DomainFrontedRequests.sys.mjs",
|
|
17 | 15 | });
|
18 | 16 | |
19 | 17 | const TorLauncherPrefs = Object.freeze({
|
... | ... | @@ -22,372 +20,9 @@ const TorLauncherPrefs = Object.freeze({ |
22 | 20 | moat_service: "extensions.torlauncher.moat_service",
|
23 | 21 | });
|
24 | 22 | |
25 | -function makeMeekCredentials(proxyType) {
|
|
26 | - // Construct the per-connection arguments.
|
|
27 | - let meekClientEscapedArgs = "";
|
|
28 | - const meekReflector = Services.prefs.getStringPref(
|
|
29 | - TorLauncherPrefs.bridgedb_reflector
|
|
30 | - );
|
|
31 | - |
|
32 | - // Escape aValue per section 3.5 of the PT specification:
|
|
33 | - // First the "<Key>=<Value>" formatted arguments MUST be escaped,
|
|
34 | - // such that all backslash, equal sign, and semicolon characters
|
|
35 | - // are escaped with a backslash.
|
|
36 | - const escapeArgValue = aValue =>
|
|
37 | - aValue
|
|
38 | - ? aValue
|
|
39 | - .replaceAll("\\", "\\\\")
|
|
40 | - .replaceAll("=", "\\=")
|
|
41 | - .replaceAll(";", "\\;")
|
|
42 | - : "";
|
|
43 | - |
|
44 | - if (meekReflector) {
|
|
45 | - meekClientEscapedArgs += "url=";
|
|
46 | - meekClientEscapedArgs += escapeArgValue(meekReflector);
|
|
47 | - }
|
|
48 | - const meekFront = Services.prefs.getStringPref(
|
|
49 | - TorLauncherPrefs.bridgedb_front
|
|
50 | - );
|
|
51 | - if (meekFront) {
|
|
52 | - if (meekClientEscapedArgs.length) {
|
|
53 | - meekClientEscapedArgs += ";";
|
|
54 | - }
|
|
55 | - meekClientEscapedArgs += "front=";
|
|
56 | - meekClientEscapedArgs += escapeArgValue(meekFront);
|
|
57 | - }
|
|
58 | - |
|
59 | - // socks5
|
|
60 | - if (proxyType === "socks") {
|
|
61 | - if (meekClientEscapedArgs.length <= 255) {
|
|
62 | - return [meekClientEscapedArgs, "\x00"];
|
|
63 | - } else {
|
|
64 | - return [
|
|
65 | - meekClientEscapedArgs.substring(0, 255),
|
|
66 | - meekClientEscapedArgs.substring(255),
|
|
67 | - ];
|
|
68 | - }
|
|
69 | - // socks4
|
|
70 | - } else {
|
|
71 | - return [meekClientEscapedArgs, undefined];
|
|
72 | - }
|
|
73 | -}
|
|
74 | - |
|
75 | -//
|
|
76 | -// Launches and controls the PT process lifetime
|
|
77 | -//
|
|
78 | -class MeekTransport {
|
|
79 | - // These members are used by consumers to setup the proxy to do requests over
|
|
80 | - // meek. They are passed to newProxyInfoWithAuth.
|
|
81 | - proxyType = null;
|
|
82 | - proxyAddress = null;
|
|
83 | - proxyPort = 0;
|
|
84 | - proxyUsername = null;
|
|
85 | - proxyPassword = null;
|
|
86 | - |
|
87 | - #inited = false;
|
|
88 | - #meekClientProcess = null;
|
|
89 | - |
|
90 | - // launches the meekprocess
|
|
91 | - async init() {
|
|
92 | - // ensure we haven't already init'd
|
|
93 | - if (this.#inited) {
|
|
94 | - throw new Error("MeekTransport: Already initialized");
|
|
95 | - }
|
|
96 | - |
|
97 | - try {
|
|
98 | - // figure out which pluggable transport to use
|
|
99 | - const supportedTransports = ["meek", "meek_lite"];
|
|
100 | - const provider = await lazy.TorProviderBuilder.build();
|
|
101 | - const proxy = (await provider.getPluggableTransports()).find(
|
|
102 | - pt =>
|
|
103 | - pt.type === "exec" &&
|
|
104 | - supportedTransports.some(t => pt.transports.includes(t))
|
|
105 | - );
|
|
106 | - if (!proxy) {
|
|
107 | - throw new Error("No supported transport found.");
|
|
108 | - }
|
|
109 | - |
|
110 | - const meekTransport = proxy.transports.find(t =>
|
|
111 | - supportedTransports.includes(t)
|
|
112 | - );
|
|
113 | - // Convert meek client path to absolute path if necessary
|
|
114 | - const meekWorkDir = lazy.TorLauncherUtil.getTorFile(
|
|
115 | - "pt-startup-dir",
|
|
116 | - false
|
|
117 | - );
|
|
118 | - if (lazy.TorLauncherUtil.isPathRelative(proxy.pathToBinary)) {
|
|
119 | - const meekPath = meekWorkDir.clone();
|
|
120 | - meekPath.appendRelativePath(proxy.pathToBinary);
|
|
121 | - proxy.pathToBinary = meekPath.path;
|
|
122 | - }
|
|
123 | - |
|
124 | - // Setup env and start meek process
|
|
125 | - const ptStateDir = lazy.TorLauncherUtil.getTorFile("tordatadir", false);
|
|
126 | - ptStateDir.append("pt_state"); // Match what tor uses.
|
|
127 | - |
|
128 | - const envAdditions = {
|
|
129 | - TOR_PT_MANAGED_TRANSPORT_VER: "1",
|
|
130 | - TOR_PT_STATE_LOCATION: ptStateDir.path,
|
|
131 | - TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
|
|
132 | - TOR_PT_CLIENT_TRANSPORTS: meekTransport,
|
|
133 | - };
|
|
134 | - if (TorSettings.proxy.enabled) {
|
|
135 | - envAdditions.TOR_PT_PROXY = TorSettings.proxy.uri;
|
|
136 | - }
|
|
137 | - |
|
138 | - const opts = {
|
|
139 | - command: proxy.pathToBinary,
|
|
140 | - arguments: proxy.options.split(/s+/),
|
|
141 | - workdir: meekWorkDir.path,
|
|
142 | - environmentAppend: true,
|
|
143 | - environment: envAdditions,
|
|
144 | - stderr: "pipe",
|
|
145 | - };
|
|
146 | - |
|
147 | - // Launch meek client
|
|
148 | - this.#meekClientProcess = await lazy.Subprocess.call(opts);
|
|
149 | - |
|
150 | - // Callback chain for reading stderr
|
|
151 | - const stderrLogger = async () => {
|
|
152 | - while (this.#meekClientProcess) {
|
|
153 | - const errString = await this.#meekClientProcess.stderr.readString();
|
|
154 | - if (errString) {
|
|
155 | - console.log(`MeekTransport: stderr => ${errString}`);
|
|
156 | - }
|
|
157 | - }
|
|
158 | - };
|
|
159 | - stderrLogger();
|
|
160 | - |
|
161 | - // Read pt's stdout until terminal (CMETHODS DONE) is reached
|
|
162 | - // returns array of lines for parsing
|
|
163 | - const getInitLines = async (stdout = "") => {
|
|
164 | - stdout += await this.#meekClientProcess.stdout.readString();
|
|
165 | - |
|
166 | - // look for the final message
|
|
167 | - const CMETHODS_DONE = "CMETHODS DONE";
|
|
168 | - let endIndex = stdout.lastIndexOf(CMETHODS_DONE);
|
|
169 | - if (endIndex != -1) {
|
|
170 | - endIndex += CMETHODS_DONE.length;
|
|
171 | - return stdout.substring(0, endIndex).split("\n");
|
|
172 | - }
|
|
173 | - return getInitLines(stdout);
|
|
174 | - };
|
|
175 | - |
|
176 | - // read our lines from pt's stdout
|
|
177 | - const meekInitLines = await getInitLines();
|
|
178 | - // tokenize our pt lines
|
|
179 | - const meekInitTokens = meekInitLines.map(line => {
|
|
180 | - const tokens = line.split(" ");
|
|
181 | - return {
|
|
182 | - keyword: tokens[0],
|
|
183 | - args: tokens.slice(1),
|
|
184 | - };
|
|
185 | - });
|
|
186 | - |
|
187 | - // parse our pt tokens
|
|
188 | - for (const { keyword, args } of meekInitTokens) {
|
|
189 | - const argsJoined = args.join(" ");
|
|
190 | - let keywordError = false;
|
|
191 | - switch (keyword) {
|
|
192 | - case "VERSION": {
|
|
193 | - if (args.length != 1 || args[0] !== "1") {
|
|
194 | - keywordError = true;
|
|
195 | - }
|
|
196 | - break;
|
|
197 | - }
|
|
198 | - case "PROXY": {
|
|
199 | - if (args.length != 1 || args[0] !== "DONE") {
|
|
200 | - keywordError = true;
|
|
201 | - }
|
|
202 | - break;
|
|
203 | - }
|
|
204 | - case "CMETHOD": {
|
|
205 | - if (args.length != 3) {
|
|
206 | - keywordError = true;
|
|
207 | - break;
|
|
208 | - }
|
|
209 | - const transport = args[0];
|
|
210 | - const proxyType = args[1];
|
|
211 | - const addrPortString = args[2];
|
|
212 | - const addrPort = addrPortString.split(":");
|
|
213 | - |
|
214 | - if (transport !== meekTransport) {
|
|
215 | - throw new Error(
|
|
216 | - `MeekTransport: Expected ${meekTransport} but found ${transport}`
|
|
217 | - );
|
|
218 | - }
|
|
219 | - if (!["socks4", "socks4a", "socks5"].includes(proxyType)) {
|
|
220 | - throw new Error(
|
|
221 | - `MeekTransport: Invalid proxy type => ${proxyType}`
|
|
222 | - );
|
|
223 | - }
|
|
224 | - if (addrPort.length != 2) {
|
|
225 | - throw new Error(
|
|
226 | - `MeekTransport: Invalid proxy address => ${addrPortString}`
|
|
227 | - );
|
|
228 | - }
|
|
229 | - const addr = addrPort[0];
|
|
230 | - const port = parseInt(addrPort[1]);
|
|
231 | - if (port < 1 || port > 65535) {
|
|
232 | - throw new Error(`MeekTransport: Invalid proxy port => ${port}`);
|
|
233 | - }
|
|
234 | - |
|
235 | - // convert proxy type to strings used by protocol-proxy-servce
|
|
236 | - this.proxyType = proxyType === "socks5" ? "socks" : "socks4";
|
|
237 | - this.proxyAddress = addr;
|
|
238 | - this.proxyPort = port;
|
|
239 | - |
|
240 | - break;
|
|
241 | - }
|
|
242 | - // terminal
|
|
243 | - case "CMETHODS": {
|
|
244 | - if (args.length != 1 || args[0] !== "DONE") {
|
|
245 | - keywordError = true;
|
|
246 | - }
|
|
247 | - break;
|
|
248 | - }
|
|
249 | - // errors (all fall through):
|
|
250 | - case "VERSION-ERROR":
|
|
251 | - case "ENV-ERROR":
|
|
252 | - case "PROXY-ERROR":
|
|
253 | - case "CMETHOD-ERROR":
|
|
254 | - throw new Error(`MeekTransport: ${keyword} => '${argsJoined}'`);
|
|
255 | - }
|
|
256 | - if (keywordError) {
|
|
257 | - throw new Error(
|
|
258 | - `MeekTransport: Invalid ${keyword} keyword args => '${argsJoined}'`
|
|
259 | - );
|
|
260 | - }
|
|
261 | - }
|
|
262 | - |
|
263 | - // register callback to cleanup on process exit
|
|
264 | - this.#meekClientProcess.wait().then(exitObj => {
|
|
265 | - this.#meekClientProcess = null;
|
|
266 | - this.uninit();
|
|
267 | - });
|
|
268 | - [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
|
|
269 | - this.proxyType
|
|
270 | - );
|
|
271 | - this.#inited = true;
|
|
272 | - } catch (ex) {
|
|
273 | - if (this.#meekClientProcess) {
|
|
274 | - this.#meekClientProcess.kill();
|
|
275 | - this.#meekClientProcess = null;
|
|
276 | - }
|
|
277 | - throw ex;
|
|
278 | - }
|
|
279 | - }
|
|
280 | - |
|
281 | - async uninit() {
|
|
282 | - this.#inited = false;
|
|
283 | - |
|
284 | - await this.#meekClientProcess?.kill();
|
|
285 | - this.#meekClientProcess = null;
|
|
286 | - this.proxyType = null;
|
|
287 | - this.proxyAddress = null;
|
|
288 | - this.proxyPort = 0;
|
|
289 | - this.proxyUsername = null;
|
|
290 | - this.proxyPassword = null;
|
|
291 | - }
|
|
292 | -}
|
|
293 | - |
|
294 | -class MeekTransportAndroid {
|
|
295 | - // These members are used by consumers to setup the proxy to do requests over
|
|
296 | - // meek. They are passed to newProxyInfoWithAuth.
|
|
297 | - proxyType = null;
|
|
298 | - proxyAddress = null;
|
|
299 | - proxyPort = 0;
|
|
300 | - proxyUsername = null;
|
|
301 | - proxyPassword = null;
|
|
302 | - |
|
303 | - #id = 0;
|
|
304 | - |
|
305 | - async init() {
|
|
306 | - // ensure we haven't already init'd
|
|
307 | - if (this.#id) {
|
|
308 | - throw new Error("MeekTransport: Already initialized");
|
|
309 | - }
|
|
310 | - const details = await lazy.EventDispatcher.instance.sendRequestForResult({
|
|
311 | - type: "GeckoView:Tor:StartMeek",
|
|
312 | - });
|
|
313 | - this.#id = details.id;
|
|
314 | - this.proxyType = "socks";
|
|
315 | - this.proxyAddress = details.address;
|
|
316 | - this.proxyPort = details.port;
|
|
317 | - [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
|
|
318 | - this.proxyType
|
|
319 | - );
|
|
320 | - }
|
|
321 | - |
|
322 | - async uninit() {
|
|
323 | - lazy.EventDispatcher.instance.sendRequest({
|
|
324 | - type: "GeckoView:Tor:StopMeek",
|
|
325 | - id: this.#id,
|
|
326 | - });
|
|
327 | - this.#id = 0;
|
|
328 | - this.proxyType = null;
|
|
329 | - this.proxyAddress = null;
|
|
330 | - this.proxyPort = 0;
|
|
331 | - this.proxyUsername = null;
|
|
332 | - this.proxyPassword = null;
|
|
333 | - }
|
|
334 | -}
|
|
335 | - |
|
336 | -//
|
|
337 | -// Callback object with a cached promise for the returned Moat data
|
|
338 | -//
|
|
339 | -class MoatResponseListener {
|
|
340 | - #response = "";
|
|
341 | - #responsePromise;
|
|
342 | - #resolve;
|
|
343 | - #reject;
|
|
344 | - constructor() {
|
|
345 | - this.#response = "";
|
|
346 | - // we need this promise here because await nsIHttpChannel::asyncOpen does
|
|
347 | - // not return only once the request is complete, it seems to return
|
|
348 | - // after it begins, so we have to get the result from this listener object.
|
|
349 | - // This promise is only resolved once onStopRequest is called
|
|
350 | - this.#responsePromise = new Promise((resolve, reject) => {
|
|
351 | - this.#resolve = resolve;
|
|
352 | - this.#reject = reject;
|
|
353 | - });
|
|
354 | - }
|
|
355 | - |
|
356 | - // callers wait on this for final response
|
|
357 | - response() {
|
|
358 | - return this.#responsePromise;
|
|
359 | - }
|
|
360 | - |
|
361 | - // noop
|
|
362 | - onStartRequest(request) {}
|
|
363 | - |
|
364 | - // resolve or reject our Promise
|
|
365 | - onStopRequest(request, status) {
|
|
366 | - try {
|
|
367 | - if (!Components.isSuccessCode(status)) {
|
|
368 | - const errorMessage =
|
|
369 | - lazy.TorLauncherUtil.getLocalizedStringForError(status);
|
|
370 | - this.#reject(new Error(errorMessage));
|
|
371 | - }
|
|
372 | - if (request.responseStatus != 200) {
|
|
373 | - this.#reject(new Error(request.responseStatusText));
|
|
374 | - }
|
|
375 | - } catch (err) {
|
|
376 | - this.#reject(err);
|
|
377 | - }
|
|
378 | - this.#resolve(this.#response);
|
|
379 | - }
|
|
380 | - |
|
381 | - // read response data
|
|
382 | - onDataAvailable(request, stream, offset, length) {
|
|
383 | - const scriptableStream = Cc[
|
|
384 | - "@mozilla.org/scriptableinputstream;1"
|
|
385 | - ].createInstance(Ci.nsIScriptableInputStream);
|
|
386 | - scriptableStream.init(stream);
|
|
387 | - this.#response += scriptableStream.read(length);
|
|
388 | - }
|
|
389 | -}
|
|
390 | - |
|
23 | +/**
|
|
24 | + * A special response listener that collects the received headers.
|
|
25 | + */
|
|
391 | 26 | class InternetTestResponseListener {
|
392 | 27 | #promise;
|
393 | 28 | #resolve;
|
... | ... | @@ -436,129 +71,45 @@ class InternetTestResponseListener { |
436 | 71 | }
|
437 | 72 | }
|
438 | 73 | |
439 | -// constructs the json objects and sends the request over moat
|
|
74 | +/**
|
|
75 | + * Constructs JSON objects and sends requests over Moat.
|
|
76 | + * The documentation about the JSON schemas to use are available at
|
|
77 | + * https://gitlab.torproject.org/tpo/anti-censorship/rdsys/-/blob/main/doc/moat.md.
|
|
78 | + */
|
|
440 | 79 | export class MoatRPC {
|
441 | - #inited = false;
|
|
442 | - #meekTransport = null;
|
|
443 | - |
|
444 | - get inited() {
|
|
445 | - return this.#inited;
|
|
446 | - }
|
|
80 | + #requestBuilder = null;
|
|
447 | 81 | |
448 | 82 | async init() {
|
449 | - if (this.#inited) {
|
|
450 | - throw new Error("MoatRPC: Already initialized");
|
|
83 | + if (this.#requestBuilder !== null) {
|
|
84 | + return;
|
|
451 | 85 | }
|
452 | 86 | |
453 | - const meekTransport =
|
|
454 | - Services.appinfo.OS === "Android"
|
|
455 | - ? new MeekTransportAndroid()
|
|
456 | - : new MeekTransport();
|
|
457 | - await meekTransport.init();
|
|
458 | - this.#meekTransport = meekTransport;
|
|
459 | - this.#inited = true;
|
|
87 | + const reflector = Services.prefs.getStringPref(
|
|
88 | + TorLauncherPrefs.bridgedb_reflector
|
|
89 | + );
|
|
90 | + const front = Services.prefs.getStringPref(TorLauncherPrefs.bridgedb_front);
|
|
91 | + const builder = new lazy.DomainFrontRequestBuilder();
|
|
92 | + await builder.init(reflector, front);
|
|
93 | + this.#requestBuilder = builder;
|
|
460 | 94 | }
|
461 | 95 | |
462 | 96 | async uninit() {
|
463 | - await this.#meekTransport?.uninit();
|
|
464 | - this.#meekTransport = null;
|
|
465 | - this.#inited = false;
|
|
466 | - }
|
|
467 | - |
|
468 | - #makeHttpHandler(uriString) {
|
|
469 | - if (!this.#inited) {
|
|
470 | - throw new Error("MoatRPC: Not initialized");
|
|
471 | - }
|
|
472 | - |
|
473 | - const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
|
|
474 | - this.#meekTransport;
|
|
475 | - |
|
476 | - const proxyPS = Cc[
|
|
477 | - "@mozilla.org/network/protocol-proxy-service;1"
|
|
478 | - ].getService(Ci.nsIProtocolProxyService);
|
|
479 | - const flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
|
|
480 | - const noTimeout = 0xffffffff; // UINT32_MAX
|
|
481 | - const proxyInfo = proxyPS.newProxyInfoWithAuth(
|
|
482 | - proxyType,
|
|
483 | - proxyAddress,
|
|
484 | - proxyPort,
|
|
485 | - proxyUsername,
|
|
486 | - proxyPassword,
|
|
487 | - undefined,
|
|
488 | - undefined,
|
|
489 | - flags,
|
|
490 | - noTimeout,
|
|
491 | - undefined
|
|
492 | - );
|
|
493 | - |
|
494 | - const uri = Services.io.newURI(uriString);
|
|
495 | - // There does not seem to be a way to directly create an nsILoadInfo from
|
|
496 | - // JavaScript, so we create a throw away non-proxied channel to get one.
|
|
497 | - const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
|
|
498 | - const loadInfo = Services.io.newChannelFromURI(
|
|
499 | - uri,
|
|
500 | - undefined,
|
|
501 | - Services.scriptSecurityManager.getSystemPrincipal(),
|
|
502 | - undefined,
|
|
503 | - secFlags,
|
|
504 | - Ci.nsIContentPolicy.TYPE_OTHER
|
|
505 | - ).loadInfo;
|
|
506 | - |
|
507 | - const httpHandler = Services.io
|
|
508 | - .getProtocolHandler("http")
|
|
509 | - .QueryInterface(Ci.nsIHttpProtocolHandler);
|
|
510 | - const ch = httpHandler
|
|
511 | - .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo)
|
|
512 | - .QueryInterface(Ci.nsIHttpChannel);
|
|
513 | - |
|
514 | - // remove all headers except for 'Host"
|
|
515 | - const headers = [];
|
|
516 | - ch.visitRequestHeaders({
|
|
517 | - visitHeader: (key, val) => {
|
|
518 | - if (key !== "Host") {
|
|
519 | - headers.push(key);
|
|
520 | - }
|
|
521 | - },
|
|
522 | - });
|
|
523 | - headers.forEach(key => ch.setRequestHeader(key, "", false));
|
|
524 | - |
|
525 | - return ch;
|
|
97 | + await this.#requestBuilder?.uninit();
|
|
98 | + this.#requestBuilder = null;
|
|
526 | 99 | }
|
527 | 100 | |
528 | 101 | async #makeRequest(procedure, args) {
|
529 | 102 | const procedureURIString = `${Services.prefs.getStringPref(
|
530 | 103 | TorLauncherPrefs.moat_service
|
531 | 104 | )}/${procedure}`;
|
532 | - const ch = this.#makeHttpHandler(procedureURIString);
|
|
533 | - |
|
534 | - // Arrange for the POST data to be sent.
|
|
535 | - const argsJson = JSON.stringify(args);
|
|
536 | - |
|
537 | - const inStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
|
538 | - Ci.nsIStringInputStream
|
|
539 | - );
|
|
540 | - inStream.setData(argsJson, argsJson.length);
|
|
541 | - const upChannel = ch.QueryInterface(Ci.nsIUploadChannel);
|
|
542 | - const contentType = "application/vnd.api+json";
|
|
543 | - upChannel.setUploadStream(inStream, contentType, argsJson.length);
|
|
544 | - ch.requestMethod = "POST";
|
|
545 | - |
|
546 | - // Make request
|
|
547 | - const listener = new MoatResponseListener();
|
|
548 | - await ch.asyncOpen(listener, ch);
|
|
549 | - |
|
550 | - // wait for response
|
|
551 | - const responseJSON = await listener.response();
|
|
552 | - |
|
553 | - // parse that JSON
|
|
554 | - return JSON.parse(responseJSON);
|
|
105 | + return this.#requestBuilder.buildPostRequest(procedureURIString, args);
|
|
555 | 106 | }
|
556 | 107 | |
557 | 108 | async testInternetConnection() {
|
558 | 109 | const uri = `${Services.prefs.getStringPref(
|
559 | 110 | TorLauncherPrefs.moat_service
|
560 | 111 | )}/circumvention/countries`;
|
561 | - const ch = this.#makeHttpHandler(uri);
|
|
112 | + const ch = this.#requestBuilder.buildHttpHandler(uri);
|
|
562 | 113 | ch.requestMethod = "HEAD";
|
563 | 114 | |
564 | 115 | const listener = new InternetTestResponseListener();
|
... | ... | @@ -566,10 +117,6 @@ export class MoatRPC { |
566 | 117 | return listener.status;
|
567 | 118 | }
|
568 | 119 | |
569 | - //
|
|
570 | - // Moat APIs
|
|
571 | - //
|
|
572 | - |
|
573 | 120 | // Receive a CAPTCHA challenge, takes the following parameters:
|
574 | 121 | // - transports: array of transport strings available to us eg: ["obfs4", "meek"]
|
575 | 122 | //
|
... | ... | @@ -166,6 +166,7 @@ EXTRA_JS_MODULES += [ |
166 | 166 | "DateTimePickerPanel.sys.mjs",
|
167 | 167 | "DeferredTask.sys.mjs",
|
168 | 168 | "Deprecated.sys.mjs",
|
169 | + "DomainFrontedRequests.sys.mjs",
|
|
169 | 170 | "DragDropFilter.sys.mjs",
|
170 | 171 | "E10SUtils.sys.mjs",
|
171 | 172 | "EventEmitter.sys.mjs",
|