[tor-commits] [torbutton/master] Bug 20111: use Unix domain sockets for SOCKS port by default

gk at torproject.org gk at torproject.org
Thu Oct 20 16:03:34 UTC 2016


commit f4980a6c647ea780eacc922846e81c0d992cbd13
Author: Kathy Brade <brade at pearlcrescent.com>
Date:   Tue Oct 11 15:37:47 2016 -0400

    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.
---
 src/chrome/content/tor-circuit-display.js |  12 +--
 src/chrome/content/torbutton.js           |  64 +++++++++-------
 src/components/startup-observer.js        | 117 +++++++++++++++++++-----------
 src/modules/tor-control-port.js           |  51 +++++++------
 src/modules/utils.js                      | 101 +++++++++++++++++++++++++-
 5 files changed, 242 insertions(+), 103 deletions(-)

diff --git a/src/chrome/content/tor-circuit-display.js b/src/chrome/content/tor-circuit-display.js
index f1cf2aa..c58c6d7 100644
--- a/src/chrome/content/tor-circuit-display.js
+++ b/src/chrome/content/tor-circuit-display.js
@@ -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);
diff --git a/src/chrome/content/torbutton.js b/src/chrome/content/torbutton.js
index d2824cb..9a8b9e2 100644
--- a/src/chrome/content/torbutton.js
+++ b/src/chrome/content/torbutton.js
@@ -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;
   }
 
diff --git a/src/components/startup-observer.js b/src/components/startup-observer.js
index f083482..6698b0b 100644
--- a/src/components/startup-observer.js
+++ b/src/components/startup-observer.js
@@ -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]);
diff --git a/src/modules/tor-control-port.js b/src/modules/tor-control-port.js
index f23daed..a7f0434 100644
--- a/src/modules/tor-control-port.js
+++ b/src/modules/tor-control-port.js
@@ -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.
diff --git a/src/modules/utils.js b/src/modules/utils.js
index 514ef51..b303485 100644
--- a/src/modules/utils.js
+++ b/src/modules/utils.js
@@ -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"];



More information about the tor-commits mailing list