Loading toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs +47 −35 Original line number Diff line number Diff line import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs"; import { TorProtocolService } from "resource://gre/modules/TorProtocolService.sys.mjs"; import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs"; import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs"; /* tor-launcher observer topics */ Loading @@ -13,19 +13,23 @@ export const TorTopics = Object.freeze({ // modeled after XMLHttpRequest // nicely encapsulates the observer register/unregister logic export class TorBootstrapRequest { constructor() { // number of ms to wait before we abandon the bootstrap attempt // a value of 0 implies we never wait this.timeout = 0; timeout = 0; // callbacks for bootstrap process status updates this.onbootstrapstatus = (progress, status) => {}; this.onbootstrapcomplete = () => {}; this.onbootstraperror = (message, details) => {}; onbootstrapstatus = (progress, status) => {}; onbootstrapcomplete = () => {}; onbootstraperror = (message, details) => {}; // internal resolve() method for bootstrap this._bootstrapPromiseResolve = null; this._bootstrapPromise = null; this._timeoutID = null; #bootstrapPromiseResolve = null; #bootstrapPromise = null; #timeoutID = null; #provider = null; constructor() { this.#provider = TorProviderBuilder.build(); } observe(subject, topic, data) { Loading @@ -41,15 +45,16 @@ export class TorBootstrapRequest { if (this.onbootstrapcomplete) { this.onbootstrapcomplete(); } this._bootstrapPromiseResolve(true); clearTimeout(this._timeoutID); this.#bootstrapPromiseResolve(true); clearTimeout(this.#timeoutID); this.#timeoutID = null; } break; } case TorTopics.BootstrapError: { console.info("TorBootstrapRequest: observerd TorBootstrapError", obj); this._stop(obj?.message, obj?.details); this.#stop(obj?.message, obj?.details); break; } } Loading @@ -57,12 +62,12 @@ export class TorBootstrapRequest { // resolves 'true' if bootstrap succeeds, false otherwise bootstrap() { if (this._bootstrapPromise) { return this._bootstrapPromise; if (this.#bootstrapPromise) { return this.#bootstrapPromise; } this._bootstrapPromise = new Promise((resolve, reject) => { this._bootstrapPromiseResolve = resolve; this.#bootstrapPromise = new Promise((resolve, reject) => { this.#bootstrapPromiseResolve = resolve; // register ourselves to listen for bootstrap events Services.obs.addObserver(this, TorTopics.BootstrapStatus); Loading @@ -70,10 +75,10 @@ export class TorBootstrapRequest { // optionally cancel bootstrap after a given timeout if (this.timeout > 0) { this._timeoutID = setTimeout(async () => { this._timeoutID = null; this.#timeoutID = setTimeout(async () => { this.#timeoutID = null; // TODO: Translate, if really used await this._stop( await this.#stop( "Tor Bootstrap process timed out", `Bootstrap attempt abandoned after waiting ${this.timeout} ms` ); Loading @@ -81,38 +86,45 @@ export class TorBootstrapRequest { } // wait for bootstrapping to begin and maybe handle error TorProtocolService.connect().catch(err => { this._stop(err.message, ""); this.#provider.connect().catch(err => { this.#stop(err.message, ""); }); }).finally(() => { // and remove ourselves once bootstrap is resolved Services.obs.removeObserver(this, TorTopics.BootstrapStatus); Services.obs.removeObserver(this, TorTopics.BootstrapError); this._bootstrapPromise = null; this.#bootstrapPromise = null; }); return this._bootstrapPromise; return this.#bootstrapPromise; } async cancel() { await this._stop(); await this.#stop(); } // Internal implementation. Do not use directly, but call cancel, instead. async _stop(message, details) { async #stop(message, details) { // first stop our bootstrap timeout before handling the error if (this._timeoutID !== null) { clearTimeout(this._timeoutID); this._timeoutID = null; if (this.#timeoutID !== null) { clearTimeout(this.#timeoutID); this.#timeoutID = null; } // stopBootstrap never throws await TorProtocolService.stopBootstrap(); try { await this.#provider.stopBootstrap(); } catch (e) { console.error("Failed to stop the bootstrap.", e); if (!message) { message = e.message; details = ""; } } if (this.onbootstraperror && message) { this.onbootstraperror(message, details); } this._bootstrapPromiseResolve(false); this.#bootstrapPromiseResolve(false); } } toolkit/components/tor-launcher/TorControlPort.sys.mjs +6 −48 Original line number Diff line number Diff line Loading @@ -981,18 +981,6 @@ class TorController { await this.#sendCommandSimple(`authenticate ${password || ""}`); } /** * Sends a GETINFO for a single key. * * @param {string} key The key to get value for * @returns {any} The return value depends on the requested key */ async getInfo(key) { this.#expectString(key, "key"); const response = await this.sendCommand(`getinfo ${key}`); return this.#getMultipleResponseValues(response)[0]; } /** * Sends a GETINFO for a single key. * control-spec.txt says "one ReplyLine is sent for each requested value", so, Loading Loading @@ -1054,9 +1042,7 @@ class TorController { const addresses = [v4[5]]; // a address:port // dir-spec.txt also states only the first one should be taken // TODO: The consumers do not care about the port or the square brackets // either. Remove them when integrating this function with the rest const v6 = reply.match(/^a\s+(\[[0-9a-fA-F:]+\]:[0-9]{1,5})$/m); const v6 = reply.match(/^a\s+\[([0-9a-fA-F:]+)\]:\d{1,5}$/m); if (v6) { addresses.push(v6[1]); } Loading Loading @@ -1091,23 +1077,6 @@ class TorController { // Configuration /** * Sends a GETCONF for a single key. * GETCONF with a single argument returns results with one or more lines that * look like `250[- ]key=value`. * Any GETCONF lines that contain a single keyword only are currently dropped. * So we can use similar parsing to that for getInfo. * * @param {string} key The key to get value for * @returns {any} A parsed config value (it depends if a parser is known) */ async getConf(key) { this.#expectString(key, "key"); return this.#getMultipleResponseValues( await this.sendCommand(`getconf ${key}`) ); } /** * Sends a GETCONF for a single key. * The function could be easily generalized to get multiple keys at once, but Loading Loading @@ -1264,12 +1233,14 @@ class TorController { // TODO: Change the consumer and make the fields more consistent with what // we get (e.g., separate key and type, and use a boolen for permanent). const info = { hsAddress: match.groups.HSAddress, typeAndKey: `${match.groups.KeyType}:${match.groups.PrivateKeyBlob}`, address: match.groups.HSAddress, keyType: match.groups.KeyType, keyBlob: match.groups.PrivateKeyBlob, flags: [], }; const maybeFlags = match.groups.other?.match(/Flags=(\S+)/); if (maybeFlags) { info.Flags = maybeFlags[1]; info.flags = maybeFlags[1].split(","); } return info; }); Loading Loading @@ -1453,19 +1424,6 @@ class TorController { ) ); } /** * Process multiple responses to a GETINFO or GETCONF request. * * @param {string} message The message to process * @returns {object[]} The keys depend on the message */ #getMultipleResponseValues(message) { return info .keyValueStringsFromMessage(message) .map(info.stringToValue) .filter(x => x); } } const controlPortInfo = {}; Loading toolkit/components/tor-launcher/TorParsers.sys.mjs +4 −1 Original line number Diff line number Diff line Loading @@ -269,11 +269,14 @@ export const TorParsers = Object.freeze({ }, parseBridgeLine(line) { if (!line) { return null; } const re = /\s*(?:(?<transport>\S+)\s+)?(?<addr>[0-9a-fA-F\.\[\]\:]+:\d{1,5})(?:\s+(?<id>[0-9a-fA-F]{40}))?(?:\s+(?<args>.+))?/; const match = re.exec(line); if (!match) { throw new Error("Invalid bridge line."); throw new Error(`Invalid bridge line: ${line}.`); } const bridge = match.groups; if (!bridge.transport) { Loading toolkit/components/tor-launcher/TorProtocolService.sys.mjs +96 −373 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs +47 −35 Original line number Diff line number Diff line import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs"; import { TorProtocolService } from "resource://gre/modules/TorProtocolService.sys.mjs"; import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs"; import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs"; /* tor-launcher observer topics */ Loading @@ -13,19 +13,23 @@ export const TorTopics = Object.freeze({ // modeled after XMLHttpRequest // nicely encapsulates the observer register/unregister logic export class TorBootstrapRequest { constructor() { // number of ms to wait before we abandon the bootstrap attempt // a value of 0 implies we never wait this.timeout = 0; timeout = 0; // callbacks for bootstrap process status updates this.onbootstrapstatus = (progress, status) => {}; this.onbootstrapcomplete = () => {}; this.onbootstraperror = (message, details) => {}; onbootstrapstatus = (progress, status) => {}; onbootstrapcomplete = () => {}; onbootstraperror = (message, details) => {}; // internal resolve() method for bootstrap this._bootstrapPromiseResolve = null; this._bootstrapPromise = null; this._timeoutID = null; #bootstrapPromiseResolve = null; #bootstrapPromise = null; #timeoutID = null; #provider = null; constructor() { this.#provider = TorProviderBuilder.build(); } observe(subject, topic, data) { Loading @@ -41,15 +45,16 @@ export class TorBootstrapRequest { if (this.onbootstrapcomplete) { this.onbootstrapcomplete(); } this._bootstrapPromiseResolve(true); clearTimeout(this._timeoutID); this.#bootstrapPromiseResolve(true); clearTimeout(this.#timeoutID); this.#timeoutID = null; } break; } case TorTopics.BootstrapError: { console.info("TorBootstrapRequest: observerd TorBootstrapError", obj); this._stop(obj?.message, obj?.details); this.#stop(obj?.message, obj?.details); break; } } Loading @@ -57,12 +62,12 @@ export class TorBootstrapRequest { // resolves 'true' if bootstrap succeeds, false otherwise bootstrap() { if (this._bootstrapPromise) { return this._bootstrapPromise; if (this.#bootstrapPromise) { return this.#bootstrapPromise; } this._bootstrapPromise = new Promise((resolve, reject) => { this._bootstrapPromiseResolve = resolve; this.#bootstrapPromise = new Promise((resolve, reject) => { this.#bootstrapPromiseResolve = resolve; // register ourselves to listen for bootstrap events Services.obs.addObserver(this, TorTopics.BootstrapStatus); Loading @@ -70,10 +75,10 @@ export class TorBootstrapRequest { // optionally cancel bootstrap after a given timeout if (this.timeout > 0) { this._timeoutID = setTimeout(async () => { this._timeoutID = null; this.#timeoutID = setTimeout(async () => { this.#timeoutID = null; // TODO: Translate, if really used await this._stop( await this.#stop( "Tor Bootstrap process timed out", `Bootstrap attempt abandoned after waiting ${this.timeout} ms` ); Loading @@ -81,38 +86,45 @@ export class TorBootstrapRequest { } // wait for bootstrapping to begin and maybe handle error TorProtocolService.connect().catch(err => { this._stop(err.message, ""); this.#provider.connect().catch(err => { this.#stop(err.message, ""); }); }).finally(() => { // and remove ourselves once bootstrap is resolved Services.obs.removeObserver(this, TorTopics.BootstrapStatus); Services.obs.removeObserver(this, TorTopics.BootstrapError); this._bootstrapPromise = null; this.#bootstrapPromise = null; }); return this._bootstrapPromise; return this.#bootstrapPromise; } async cancel() { await this._stop(); await this.#stop(); } // Internal implementation. Do not use directly, but call cancel, instead. async _stop(message, details) { async #stop(message, details) { // first stop our bootstrap timeout before handling the error if (this._timeoutID !== null) { clearTimeout(this._timeoutID); this._timeoutID = null; if (this.#timeoutID !== null) { clearTimeout(this.#timeoutID); this.#timeoutID = null; } // stopBootstrap never throws await TorProtocolService.stopBootstrap(); try { await this.#provider.stopBootstrap(); } catch (e) { console.error("Failed to stop the bootstrap.", e); if (!message) { message = e.message; details = ""; } } if (this.onbootstraperror && message) { this.onbootstraperror(message, details); } this._bootstrapPromiseResolve(false); this.#bootstrapPromiseResolve(false); } }
toolkit/components/tor-launcher/TorControlPort.sys.mjs +6 −48 Original line number Diff line number Diff line Loading @@ -981,18 +981,6 @@ class TorController { await this.#sendCommandSimple(`authenticate ${password || ""}`); } /** * Sends a GETINFO for a single key. * * @param {string} key The key to get value for * @returns {any} The return value depends on the requested key */ async getInfo(key) { this.#expectString(key, "key"); const response = await this.sendCommand(`getinfo ${key}`); return this.#getMultipleResponseValues(response)[0]; } /** * Sends a GETINFO for a single key. * control-spec.txt says "one ReplyLine is sent for each requested value", so, Loading Loading @@ -1054,9 +1042,7 @@ class TorController { const addresses = [v4[5]]; // a address:port // dir-spec.txt also states only the first one should be taken // TODO: The consumers do not care about the port or the square brackets // either. Remove them when integrating this function with the rest const v6 = reply.match(/^a\s+(\[[0-9a-fA-F:]+\]:[0-9]{1,5})$/m); const v6 = reply.match(/^a\s+\[([0-9a-fA-F:]+)\]:\d{1,5}$/m); if (v6) { addresses.push(v6[1]); } Loading Loading @@ -1091,23 +1077,6 @@ class TorController { // Configuration /** * Sends a GETCONF for a single key. * GETCONF with a single argument returns results with one or more lines that * look like `250[- ]key=value`. * Any GETCONF lines that contain a single keyword only are currently dropped. * So we can use similar parsing to that for getInfo. * * @param {string} key The key to get value for * @returns {any} A parsed config value (it depends if a parser is known) */ async getConf(key) { this.#expectString(key, "key"); return this.#getMultipleResponseValues( await this.sendCommand(`getconf ${key}`) ); } /** * Sends a GETCONF for a single key. * The function could be easily generalized to get multiple keys at once, but Loading Loading @@ -1264,12 +1233,14 @@ class TorController { // TODO: Change the consumer and make the fields more consistent with what // we get (e.g., separate key and type, and use a boolen for permanent). const info = { hsAddress: match.groups.HSAddress, typeAndKey: `${match.groups.KeyType}:${match.groups.PrivateKeyBlob}`, address: match.groups.HSAddress, keyType: match.groups.KeyType, keyBlob: match.groups.PrivateKeyBlob, flags: [], }; const maybeFlags = match.groups.other?.match(/Flags=(\S+)/); if (maybeFlags) { info.Flags = maybeFlags[1]; info.flags = maybeFlags[1].split(","); } return info; }); Loading Loading @@ -1453,19 +1424,6 @@ class TorController { ) ); } /** * Process multiple responses to a GETINFO or GETCONF request. * * @param {string} message The message to process * @returns {object[]} The keys depend on the message */ #getMultipleResponseValues(message) { return info .keyValueStringsFromMessage(message) .map(info.stringToValue) .filter(x => x); } } const controlPortInfo = {}; Loading
toolkit/components/tor-launcher/TorParsers.sys.mjs +4 −1 Original line number Diff line number Diff line Loading @@ -269,11 +269,14 @@ export const TorParsers = Object.freeze({ }, parseBridgeLine(line) { if (!line) { return null; } const re = /\s*(?:(?<transport>\S+)\s+)?(?<addr>[0-9a-fA-F\.\[\]\:]+:\d{1,5})(?:\s+(?<id>[0-9a-fA-F]{40}))?(?:\s+(?<args>.+))?/; const match = re.exec(line); if (!match) { throw new Error("Invalid bridge line."); throw new Error(`Invalid bridge line: ${line}.`); } const bridge = match.groups; if (!bridge.transport) { Loading
toolkit/components/tor-launcher/TorProtocolService.sys.mjs +96 −373 File changed.Preview size limit exceeded, changes collapsed. Show changes