[tor-commits] [stem/master] Server descriptor reformatting
atagar at torproject.org
atagar at torproject.org
Sun Apr 15 02:50:20 UTC 2012
commit 57ed21a26d440d32e01924cbc027d97665fe8dbc
Author: Damian Johnson <atagar at torproject.org>
Date: Tue Apr 10 09:03:39 2012 -0700
Server descriptor reformatting
Cleaning up the recent changes to split apart relay and bridge descriptors,
dropping an unnecessary parser class and rearranging things to be more
readable.
---
stem/descriptor/server_descriptor.py | 204 +++++++++++++++++++---------------
1 files changed, 112 insertions(+), 92 deletions(-)
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index a0f688c..8548cf2 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -238,31 +238,28 @@ class ServerDescriptorV3(stem.descriptor.Descriptor):
self.read_history = None
self.write_history = None
self.eventdns = True
+ self._unrecognized_lines = []
+
+ self._annotation_lines = annotations if annotations else []
+ self._annotation_dict = None # cached breakdown of key/value mappings
# TODO: Until we have a proper ExitPolicy class this is just a list of the
# exit policy strings...
self.exit_policy = []
- self._unrecognized_lines = []
+ # A descriptor contains a series of 'keyword lines' which are simply a
+ # keyword followed by an optional value. Lines can also be followed by a
+ # signature block.
+ #
+ # We care about the ordering of 'accept' and 'reject' entries because this
+ # influences the resulting exit policy, but for everything else the order
+ # does not matter so breaking it into key / value pairs.
- if annotations:
- self._annotation_lines = annotations
- self._annotation_dict = {}
-
- for line in annotations:
- if " " in line:
- key, value = line.split(" ", 1)
- self._annotation_dict[key] = value
- else: self._annotation_dict[line] = None
- else:
- self._annotation_lines = []
- self._annotation_dict = {}
-
- contents = _DescriptorContents(raw_contents, validate)
- self.exit_policy = contents.exit_policy
- self._parse(contents.entries, validate)
- if validate: self._check_constraints(contents)
+ entries, first_keyword, last_keyword, self.exit_policy = \
+ _get_descriptor_components(raw_contents, validate)
+ self._parse(entries, validate)
+ if validate: self._check_constraints(entries, first_keyword, last_keyword)
def get_unrecognized_lines(self):
return list(self._unrecognized_lines)
@@ -279,6 +276,17 @@ class ServerDescriptorV3(stem.descriptor.Descriptor):
dict with the key/value pairs in our annotations
"""
+ if self._annotation_dict == None:
+ annotation_dict = {}
+
+ for line in self._annotation_lines:
+ if " " in line:
+ key, value = line.split(" ", 1)
+ annotation_dict[key] = value
+ else: annotation_dict[line] = None
+
+ self._annotation_dict = annotation_dict
+
return self._annotation_dict
def get_annotation_lines(self):
@@ -306,7 +314,6 @@ class ServerDescriptorV3(stem.descriptor.Descriptor):
ValueError if an error occures in validation
"""
-
for keyword, values in entries.items():
# most just work with the first (and only) value
value, block_contents = values[0]
@@ -460,13 +467,15 @@ class ServerDescriptorV3(stem.descriptor.Descriptor):
else:
self._unrecognized_lines.append(line)
- def _check_constraints(self, contents):
+ def _check_constraints(self, entries, first_keyword, last_keyword):
"""
Does a basic check that the entries conform to this descriptor type's
constraints.
Arguments:
- contents (_DescriptorContents) - contents to be validated
+ entries (dict) - keyword => (value, pgp key) entries
+ first_keyword (str) - keyword of the first line
+ last_keyword (str) - keyword of the last line
Raises:
ValueError if an issue arises in validation
@@ -475,21 +484,21 @@ class ServerDescriptorV3(stem.descriptor.Descriptor):
required_fields = self._required_fields()
if required_fields:
for keyword in required_fields:
- if not keyword in contents.entries:
+ if not keyword in entries:
raise ValueError("Descriptor must have a '%s' entry" % keyword)
single_fields = self._single_fields()
if single_fields:
for keyword in self._single_fields():
- if keyword in contents.entries and len(contents.entries[keyword]) > 1:
+ if keyword in entries and len(entries[keyword]) > 1:
raise ValueError("The '%s' entry can only appear once in a descriptor" % keyword)
- first_keyword = self._first_keyword()
- if first_keyword and not contents.first_keyword == first_keyword:
+ expected_first_keyword = self._first_keyword()
+ if expected_first_keyword and not first_keyword == expected_first_keyword:
raise ValueError("Descriptor must start with a '%s' entry" % first_keyword)
- last_keyword = self._last_keyword()
- if last_keyword and not contents.last_keyword == self._last_keyword():
+ expected_last_keyword = self._last_keyword()
+ if expected_last_keyword and not last_keyword == expected_last_keyword:
raise ValueError("Descriptor must end with a '%s' entry" % last_keyword)
if not self.exit_policy:
@@ -581,23 +590,28 @@ class BridgeDescriptorV3(ServerDescriptorV3):
def _parse(self, entries, validate):
ServerDescriptorV3._parse(self, entries, validate)
+ if validate: self._check_scrubbing()
+
+ def _check_scrubbing(self):
+ """
+ Checks that our attributes have been scrubbed in accordance with the bridge
+ descriptor specification.
+ """
- if validate:
- # checks that we properly scrubbed fields
- if self.nickname != "Unnamed":
- raise ValueError("Router line's nickname should be scrubbed to be 'Unnamed': %s" % self.nickname)
- elif not self.address.startswith("10."):
- raise ValueError("Router line's address should be scrubbed to be '10.x.x.x': %s" % self.address)
- elif self.contact and self.contact != "somebody":
- raise ValueError("Contact line should be scrubbed to be 'somebody', but instead had '%s'" % self.contact)
-
- for line in self.get_unrecognized_lines():
- if line.startswith("onion-key "):
- raise ValueError("Bridge descriptors should have their onion-key scrubbed: %s" % self.onion_key)
- elif line.startswith("signing-key "):
- raise ValueError("Bridge descriptors should have their signing-key scrubbed: %s" % self.signing_key)
- elif line.startswith("router-signature "):
- raise ValueError("Bridge descriptors should have their signature scrubbed: %s" % self.signature)
+ if self.nickname != "Unnamed":
+ raise ValueError("Router line's nickname should be scrubbed to be 'Unnamed': %s" % self.nickname)
+ elif not self.address.startswith("10."):
+ raise ValueError("Router line's address should be scrubbed to be '10.x.x.x': %s" % self.address)
+ elif self.contact and self.contact != "somebody":
+ raise ValueError("Contact line should be scrubbed to be 'somebody', but instead had '%s'" % self.contact)
+
+ for line in self.get_unrecognized_lines():
+ if line.startswith("onion-key "):
+ raise ValueError("Bridge descriptors should have their onion-key scrubbed: %s" % line)
+ elif line.startswith("signing-key "):
+ raise ValueError("Bridge descriptors should have their signing-key scrubbed: %s" % line)
+ elif line.startswith("router-signature "):
+ raise ValueError("Bridge descriptors should have their signature scrubbed: %s" % line)
def _required_fields(self):
# bridge required fields are the same as a relay descriptor, minus items
@@ -617,10 +631,9 @@ class BridgeDescriptorV3(ServerDescriptorV3):
def _first_keyword(self):
return "router"
-class _DescriptorContents:
+def _get_descriptor_components(raw_contents, validate):
"""
- Initial breakup of the server descriptor contents to make parsing easier
- later.
+ Initial breakup of the server descriptor contents to make parsing easier.
A descriptor contains a series of 'keyword lines' which are simply a keyword
followed by an optional value. Lines can also be followed by a signature
@@ -630,55 +643,62 @@ class _DescriptorContents:
influences the resulting exit policy, but for everything else the order does
not matter so breaking it into key / value pairs.
- Attributes:
- entries (dict) - keyword => (value, pgp key) entries
- first_keyword (str) - keyword of the first line
- last_keyword (str) - keyword of the last line
- exit_policy (list) - lines containing the exit policy
+ Arguments:
+ raw_contents (str) - descriptor content provided by the relay
+ validate (bool) - checks the validity of the descriptor's content if
+ True, skips these checks otherwise
+
+ Returns:
+ tuple with the following attributes...
+ entries (dict) - keyword => (value, pgp key) entries
+ first_keyword (str) - keyword of the first line
+ last_keyword (str) - keyword of the last line
+ exit_policy (list) - lines containing the exit policy
"""
- def __init__(self, raw_contents, validate):
- self.entries = {}
- self.first_keyword = None
- self.last_keyword = None
- self.exit_policy = []
- remaining_lines = raw_contents.split("\n")
+ entries = {}
+ first_keyword = None
+ last_keyword = None
+ exit_policy = []
+ remaining_lines = raw_contents.split("\n")
+
+ while remaining_lines:
+ line = remaining_lines.pop(0)
- while remaining_lines:
- line = remaining_lines.pop(0)
-
- # last line can be empty
- if not line and not remaining_lines: continue
-
- # Some lines have an 'opt ' for backward compatability. They should be
- # ignored. This prefix is being removed in...
- # https://trac.torproject.org/projects/tor/ticket/5124
-
- if line.startswith("opt "): line = line[4:]
-
- line_match = KEYWORD_LINE.match(line)
-
- if not line_match:
- if not validate: continue
- raise ValueError("Line contains invalid characters: %s" % line)
-
- keyword, value = line_match.groups()
-
- if not self.first_keyword: self.first_keyword = keyword
- self.last_keyword = keyword
-
- try:
- block_contents = _get_pseudo_pgp_block(remaining_lines)
- except ValueError, exc:
- if not validate: continue
- raise exc
-
- if keyword in ("accept", "reject"):
- self.exit_policy.append("%s %s" % (keyword, value))
- elif keyword in self.entries:
- self.entries[keyword].append((value, block_contents))
- else:
- self.entries[keyword] = [(value, block_contents)]
+ # last line can be empty
+ if not line and not remaining_lines: continue
+
+ # Some lines have an 'opt ' for backward compatability. They should be
+ # ignored. This prefix is being removed in...
+ # https://trac.torproject.org/projects/tor/ticket/5124
+
+ if line.startswith("opt "): line = line[4:]
+
+ line_match = KEYWORD_LINE.match(line)
+
+ if not line_match:
+ if not validate: continue
+ raise ValueError("Line contains invalid characters: %s" % line)
+
+ keyword, value = line_match.groups()
+
+ if not first_keyword: first_keyword = keyword
+ last_keyword = keyword
+
+ try:
+ block_contents = _get_pseudo_pgp_block(remaining_lines)
+ except ValueError, exc:
+ if not validate: continue
+ raise exc
+
+ if keyword in ("accept", "reject"):
+ exit_policy.append("%s %s" % (keyword, value))
+ elif keyword in entries:
+ entries[keyword].append((value, block_contents))
+ else:
+ entries[keyword] = [(value, block_contents)]
+
+ return entries, first_keyword, last_keyword, exit_policy
def _get_pseudo_pgp_block(remaining_contents):
"""
More information about the tor-commits
mailing list