Skip to content
Snippets Groups Projects
Verified Commit 7272b554 authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame :jack_o_lantern:
Browse files

fixup! Bug 40597: Implement TorSettings module

Batch of changes requested in the MR.
parent 51e8c714
2 merge requests!898Bug 42252: Add further support for TorController in firefox-android,!877Bug 42343: Read built-in bridges from pt_config.json instead of preferences.
{
"_comment": "Used for dev build, replaced for release builds in tor-browser-build. This file is copied from tor-browser-build cb513eec:tor-expert-bundle/pt_config.json",
"recommendedDefault" : "obfs4",
"pluggableTransports" : {
"lyrebird" : "ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec ${pt_path}lyrebird${pt_extension}",
......
......@@ -207,18 +207,146 @@ class TorSettingsImpl {
*/
#initialized = false;
/**
* Keep track on when we are initializing to allow calling setters and
* During some phases of the initialization, allow calling setters and
* getters without throwing errors.
*
* @type {boolean}
*/
#initializing = false;
#allowUninitialized = false;
constructor() {
this.initializedPromise = new Promise((resolve, reject) => {
this.#initComplete = resolve;
this.#initFailed = reject;
});
this.#addProperties("quickstart", {
enabled: {},
});
this.#addProperties("bridges", {
enabled: {},
source: {
transform: val => {
if (Object.values(TorBridgeSource).includes(val)) {
return val;
}
lazy.logger.error(`Not a valid bridge source: "${val}"`);
return TorBridgeSource.Invalid;
},
},
bridge_strings: {
transform: val => {
if (Array.isArray(val)) {
return [...val];
}
return parseBridgeStrings(val);
},
copy: val => [...val],
equal: (val1, val2) => this.#arrayEqual(val1, val2),
},
builtin_type: {
callback: val => {
if (!val) {
// Make sure that the source is not BuiltIn
if (this.bridges.source === TorBridgeSource.BuiltIn) {
this.bridges.source = TorBridgeSource.Invalid;
}
return;
}
const bridgeStrings = this.#getBuiltinBridges(val);
if (bridgeStrings.length) {
this.bridges.bridge_strings = bridgeStrings;
return;
}
lazy.logger.error(`No built-in ${val} bridges found`);
// Change to be empty, this will trigger this callback again,
// but with val as "".
this.bridges.builtin_type == "";
},
},
});
this.#addProperties("proxy", {
enabled: {
callback: val => {
if (val) {
return;
}
// Reset proxy settings.
this.proxy.type = TorProxyType.Invalid;
this.proxy.address = "";
this.proxy.port = 0;
this.proxy.username = "";
this.proxy.password = "";
},
},
type: {
transform: val => {
if (Object.values(TorProxyType).includes(val)) {
return val;
}
lazy.logger.error(`Not a valid proxy type: "${val}"`);
return TorProxyType.Invalid;
},
},
address: {},
port: {
transform: val => {
if (val === 0) {
// This is a valid value that "unsets" the port.
// Keep this value without giving a warning.
// NOTE: In contrast, "0" is not valid.
return 0;
}
// Unset to 0 if invalid null is returned.
return this.#parsePort(val, false) ?? 0;
},
},
username: {},
password: {},
uri: {
getter: () => {
const { type, address, port, username, password } = this.proxy;
switch (type) {
case TorProxyType.Socks4:
return `socks4a://${address}:${port}`;
case TorProxyType.Socks5:
if (username) {
return `socks5://${username}:${password}@${address}:${port}`;
}
return `socks5://${address}:${port}`;
case TorProxyType.HTTPS:
if (username) {
return `http://${username}:${password}@${address}:${port}`;
}
return `http://${address}:${port}`;
}
return null;
},
},
});
this.#addProperties("firewall", {
enabled: {
callback: val => {
if (!val) {
this.firewall.allowed_ports = "";
}
},
},
allowed_ports: {
transform: val => {
if (!Array.isArray(val)) {
val = val === "" ? [] : val.split(",");
}
// parse and remove duplicates
const portSet = new Set(val.map(p => this.#parsePort(p, true)));
// parsePort returns null for failed parses, so remove it.
portSet.delete(null);
return [...portSet];
},
copy: val => [...val],
equal: (val1, val2) => this.#arrayEqual(val1, val2),
},
});
}
/**
......@@ -299,7 +427,6 @@ class TorSettingsImpl {
* TorSettingProperty property value.
*/
#addProperties(groupname, propParams) {
const self = this;
// Create a new object to hold all these settings.
const group = {};
for (const name in propParams) {
......@@ -307,11 +434,17 @@ class TorSettingsImpl {
Object.defineProperty(group, name, {
get: getter
? () => {
self.#checkIfInitialized(false);
// Allow getting in loadFromPrefs before we are initialized.
if (!this.#allowUninitialized) {
this.#checkIfInitialized();
}
return getter();
}
: () => {
self.#checkIfInitialized(false);
// Allow getting in loadFromPrefs before we are initialized.
if (!this.#allowUninitialized) {
this.#checkIfInitialized();
}
let val = this.#settings[groupname][name];
if (copy) {
val = copy(val);
......@@ -322,7 +455,10 @@ class TorSettingsImpl {
set: getter
? undefined
: val => {
self.#checkIfInitialized(false);
// Allow setting in loadFromPrefs before we are initialized.
if (!this.#allowUninitialized) {
this.#checkIfInitialized();
}
const prevVal = this.#settings[groupname][name];
this.freezeNotifications();
try {
......@@ -421,7 +557,6 @@ class TorSettingsImpl {
lazy.logger.warn("Called init twice.");
return;
}
this.#initializing = true;
try {
await this.#initInternal();
this.#initialized = true;
......@@ -429,8 +564,6 @@ class TorSettingsImpl {
} catch (e) {
this.#initFailed(e);
throw e;
} finally {
this.#initializing = false;
}
}
......@@ -439,137 +572,10 @@ class TorSettingsImpl {
* it easier to update initializatedPromise.
*/
async #initInternal() {
this.#addProperties("quickstart", {
enabled: {},
});
this.#addProperties("bridges", {
enabled: {},
source: {
transform: val => {
if (Object.values(TorBridgeSource).includes(val)) {
return val;
}
lazy.logger.error(`Not a valid bridge source: "${val}"`);
return TorBridgeSource.Invalid;
},
},
bridge_strings: {
transform: val => {
if (Array.isArray(val)) {
return [...val];
}
return parseBridgeStrings(val);
},
copy: val => [...val],
equal: (val1, val2) => this.#arrayEqual(val1, val2),
},
builtin_type: {
callback: val => {
if (!val) {
// Make sure that the source is not BuiltIn
if (this.bridges.source === TorBridgeSource.BuiltIn) {
this.bridges.source = TorBridgeSource.Invalid;
}
return;
}
const bridgeStrings = this.#getBuiltinBridges(val);
if (bridgeStrings.length) {
this.bridges.bridge_strings = bridgeStrings;
return;
}
lazy.logger.error(`No built-in ${val} bridges found`);
// Change to be empty, this will trigger this callback again,
// but with val as "".
this.bridges.builtin_type == "";
},
},
});
this.#addProperties("proxy", {
enabled: {
callback: val => {
if (val) {
return;
}
// Reset proxy settings.
this.proxy.type = TorProxyType.Invalid;
this.proxy.address = "";
this.proxy.port = 0;
this.proxy.username = "";
this.proxy.password = "";
},
},
type: {
transform: val => {
if (Object.values(TorProxyType).includes(val)) {
return val;
}
lazy.logger.error(`Not a valid proxy type: "${val}"`);
return TorProxyType.Invalid;
},
},
address: {},
port: {
transform: val => {
if (val === 0) {
// This is a valid value that "unsets" the port.
// Keep this value without giving a warning.
// NOTE: In contrast, "0" is not valid.
return 0;
}
// Unset to 0 if invalid null is returned.
return this.#parsePort(val, false) ?? 0;
},
},
username: {},
password: {},
uri: {
getter: () => {
const { type, address, port, username, password } = this.proxy;
switch (type) {
case TorProxyType.Socks4:
return `socks4a://${address}:${port}`;
case TorProxyType.Socks5:
if (username) {
return `socks5://${username}:${password}@${address}:${port}`;
}
return `socks5://${address}:${port}`;
case TorProxyType.HTTPS:
if (username) {
return `http://${username}:${password}@${address}:${port}`;
}
return `http://${address}:${port}`;
}
return null;
},
},
});
this.#addProperties("firewall", {
enabled: {
callback: val => {
if (!val) {
this.firewall.allowed_ports = "";
}
},
},
allowed_ports: {
transform: val => {
if (!Array.isArray(val)) {
val = val === "" ? [] : val.split(",");
}
// parse and remove duplicates
const portSet = new Set(val.map(p => this.#parsePort(p, true)));
// parsePort returns null for failed parses, so remove it.
portSet.delete(null);
return [...portSet];
},
copy: val => [...val],
equal: (val1, val2) => this.#arrayEqual(val1, val2),
},
});
try {
const req = await fetch("chrome://global/content/pt_config.json");
const config = await req.json();
lazy.logger.debug("Loaded pt_config.json", config);
this.#recommendedPT = config.recommendedDefault;
this.#builtinBridges = config.bridges;
} catch (e) {
......@@ -584,8 +590,10 @@ class TorSettingsImpl {
// Do not want notifications for initially loaded prefs.
this.freezeNotifications();
try {
this.#allowUninitialized = true;
this.#loadFromPrefs();
} finally {
this.#allowUninitialized = false;
this.#notificationQueue.clear();
this.thawNotifications();
}
......@@ -606,13 +614,9 @@ class TorSettingsImpl {
/**
* Check whether the object has been successfully initialized, and throw if
* it has not.
*
* @param {boolean} fatal If true, this will always be an error; if false, it
* will be ignored during initialization (setters and getters are called by
* #loadFromPrefs).
*/
#checkIfInitialized(fatal) {
if (!this.#initialized && (!this.#initializing || fatal)) {
#checkIfInitialized() {
if (!this.#initialized) {
lazy.logger.trace("Not initialized code path.");
throw new Error(
"TorSettings has not been initialized yet, or its initialization failed"
......@@ -652,7 +656,7 @@ class TorSettingsImpl {
*/
async #handleProcessReady() {
// push down settings to tor
await this.applySettings();
await this.#applySettings(true);
lazy.logger.info("Ready");
Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
}
......@@ -739,7 +743,7 @@ class TorSettingsImpl {
saveToPrefs() {
lazy.logger.debug("saveToPrefs()");
this.#checkIfInitialized(true);
this.#checkIfInitialized();
/* Quickstart */
Services.prefs.setBoolPref(
......@@ -828,12 +832,23 @@ class TorSettingsImpl {
* Push our settings down to the tor provider.
*/
async applySettings() {
lazy.logger.debug("applySettings()");
this.#checkIfInitialized();
return this.#applySettings(false);
}
await this.initializedPromise;
/**
* Internal implementation of applySettings that does not check if we are
* initialized.
*/
async #applySettings(allowUninitialized) {
lazy.logger.debug("#applySettings()");
const settingsMap = new Map();
// #applySettings can be called only when #allowUninitialized is false
this.#allowUninitialized = allowUninitialized;
try {
/* Bridges */
const haveBridges =
this.bridges.enabled && !!this.bridges.bridge_strings.length;
......@@ -885,12 +900,13 @@ class TorSettingsImpl {
} else {
settingsMap.set(TorConfigKeys.reachableAddresses, null);
}
} finally {
this.#allowUninitialized = false;
}
/* Push to Tor */
const provider = await lazy.TorProviderBuilder.build();
await provider.writeSettings(settingsMap);
return this;
}
/**
......@@ -900,7 +916,7 @@ class TorSettingsImpl {
*/
setSettings(settings) {
lazy.logger.debug("setSettings()");
this.#checkIfInitialized(true);
this.#checkIfInitialized();
const backup = this.getSettings();
const backupNotifications = [...this.#notificationQueue];
......@@ -965,7 +981,7 @@ class TorSettingsImpl {
*/
getSettings() {
lazy.logger.debug("getSettings()");
this.#checkIfInitialized(true);
this.#checkIfInitialized();
return structuredClone(this.#settings);
}
......@@ -976,7 +992,7 @@ class TorSettingsImpl {
* @returns {string[]} An array with PT identifiers
*/
get builtinBridgeTypes() {
this.#checkIfInitialized(true);
this.#checkIfInitialized();
const types = Object.keys(this.#builtinBridges);
const recommendedIndex = types.indexOf(this.#recommendedPT);
if (recommendedIndex > 0) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment