Bug 14271: Make Torbutton work with Unix Domain Socket option
[torbutton.git] / src / modules / tor-control-port.js
blobf23daed1b09290b81ac41d29e4a77ba830092336
1 // A module for TorBrowser that provides an asynchronous controller for
2 // Tor, through its ControlPort.
3 //
4 // This file is written in call stack order (later functions
5 // call earlier functions). The file can be processed
6 // with docco.js to produce pretty documentation.
7 //
8 // To import the module, use
9 //
10 //  let { controller } = Components.utils.import("path/to/tor-control-port.js");
12 // See the last function defined in this file:
13 //   controller(socketFile, host, port, password, onError)
14 // for usage of the controller function.
16 /* jshint esnext: true */
17 /* jshint -W097 */
18 /* global Components, console, Services */
19 "use strict";
21 // ### Mozilla Abbreviations
22 let {classes: Cc, interfaces: Ci, results: Cr, Constructor: CC, utils: Cu } = Components;
24 // ### Import Mozilla Services
25 Cu.import("resource://gre/modules/Services.jsm");
27 // __log__.
28 // Logging function
29 let log;
30 if ((typeof console) !== "undefined") {
31   log = x => console.log(typeof(x) === "string" ? x.trimRight().replace(/\r\n/g, "\n") : JSON.stringify(x));
32 } else {
33   let logger = Cc["@torproject.org/torbutton-logger;1"]
34                  .getService(Components.interfaces.nsISupports).wrappedJSObject;
35   log = x => logger.eclog(3, x.trimRight().replace(/\r\n/g, "\n"));
38 // ### announce this file
39 log("Loading tor-control-port.js\n");
41 // ## io
42 // I/O utilities namespace
43 let io = {};
45 // __io.asyncSocketStreams(socketFile, host, port)__.
46 // Creates a pair of asynchronous input and output streams for a socket at the
47 // given socketFile or host and port.
48 io.asyncSocketStreams = function (socketFile, host, port) {
49   let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
50               .getService(Components.interfaces.nsISocketTransportService),
51           UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED;
53   // Create an instance of a socket transport.
54   let socketTransport;
55   if (socketFile) {
56     socketTransport = sts.createUnixDomainTransport(socketFile);
57   } else {
58     socketTransport = sts.createTransport(null, 0, host, port, null);
59   }
61   // Open unbuffered asynchronous outputStream.
62   let outputStream = socketTransport.openOutputStream(UNBUFFERED, 1, 1)
63                       .QueryInterface(Ci.nsIAsyncOutputStream),
64       // Open unbuffered asynchronous inputStream.
65       inputStream = socketTransport.openInputStream(UNBUFFERED, 1, 1)
66                       .QueryInterface(Ci.nsIAsyncInputStream);
67   return [inputStream, outputStream];
70 // __io.pumpInputStream(scriptableInputStream, onInputData, onError)__.
71 // Run an "input stream pump" that takes an input stream and
72 // asynchronously pumps incoming data to the onInputData callback.
73 io.pumpInputStream = function (inputStream, onInputData, onError) {
74   // Wrap raw inputStream with a "ScriptableInputStream" so we can read incoming data.
75   let ScriptableInputStream = Components.Constructor(
76     "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
77       scriptableInputStream = new ScriptableInputStream(inputStream),
78       // A private method to read all data available on the input stream.
79       readAll = function() {
80         return scriptableInputStream.read(scriptableInputStream.available());
81       },
82       pump = Cc["@mozilla.org/network/input-stream-pump;1"]
83                .createInstance(Components.interfaces.nsIInputStreamPump);
84   // Start the pump.
85   pump.init(inputStream, -1, -1, 0, 0, true);
86   // Tell the pump to read all data whenever it is available, and pass the data
87   // to the onInputData callback. The first argument to asyncRead implements
88   // nsIStreamListener.
89   pump.asyncRead({ onStartRequest: function (request, context) { },
90                    onStopRequest: function (request, context, code) { },
91                    onDataAvailable : function (request, context, stream, offset, count) {
92                      try {
93                        onInputData(readAll());
94                      } catch (error) {
95                        // readAll() or onInputData(...) has thrown an error.
96                        // Notify calling code through onError.
97                        onError(error);
98                      }
99                    } }, null);
102 // __io.asyncSocket(socketFile, host, port, onInputData, onError)__.
103 // Creates an asynchronous, text-oriented UNIX domain socket (if socketFile
104 // is defined) or TCP socket at host:port.
105 // The onInputData callback should accept a single argument, which will be called
106 // repeatedly, whenever incoming text arrives. Returns a socket object with two methods:
107 // socket.write(text) and socket.close(). onError will be passed the error object
108 // whenever a write fails.
109 io.asyncSocket = function (socketFile, host, port, onInputData, onError) {
110   let [inputStream, outputStream] = io.asyncSocketStreams(socketFile, host,
111                                                           port),
112       pendingWrites = [];
113   // Run an input stream pump to send incoming data to the onInputData callback.
114   io.pumpInputStream(inputStream, onInputData, onError);
115   // Return the "socket object" as described.
116   return {
117            // Write a message to the socket.
118            write : function(aString) {
119              pendingWrites.push(aString);
120              outputStream.asyncWait(
121                // Implement an nsIOutputStreamCallback:
122                { onOutputStreamReady : function () {
123                  let totalString = pendingWrites.join("");
124                    try {
125                      outputStream.write(totalString, totalString.length);
126                      log("controlPort << " + totalString);
127                    } catch (err) {
128                      onError(err);
129                    }
130                    pendingWrites = [];
131                } },
132                0, 0, Services.tm.currentThread);
133            },
134            // Close the socket.
135            close : function () {
136              // Close stream objects.
137              inputStream.close();
138              outputStream.close();
139            }
140          };
143 // __io.onDataFromOnLine(onLine)__.
144 // Converts a callback that expects incoming individual lines of text to a callback that
145 // expects incoming raw socket string data.
146 io.onDataFromOnLine = function (onLine) {
147   // A private variable that stores the last unfinished line.
148   let pendingData = "";
149   // Return a callback to be passed to io.asyncSocket. First, splits data into lines of
150   // text. If the incoming data is not terminated by CRLF, then the last
151   // unfinished line will be stored in pendingData, to be prepended to the data in the
152   // next call to onData. The already complete lines of text are then passed in sequence
153   // to onLine.
154   return function (data) {
155     let totalData = pendingData + data,
156         lines = totalData.split("\r\n"),
157         n = lines.length;
158     pendingData = lines[n - 1];
159     // Call onLine for all completed lines.
160     lines.slice(0,-1).map(onLine);
161   };
164 // __io.onLineFromOnMessage(onMessage)__.
165 // Converts a callback that expects incoming control port multiline message strings to a
166 // callback that expects individual lines.
167 io.onLineFromOnMessage = function (onMessage) {
168   // A private variable that stores the last unfinished line.
169   let pendingLines = [],
170       // A private variable to monitor whether we are receiving a multiline
171       // value, beginning with ###+ and ending with a single ".".
172       multilineValueInProgress = false;
173   // Return a callback that expects individual lines.
174   return function (line) {
175     // Add to the list of pending lines.
176     pendingLines.push(line);
177     // 'Multiline values' are possible. We avoid interrupting one by detecting it
178     // and waiting for a terminating "." on its own line.
179     // (See control-spec section 3.9 and https://trac.torproject.org/16990#comment:28
180     if (line.match(/^\d\d\d\+.+?=$/) && pendingLines.length === 1) {
181       multilineValueInProgress = true;
182     }
183     if (multilineValueInProgress && line.match(/^\.$/)) {
184       multilineValueInProgress = false;
185     }
186     // If line is the last in a message, then pass on the full multiline message.
187     if (!multilineValueInProgress &&
188         line.match(/^\d\d\d /) &&
189         (pendingLines.length === 1 ||
190          pendingLines[0].substring(0,3) === line.substring(0,3))) {
191       // Combine pending lines to form message.
192       let message = pendingLines.join("\r\n");
193       log("controlPort >> " + message);
194       // Wipe pendingLines before we call onMessage, in case onMessage throws an error.
195       pendingLines = [];
196       // Pass multiline message to onMessage.
197       onMessage(message);
198     }
199   };
202 // __io.callbackDispatcher()__.
203 // Returns dispatcher object with three member functions:
204 // dispatcher.addCallback(regex, callback), dispatcher.removeCallback(callback),
205 // and dispatcher.pushMessage(message).
206 // Pass pushMessage to another function that needs a callback with a single string
207 // argument. Whenever dispatcher.pushMessage receives a string, the dispatcher will
208 // check for any regex matches and pass the string on to the corresponding callback(s).
209 io.callbackDispatcher = function () {
210   let callbackPairs = [],
211       removeCallback = function (aCallback) {
212         callbackPairs = callbackPairs.filter(function ([regex, callback]) {
213           return callback !== aCallback;
214         });
215       },
216       addCallback = function (regex, callback) {
217         if (callback) {
218           callbackPairs.push([regex, callback]);
219         }
220         return function () { removeCallback(callback); };
221       },
222       pushMessage = function (message) {
223         for (let [regex, callback] of callbackPairs) {
224           if (message.match(regex)) {
225             callback(message);
226           }
227         }
228       };
229   return { pushMessage : pushMessage, removeCallback : removeCallback,
230            addCallback : addCallback };
233 // __io.matchRepliesToCommands(asyncSend, dispatcher)__.
234 // Takes asyncSend(message), an asynchronous send function, and the callback
235 // dispatcher, and returns a function Promise<response> sendCommand(command).
236 io.matchRepliesToCommands = function (asyncSend, dispatcher) {
237   let commandQueue = [],
238       sendCommand = function (command, replyCallback, errorCallback) {
239         commandQueue.push([command, replyCallback, errorCallback]);
240         asyncSend(command);
241       };
242   // Watch for responses (replies or error messages)
243   dispatcher.addCallback(/^[245]\d\d/, function (message) {
244     let [command, replyCallback, errorCallback] = commandQueue.shift();
245     if (message.match(/^2/) && replyCallback) replyCallback(message);
246     if (message.match(/^[45]/) && errorCallback) {
247       errorCallback(new Error(command + " -> " + message));
248     }
249   });
250   // Create and return a version of sendCommand that returns a Promise.
251   return command => new Promise(function (replyCallback, errorCallback) {
252     sendCommand(command, replyCallback, errorCallback);
253   });
256 // __io.controlSocket(socketFile, host, port, password, onError)__.
257 // Instantiates and returns a socket to a tor ControlPort at socketFile or
258 // host:port, authenticating with the given password. onError is called with an
259 // error object as its single argument whenever an error occurs. Example:
261 //     // Open the socket
262 //     let socket = controlSocket(undefined, "127.0.0.1", 9151, "MyPassw0rd",
263 //                    function (error) { console.log(error.message || error); });
264 //     // Send command and receive "250" reply or error message
265 //     socket.sendCommand(commandText, replyCallback, errorCallback);
266 //     // Register or deregister for "650" notifications
267 //     // that match regex
268 //     socket.addNotificationCallback(regex, callback);
269 //     socket.removeNotificationCallback(callback);
270 //     // Close the socket permanently
271 //     socket.close();
272 io.controlSocket = function (socketFile, host, port, password, onError) {
273   // Produce a callback dispatcher for Tor messages.
274   let mainDispatcher = io.callbackDispatcher(),
275       // Open the socket and convert format to Tor messages.
276       socket = io.asyncSocket(socketFile, host, port,
277                               io.onDataFromOnLine(
278                                    io.onLineFromOnMessage(mainDispatcher.pushMessage)),
279                               onError),
280       // Controllers should send commands terminated by CRLF.
281       writeLine = function (text) { socket.write(text + "\r\n"); },
282       // Create a sendCommand method from writeLine.
283       sendCommand = io.matchRepliesToCommands(writeLine, mainDispatcher),
284       // Create a secondary callback dispatcher for Tor notification messages.
285       notificationDispatcher = io.callbackDispatcher();
286   // Pass asynchronous notifications to notification dispatcher.
287   mainDispatcher.addCallback(/^650/, notificationDispatcher.pushMessage);
288   // Log in to control port.
289   sendCommand("authenticate " + (password || ""));
290   // Activate needed events.
291   sendCommand("setevents stream");
292   return { close : socket.close, sendCommand : sendCommand,
293            addNotificationCallback : notificationDispatcher.addCallback,
294            removeNotificationCallback : notificationDispatcher.removeCallback };
297 // ## utils
298 // A namespace for utility functions
299 let utils = {};
301 // __utils.identity(x)__.
302 // Returns its argument unchanged.
303 utils.identity = function (x) { return x; };
305 // __utils.isString(x)__.
306 // Returns true iff x is a string.
307 utils.isString = function (x) {
308   return typeof(x) === 'string' || x instanceof String;
311 // __utils.capture(string, regex)__.
312 // Takes a string and returns an array of capture items, where regex must have a single
313 // capturing group and use the suffix /.../g to specify a global search.
314 utils.capture = function (string, regex) {
315   let matches = [];
316   // Special trick to use string.replace for capturing multiple matches.
317   string.replace(regex, function (a, captured) {
318     matches.push(captured);
319   });
320   return matches;
323 // __utils.extractor(regex)__.
324 // Returns a function that takes a string and returns an array of regex matches. The
325 // regex must use the suffix /.../g to specify a global search.
326 utils.extractor = function (regex) {
327   return function (text) {
328     return utils.capture(text, regex);
329   };
332 // __utils.splitLines(string)__.
333 // Splits a string into an array of strings, each corresponding to a line.
334 utils.splitLines = function (string) { return string.split(/\r?\n/); };
336 // __utils.splitAtSpaces(string)__.
337 // Splits a string into chunks between spaces. Does not split at spaces
338 // inside pairs of quotation marks.
339 utils.splitAtSpaces = utils.extractor(/((\S*?"(.*?)")+\S*|\S+)/g);
341 // __utils.splitAtFirst(string, regex)__.
342 // Splits a string at the first instance of regex match. If no match is
343 // found, returns the whole string.
344 utils.splitAtFirst = function (string, regex) {
345   let match = string.match(regex);
346   return match ? [ string.substring(0, match.index),
347                    string.substring(match.index + match[0].length) ]
348                : string;
351 // __utils.splitAtEquals(string)__.
352 // Splits a string into chunks between equals. Does not split at equals
353 // inside pairs of quotation marks.
354 utils.splitAtEquals = utils.extractor(/(([^=]*?"(.*?)")+[^=]*|[^=]+)/g);
356 // __utils.mergeObjects(arrayOfObjects)__.
357 // Takes an array of objects like [{"a":"b"},{"c":"d"}] and merges to a single object.
358 // Pure function.
359 utils.mergeObjects = function (arrayOfObjects) {
360   let result = {};
361   for (let obj of arrayOfObjects) {
362     for (let key in obj) {
363       result[key] = obj[key];
364     }
365   }
366   return result;
369 // __utils.listMapData(parameterString, listNames)__.
370 // Takes a list of parameters separated by spaces, of which the first several are
371 // unnamed, and the remainder are named, in the form `NAME=VALUE`. Apply listNames
372 // to the unnamed parameters, and combine them in a map with the named parameters.
373 // Example: `40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH`
375 //     utils.listMapData("40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH",
376 //                       ["streamID", "event", "circuitID", "IP"])
377 //     // --> {"streamID" : "40", "event" : "FAILED", "circuitID" : "0",
378 //     //      "address" : "95.78.59.36:80", "REASON" : "CANT_ATTACH"}"
379 utils.listMapData = function (parameterString, listNames) {
380   // Split out the space-delimited parameters.
381   let parameters = utils.splitAtSpaces(parameterString),
382       dataMap = {};
383   // Assign listNames to the first n = listNames.length parameters.
384   for (let i = 0; i < listNames.length; ++i) {
385     dataMap[listNames[i]] = parameters[i];
386   }
387   // Read key-value pairs and copy these to the dataMap.
388   for (let i = listNames.length; i < parameters.length; ++i) {
389     let [key, value] = utils.splitAtEquals(parameters[i]);
390     if (key && value) {
391       dataMap[key] = value;
392     }
393   }
394   return dataMap;
397 // __utils.rejectPromise(errorMessage)__.
398 // Returns a rejected promise with the given error message.
399 utils.rejectPromise = errorMessage => Promise.reject(new Error(errorMessage));
401 // ## info
402 // A namespace for functions related to tor's GETINFO and GETCONF command.
403 let info = {};
405 // __info.keyValueStringsFromMessage(messageText)__.
406 // Takes a message (text) response to GETINFO or GETCONF and provides
407 // a series of key-value strings, which are either multiline (with a `250+` prefix):
409 //     250+config/defaults=
410 //     AccountingMax "0 bytes"
411 //     AllowDotExit "0"
412 //     .
414 // or single-line (with a `250-` or `250 ` prefix):
416 //     250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
417 info.keyValueStringsFromMessage = utils.extractor(/^(250\+[\s\S]+?^\.|250[- ].+?)$/gmi);
419 // __info.applyPerLine(transformFunction)__.
420 // Returns a function that splits text into lines,
421 // and applies transformFunction to each line.
422 info.applyPerLine = function (transformFunction) {
423   return function (text) {
424     return utils.splitLines(text.trim()).map(transformFunction);
425   };
428 // __info.routerStatusParser(valueString)__.
429 // Parses a router status entry as, described in
430 // https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt
431 // (search for "router status entry")
432 info.routerStatusParser = function (valueString) {
433   let lines = utils.splitLines(valueString),
434       objects = [];
435   for (let line of lines) {
436     // Drop first character and grab data following it.
437     let myData = line.substring(2),
438     // Accumulate more maps with data, depending on the first character in the line.
439         dataFun = {
440           "r" : data => utils.listMapData(data, ["nickname", "identity", "digest",
441                                                  "publicationDate", "publicationTime",
442                                                  "IP", "ORPort", "DirPort"]),
443           "a" : data => ({ "IPv6" :  data }),
444           "s" : data => ({ "statusFlags" : utils.splitAtSpaces(data) }),
445           "v" : data => ({ "version" : data }),
446           "w" : data => utils.listMapData(data, []),
447           "p" : data => ({ "portList" : data.split(",") }),
448         }[line.charAt(0)];
449     if (dataFun !== undefined) {
450       objects.push(dataFun(myData));
451     }
452   }
453   return utils.mergeObjects(objects);
456 // __info.circuitStatusParser(line)__.
457 // Parse the output of a circuit status line.
458 info.circuitStatusParser = function (line) {
459   let data = utils.listMapData(line, ["id","status","circuit"]),
460       circuit = data.circuit;
461   // Parse out the individual circuit IDs and names.
462   if (circuit) {
463     data.circuit = circuit.split(",").map(function (x) {
464       return x.split(/~|=/);
465     });
466   }
467   return data;
470 // __info.streamStatusParser(line)__.
471 // Parse the output of a stream status line.
472 info.streamStatusParser = function (text) {
473   return utils.listMapData(text, ["StreamID", "StreamStatus",
474                                   "CircuitID", "Target"]);
477 // __info.bridgeParser(bridgeLine)__.
478 // Takes a single line from a `getconf bridge` result and returns
479 // a map containing the bridge's type, address, and ID.
480 info.bridgeParser = function(bridgeLine) {
481   let result = {},
482       tokens = bridgeLine.split(/\s+/);
483   // First check if we have a "vanilla" bridge:
484   if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
485     result.type = "vanilla";
486     [result.address, result.ID] = tokens;
487   // Several bridge types have a similar format:
488   } else {
489     result.type = tokens[0];
490     if (["flashproxy", "fte", "meek", "obfs3", "obfs4", "scramblesuit"]
491                .indexOf(result.type) >= 0) {
492       [result.address, result.ID] = tokens.slice(1);
493     }
494   }
495   return result.type ? result : null;
498 // __info.parsers__.
499 // A map of GETINFO and GETCONF keys to parsing function, which convert
500 // result strings to JavaScript data.
501 info.parsers = {
502   "ns/id/" : info.routerStatusParser,
503   "ip-to-country/" : utils.identity,
504   "circuit-status" : info.applyPerLine(info.circuitStatusParser),
505   "bridge" : info.bridgeParser,
506   // Currently unused parsers:
507   //  "ns/name/" : info.routerStatusParser,
508   //  "stream-status" : info.applyPerLine(info.streamStatusParser),
509   //  "version" : utils.identity,
510   //  "config-file" : utils.identity,
513 // __info.getParser(key)__.
514 // Takes a key and determines the parser function that should be used to
515 // convert its corresponding valueString to JavaScript data.
516 info.getParser = function(key) {
517   return info.parsers[key] ||
518          info.parsers[key.substring(0, key.lastIndexOf("/") + 1)];
521 // __info.stringToValue(string)__.
522 // Converts a key-value string as from GETINFO or GETCONF to a value.
523 info.stringToValue = function (string) {
524   // key should look something like `250+circuit-status=` or `250-circuit-status=...`
525   // or `250 circuit-status=...`
526   let matchForKey = string.match(/^250[ +-](.+?)=/),
527       key = matchForKey ? matchForKey[1] : null;
528   if (key === null) return null;
529   // matchResult finds a single-line result for `250-` or `250 `,
530   // or a multi-line one for `250+`.
531   let matchResult = string.match(/^250[ -].+?=(.*)$/) ||
532                     string.match(/^250\+.+?=([\s\S]*?)^\.$/m),
533       // Retrieve the captured group (the text of the value in the key-value pair)
534       valueString = matchResult ? matchResult[1] : null,
535       // Get the parser function for the key found.
536       parse = info.getParser(key.toLowerCase());
537   if (parse === undefined) {
538     throw new Error("No parser found for '" + key + "'");
539   }
540   // Return value produced by the parser.
541   return parse(valueString);
544 // __info.getMultipleResponseValues(message)__.
545 // Process multiple responses to a GETINFO or GETCONF request.
546 info.getMultipleResponseValues = function (message) {
547   return info.keyValueStringsFromMessage(message)
548              .map(info.stringToValue)
549              .filter(utils.identity);
552 // __info.getInfo(controlSocket, key)__.
553 // Sends GETINFO for a single key. Returns a promise with the result.
554 info.getInfo = function (aControlSocket, key) {
555   if (!utils.isString(key)) {
556     return utils.rejectPromise("key argument should be a string");
557   }
558   return aControlSocket
559     .sendCommand("getinfo " + key)
560     .then(response => info.getMultipleResponseValues(response)[0]);
563 // __info.getConf(aControlSocket, key)__.
564 // Sends GETCONF for a single key. Returns a promise with the result.
565 info.getConf = function (aControlSocket, key) {
566   // GETCONF with a single argument returns results with
567   // one or more lines that look like `250[- ]key=value`.
568   // Any GETCONF lines that contain a single keyword only are currently dropped.
569   // So we can use similar parsing to that for getInfo.
570   if (!utils.isString(key)) {
571     return utils.rejectPromise("key argument should be a string");
572   }
573   return aControlSocket.sendCommand("getconf " + key)
574                        .then(info.getMultipleResponseValues);
577 // ## event
578 // Handlers for events
580 let event = {};
582 // __event.parsers__.
583 // A map of EVENT keys to parsing functions, which convert result strings to JavaScript
584 // data.
585 event.parsers = {
586   "stream" : info.streamStatusParser,
587   // Currently unused:
588   // "circ" : info.circuitStatusParser,
591 // __event.messageToData(type, message)__.
592 // Extract the data from an event. Note, at present
593 // we only extract streams that look like `"650" SP...`
594 event.messageToData = function (type, message) {
595   let dataText = message.match(/^650 \S+?\s(.*)/m)[1];
596   return dataText ? event.parsers[type.toLowerCase()](dataText) : null;
599 // __event.watchEvent(controlSocket, type, filter, onData)__.
600 // Watches for a particular type of event. If filter(data) returns true, the event's
601 // data is passed to the onData callback. Returns a zero arg function that
602 // stops watching the event. Note: we only observe `"650" SP...` events
603 // currently (no `650+...` or `650-...` events).
604 event.watchEvent = function (controlSocket, type, filter, onData) {
605   return controlSocket.addNotificationCallback(new RegExp("^650 " + type),
606     function (message) {
607       let data = event.messageToData(type, message);
608       if (filter === null || filter(data)) {
609         onData(data);
610       }
611     });
614 // ## tor
615 // Things related to the main controller.
616 let tor = {};
618 // __tor.controllerCache__.
619 // A map from "unix:socketpath" or "host:port" to controller objects. Prevents
620 // redundant instantiation of control sockets.
621 tor.controllerCache = {};
623 // __tor.controller(socketFile, host, port, password, onError)__.
624 // Creates a tor controller at the given socketFile or host and port, with the
625 // given password.
626 // onError returns asynchronously whenever a connection error occurs.
627 tor.controller = function (socketFile, host, port, password, onError) {
628   let socket = io.controlSocket(socketFile, host, port, password, onError),
629       isOpen = true;
630   return { getInfo : key => info.getInfo(socket, key),
631            getConf : key => info.getConf(socket, key),
632            watchEvent : (type, filter, onData) =>
633                           event.watchEvent(socket, type, filter, onData),
634            isOpen : () => isOpen,
635            close : () => { isOpen = false; socket.close(); }
636          };
639 // ## Export
641 // __controller(socketFile, host, port, password, onError)__.
642 // Instantiates and returns a controller object connected to a tor ControlPort
643 // on socketFile or at host:port, authenticating with the given password, if
644 // the controller doesn't yet exist. Otherwise returns the existing controller
645 // to the given socketFile or host:port.
646 // onError is called with an error object as its single argument whenever
647 // an error occurs. Example:
649 //     // Get the controller
650 //     let c = controller(undefined, "127.0.0.1", 9151, "MyPassw0rd",
651 //                    function (error) { console.log(error.message || error); });
652 //     // Send command and receive `250` reply or error message in a promise:
653 //     let replyPromise = c.getInfo("ip-to-country/16.16.16.16");
654 //     // Close the controller permanently
655 //     c.close();
656 var controller = function (socketFile, host, port, password, onError) {
657   let dest = (socketFile) ? "unix:" + socketFile.path : host + ":" + port,
658       maybeController = tor.controllerCache[dest];
659   return (tor.controllerCache[dest] =
660            (maybeController && maybeController.isOpen()) ?
661              maybeController :
662              tor.controller(socketFile, host, port, password, onError));
665 // Export the controller function for external use.
666 var EXPORTED_SYMBOLS = ["controller"];