commit 548c10907863257c636e9393babe707fe79a4f40 Author: Arthur Edelstein arthuredelstein@gmail.com Date: Thu Nov 12 16:09:43 2015 -0800
Bug 17568: Clean up tor-control-port.js --- src/modules/tor-control-port.js | 113 ++++++++++++++------------------------- 1 file changed, 41 insertions(+), 72 deletions(-)
diff --git a/src/modules/tor-control-port.js b/src/modules/tor-control-port.js index 2b41897..c5010de 100644 --- a/src/modules/tor-control-port.js +++ b/src/modules/tor-control-port.js @@ -209,7 +209,7 @@ io.callbackDispatcher = function () {
// __io.matchRepliesToCommands(asyncSend, dispatcher)__. // Takes asyncSend(message), an asynchronous send function, and the callback -// displatcher, and returns a function Promise<response> sendCommand(command). +// dispatcher, and returns a function Promise<response> sendCommand(command). io.matchRepliesToCommands = function (asyncSend, dispatcher) { let commandQueue = [], sendCommand = function (command, replyCallback, errorCallback) { @@ -254,7 +254,7 @@ io.controlSocket = function (host, port, password, onError) { io.onDataFromOnLine( io.onLineFromOnMessage(mainDispatcher.pushMessage)), onError), - // Tor expects any commands to be terminated by CRLF. + // Controllers should send commands terminated by CRLF. writeLine = function (text) { socket.write(text + "\r\n"); }, // Create a sendCommand method from writeLine. sendCommand = io.matchRepliesToCommands(writeLine, mainDispatcher), @@ -371,6 +371,10 @@ utils.listMapData = function (parameterString, listNames) { return dataMap; };
+// __utils.rejectPromise(errorMessage)__. +// Returns a rejected promise with the given error message. +utils.rejectPromise = errorMessage => Promise.reject(new Error(errorMessage)); + // ## info // A namespace for functions related to tor's GETINFO and GETCONF command. let info = info || {}; @@ -387,7 +391,7 @@ let info = info || {}; // or single-line (with a `250-` or `250 ` prefix): // // 250-version=0.2.6.0-alpha-dev (git-b408125288ad6943) -info.keyValueStringsFromMessage = utils.extractor(/^(250+[\s\S]+?^.|250[-\ ].+?)$/gmi); +info.keyValueStringsFromMessage = utils.extractor(/^(250+[\s\S]+?^.|250[- ].+?)$/gmi);
// __info.applyPerLine(transformFunction)__. // Returns a function that splits text into lines, @@ -400,7 +404,7 @@ info.applyPerLine = function (transformFunction) {
// __info.routerStatusParser(valueString)__. // Parses a router status entry as, described in -// https://gitweb.torproject.org/torspec.git/blob/HEAD:/dir-spec.txt +// https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt // (search for "router status entry") info.routerStatusParser = function (valueString) { let lines = utils.splitLines(valueString), @@ -418,7 +422,6 @@ info.routerStatusParser = function (valueString) { "v" : data => ({ "version" : data }), "w" : data => utils.listMapData(data, []), "p" : data => ({ "portList" : data.split(",") }), - "m" : data => utils.listMapData(data, []) }[line.charAt(0)]; if (dataFun !== undefined) { objects.push(dataFun(myData)); @@ -448,21 +451,6 @@ info.streamStatusParser = function (text) { "CircuitID", "Target"]); };
-// __info.configTextParser(text)__. -// Parse the output of a `getinfo config-text`. -info.configTextParser = function(text) { - let result = {}; - utils.splitLines(text).map(function(line) { - let [name, value] = utils.splitAtFirst(line, /\s/); - if (name) { - if (!result.hasOwnProperty(name)) result[name] = []; - result[name].push(value); - } - }); - return result; -}; - - // __info.bridgeParser(bridgeLine)__. // Takes a single line from a `getconf bridge` result and returns // a map containing the bridge's type, address, and ID. @@ -488,16 +476,15 @@ info.bridgeParser = function(bridgeLine) { // A map of GETINFO and GETCONF keys to parsing function, which convert // result strings to JavaScript data. info.parsers = { - "version" : utils.identity, - "config-file" : utils.identity, - "config-defaults-file" : utils.identity, - "config-text" : info.configTextParser, "ns/id/" : info.routerStatusParser, - "ns/name/" : info.routerStatusParser, "ip-to-country/" : utils.identity, "circuit-status" : info.applyPerLine(info.circuitStatusParser), - "stream-status" : info.applyPerLine(info.streamStatusParser), - "bridge" : info.bridgeParser + "bridge" : info.bridgeParser, + // Currently unused parsers: + // "ns/name/" : info.routerStatusParser, + // "stream-status" : info.applyPerLine(info.streamStatusParser), + // "version" : utils.identity, + // "config-file" : utils.identity, };
// __info.getParser(key)__. @@ -512,13 +499,14 @@ info.getParser = function(key) { // Converts a key-value string as from GETINFO or GETCONF to a value. info.stringToValue = function (string) { // key should look something like `250+circuit-status=` or `250-circuit-status=...` - // or `250 circuit-status...` - let matchForKey = string.match(/^250[ +-](.+?)=/mi), + // or `250 circuit-status=...` + let matchForKey = string.match(/^250[ +-](.+?)=/), key = matchForKey ? matchForKey[1] : null; if (key === null) return null; - // matchResult finds a single-line result for `250-` or a multi-line one for `250+`. - let matchResult = string.match(/^250[ -].+?=(.*?)$/mi) || - string.match(/^250+.+?=([\s\S]*?)^.$/mi), + // matchResult finds a single-line result for `250-` or `250 `, + // or a multi-line one for `250+`. + let matchResult = string.match(/^250[ -].+?=(.*)$/) || + string.match(/^250+.+?=([\s\S]*?)^.$/m), // Retrieve the captured group (the text of the value in the key-value pair) valueString = matchResult ? matchResult[1] : null, // Get the parser function for the key found. @@ -538,48 +526,27 @@ info.getMultipleResponseValues = function (message) { .filter(utils.identity); };
-// __info.getInfoMultiple(aControlSocket, keys)__. -// Sends GETINFO for an array of keys. Returns a promise with an array of results. -info.getInfoMultiple = function (aControlSocket, keys) { - /* - if (!(keys instanceof Array)) { - throw new Error("keys argument should be an array"); - } - if (!(onData instanceof Function)) { - throw new Error("onData argument should be a function"); - } - let parsers = keys.map(info.getParser); - if (parsers.indexOf("unknown") !== -1) { - throw new Error("unknown key"); - } - if (parsers.indexOf("not supported") !== -1) { - throw new Error("unsupported key"); - } - */ - return aControlSocket.sendCommand("getinfo " + keys.join(" ")) - .then(info.getMultipleResponseValues); -}; - // __info.getInfo(controlSocket, key)__. // Sends GETINFO for a single key. Returns a promise with the result. info.getInfo = function (aControlSocket, key) { - /* if (!utils.isString(key)) { - throw new Error("key argument should be a string"); - } - if (!(onValue instanceof Function)) { - throw new Error("onValue argument should be a function"); + return utils.rejectPromise("key argument should be a string"); } - */ - return info.getInfoMultiple(aControlSocket, [key]).then(data => data[0]); + return aControlSocket + .sendCommand("getinfo " + key) + .then(response => info.getMultipleResponseValues(response)[0]); };
// __info.getConf(aControlSocket, key)__. // Sends GETCONF for a single key. Returns a promise with the result. info.getConf = function (aControlSocket, key) { - // GETCONF with a single argument returns results that look like - // results from GETINFO with multiple arguments. - // So we can use the same kind of parsing for + // GETCONF with a single argument returns results with + // one or more lines that look like `250[- ]key=value`. + // Any GETCONF lines that contain a single keyword only are currently dropped. + // So we can use similar parsing to that for getInfo. + if (!utils.isString(key)) { + return utils.rejectPromise("key argument should be a string"); + } return aControlSocket.sendCommand("getconf " + key) .then(info.getMultipleResponseValues); }; @@ -594,22 +561,25 @@ let event = event || {}; // data. event.parsers = { "stream" : info.streamStatusParser, - "circ" : info.circuitStatusParser + // Currently unused: + // "circ" : info.circuitStatusParser, };
// __event.messageToData(type, message)__. -// Extract the data from an event. +// Extract the data from an event. Note, at present +// we only extract streams that look like `"650" SP...` event.messageToData = function (type, message) { - let dataText = message.match(/^650 \S+?\s(.*?)$/mi)[1]; + let dataText = message.match(/^650 \S+?\s(.*)/m)[1]; return dataText ? event.parsers[type.toLowerCase()](dataText) : null; };
// __event.watchEvent(controlSocket, type, filter, onData)__. // Watches for a particular type of event. If filter(data) returns true, the event's // data is passed to the onData callback. Returns a zero arg function that -// stops watching the event. +// stops watching the event. Note: we only observe `"650" SP...` events +// currently (no `650+...` or `650-...` events). event.watchEvent = function (controlSocket, type, filter, onData) { - return controlSocket.addNotificationCallback(new RegExp("^650." + type, "i"), + return controlSocket.addNotificationCallback(new RegExp("^650 " + type), function (message) { let data = event.messageToData(type, message); if (filter === null || filter(data)) { @@ -634,7 +604,6 @@ tor.controller = function (host, port, password, onError) { let socket = io.controlSocket(host, port, password, onError), isOpen = true; return { getInfo : key => info.getInfo(socket, key), - getInfoMultiple : keys => info.getInfoMultiple(socket, keys), getConf : key => info.getConf(socket, key), watchEvent : (type, filter, onData) => event.watchEvent(socket, type, filter, onData), @@ -655,8 +624,8 @@ tor.controller = function (host, port, password, onError) { // // Get the controller // let c = controller("127.0.0.1", 9151, "MyPassw0rd", // function (error) { console.log(error.message || error); }); -// // Send command and receive `250` reply or error message -// c.getInfo("ip-to-country/16.16.16.16", console.log); +// // Send command and receive `250` reply or error message in a promise: +// let replyPromise = c.getInfo("ip-to-country/16.16.16.16"); // // Close the controller permanently // c.close(); let controller = function (host, port, password, onError) {