[tor-commits] [stem/master] Various modifications to GETCONF parsing
atagar at torproject.org
atagar at torproject.org
Wed Jul 4 21:34:20 UTC 2012
commit b8959dfe154180018071a07d7ad8eceb68a4c516
Author: Ravi Chandra Padmala <neenaoffline at gmail.com>
Date: Sun Jun 17 18:05:54 2012 +0530
Various modifications to GETCONF parsing
* Change the way entries are stored. Now only stores one value unless the caller
explicitly mentions that he want to retrieve multiple values using the
'multiple' argument.
* Stop checking if the received configuration options are the ones that were
requested. (The HiddenService options do this)
* Minor documentation fixes.
---
stem/control.py | 35 +++++++++++++++------------------
stem/response/__init__.py | 5 ++-
stem/response/getconf.py | 40 +++++++++++++++++++-------------------
stem/response/getinfo.py | 2 +-
stem/socket.py | 16 +++++++-------
test/integ/control/controller.py | 12 +++++-----
test/unit/response/getconf.py | 15 ++++++-------
7 files changed, 61 insertions(+), 64 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index a616b5c..d14d723 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -453,7 +453,9 @@ class Controller(BaseController):
* dict with the param => response mapping if our param was a list
* default if one was provided and our call failed
- :raises: :class:`stem.socket.ControllerError` if the call fails, and we weren't provided a default response
+ :raises:
+ :class:`stem.socket.ControllerError` if the call fails, and we weren't provided a default response
+ :class:`stem.socket.InvalidArguments` if the 'param' requested was invalid
"""
# TODO: add caching?
@@ -530,8 +532,8 @@ class Controller(BaseController):
"""
return stem.connection.get_protocolinfo(self)
-
- def get_conf(self, param, default = None):
+
+ def get_conf(self, param, default = UNDEFINED, multiple = False):
"""
Queries the control socket for the values of given configuration options. If
provided a default then that's returned as if the GETCONF option is undefined
@@ -540,17 +542,21 @@ class Controller(BaseController):
:param str,list param: GETCONF option or options to be queried
:param object default: response if the query fails
+ :param bool multiple: if True, the value(s) provided are lists of all returned values,
+ otherwise this just provides the first value
:returns:
Response depends upon how we were called as follows...
- * str with the response if our param was a str
- * dict with the param => response mapping if our param was a list
+ * str with the response if our param was a str and multiple was False
+ * dict with the param (str) => response (str) mapping if our param was a list and multiple was False
+ * list with the response strings if our param was a str and multiple was True
+ * dict with the param (str) => response (list) mapping if our param was a list and multiple was True
* default if one was provided and our call failed
:raises:
:class:`stem.socket.ControllerError` if the call fails, and we weren't provided a default response
- :class:`stem.socket.InvalidArguments` if configuration options requested was invalid
+ :class:`stem.socket.InvalidArguments` if the configuration options requested were invalid
"""
if isinstance(param, str):
@@ -561,23 +567,14 @@ class Controller(BaseController):
try:
response = self.msg("GETCONF %s" % " ".join(param))
- stem.response.convert("GETCONF", response)
-
- # error if we got back different parameters than we requested
- requested_params = set(param)
- reply_params = set(response.entries.keys())
-
- if requested_params != reply_params:
- requested_label = ", ".join(requested_params)
- reply_label = ", ".join(reply_params)
-
- raise stem.socket.ProtocolError("GETCONF reply doesn't match the parameters that we requested. Queried '%s' but got '%s'." % (requested_label, reply_label))
+ stem.response.convert("GETCONF", response, multiple = multiple)
if is_multiple:
return response.entries
else:
- return response.entries[param[0]]
+ try: return response.entries[param[0]]
+ except KeyError: raise stem.socket.InvalidRequest("Received empty string")
except stem.socket.ControllerError, exc:
- if default is None: raise exc
+ if default is UNDEFINED: raise exc
else: return default
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index 13c65b3..590e4c2 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -42,7 +42,7 @@ KEY_ARG = re.compile("^(\S+)=")
CONTROL_ESCAPES = {r"\\": "\\", r"\"": "\"", r"\'": "'",
r"\r": "\r", r"\n": "\n", r"\t": "\t"}
-def convert(response_type, message):
+def convert(response_type, message, **kwargs):
"""
Converts a ControlMessage into a particular kind of tor response. This does
an in-place conversion of the message from being a ControlMessage to a
@@ -57,6 +57,7 @@ def convert(response_type, message):
:param str response_type: type of tor response to convert to
:param stem.response.ControlMessage message: message to be converted
+ :param kwargs: optional keyword arguments to be passed to the parser method
:raises:
* :class:`stem.socket.ProtocolError` the message isn't a proper response of that type
@@ -84,7 +85,7 @@ def convert(response_type, message):
else: raise TypeError("Unsupported response type: %s" % response_type)
message.__class__ = response_class
- message._parse_message()
+ message._parse_message(**kwargs)
class ControlMessage:
"""
diff --git a/stem/response/getconf.py b/stem/response/getconf.py
index fd2256c..9ab15c7 100644
--- a/stem/response/getconf.py
+++ b/stem/response/getconf.py
@@ -1,14 +1,6 @@
import stem.socket
import stem.response
-def _split_line(line):
- if line.is_next_mapping(quoted = False):
- return line.split("=", 1) # TODO: make this part of the ControlLine?
- elif line.is_next_mapping(quoted = True):
- return line.pop_mapping(True).items()[0]
- else:
- return (line.pop(), None)
-
class GetConfResponse(stem.response.ControlMessage):
"""
Reply for a GETCONF query.
@@ -18,7 +10,12 @@ class GetConfResponse(stem.response.ControlMessage):
of strings)
"""
- def _parse_message(self):
+ def _parse_message(self, multiple = False):
+ """
+ :param bool multiple:
+ if True stores each value in a list, otherwise stores only the first
+ values as a sting
+ """
# Example:
# 250-CookieAuthentication=0
# 250-ControlPort=9100
@@ -27,7 +24,7 @@ class GetConfResponse(stem.response.ControlMessage):
self.entries = {}
remaining_lines = list(self)
-
+
if self.content() == [("250", " ", "OK")]: return
if not self.is_ok():
@@ -35,7 +32,7 @@ class GetConfResponse(stem.response.ControlMessage):
for code, _, line in self.content():
if code == "552" and line.startswith("Unrecognized configuration key \"") and line.endswith("\""):
unrecognized_keywords.append(line[32:-1])
-
+
if unrecognized_keywords:
raise stem.socket.InvalidArguments("552", "GETCONF request contained unrecognized keywords: %s\n" \
% ', '.join(unrecognized_keywords), unrecognized_keywords)
@@ -44,15 +41,18 @@ class GetConfResponse(stem.response.ControlMessage):
while remaining_lines:
line = remaining_lines.pop(0)
-
- key, value = _split_line(line)
+
+ if line.is_next_mapping(quoted = False):
+ key, value = line.split("=", 1) # TODO: make this part of the ControlLine?
+ elif line.is_next_mapping(quoted = True):
+ key, value = line.pop_mapping(True).items()[0]
+ else:
+ key, value = (line.pop(), None)
+
entry = self.entries.get(key, None)
-
- if type(entry) == str and entry != value:
- self.entries[key] = [entry]
- self.entries[key].append(value)
- elif type(entry) == list and not value in entry:
- self.entries[key].append(value)
+
+ if multiple:
+ self.entries.setdefault(key, []).append(value)
else:
- self.entries[key] = value
+ self.entries.setdefault(key, value)
diff --git a/stem/response/getinfo.py b/stem/response/getinfo.py
index f467769..a2cce57 100644
--- a/stem/response/getinfo.py
+++ b/stem/response/getinfo.py
@@ -29,7 +29,7 @@ class GetInfoResponse(stem.response.ControlMessage):
for code, _, line in self.content():
if code == '552' and line.startswith("Unrecognized key \"") and line.endswith("\""):
unrecognized_keywords.append(line[18:-1])
-
+
if unrecognized_keywords:
raise stem.socket.InvalidArguments("552", "GETINFO request contained unrecognized keywords: %s\n" \
% ', '.join(unrecognized_keywords), unrecognized_keywords)
diff --git a/stem/socket.py b/stem/socket.py
index 9e1f5b7..6972929 100644
--- a/stem/socket.py
+++ b/stem/socket.py
@@ -29,7 +29,7 @@ as instances of the :class:`stem.response.ControlMessage` class.
ControllerError - Base exception raised when using the controller.
|- ProtocolError - Malformed socket data.
|- InvalidRequest - Invalid request.
- +- InvalidArguments - Invalid request parameters.
+ | +- InvalidArguments - Invalid request parameters.
+- SocketError - Communication with the socket failed.
+- SocketClosed - Socket has been shut down.
"""
@@ -553,7 +553,7 @@ class ProtocolError(ControllerError):
class InvalidRequest(ControllerError):
"""
Base Exception class for invalid requests
-
+
:var str code: The error code returned by Tor (if applicable)
:var str message: The error message returned by Tor (if applicable) or a human
readable error message
@@ -562,11 +562,11 @@ class InvalidRequest(ControllerError):
def __init__(self, code = None, message = None):
"""
Initializes an InvalidRequest object.
-
+
:param str code: The error code returned by Tor (if applicable)
:param str message: The error message returned by Tor (if applicable) or a
human readable error message
-
+
:returns: object of InvalidRequest class
"""
@@ -576,22 +576,22 @@ class InvalidRequest(ControllerError):
class InvalidArguments(InvalidRequest):
"""
Exception class for invalid requests which contain invalid arguments.
-
+
:var str code: The error code returned by Tor (if applicable)
:var str message: The error message returned by Tor (if applicable) or a human
readable error message
:var list arguments: a list of arguments which were invalid
"""
-
+
def __init__(self, code = None, message = None, arguments = None):
"""
Initializes an InvalidArguments object.
-
+
:param str code: The error code returned by Tor (if applicable)
:param str message: The error message returned by Tor (if applicable) or a
human readable error message
:param list arguments: a list of arguments which were invalid
-
+
:returns: object of InvalidArguments class
"""
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 2441eac..db7941c 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -147,18 +147,18 @@ class TestController(unittest.TestCase):
socket = runner.get_tor_socket()
if isinstance(socket, stem.socket.ControlPort):
- socket = str(socket.get_port())
+ connection_value = str(socket.get_port())
config_key = "ControlPort"
elif isinstance(socket, stem.socket.ControlSocketFile):
- socket = str(socket.get_socket_path())
+ connection_value = str(socket.get_socket_path())
config_key = "ControlSocket"
- self.assertEqual(socket, controller.get_conf(config_key))
- self.assertEqual(socket, controller.get_conf(config_key, "la-di-dah"))
+ self.assertEqual(connection_value, controller.get_conf(config_key))
+ self.assertEqual(connection_value, controller.get_conf(config_key, "la-di-dah"))
# succeessful batch query
- expected = {config_key: socket}
+ expected = {config_key: connection_value}
self.assertEqual(expected, controller.get_conf([config_key]))
self.assertEqual(expected, controller.get_conf([config_key], "la-di-dah"))
@@ -173,7 +173,7 @@ class TestController(unittest.TestCase):
# empty input
- self.assertRaises(stem.socket.ControllerError, controller.get_conf, "")
+ self.assertRaises(stem.socket.InvalidRequest, controller.get_conf, "")
self.assertEqual("la-di-dah", controller.get_conf("", "la-di-dah"))
self.assertEqual({}, controller.get_conf([]))
diff --git a/test/unit/response/getconf.py b/test/unit/response/getconf.py
index a9f1cc8..e0020fb 100644
--- a/test/unit/response/getconf.py
+++ b/test/unit/response/getconf.py
@@ -24,12 +24,11 @@ MULTIVALUE_RESPONSE = """\
250-ControlPort=9100
250-ExitPolicy=accept 34.3.4.5
250-ExitPolicy=accept 3.4.53.3
-250-ExitPolicy=reject 23.245.54.3
-250-ExitPolicy=accept 34.3.4.5
250-ExitPolicy=accept 3.4.53.3
250 ExitPolicy=reject 23.245.54.3"""
-UNRECOGNIZED_KEY_RESPONSE = "552 Unrecognized configuration key \"yellowbrickroad\""
+UNRECOGNIZED_KEY_RESPONSE = '''552-Unrecognized configuration key "brickroad"
+552 Unrecognized configuration key "submarine"'''
INVALID_RESPONSE = """\
123-FOO
@@ -82,11 +81,11 @@ class TestGetConfResponse(unittest.TestCase):
"""
control_message = mocking.get_message(MULTIVALUE_RESPONSE)
- stem.response.convert("GETCONF", control_message)
+ stem.response.convert("GETCONF", control_message, multiple = True)
expected = {
- "ControlPort": "9100",
- "ExitPolicy": ["accept 34.3.4.5", "accept 3.4.53.3", "reject 23.245.54.3"]
+ "ControlPort": ["9100"],
+ "ExitPolicy": ["accept 34.3.4.5", "accept 3.4.53.3", "accept 3.4.53.3", "reject 23.245.54.3"]
}
self.assertEqual(expected, control_message.entries)
@@ -100,9 +99,9 @@ class TestGetConfResponse(unittest.TestCase):
self.assertRaises(stem.socket.InvalidArguments, stem.response.convert, "GETCONF", control_message)
try:
- stem.response.convert("GETCONF", control_message)
+ stem.response.convert("GETCONF", control_message, multiple = True)
except stem.socket.InvalidArguments, exc:
- self.assertEqual(exc.arguments, ["yellowbrickroad"])
+ self.assertEqual(exc.arguments, ["brickroad", "submarine"])
def test_invalid_content(self):
"""
More information about the tor-commits
mailing list