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",
|