[tor-commits] [stem/master] Add GetConfResponse class for parsing GETCONF responses
atagar at torproject.org
atagar at torproject.org
Wed Jul 4 21:34:20 UTC 2012
commit 5701329478dfc363da17d80cafca26f07a9a9def
Author: Ravi Chandra Padmala <neenaoffline at gmail.com>
Date: Fri Jun 8 12:41:09 2012 +0530
Add GetConfResponse class for parsing GETCONF responses
---
run_tests.py | 2 +
stem/response/__init__.py | 12 +++++-
stem/response/getconf.py | 53 +++++++++++++++++++++++++
test/unit/response/getconf.py | 87 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 153 insertions(+), 1 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 7ccedd3..ea12531 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -22,6 +22,7 @@ import test.unit.descriptor.extrainfo_descriptor
import test.unit.response.control_line
import test.unit.response.control_message
import test.unit.response.getinfo
+import test.unit.response.getconf
import test.unit.response.protocolinfo
import test.unit.response.authchallenge
import test.unit.util.conf
@@ -110,6 +111,7 @@ UNIT_TESTS = (
test.unit.response.control_message.TestControlMessage,
test.unit.response.control_line.TestControlLine,
test.unit.response.getinfo.TestGetInfoResponse,
+ test.unit.response.getconf.TestGetConfResponse,
test.unit.response.protocolinfo.TestProtocolInfoResponse,
test.unit.response.authchallenge.TestAuthChallengeResponse,
test.unit.connection.authentication.TestAuthenticate,
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index dfae1d3..ae3af4c 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -25,7 +25,7 @@ Parses replies from the control socket.
from __future__ import with_statement
-__all__ = ["getinfo", "protocolinfo", "authchallenge", "convert", "ControlMessage", "ControlLine"]
+__all__ = ["getinfo", "getconf", "protocolinfo", "authchallenge", "convert", "ControlMessage", "ControlLine"]
import re
import threading
@@ -49,6 +49,7 @@ def convert(response_type, message):
subclass for its response type. Recognized types include...
* GETINFO
+ * GETCONF
* PROTOCOLINFO
* AUTHCHALLENGE
@@ -59,10 +60,12 @@ def convert(response_type, message):
:raises:
* :class:`stem.socket.ProtocolError` the message isn't a proper response of that type
+ * :class:`stem.response.InvalidRequest` the request was invalid
* TypeError if argument isn't a :class:`stem.response.ControlMessage` or response_type isn't supported
"""
import stem.response.getinfo
+ import stem.response.getconf
import stem.response.protocolinfo
import stem.response.authchallenge
@@ -71,6 +74,8 @@ def convert(response_type, message):
if response_type == "GETINFO":
response_class = stem.response.getinfo.GetInfoResponse
+ elif response_type == "GETCONF":
+ response_class = stem.response.getconf.GetConfResponse
elif response_type == "PROTOCOLINFO":
response_class = stem.response.protocolinfo.ProtocolInfoResponse
elif response_type == "AUTHCHALLENGE":
@@ -408,3 +413,8 @@ def _get_quote_indeces(line, escaped):
return tuple(indices)
+class InvalidRequest(Exception):
+ """
+ Base Exception class for invalid requests
+ """
+ pass
diff --git a/stem/response/getconf.py b/stem/response/getconf.py
new file mode 100644
index 0000000..8dcd483
--- /dev/null
+++ b/stem/response/getconf.py
@@ -0,0 +1,53 @@
+import re
+
+import stem.socket
+import stem.response
+
+class GetConfResponse(stem.response.ControlMessage):
+ """
+ Reply for a GETCONF query.
+
+ :var dict entries: mapping between the queried options and their values
+ """
+
+ def _parse_message(self):
+ # Example:
+ # 250-CookieAuthentication=0
+ # 250-ControlPort=9100
+ # 250-DataDirectory=/home/neena/.tor
+ # 250 DirPort
+
+ self.entries = {}
+ remaining_lines = list(self)
+
+ if self.content() == [("250", " ", "OK")]: return
+
+ if not self.is_ok():
+ unrecognized_keywords = []
+ for code, _, line in self.content():
+ if code == '552':
+ try:
+ # to parse: 552 Unrecognized configuration key "zinc"
+ unrecognized_keywords.append(re.search('"([^"]+)"', line).groups()[0])
+ except:
+ pass
+
+ if unrecognized_keywords:
+ raise stem.response.InvalidRequest("GETCONF request contained unrecognized keywords: %s\n" \
+ % ', '.join(unrecognized_keywords))
+ else:
+ raise stem.socket.ProtocolError("GETCONF response contained a non-OK status code:\n%s" % self)
+
+ while remaining_lines:
+ line = remaining_lines.pop(0)
+
+ if '=' in line:
+ if line[line.find("=") + 1] == "\"":
+ key, value = line.pop_mapping(True)
+ else:
+ key, value = line.split("=", 1)
+ else:
+ key, value = (line, None)
+
+ self.entries[key] = value
+
diff --git a/test/unit/response/getconf.py b/test/unit/response/getconf.py
new file mode 100644
index 0000000..b82256b
--- /dev/null
+++ b/test/unit/response/getconf.py
@@ -0,0 +1,87 @@
+"""
+Unit tests for the stem.response.getconf.GetConfResponse class.
+"""
+
+import unittest
+
+import stem.socket
+import stem.response
+import stem.response.getinfo
+import test.mocking as mocking
+
+EMPTY_RESPONSE = "250 OK"
+
+SINGLE_RESPONSE = """\
+250 DataDirectory=/home/neena/.tor"""
+
+BATCH_RESPONSE = """\
+250-CookieAuthentication=0
+250-ControlPort=9100
+250-DataDirectory=/tmp/fake dir
+250 DirPort"""
+
+UNRECOGNIZED_KEY_RESPONSE = "552 Unrecognized configuration key \"yellowbrickroad\""
+
+INVALID_RESPONSE = """\
+123-FOO
+232 BAR"""
+
+class TestGetConfResponse(unittest.TestCase):
+ def test_empty_response(self):
+ """
+ Parses a GETCONF reply without options (just calling "GETCONF").
+ """
+
+ control_message = mocking.get_message(EMPTY_RESPONSE)
+ stem.response.convert("GETCONF", control_message)
+
+ # now this should be a GetConfResponse (ControlMessage subclass)
+ self.assertTrue(isinstance(control_message, stem.response.ControlMessage))
+ self.assertTrue(isinstance(control_message, stem.response.getconf.GetConfResponse))
+
+ self.assertEqual({}, control_message.entries)
+
+ def test_single_response(self):
+ """
+ Parses a GETCONF reply response for a single parameter.
+ """
+
+ control_message = mocking.get_message(SINGLE_RESPONSE)
+ stem.response.convert("GETCONF", control_message)
+ self.assertEqual({"DataDirectory": "/home/neena/.tor"}, control_message.entries)
+
+ def test_batch_response(self):
+ """
+ Parses a GETCONF reply for muiltiple parameters.
+ """
+
+ control_message = mocking.get_message(BATCH_RESPONSE)
+ stem.response.convert("GETCONF", control_message)
+
+ expected = {
+ "CookieAuthentication": "0",
+ "ControlPort": "9100",
+ "DataDirectory": "/tmp/fake dir",
+ "DirPort": None,
+ }
+
+ self.assertEqual(expected, control_message.entries)
+
+ def test_unrecognized_key_response(self):
+ """
+ Parses a GETCONF reply that contains an error code with an unrecognized key.
+ """
+
+ control_message = mocking.get_message(UNRECOGNIZED_KEY_RESPONSE)
+ self.assertRaises(stem.response.InvalidRequest, stem.response.convert, "GETCONF", control_message)
+
+ def test_invalid_multiline_content(self):
+ """
+ Parses a malformed GETCONF reply that contains an invalid response code.
+ This is a proper controller message, but malformed according to the
+ GETCONF's spec.
+ """
+
+ control_message = mocking.get_message(INVALID_RESPONSE)
+ self.assertRaises(stem.socket.ProtocolError, stem.response.convert, "GETCONF", control_message)
+
More information about the tor-commits
mailing list