Commit f4980a6c authored by Kathleen Brade's avatar Kathleen Brade Committed by Georg Koppen
Browse files

Bug 20111: use Unix domain sockets for SOCKS port by default

Retrieve SOCKS port configuration from Tor Launcher if available.
Support the TOR_SOCKS_IPC_PATH environment variable.

Consistently use IPC to refer to Unix domain sockets.

Enhance torbutton_local_tor_check() to correctly handle spaces and
escaped characters within the 'GETINFO net/listeners/socks' response.

Within startup-observer.js, use Services.jsm, Cc and similar
constants, and remove unused code.
parent 70e5f3b2
......@@ -6,7 +6,7 @@
// with docco.js to produce pretty documentation.
//
// This script is to be embedded in torbutton.xul. It defines a single global
// function, createTorCircuitDisplay(socketFile, host, port, password), which
// function, createTorCircuitDisplay(ipcFile, host, port, password), which
// activates the automatic Tor circuit display for the current tab and any
// future tabs.
//
......@@ -16,9 +16,9 @@
/* global document, gBrowser, Components */
// ### Main function
// __createTorCircuitDisplay(socketFile, host, port, password, enablePrefName)__.
// __createTorCircuitDisplay(ipcFile, host, port, password, enablePrefName)__.
// The single function that prepares tor circuit display. Connects to a tor
// control port with the given socketFile or host plus port, and password, and
// control port with the given ipcFile or host plus port, and password, and
// binds to a named bool pref whose value determines whether the circuit display
// is enabled or disabled.
let createTorCircuitDisplay = (function () {
......@@ -312,11 +312,11 @@ let syncDisplayWithSelectedTab = (function() {
// ## Main function
// __setupDisplay(socketFile, host, port, password, enablePrefName)__.
// __setupDisplay(ipcFile, host, port, password, enablePrefName)__.
// Once called, the Tor circuit display will be started whenever
// the "enablePref" is set to true, and stopped when it is set to false.
// A reference to this function (called createTorCircuitDisplay) is exported as a global.
let setupDisplay = function (socketFile, host, port, password, enablePrefName) {
let setupDisplay = function (ipcFile, host, port, password, enablePrefName) {
let myController = null,
stopCollectingIsolationData = null,
stop = function() {
......@@ -330,7 +330,7 @@ let setupDisplay = function (socketFile, host, port, password, enablePrefName) {
},
start = function () {
if (!myController) {
myController = controller(socketFile, host, port || 9151, password,
myController = controller(ipcFile, host, port || 9151, password,
function (err) {
// An error has occurred.
logger.eclog(5, err);
......
......@@ -10,6 +10,7 @@
let { LoadContextInfo } = Cu.import('resource://gre/modules/LoadContextInfo.jsm');
let { Services } = Cu.import("resource://gre/modules/Services.jsm");
let { showDialog } = Cu.import("resource://torbutton/modules/utils.js");
let { unescapeTorString } = Cu.import("resource://torbutton/modules/utils.js");
const k_tb_last_browser_version_pref = "extensions.torbutton.lastBrowserVersion";
const k_tb_browser_update_needed_pref = "extensions.torbutton.updateNeeded";
......@@ -33,9 +34,9 @@ var m_tb_window_width = window.outerWidth;
var m_tb_tbb = false;
var m_tb_control_socket_file = null; // Set if using a UNIX domain socket.
var m_tb_control_port = null; // Set if not using a socket.
var m_tb_control_host = null; // Set if not using a socket.
var m_tb_control_ipc_file = null; // Set if using IPC (UNIX domain socket).
var m_tb_control_port = null; // Set if using TCP.
var m_tb_control_host = null; // Set if using TCP.
var m_tb_control_pass = null;
var m_tb_control_desc = null; // For logging.
......@@ -327,15 +328,15 @@ function torbutton_init() {
m_tb_control_pass = tlps.TorGetPassword(false);
} catch(e) {}
// Try to get control port socket (an nsIFile) from Tor Launcher, since
// Tor Launcher knows how to handle its own preferences and how to
// Try to get the control port IPC file (an nsIFile) from Tor Launcher,
// since Tor Launcher knows how to handle its own preferences and how to
// resolve relative paths.
try {
m_tb_control_socket_file = tlps.TorGetControlSocketFile();
m_tb_control_ipc_file = tlps.TorGetControlIPCFile();
} catch(e) {}
if (m_tb_control_socket_file) {
m_tb_control_desc = m_tb_control_socket_file.path;
if (m_tb_control_ipc_file) {
m_tb_control_desc = m_tb_control_ipc_file.path;
} else {
if (environ.exists("TOR_CONTROL_PORT")) {
m_tb_control_port = environ.get("TOR_CONTROL_PORT");
......@@ -438,7 +439,7 @@ function torbutton_init() {
torbutton_notify_if_update_needed();
torbutton_update_sync_ui();
createTorCircuitDisplay(m_tb_control_socket_file, m_tb_control_host,
createTorCircuitDisplay(m_tb_control_ipc_file, m_tb_control_host,
m_tb_control_port, m_tb_control_pass,
"extensions.torbutton.display_circuit");
......@@ -1009,8 +1010,8 @@ function torbutton_send_ctrl_cmd(command) {
let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService);
let socket;
if (m_tb_control_socket_file) {
socket = sts.createUnixDomainTransport(m_tb_control_socket_file);
if (m_tb_control_ipc_file) {
socket = sts.createUnixDomainTransport(m_tb_control_ipc_file);
} else {
socket = sts.createTransport(null, 0, m_tb_control_host,
m_tb_control_port, null);
......@@ -1350,7 +1351,7 @@ function torbutton_do_new_identity() {
torbutton_log(3, "New Identity: Sending NEWNYM");
// We only support TBB for newnym.
if (!m_tb_control_pass || (!m_tb_control_socket_file && !m_tb_control_port)) {
if (!m_tb_control_pass || (!m_tb_control_ipc_file && !m_tb_control_port)) {
var warning = torbutton_get_property_string("torbutton.popup.no_newnym");
torbutton_log(5, "Torbutton cannot safely newnym. It does not have access to the Tor Control Port.");
window.alert(warning);
......@@ -1508,7 +1509,7 @@ function torbutton_do_tor_check()
const kEnvUseTransparentProxy = "TOR_TRANSPROXY";
var env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
if ((m_tb_control_socket_file || m_tb_control_port) &&
if ((m_tb_control_ipc_file || m_tb_control_port) &&
!env.exists(kEnvUseTransparentProxy) &&
!env.exists(kEnvSkipControlPortTest) &&
m_tb_prefs.getBoolPref("extensions.torbutton.local_tor_check")) {
......@@ -1559,19 +1560,20 @@ function torbutton_local_tor_check()
}
// Sample response: net/listeners/socks="127.0.0.1:9149" "127.0.0.1:9150"
// First, check for command argument prefix.
// First, check for and remove the command argument prefix.
if (0 != resp.indexOf(kCmdArg + '=')) {
logUnexpectedResponse();
return false;
}
resp = resp.substr(kCmdArg.length + 1);
// Retrieve configured proxy settings and check each listener against them.
// When a Unix domain socket is configured, a file URL should be present in
// network.proxy.socks.
// When the SOCKS prefs are set to use IPC (e.g., a Unix domain socket), a
// file URL should be present in network.proxy.socks.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1211567
let socksAddr = m_tb_prefs.getCharPref("network.proxy.socks");
let socksPort = m_tb_prefs.getIntPref("network.proxy.socks_port");
let socketPath;
let socksIPCPath;
if (socksAddr && socksAddr.startsWith("file:")) {
// Convert the file URL to a file path.
try {
......@@ -1579,19 +1581,29 @@ function torbutton_local_tor_check()
.getService(Ci.nsIIOService);
let fph = ioService.getProtocolHandler("file")
.QueryInterface(Ci.nsIFileProtocolHandler);
socketPath = fph.getFileFromURLSpec(socksAddr).path;
socksIPCPath = fph.getFileFromURLSpec(socksAddr).path;
} catch (e) {
torbutton_log(5, "Local Tor check: Unix domain socket error: " + e);
torbutton_log(5, "Local Tor check: IPC file error: " + e);
return false;
}
} else {
socksAddr = removeBrackets(socksAddr);
}
let addrArray = resp.substr(kCmdArg.length + 1).split(' ');
// Split into quoted strings. This code is adapted from utils.splitAtSpaces()
// within tor-control-port.js; someday this code should use the entire
// tor-control-port.js framework.
let addrArray = [];
resp.replace(/((\S*?"(.*?)")+\S*|\S+)/g, function (a, captured) {
addrArray.push(captured);
});
let foundSocksListener = false;
for (let i = 0; !foundSocksListener && (i < addrArray.length); ++i) {
let addr = addrArray[i];
let addr;
try { addr = unescapeTorString(addrArray[i]); } catch (e) {}
if (!addr)
continue;
// Remove double quotes if present.
let len = addr.length;
......@@ -1599,14 +1611,14 @@ function torbutton_local_tor_check()
addr = addr.substring(1, len - 1);
if (addr.startsWith("unix:")) {
if (!socketPath)
if (!socksIPCPath)
continue;
// Check against the configured UNIX domain socket proxy.
let path = addr.substring(5);
torbutton_log(2, "Tor socks listener (socket): " + path);
foundSocksListener = (socketPath === path);
} else if (!socketPath) {
torbutton_log(2, "Tor socks listener (Unix domain socket): " + path);
foundSocksListener = (socksIPCPath === path);
} else if (!socksIPCPath) {
// Check against the configured TCP proxy. We expect addr:port where addr
// may be an IPv6 address; that is, it may contain colon characters.
// Also, we remove enclosing square brackets before comparing addresses
......@@ -2088,7 +2100,7 @@ function torbutton_check_protections()
// See https://trac.torproject.org/projects/tor/ticket/10353 for more info.
document.getElementById("torbutton-cookie-protector").hidden = m_tb_prefs.getBoolPref("browser.privatebrowsing.autostart");
if (!m_tb_control_pass || (!m_tb_control_socket_file && !m_tb_control_port)) {
if (!m_tb_control_pass || (!m_tb_control_ipc_file && !m_tb_control_port)) {
document.getElementById("torbutton-new-identity").disabled = true;
}
......
......@@ -15,6 +15,13 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
// Module specific constants
const kMODULE_NAME = "Startup";
......@@ -22,10 +29,9 @@ const kMODULE_CONTRACTID = "@torproject.org/startup-observer;1";
const kMODULE_CID = Components.ID("06322def-6fde-4c06-aef6-47ae8e799629");
function StartupObserver() {
this.logger = Components.classes["@torproject.org/torbutton-logger;1"]
.getService(Components.interfaces.nsISupports).wrappedJSObject;
this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
this.logger = Cc["@torproject.org/torbutton-logger;1"]
.getService(Ci.nsISupports).wrappedJSObject;
this._prefs = Services.prefs;
this.logger.log(3, "Startup Observer created");
var env = Cc["@mozilla.org/process/environment;1"]
......@@ -61,41 +67,74 @@ StartupObserver.prototype = {
// some weird proxy caching code that showed up in FF15.
// Otherwise, homepage domain loads fail forever.
setProxySettings: function() {
if (!this.is_tbb)
return;
// Bug 1506: Still want to get these env vars
var environ = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
if (environ.exists("TOR_SOCKS_PORT")) {
if (this.is_tbb) {
this._prefs.setIntPref('network.proxy.socks_port',
parseInt(environ.get("TOR_SOCKS_PORT")));
this._prefs.setBoolPref('network.proxy.socks_remote_dns', true);
this._prefs.setIntPref('network.proxy.type', 1);
let environ = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
if (environ.exists("TOR_TRANSPROXY")) {
this.logger.log(3, "Resetting Tor settings to transproxy");
this._prefs.setBoolPref("network.proxy.socks_remote_dns", false);
this._prefs.setIntPref("network.proxy.type", 0);
this._prefs.setIntPref("network.proxy.socks_port", 0);
this._prefs.setCharPref("network.proxy.socks", "");
} else {
// Try to retrieve SOCKS proxy settings from Tor Launcher.
let socksPortInfo;
try {
let tlps = Cc["@torproject.org/torlauncher-protocol-service;1"]
.getService(Ci.nsISupports).wrappedJSObject;
socksPortInfo = tlps.TorGetSOCKSPortInfo();
} catch(e) {}
// If Tor Launcher is not available, check environment variables.
if (!socksPortInfo) {
socksPortInfo = { ipcFile: undefined, host: undefined, port: 0 };
let isWindows = Services.appinfo.OS === "WINNT";
if (!isWindows && environ.exists("TOR_SOCKS_IPC_PATH")) {
socksPortInfo.ipcFile = new FileUtils.File(
environ.get("TOR_SOCKS_IPC_PATH"));
}
else
{
if (environ.exists("TOR_SOCKS_HOST"))
socksPortInfo.host = environ.get("TOR_SOCKS_HOST");
if (environ.exists("TOR_SOCKS_PORT"))
socksPortInfo.port = parseInt(environ.get("TOR_SOCKS_PORT"));
}
}
this.logger.log(3, "Reset socks port to "+environ.get("TOR_SOCKS_PORT"));
}
if (environ.exists("TOR_SOCKS_HOST")) {
if (this.is_tbb) {
this._prefs.setCharPref('network.proxy.socks', environ.get("TOR_SOCKS_HOST"));
// Adjust network.proxy prefs.
if (socksPortInfo.ipcFile) {
let fph = Services.io.getProtocolHandler("file")
.QueryInterface(Ci.nsIFileProtocolHandler);
let fileURI = fph.newFileURI(socksPortInfo.ipcFile);
this.logger.log(3, "Reset socks to "+fileURI.spec);
this._prefs.setCharPref("network.proxy.socks", fileURI.spec);
this._prefs.setIntPref("network.proxy.socks_port", 0);
} else {
if (socksPortInfo.host) {
this._prefs.setCharPref("network.proxy.socks", socksPortInfo.host);
this.logger.log(3, "Reset socks host to "+socksPortInfo.host);
}
if (socksPortInfo.port) {
this._prefs.setIntPref("network.proxy.socks_port",
socksPortInfo.port);
this.logger.log(3, "Reset socks port to "+socksPortInfo.port);
}
}
}
if (environ.exists("TOR_TRANSPROXY")) {
this.logger.log(3, "Resetting Tor settings to transproxy");
if (this.is_tbb) {
this._prefs.setBoolPref('network.proxy.socks_remote_dns', false);
this._prefs.setIntPref('network.proxy.type', 0);
this._prefs.setIntPref('network.proxy.socks_port', 0);
this._prefs.setCharPref('network.proxy.socks', "");
if (socksPortInfo.ipcFile || socksPortInfo.host || socksPortInfo.port) {
this._prefs.setBoolPref("network.proxy.socks_remote_dns", true);
this._prefs.setIntPref("network.proxy.type", 1);
}
}
// Force prefs to be synced to disk
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
prefService.savePrefFile(null);
this._prefs.savePrefFile(null);
this.logger.log(3, "Synced network settings to environment.");
},
......@@ -109,16 +148,14 @@ StartupObserver.prototype = {
}
// In all cases, force prefs to be synced to disk
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
prefService.savePrefFile(null);
this._prefs.savePrefFile(null);
},
QueryInterface: function(iid) {
if (iid.equals(Components.interfaces.nsISupports)) {
if (iid.equals(Ci.nsISupports)) {
return this;
}
if(iid.equals(Components.interfaces.nsIClassInfo)) {
if(iid.equals(Ci.nsIClassInfo)) {
return this;
}
return this;
......@@ -141,12 +178,4 @@ StartupObserver.prototype = {
};
/**
* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
*/
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
if (XPCOMUtils.generateNSGetFactory)
var NSGetFactory = XPCOMUtils.generateNSGetFactory([StartupObserver]);
else
var NSGetModule = XPCOMUtils.generateNSGetModule([StartupObserver]);
var NSGetFactory = XPCOMUtils.generateNSGetFactory([StartupObserver]);
......@@ -10,7 +10,7 @@
// let { controller } = Components.utils.import("path/to/tor-control-port.js");
//
// See the last function defined in this file:
// controller(socketFile, host, port, password, onError)
// controller(ipcFile, host, port, password, onError)
// for usage of the controller function.
/* jshint esnext: true */
......@@ -42,18 +42,18 @@ log("Loading tor-control-port.js\n");
// I/O utilities namespace
let io = {};
// __io.asyncSocketStreams(socketFile, host, port)__.
// __io.asyncSocketStreams(ipcFile, host, port)__.
// Creates a pair of asynchronous input and output streams for a socket at the
// given socketFile or host and port.
io.asyncSocketStreams = function (socketFile, host, port) {
// given ipcFile or host and port.
io.asyncSocketStreams = function (ipcFile, host, port) {
let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Components.interfaces.nsISocketTransportService),
UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED;
// Create an instance of a socket transport.
let socketTransport;
if (socketFile) {
socketTransport = sts.createUnixDomainTransport(socketFile);
if (ipcFile) {
socketTransport = sts.createUnixDomainTransport(ipcFile);
} else {
socketTransport = sts.createTransport(null, 0, host, port, null);
}
......@@ -99,16 +99,15 @@ io.pumpInputStream = function (inputStream, onInputData, onError) {
} }, null);
};
// __io.asyncSocket(socketFile, host, port, onInputData, onError)__.
// Creates an asynchronous, text-oriented UNIX domain socket (if socketFile
// is defined) or TCP socket at host:port.
// __io.asyncSocket(ipcFile, host, port, onInputData, onError)__.
// Creates an asynchronous, text-oriented IPC socket (if ipcFile is defined)
// or a TCP socket at host:port.
// The onInputData callback should accept a single argument, which will be called
// repeatedly, whenever incoming text arrives. Returns a socket object with two methods:
// socket.write(text) and socket.close(). onError will be passed the error object
// whenever a write fails.
io.asyncSocket = function (socketFile, host, port, onInputData, onError) {
let [inputStream, outputStream] = io.asyncSocketStreams(socketFile, host,
port),
io.asyncSocket = function (ipcFile, host, port, onInputData, onError) {
let [inputStream, outputStream] = io.asyncSocketStreams(ipcFile, host, port),
pendingWrites = [];
// Run an input stream pump to send incoming data to the onInputData callback.
io.pumpInputStream(inputStream, onInputData, onError);
......@@ -253,8 +252,8 @@ io.matchRepliesToCommands = function (asyncSend, dispatcher) {
});
};
// __io.controlSocket(socketFile, host, port, password, onError)__.
// Instantiates and returns a socket to a tor ControlPort at socketFile or
// __io.controlSocket(ipcFile, host, port, password, onError)__.
// Instantiates and returns a socket to a tor ControlPort at ipcFile or
// host:port, authenticating with the given password. onError is called with an
// error object as its single argument whenever an error occurs. Example:
//
......@@ -269,11 +268,11 @@ io.matchRepliesToCommands = function (asyncSend, dispatcher) {
// socket.removeNotificationCallback(callback);
// // Close the socket permanently
// socket.close();
io.controlSocket = function (socketFile, host, port, password, onError) {
io.controlSocket = function (ipcFile, host, port, password, onError) {
// Produce a callback dispatcher for Tor messages.
let mainDispatcher = io.callbackDispatcher(),
// Open the socket and convert format to Tor messages.
socket = io.asyncSocket(socketFile, host, port,
socket = io.asyncSocket(ipcFile, host, port,
io.onDataFromOnLine(
io.onLineFromOnMessage(mainDispatcher.pushMessage)),
onError),
......@@ -620,12 +619,12 @@ let tor = {};
// redundant instantiation of control sockets.
tor.controllerCache = {};
// __tor.controller(socketFile, host, port, password, onError)__.
// Creates a tor controller at the given socketFile or host and port, with the
// __tor.controller(ipcFile, host, port, password, onError)__.
// Creates a tor controller at the given ipcFile or host and port, with the
// given password.
// onError returns asynchronously whenever a connection error occurs.
tor.controller = function (socketFile, host, port, password, onError) {
let socket = io.controlSocket(socketFile, host, port, password, onError),
tor.controller = function (ipcFile, host, port, password, onError) {
let socket = io.controlSocket(ipcFile, host, port, password, onError),
isOpen = true;
return { getInfo : key => info.getInfo(socket, key),
getConf : key => info.getConf(socket, key),
......@@ -638,11 +637,11 @@ tor.controller = function (socketFile, host, port, password, onError) {
// ## Export
// __controller(socketFile, host, port, password, onError)__.
// __controller(ipcFile, host, port, password, onError)__.
// Instantiates and returns a controller object connected to a tor ControlPort
// on socketFile or at host:port, authenticating with the given password, if
// on ipcFile or at host:port, authenticating with the given password, if
// the controller doesn't yet exist. Otherwise returns the existing controller
// to the given socketFile or host:port.
// to the given ipcFile or host:port.
// onError is called with an error object as its single argument whenever
// an error occurs. Example:
//
......@@ -653,13 +652,13 @@ tor.controller = function (socketFile, host, port, password, onError) {
// let replyPromise = c.getInfo("ip-to-country/16.16.16.16");
// // Close the controller permanently
// c.close();
var controller = function (socketFile, host, port, password, onError) {
let dest = (socketFile) ? "unix:" + socketFile.path : host + ":" + port,
var controller = function (ipcFile, host, port, password, onError) {
let dest = (ipcFile) ? "unix:" + ipcFile.path : host + ":" + port,
maybeController = tor.controllerCache[dest];
return (tor.controllerCache[dest] =
(maybeController && maybeController.isOpen()) ?
maybeController :
tor.controller(socketFile, host, port, password, onError));
tor.controller(ipcFile, host, port, password, onError));
};
// Export the controller function for external use.
......
......@@ -74,5 +74,104 @@ var showDialog = function (parent, url, name, features) {
}
};
// ## Tor control protocol utility functions
let _torControl = {
// Unescape Tor Control string aStr (removing surrounding "" and \ escapes).
// Based on Vidalia's src/common/stringutil.cpp:string_unescape().
// Returns the unescaped string. Throws upon failure.
// Within Tor Launcher, the file components/tl-protocol.js also contains a
// copy of _strUnescape().
_strUnescape: function(aStr)
{
if (!aStr)
return aStr;
var len = aStr.length;
if ((len < 2) || ('"' != aStr.charAt(0)) || ('"' != aStr.charAt(len - 1)))
return aStr;
const kHexRE = /[0-9A-Fa-f]{2}/;
const kOctalRE = /[0-7]{3}/;
var rv = "";
var i = 1;
var lastCharIndex = len - 2;
while (i <= lastCharIndex)
{
var c = aStr.charAt(i);
if ('\\' == c)
{
if (++i > lastCharIndex)
throw new Error("missing character after \\");
c = aStr.charAt(i);
if ('n' == c)
rv += '\n';
else if ('r' == c)
rv += '\r';
else if ('t' == c)
rv += '\t';
else if ('x' == c)
{
if ((i + 2) > lastCharIndex)
throw new Error("not enough hex characters");
let s = aStr.substr(i + 1, 2);
if (!kHexRE.test(s))
throw new Error("invalid hex characters");
let val = parseInt(s, 16);
rv += String.fromCharCode(val);
i += 3;
}
else if (this._isDigit(c))
{
let s = aStr.substr(i, 3);
if ((i + 2) > lastCharIndex)
throw new Error("not enough octal characters");
if (!kOctalRE.test(s))
throw new Error("invalid octal characters");
let val = parseInt(s, 8);
rv += String.fromCharCode(val);
i += 3;
}
else // "\\" and others
{
rv += c;
++i;
}
}
else if ('"' == c)
throw new Error("unescaped \" within string");
else
{
rv += c;
++i;
}
}
// Convert from UTF-8 to Unicode. TODO: is UTF-8 always used in protocol?
return decodeURIComponent(escape(rv));
}, // _strUnescape()
// Within Tor Launcher, the file components/tl-protocol.js also contains a
// copy of _isDigit().
_isDigit: function(aChar)
{
const kRE = /^\d$/;
return aChar && kRE.test(aChar);
},
}; // _torControl
// __unescapeTorString(str, resultObj)__.
// Unescape Tor Control string str (removing surrounding "" and \ escapes).
// Returns the unescaped string. Throws upon failure.
var unescapeTorString = function(str) {
return _torControl._strUnescape(str);
};
// Export utility functions for external use.
let EXPORTED_SYMBOLS = ["bindPrefAndInit", "getPrefValue", "getEnv", "showDialog"];
let EXPORTED_SYMBOLS = ["bindPrefAndInit", "getPrefValue", "getEnv",
"showDialog", "unescapeTorString"];