commit de8cb7a32e0d2a70812322ce1d6a1f4627ef33f0 Author: Arthur Edelstein arthuredelstein@gmail.com Date: Mon Feb 16 17:47:13 2015 -0800
Bug #13882: Fix display of bridges after bridge settings have been changed --- src/chrome/content/tor-circuit-display.js | 71 ++++++++++------------- src/modules/tor-control-port.js | 88 ++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 60 deletions(-)
diff --git a/src/chrome/content/tor-circuit-display.js b/src/chrome/content/tor-circuit-display.js index 35a7dfc..e2628b4 100644 --- a/src/chrome/content/tor-circuit-display.js +++ b/src/chrome/content/tor-circuit-display.js @@ -43,41 +43,23 @@ let logger = Cc["@torproject.org/torbutton-logger;1"] let credentialsToNodeDataMap = {}, // A mutable map that reports `true` for IDs of "mature" circuits // (those that have conveyed a stream). - knownCircuitIDs = {}, - // A map from bridge fingerprint to its IP address. - bridgeIDtoIPmap = new Map(); + knownCircuitIDs = {};
// __trimQuotes(s)__. // Removes quotation marks around a quoted string. let trimQuotes = s => s ? s.match(/^"(.*)"$/)[1] : undefined;
-// __readBridgeIPs(controller)__. -// Gets a map from bridge ID to bridge IP, and stores it -// in `bridgeIDtoIPmap`. -let readBridgeIPs = function (controller) { - Task.spawn(function* () { - let configText = yield controller.getInfo("config-text"), - bridgeEntries = configText.Bridge; - if (bridgeEntries) { - bridgeEntries.map(entry => { - let IPplusPort, ID, - tokens = entry.split(/\s+/); - // First check if we have a "vanilla" bridge: - if (tokens[0].match(/^\d+.\d+.\d+.\d+/)) { - [IPplusPort, ID] = tokens; - // Several bridge types have a similar format: - } else if (["fte", "obfs3", "obfs4", "scramblesuit"] - .indexOf(tokens[0]) >= 0) { - [IPplusPort, ID] = tokens.slice(1); - } - // (For now, we aren't dealing with meek bridges and flashproxy.) - if (IPplusPort && ID) { - let IP = IPplusPort.split(":")[0]; - bridgeIDtoIPmap.set(ID.toUpperCase(), IP); - } - }); +// __getBridge(id)__. +// Gets the bridge parameters for a given node ID. If the node +// is not currently used as a bridge, returns null. +let getBridge = function* (controller, id) { + let bridges = yield controller.getConf("bridge"); + for (let bridge of bridges) { + if (bridge.ID.toUpperCase() === id.toUpperCase()) { + return bridge; } - }).then(null, Cu.reportError); + } + return null; };
// nodeDataForID(controller, id)__. @@ -85,17 +67,22 @@ let readBridgeIPs = function (controller) { // Example: `nodeDataForID(controller, "20BC91DC525C3DC9974B29FBEAB51230DE024C44")` // => `{ type : "default", ip : "12.23.34.45", countryCode : "fr" }` let nodeDataForID = function* (controller, id) { - let result = {}; // type, ip, countryCode; - if (bridgeIDtoIPmap.has(id.toUpperCase())) { - result.ip = bridgeIDtoIPmap.get(id.toUpperCase()); + let result = {}, + bridge = yield getBridge(controller, id); // type, ip, countryCode; + if (bridge) { result.type = "bridge"; - } else { - // Get the IP address for the given node ID. + result.bridgeType = bridge.type; + // Attempt to get an IP address from bridge address string. try { - let statusMap = yield controller.getInfo("ns/id/" + id); - result.ip = statusMap.IP; + result.ip = bridge.address.split(":")[0]; } catch (e) { } + } else { result.type = "default"; + // Get the IP address for the given node ID. + try { + let statusMap = yield controller.getInfo("ns/id/" + id); + result.ip = statusMap.IP; + } catch (e) { } } if (result.ip) { // Get the country code for the node's IP address. @@ -141,8 +128,10 @@ let collectIsolationData = function (aController) { logger.eclog(3, "streamEvent.CircuitID: " + streamEvent.CircuitID); knownCircuitIDs[streamEvent.CircuitID] = true; let circuitStatus = yield getCircuitStatusByID(aController, streamEvent.CircuitID), - credentials = trimQuotes(circuitStatus.SOCKS_USERNAME) + ":" + - trimQuotes(circuitStatus.SOCKS_PASSWORD); + credentials = circuitStatus ? + (trimQuotes(circuitStatus.SOCKS_USERNAME) + ":" + + trimQuotes(circuitStatus.SOCKS_PASSWORD)) : + null; if (credentials) { let nodeData = yield nodeDataForCircuit(aController, circuitStatus); credentialsToNodeDataMap[credentials] = nodeData; @@ -195,11 +184,12 @@ let showCircuitDisplay = function (show) { // `"France (12.34.56.78)"`. let nodeLines = function (nodeData) { let result = [uiString("this_browser")]; - for (let {ip, countryCode, type} of nodeData) { + for (let {ip, countryCode, type, bridgeType} of nodeData) { let bridge = type === "bridge"; result.push((countryCode ? localizedCountryNameFromCode(countryCode) : uiString("unknown_country")) + - " (" + (bridge ? uiString("tor_bridge") + " (" + (bridge ? (uiString("tor_bridge") + + ((bridgeType !== "vanilla") ? (": " + bridgeType) : "")) : (ip || uiString("ip_unknown"))) + ")"); } result[4] = uiString("internet"); @@ -340,7 +330,6 @@ let setupDisplay = function (host, port, password, enablePrefName) { logger.eclog(5, "Disabling tor display circuit because of an error."); stop(); }); - readBridgeIPs(myController); syncDisplayWithSelectedTab(true); collectIsolationData(myController); } diff --git a/src/modules/tor-control-port.js b/src/modules/tor-control-port.js index 3fa7756..a55a0d8 100644 --- a/src/modules/tor-control-port.js +++ b/src/modules/tor-control-port.js @@ -372,22 +372,22 @@ utils.listMapData = function (parameterString, listNames) { };
// ## info -// A namespace for functions related to tor's GETINFO command. +// A namespace for functions related to tor's GETINFO and GETCONF command. let info = info || {};
// __info.keyValueStringsFromMessage(messageText)__. -// Takes a message (text) response to GETINFO and provides a series of key-value -// strings, which are either multiline (with a `250+` prefix): +// Takes a message (text) response to GETINFO or GETCONF and provides +// a series of key-value strings, which are either multiline (with a `250+` prefix): // // 250+config/defaults= // AccountingMax "0 bytes" // AllowDotExit "0" // . // -// or single-line (with a `250-` prefix): +// 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, @@ -462,9 +462,33 @@ info.configTextParser = function(text) { 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. +info.bridgeParser = function(bridgeLine) { + let result = {}, + tokens = bridgeLine.split(/\s+/); + // First check if we have a "vanilla" bridge: + if (tokens[0].match(/^\d+.\d+.\d+.\d+/)) { + result.type = "vanilla"; + [result.address, result.ID] = tokens; + // Several bridge types have a similar format: + } else { + result.type = tokens[0]; + if (["fte", "obfs3", "obfs4", "scramblesuit"] + .indexOf(result.type) >= 0) { + [result.address, result.ID] = tokens.slice(1); + } else if (result.type === "meek") { + // do nothing for now + } + } + return result.type ? result : null; +}; + // __info.parsers__. -// A map of GETINFO keys to parsing function, which convert result strings to JavaScript -// data. +// 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, @@ -474,7 +498,8 @@ info.parsers = { "ns/name/" : info.routerStatusParser, "ip-to-country/" : utils.identity, "circuit-status" : info.applyPerLine(info.circuitStatusParser), - "stream-status" : info.applyPerLine(info.streamStatusParser) + "stream-status" : info.applyPerLine(info.streamStatusParser), + "bridge" : info.bridgeParser };
// __info.getParser(key)__. @@ -482,22 +507,37 @@ info.parsers = { // convert its corresponding valueString to JavaScript data. info.getParser = function(key) { return info.parsers[key] || - info.parsers[key.substring(0, key.lastIndexOf("/") + 1)] || - "unknown"; + info.parsers[key.substring(0, key.lastIndexOf("/") + 1)]; };
// __info.stringToValue(string)__. -// Converts a key-value string as from GETINFO to a value. +// 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=...` - let key = string.match(/^250[+-](.+?)=/mi)[1], - // matchResult finds a single-line result for `250-` or a multi-line one for `250+`. - matchResult = string.match(/250-.+?=(.*?)$/mi) || + // or `250 circuit-status...` + let matchForKey = string.match(/^250[ +-](.+?)=/mi), + 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), // Retrieve the captured group (the text of the value in the key-value pair) - valueString = matchResult ? matchResult[1] : null; - // Return value where the latter has been parsed according to the key requested. - return info.getParser(key)(valueString); + valueString = matchResult ? matchResult[1] : null, + // Get the parser functino for the key found. + parse = info.getParser(key.toLowerCase()); + if (parse === undefined) { + throw new Error("No parser found for '" + key + "'"); + } + // Return value produced by the parser. + return parse(valueString); +}; + +// __info.getMultipleResponseValues(message)__. +// Process multiple responses to a GETINFO or GETCONF request. +info.getMultipleResponseValues = function (message) { + return info.keyValueStringsFromMessage(message) + .map(info.stringToValue) + .filter(utils.identity); };
// __info.getInfoMultiple(aControlSocket, keys)__. @@ -519,8 +559,7 @@ info.getInfoMultiple = function (aControlSocket, keys) { } */ return aControlSocket.sendCommand("getinfo " + keys.join(" ")) - .then(message => info.keyValueStringsFromMessage(message) - .map(info.stringToValue)); + .then(info.getMultipleResponseValues); };
// __info.getInfo(controlSocket, key)__. @@ -537,6 +576,16 @@ info.getInfo = function (aControlSocket, key) { return info.getInfoMultiple(aControlSocket, [key]).then(data => data[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 + return aControlSocket.sendCommand("getconf " + key) + .then(info.getMultipleResponseValues); +}; + // ## event // Handlers for events
@@ -587,6 +636,7 @@ tor.controller = function (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 : function (type, filter, onData) { event.watchEvent(socket, type, filter, onData); },