[tor-commits] [bridgedb/develop] Add bridgedb.parse.descriptors module.

isis at torproject.org isis at torproject.org
Thu Feb 19 02:21:11 UTC 2015


commit 7869e4c7cd1e43f9354480f6cafba0794fd86433
Author: Isis Lovecruft <isis at torproject.org>
Date:   Sun Jul 6 19:20:43 2014 +0000

    Add bridgedb.parse.descriptors module.
    
    This module implements parsing bridge descriptors with Stem.
    
     * FIXES (partially) #9380.
---
 lib/bridgedb/parse/descriptors.py |  151 +++++++++++++++++++++++++++++++++++++
 1 file changed, 151 insertions(+)

diff --git a/lib/bridgedb/parse/descriptors.py b/lib/bridgedb/parse/descriptors.py
new file mode 100644
index 0000000..bedc0b1
--- /dev/null
+++ b/lib/bridgedb/parse/descriptors.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_descriptors ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+#           please also see AUTHORS file
+# :copyright: (c) 2007-2014, The Tor Project, Inc.
+#             (c) 2014, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+from __future__ import print_function
+
+import datetime
+import logging
+
+from stem.descriptor import extrainfo_descriptor
+from stem.descriptor import networkstatus
+from stem.descriptor import server_descriptor
+from stem.descriptor import parse_file
+
+from bridgedb import safelog
+
+
+def parseNetworkStatusFile(filename, validate=True):
+    """Parse a file which contains an ``@type bridge-networkstatus`` document.
+
+    :rtype: dict
+    :returns: A dictionary of
+        :api:`stem.descriptor.router_status_entry.RouterStatusEntryV2`.
+    """
+    logging.info("Parsing networkstatus entries with Stem: %s" % filename)
+
+    fh = open(filename)
+    descriptors = fh.read()
+    fh.close()
+
+    # See ticket #12254 for why networkstatus-bridges documents don't look
+    # anything like the networkstatus v2 documents that they are purported to
+    # look like. They are missing all headers, and the entire footer including
+    # authority signatures.
+    #
+    # https://trac.torproject.org/projects/tor/ticket/12254
+    #
+    # As such, they do not currently start with a "published" line with an
+    # ISO8601 timestamp, as stem expects them to:
+    #
+    if not descriptors.startswith("published"):
+        precise = datetime.datetime.now().isoformat(sep=chr(0x20))
+        timestamp = precise.rsplit('.', 1)[0]
+        descriptors = "published {t}\n{d}".format(t=timestamp, d=descriptors)
+    else:
+        logging.warn(
+            ("Networkstatus file '%s' started with 'published' line! Please "
+             "revise this function!") % filename)
+
+    routers = networkstatus.BridgeNetworkStatusDocument(descriptors,
+                                                        validate=validate)
+    return routers
+
+def parseServerDescriptorsFile(filename, validate=False):
+    """Parse a file which contains ``@type bridge-server-descriptor``s.
+
+    .. note:: ``validate`` defaults to ``False`` because there appears to be a
+        bug in Leekspin, the fake descriptor generator, where Stem thinks the
+        fingerprint doesn't match the key…
+
+    .. note:: We have to lie to Stem, pretending that these are ``@type
+        server-descriptor``s, **not** ``@type bridge-server-descriptor``s.
+        See ticket `#11257`_.
+
+    .. _`#11257`: https://trac.torproject.org/projects/tor/ticket/11257
+
+    :param str filename: The file to parse descriptors from.
+    :param bool validate: Whether or not to validate descriptor
+        contents. (default: ``False``)
+    :rtype: list
+    :returns: A list of
+        :api:`stem.descriptor.server_descriptor.RelayDescriptor`s.
+    """
+    logging.info("Parsing server descriptors with Stem: %s" % filename)
+    descriptorType = 'server-descriptor 1.0'
+    document = parse_file(filename, descriptorType, validate=validate)
+
+    routers = [router for router in document]
+    return routers
+
+def deduplicate(descriptors):
+    duplicates = []
+    nonDuplicates = []
+
+    for descriptor in descriptors:
+        router = descriptors.pop(descriptors.index(descriptor))
+        fingerprint = router.fingerprint
+
+        logging.debug("Deduplicating %s descriptor for router %s"
+                      % (str(router.__class__).rsplit('.', 1)[1],
+                         safelog.logSafely(fingerprint)))
+
+        for possibleDuplicate in descriptors:
+            if fingerprint == possibleDuplicate.fingerprint:
+                logging.warn("Duplicate extra-info descriptor for router %s"
+                             % safelog.logSafely(fingerprint))
+                if router.published > possibleDuplicate.published:
+                    # The router is newer than the duplicate, so get rid of
+                    # the duplicate:
+                    duplicates.append(possibleDuplicate)
+                elif router.published < possibleDuplicate.published:
+                    # The router is older than the duplicate, so replace our
+                    # router:
+                    duplicates.append(router)
+                    router = possibleDuplicate
+                else:
+                    duplicates.append(possibleDuplicate)
+                    logging.warn(("Duplicate descriptor and original "
+                                  "descriptor for router %s both had the same "
+                                  "timestamp: %s")
+                                 % (safelog.logSafely(fingerprint),
+                                    router.published))
+            else:
+                nonDuplicates.append(router)
+
+    logging.info("Descriptor deduplication finished.")
+    logging.info("Number of duplicates: %d" % len(duplicates))
+    logging.info("Number of non-duplicates: %d" % len(nonDuplicates))
+    return nonDuplicates
+
+
+def parseBridgeExtraInfoFiles(*filenames, **kwargs):
+    """Parse files which contain ``@type bridge-extrainfo-descriptor``s.
+
+    :kwargs: If there is a ``'validate'`` keyword argument, its value will be
+        passed along as the ``'validate'`` argument to
+        :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`.
+    """
+    descriptors = []
+    descriptorType = 'bridge-extra-info 1.1'
+
+    validate = False
+    if ('validate' in kwargs) and (kwargs['validate'] is True):
+        validate = True
+
+    for filename in filenames:
+        logging.info("Parsing %s descriptors with Stem: %s"
+                     % (descriptorType, filename))
+        document = parse_file(filename, descriptorType, validate=validate)
+        descriptors.extend([router for router in document])
+
+    routers = deduplicate(descriptors)
+    return routers





More information about the tor-commits mailing list