[tor-commits] [bridgedb/develop] Refactor getBridgesForIP() to use b.https.request.HTTPSBridgeRequest.

isis at torproject.org isis at torproject.org
Thu Jun 25 07:10:54 UTC 2015


commit 7b880a1fe0f346c85364b255eee93995a6b31b94
Author: Isis Lovecruft <isis at torproject.org>
Date:   Wed Apr 1 08:40:50 2015 +0000

    Refactor getBridgesForIP() to use b.https.request.HTTPSBridgeRequest.
    
     * RENAME b.D.IPBasedDistributor.getBridgesForIP() →
       b.D.IPBasedDistributor.getBridges().
     * CREATE new bridgedb.https package.
     * CREATE new bridgedb.https.request module.
     * CHANGE b.D.IPBasedDistributor.getBridges() to use
       bridgedb.https.request.HTTPSBridgeRequest.
---
 doc/sphinx/source/bridgedb.https.rst |   10 +++
 doc/sphinx/source/bridgedb.rst       |    1 +
 lib/bridgedb/Dist.py                 |  143 ++++++++++++++--------------------
 lib/bridgedb/HTTPServer.py           |  142 ++++++++++++++-------------------
 lib/bridgedb/https/__init__.py       |    1 +
 lib/bridgedb/https/request.py        |  143 ++++++++++++++++++++++++++++++++++
 lib/bridgedb/test/legacy_Tests.py    |   26 +++----
 lib/bridgedb/test/test_HTTPServer.py |   27 ++++---
 setup.py                             |    1 +
 9 files changed, 301 insertions(+), 193 deletions(-)

diff --git a/doc/sphinx/source/bridgedb.https.rst b/doc/sphinx/source/bridgedb.https.rst
new file mode 100644
index 0000000..b0ee95c
--- /dev/null
+++ b/doc/sphinx/source/bridgedb.https.rst
@@ -0,0 +1,10 @@
+.. _https-pkg:
+
+bridgedb.https
+---------------
+
+.. contents:: bridgedb.https
+    :depth: 3
+
+.. automodule:: bridgedb.https.__init__
+.. automodule:: bridgedb.https.request
diff --git a/doc/sphinx/source/bridgedb.rst b/doc/sphinx/source/bridgedb.rst
index 3f87647..61ab07f 100644
--- a/doc/sphinx/source/bridgedb.rst
+++ b/doc/sphinx/source/bridgedb.rst
@@ -17,6 +17,7 @@ BridgeDB Package and Module Documentation
     bridgedb.email
     bridgedb.Filters
     bridgedb.geo
+    bridgedb.https
     bridgedb.HTTPServer
     bridgedb.interfaces
     bridgedb.Main
diff --git a/lib/bridgedb/Dist.py b/lib/bridgedb/Dist.py
index 26e952e..3f7835b 100644
--- a/lib/bridgedb/Dist.py
+++ b/lib/bridgedb/Dist.py
@@ -305,118 +305,92 @@ class IPBasedDistributor(Distributor):
         """Assign a bridge to this distributor."""
         self.splitter.insert(bridge)
 
-    def getBridgesForIP(self, ip, epoch, N=1, countryCode=None,
-                        bridgeFilterRules=None):
+    def _buildHashringFilters(self, previousFilters, clientCluster):
+        totalRings = self.nClusters + len(self.categories)
+        g = filterAssignBridgesToRing(self.splitter.hmac, totalRings, clientCluster)
+        previousFilters.append(g)
+        return frozenset(previousFilters)
+
+    def getBridges(self, bridgeRequest, interval, N=1):
         """Return a list of bridges to give to a user.
 
-        :param str ip: The user's IP address, as a dotted quad.
-        :param str epoch: The time period when we got this request.  This can
-                          be any string, so long as it changes with every
-                          period.
+        :type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest`
+        :param bridgeRequest: A :class:`~bridgedb.bridgerequest.BridgeRequestBase`
+            with the :data:`~bridgedb.bridgerequest.BridgeRequestBase.client`
+            attribute set to a string containing the client's IP address.
+        :param str interval: The time period when we got this request.  This
+            can be any string, so long as it changes with every period.
         :param int N: The number of bridges to try to give back. (default: 1)
-        :param str countryCode: The two-letter geoip country code of the
-            client's IP address. If given, it is assumed that any bridges
-            distributed to that client should not be blocked in that
-            country. (default: None)
-        :param list bridgeFilterRules: A list of callables used filter the
-                                       bridges returned in the response to the
-                                       client. See :mod:`~bridgedb.Filters`.
         :rtype: list
         :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
-                 the response. See
-                 :meth:`bridgedb.HTTPServer.WebResource.getBridgeRequestAnswer`
-                 for an example of how this is used.
+            the response. See
+            :meth:`bridgedb.HTTPServer.WebResourceBridges.getBridgeRequestAnswer`
+            for an example of how this is used.
         """
         logging.info("Attempting to return %d bridges to client %s..."
-                     % (N, ip))
-
-        if not bridgeFilterRules:
-            bridgeFilterRules=[]
+                     % (N, bridgeRequest.client))
 
         if not len(self.splitter):
             logging.warn("Bailing! Splitter has zero bridges!")
             return []
 
-        logging.debug("Bridges in splitter:\t%d" % len(self.splitter))
-        logging.debug("Client request epoch:\t%s" % epoch)
-        logging.debug("Active bridge filters:\t%s"
-                      % ' '.join([x.func_name for x in bridgeFilterRules]))
-
-        area = self.areaMapper(ip)
-        logging.debug("IP mapped to area:\t%s" % area)
-
-        key1 = ''
-        pos = 0
-        n = self.nClusters
-
-        # only one of ip categories or area clustering is active
-        # try to match the request to an ip category
+        # The cluster the client should draw bridges from:
+        clientCluster = self.nClusters
+        # First, check if the client's IP is one of the known proxies in one
+        # of our :data:`catagories`:
         for category in self.categories:
-            # IP Categories
-            if ip in category:
+            if bridgeRequest.client in category:
                 # The tag is a tag applied to a proxy IP address when it is
                 # added to the bridgedb.proxy.ProxySet. For Tor Exit relays,
                 # the default is 'exit_relay'. For other proxies loaded from
                 # the PROXY_LIST_FILES config option, the default tag is the
                 # full filename that the IP address originally came from.
-                tag = category.getTag(ip)
-                logging.info("Client was from known proxy (tag: %s): %s" % (tag, ip))
-                g = filterAssignBridgesToRing(self.splitter.hmac,
-                                              self.nClusters +
-                                              len(self.categories),
-                                              n)
-                bridgeFilterRules.append(g)
+                tag = category.getTag(bridgeRequest.client)
+                logging.info("Client was from known proxy (tag: %s): %s" %
+                             (tag, bridgeRequest.client))
                 # Cluster Tor/proxy users into four groups.  This means that
                 # no matter how many different Tor Exits or proxies a client
                 # uses, the most they can ever get is four different sets of
                 # bridge lines (per period).
-                group = (int(ipaddr.IPAddress(ip)) % 4) + 1
-                logging.debug(("Assigning client hashring position based on: "
-                               "known-proxy<%s>%s") % (epoch, group))
-                pos = self.areaOrderHmac("known-proxy<%s>%s" % (epoch, group))
-                key1 = getHMAC(self.splitter.key,
-                               "Order-Bridges-In-Ring-%d" % n)
+                group = (int(ipaddr.IPAddress(bridgeRequest.client)) % 4) + 1
+                area = "known-proxy-group-%d" % group
                 break
-            n += 1
-
-        # if no category matches, use area clustering
+            clientCluster += 1
+        # If the client wasn't using Tor or any other known proxy, select the
+        # client's cluster number based upon the /16 of the client's IP
+        # address:
         else:
-            # IP clustering
-            h = int( self.areaClusterHmac(area)[:8], 16)
-            # length of numClusters
-            clusterNum = h % self.nClusters
-
-            g = filterAssignBridgesToRing(self.splitter.hmac,
-                                          self.nClusters +
-                                          len(self.categories),
-                                          clusterNum)
-            bridgeFilterRules.append(g)
-            pos = self.areaOrderHmac("<%s>%s" % (epoch, area))
-            key1 = getHMAC(self.splitter.key,
-                           "Order-Bridges-In-Ring-%d" % clusterNum)
-
-        # try to find a cached copy
-        ruleset = frozenset(bridgeFilterRules)
-
-        # See if we have a cached copy of the ring,
-        # otherwise, add a new ring and populate it
-        if ruleset in self.splitter.filterRings.keys():
-            logging.debug("Cache hit %s" % ruleset)
-            _, ring = self.splitter.filterRings[ruleset]
-
-        # else create the ring and populate it
+            # Areas (i.e. /16s) are grouped into the number of rings specified
+            # by the N_IP_CLUSTERS configuration option.
+            area = self.areaMapper(bridgeRequest.client)
+            logging.debug("IP mapped to area:\t%s" % area)
+            clientCluster = int(self.areaClusterHmac(area)[:8], 16) % self.nClusters
+
+        pos = self.areaOrderHmac("<%s>%s" % (interval, area))
+        filters = self._buildHashringFilters(bridgeRequest.filters, clientCluster)
+
+        logging.debug("Assigned client hashring position based on: <%s>%s" %
+                      (interval, area))
+        logging.debug("Bridges in splitter:\t%d" % len(self.splitter))
+        logging.debug("Active bridge filters:\t%s" %
+                      ' '.join([x.func_name for x in filters]))
+
+        # Check wheth we have a cached copy of the hashring:
+        if filters in self.splitter.filterRings.keys():
+            logging.debug("Cache hit %s" % filters)
+            _, ring = self.splitter.filterRings[filters]
+        # Otherwise, construct a new hashring and populate it:
         else:
-            logging.debug("Cache miss %s" % ruleset)
+            logging.debug("Cache miss %s" % filters)
+            key1 = getHMAC(self.splitter.key, "Order-Bridges-In-Ring-%d" % clientCluster)
             ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
-            self.splitter.addRing(ring,
-                                  ruleset,
-                                  filterBridgesByRules(bridgeFilterRules),
+            self.splitter.addRing(ring, filters, filterBridgesByRules(filters),
                                   populate_from=self.splitter.bridges)
 
-        # get an appropriate number of bridges
-        numBridgesToReturn = getNumBridgesPerAnswer(ring,
-                                                    max_bridges_per_answer=N)
-        answer = ring.getBridges(pos, numBridgesToReturn)
+        # Determine the appropriate number of bridges to give to the client:
+        returnNum = getNumBridgesPerAnswer(ring, max_bridges_per_answer=N)
+        answer = ring.getBridges(pos, returnNum)
+
         return answer
 
     def __len__(self):
@@ -425,6 +399,7 @@ class IPBasedDistributor(Distributor):
     def dumpAssignments(self, f, description=""):
         self.splitter.dumpAssignments(f, description)
 
+
 class EmailBasedDistributor(Distributor):
     """Object that hands out bridges based on the email address of an incoming
     request and the current time period.
diff --git a/lib/bridgedb/HTTPServer.py b/lib/bridgedb/HTTPServer.py
index 8c58380..179c061 100644
--- a/lib/bridgedb/HTTPServer.py
+++ b/lib/bridgedb/HTTPServer.py
@@ -23,8 +23,6 @@ import os
 from functools import partial
 
 from ipaddr import IPv4Address
-from ipaddr import IPv6Address
-from ipaddr import IPAddress
 
 import mako.exceptions
 from mako.template import Template
@@ -32,15 +30,11 @@ from mako.lookup import TemplateLookup
 
 from twisted.internet import reactor
 from twisted.internet.error import CannotListenError
-from twisted.python import filepath
 from twisted.web import resource
 from twisted.web import server
 from twisted.web import static
 from twisted.web.util import redirectTo
 
-import bridgedb.Dist
-import bridgedb.geo
-
 from bridgedb import captcha
 from bridgedb import crypto
 from bridgedb import strings
@@ -50,6 +44,7 @@ from bridgedb.Filters import filterBridgesByIP4
 from bridgedb.Filters import filterBridgesByIP6
 from bridgedb.Filters import filterBridgesByTransport
 from bridgedb.Filters import filterBridgesByNotBlockedIn
+from bridgedb.https.request import HTTPSBridgeRequest
 from bridgedb.parse import headers
 from bridgedb.parse.addr import isIPAddress
 from bridgedb.qrcodes import generateQR
@@ -671,6 +666,18 @@ class WebResourceBridges(resource.Resource):
 
         return response
 
+    def getClientIP(self, request):
+        """Get the client's IP address from the :header:`X-Forwarded-For`
+        header, or from the :api:`request <twisted.web.server.Request>`.
+
+        :type request: :api:`twisted.web.http.Request`
+        :param request: A ``Request`` object for a
+            :api:`twisted.web.resource.Resource`.
+        :rtype: None or str
+        :returns: The client's IP address, if it was obtainable.
+        """
+        return getClientIP(request, self.useForwardedHeader)
+
     def getBridgeRequestAnswer(self, request):
         """Respond to a client HTTP request for bridges.
 
@@ -680,104 +687,75 @@ class WebResourceBridges(resource.Resource):
         :rtype: str
         :returns: A plaintext or HTML response to serve.
         """
-        # XXX why are we getting the interval if our distributor might be
-        # using bridgedb.schedule.Unscheduled?
+        bridgeLines = None
         interval = self.schedule.intervalStart(time.time())
-        bridges = ( )
-        ip = None
-        countryCode = None
-
-        ip = getClientIP(request, self.useForwardedForHeader)
-
-        # Look up the country code of the input IP
-        if isIPAddress(ip):
-            countryCode = bridgedb.geo.getCountryCode(IPAddress(ip))
-        else:
-            logging.warn("Invalid IP detected; skipping country lookup.")
-            countryCode = None
-
-        # XXX separate function again
-        format = request.args.get("format", None)
-        if format and len(format): format = format[0] # choose the first arg
-
-        # do want any options?
-        transport = ipv6 = unblocked = False
-
-        ipv6 = request.args.get("ipv6", False)
-        if ipv6: ipv6 = True # if anything after ?ipv6=
-
-        # XXX oh dear hell. why not check for the '?transport=' arg before
-        # regex'ing? And why not compile the regex once, somewhere outside
-        # this function and class?
-        try:
-            # validate method name
-            transport = re.match('[_a-zA-Z][_a-zA-Z0-9]*',
-                    request.args.get("transport")[0]).group()
-        except (TypeError, IndexError, AttributeError):
-            transport = None
-
-        try:
-            unblocked = re.match('[a-zA-Z]{2,4}',
-                    request.args.get("unblocked")[0]).group()
-        except (TypeError, IndexError, AttributeError):
-            unblocked = False
+        ip = self.getClientIP(request)
 
         logging.info("Replying to web request from %s. Parameters were %r"
                      % (ip, request.args))
 
-        rules = []
-        bridgeLines = None
-
         if ip:
-            if ipv6:
-                rules.append(filterBridgesByIP6)
-                addressClass = IPv6Address
-            else:
-                rules.append(filterBridgesByIP4)
-                addressClass = IPv4Address
+            bridgeRequest = HTTPSBridgeRequest()
+            bridgeRequest.client = ip
+            bridgeRequest.isValid(True)
+            bridgeRequest.withIPversion(request.args)
+            bridgeRequest.withPluggableTransportType(request.args)
+            bridgeRequest.withoutBlockInCountry(request)
+            bridgeRequest.generateFilters()
+
+            bridges = self.distributor.getBridges(bridgeRequest, interval)
+            bridgeLines = [replaceControlChars(bridge.getBridgeLine(
+                bridgeRequest, self.includeFingerprints)) for bridge in bridges]
 
-            if transport:
-                #XXX: A cleaner solution would differentiate between
-                # addresses by protocol rather than have separate lists
-                # Tor to be a transport, and selecting between them.
-                rules = [filterBridgesByTransport(transport, addressClass)]
+        return self.renderAnswer(request, bridgeLines)
 
-            if unblocked:
-                rules.append(filterBridgesByNotBlockedIn(unblocked,
-                    addressClass, transport))
+    def getResponseFormat(self, request):
+        """Determine the requested format for the response.
+
+        :type request: :api:`twisted.web.http.Request`
+        :param request: A ``Request`` object containing the HTTP method, full
+            URI, and any URL/POST arguments and headers present.
+        :rtype: ``None`` or str
+        :returns: The argument of the first occurence of the ``format=`` HTTP
+            GET parameter, if any were present. (The only one which currently
+            has any effect is ``format=plain``, see note in
+            :meth:`renderAnswer`.)  Otherwise, returns ``None``.
+        """
+        format = request.args.get("format", None)
+        if format and len(format):
+            format = format[0]  # Choose the first arg
+        return format
 
-            bridges = self.distributor.getBridgesForIP(ip, interval,
-                                                       self.nBridgesToGive,
-                                                       countryCode,
-                                                       bridgeFilterRules=rules)
-            bridgeLines = [replaceControlChars(b.getConfigLine(
-                includeFingerprint=self.includeFingerprints,
-                addressClass=addressClass,
-                transport=transport,
-                request=bridgedb.Dist.uniformMap(ip))) for b in bridges]
+    def renderAnswer(self, request, bridgeLines=None):
+        """Generate a response for a client which includes **bridgesLines**.
 
-        answer = self.renderAnswer(request, bridgeLines, format)
-        return answer
+        .. note: The generated response can be plain or HTML. A plain response
+            looks like::
 
-    def renderAnswer(self, request, bridgeLines=None, format=None):
-        """Generate a response for a client which includes **bridges**.
+                voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF
+                voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567
 
-        The generated response can be plaintext or HTML.
+            That is, there is no HTML, what you see is what you get, and what
+            you get is suitable for pasting directly into Tor Launcher (or
+            into a torrc, if you prepend ``"Bridge "`` to each line). The
+            plain format can be requested from BridgeDB's web service by
+            adding an ``&format=plain`` HTTP GET parameter to the URL. Also
+            note that you won't get a QRCode, usage instructions, error
+            messages, or any other fanciness if you use the plain format.
 
         :type request: :api:`twisted.web.http.Request`
         :param request: A ``Request`` object containing the HTTP method, full
             URI, and any URL/POST arguments and headers present.
         :type bridgeLines: list or None
         :param bridgeLines: A list of strings used to configure a Tor client
-            to use a bridge.
-        :type format: str or None
-        :param format: If ``'plain'``, return a plaintext response. Otherwise,
-            use the :file:`bridgedb/templates/bridges.html` template to render
-            an HTML response page which includes the **bridges**.
+            to use a bridge. If ``None``, then the returned page will instead
+            explain that there were no bridges of the type they requested,
+            with instructions on how to proceed.
         :rtype: str
         :returns: A plaintext or HTML response to serve.
         """
         rtl = False
+        format = self.getResponseFormat(request)
 
         if format == 'plain':
             request.setHeader("Content-Type", "text/plain")
diff --git a/lib/bridgedb/https/__init__.py b/lib/bridgedb/https/__init__.py
new file mode 100644
index 0000000..f1210ec
--- /dev/null
+++ b/lib/bridgedb/https/__init__.py
@@ -0,0 +1 @@
+"""Servers for BridgeDB's HTTPS bridge distributor."""
diff --git a/lib/bridgedb/https/request.py b/lib/bridgedb/https/request.py
new file mode 100644
index 0000000..a8f6920
--- /dev/null
+++ b/lib/bridgedb/https/request.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8; test-case-name: bridgedb.test.test_https_request; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+#             (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""
+.. py:module:: bridgedb.https.request
+    :synopsis: Classes for parsing and storing information about requests for
+               bridges which are sent to the HTTPS distributor.
+
+bridgedb.https.request
+======================
+
+Classes for parsing and storing information about requests for bridges
+which are sent to the HTTPS distributor.
+
+::
+
+  bridgedb.https.request
+   |
+   |_ HTTPSBridgeRequest - A request for bridges which was received through
+                           the HTTPS distributor.
+..
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import ipaddr
+import logging
+import re
+
+from bridgedb import bridgerequest
+from bridgedb import geo
+from bridgedb.parse import addr
+
+
+#: A regular expression for matching the Pluggable Transport methodname in
+#: HTTP GET request parameters.
+TRANSPORT_REGEXP = "[_a-zA-Z][_a-zA-Z0-9]*"
+TRANSPORT_PATTERN = re.compile(TRANSPORT_REGEXP)
+
+UNBLOCKED_REGEXP = "[a-zA-Z]{2}"
+UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP)
+
+
+class HTTPSBridgeRequest(bridgerequest.BridgeRequestBase):
+    """We received a request for bridges through the HTTPS distributor."""
+
+    def __init__(self, addClientCountryCode=True):
+        """Process a new bridge request received through the
+        :class:`~bridgedb.Dist.IPBasedDistributor`.
+
+        :param bool addClientCountryCode: If ``True``, then calling
+            :meth:`withoutBlockInCountry` will attempt to add the client's own
+            country code, geolocated from her IP, to the ``notBlockedIn``
+            countries list.
+        """
+        super(HTTPSBridgeRequest, self).__init__()
+        self.addClientCountryCode = addClientCountryCode
+
+    def withIPversion(self, parameters):
+        """Determine if the request **parameters** were for bridges with IPv6
+        addresses or not.
+
+        .. note:: If there is an ``ipv6=`` parameter with anything non-zero
+            after it, then we assume the client wanted IPv6 bridges.
+
+        :param parameters: The :api:`twisted.web.http.Request.args`.
+        """
+        if parameters.get("ipv6", False):
+            logging.info("HTTPS request for bridges with IPv6 addresses.")
+            self.withIPv6()
+
+    def withoutBlockInCountry(self, request):
+        """Determine which countries the bridges for this **request** should
+        not be blocked in.
+
+        .. note:: Currently, a **request** for unblocked bridges is recognized
+            if it contains an HTTP GET parameter ``unblocked=`` whose value is
+            a comma-separater list of two-letter country codes.  Any
+            two-letter country code found in the
+            :api:`request <twisted.web.http.Request>` ``unblocked=`` HTTP GET
+            parameter will be added to the :data:`notBlockedIn` list.
+
+        If :data:`addClientCountryCode` is ``True``, the the client's own
+        geolocated country code will be added to the to the
+        :data`notBlockedIn` list.
+
+        :type request: :api:`twisted.web.http.Request`
+        :param request: A ``Request`` object containing the HTTP method, full
+            URI, and any URL/POST arguments and headers present.
+        """
+        countryCodes = request.args.get("unblocked", list())
+
+        for countryCode in countryCodes:
+            try:
+                country = UNBLOCKED_PATTERN.match(countryCode).group()
+            except (TypeError, AttributeError):
+                pass
+            else:
+                if country:
+                    self.notBlockedIn.append(country)
+                    logging.info("HTTPS request for bridges not blocked in: %r"
+                                 % country)
+
+        if self.addClientCountryCode:
+            # Look up the country code of the input IP, and request bridges
+            # not blocked in that country.
+            if addr.isIPAddress(self.client):
+                country = geo.getCountryCode(ipaddr.IPAddress(self.client))
+                if country:
+                    self.notBlockedIn.append(country)
+                    logging.info(
+                        ("HTTPS client's bridges also shouldn't be blocked "
+                         "in their GeoIP country code: %s") % country)
+
+    def withPluggableTransportType(self, parameters):
+        """This request included a specific Pluggable Transport identifier.
+
+        Add any Pluggable Transport methodname found in the HTTP GET
+        **parameters** to the list of ``transports``. Currently, a request for
+        a transport is recognized if the request contains the
+        ``'transport='`` parameter.
+
+        :param parameters: The :api:`twisted.web.http.Request.args`.
+        """
+        for methodname in parameters.get("transport", list()):
+            try:
+                transport = TRANSPORT_PATTERN.match(methodname).group()
+            except (TypeError, AttributeError):
+                pass
+            else:
+                if transport:
+                    self.transports.append(transport)
+                    logging.info("HTTPS request for transport type: %r"
+                                 % transport)
diff --git a/lib/bridgedb/test/legacy_Tests.py b/lib/bridgedb/test/legacy_Tests.py
index 2e4d56f..97134e4 100644
--- a/lib/bridgedb/test/legacy_Tests.py
+++ b/lib/bridgedb/test/legacy_Tests.py
@@ -190,8 +190,8 @@ class IPBridgeDistTests(unittest.TestCase):
         d = bridgedb.Dist.IPBasedDistributor(self.dumbAreaMapper, 3, "Foo")
         for _ in xrange(256):
             d.insert(fakeBridge())
-        n = d.getBridgesForIP("1.2.3.4", "x", 2)
-        n2 = d.getBridgesForIP("1.2.3.4", "x", 2)
+        n = d.getBridges("1.2.3.4", "x", 2)
+        n2 = d.getBridges("1.2.3.4", "x", 2)
         self.assertEquals(n, n2)
 
     def testDistWithCategories(self):
@@ -205,8 +205,8 @@ class IPBridgeDistTests(unittest.TestCase):
             # Make sure that the categories do not overlap
             f = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(4)])
             g = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(3)] + ['255'])
-            n = d.getBridgesForIP(g(), "x", 10)
-            n2 = d.getBridgesForIP(f(), "x", 10)
+            n = d.getBridges(g(), "x", 10)
+            n2 = d.getBridges(f(), "x", 10)
 
             assert(len(n) > 0)
             assert(len(n2) > 0)
@@ -228,7 +228,7 @@ class IPBridgeDistTests(unittest.TestCase):
     #        d.insert(fakeBridge())
     #    for _ in xrange(32):
     #        i = randomIP()
-    #        n = d.getBridgesForIP(i, "x", 5)
+    #        n = d.getBridges(i, "x", 5)
     #        count = 0
     #        fps = {}
     #        for b in n:
@@ -246,7 +246,7 @@ class IPBridgeDistTests(unittest.TestCase):
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(500):
-            bridges = d.getBridgesForIP(randomIPv4String(),
+            bridges = d.getBridges(randomIPv4String(),
                                         "faketimestamp",
                                         bridgeFilterRules=[filterBridgesByIP6])
             bridge = random.choice(bridges)
@@ -262,7 +262,7 @@ class IPBridgeDistTests(unittest.TestCase):
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(500):
-            bridges = d.getBridgesForIP(randomIPv4String(),
+            bridges = d.getBridges(randomIPv4String(),
                                         "faketimestamp",
                                         bridgeFilterRules=[filterBridgesByIP4])
             bridge = random.choice(bridges)
@@ -278,7 +278,7 @@ class IPBridgeDistTests(unittest.TestCase):
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(50):
-            bridges = d.getBridgesForIP(randomIPv4String(),
+            bridges = d.getBridges(randomIPv4String(),
                                         "faketimestamp", 1,
                                         bridgeFilterRules=[
                                             filterBridgesByIP4,
@@ -302,7 +302,7 @@ class IPBridgeDistTests(unittest.TestCase):
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(5):
-            b = d.getBridgesForIP(randomIPv4String(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[
                 filterBridgesByIP4, filterBridgesByIP6])
             assert len(b) == 0
 
@@ -325,10 +325,10 @@ class IPBridgeDistTests(unittest.TestCase):
             b.blockingCountries[key] = set(['cn'])
 
         for i in xrange(5):
-            b = d.getBridgesForIP(randomIPv4String(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[
                 filterBridgesByNotBlockedIn("cn")])
             assert len(b) == 0
-            b = d.getBridgesForIP(randomIPv4String(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[
                 filterBridgesByNotBlockedIn("us")])
             assert len(b) > 0
 
@@ -356,7 +356,7 @@ class IPBridgeDistTests(unittest.TestCase):
         # we probably will get at least one bridge back!
         # it's pretty unlikely to lose a coin flip 250 times in a row
         for i in xrange(5):
-            b = d.getBridgesForIP(randomIPString(), "x", 1,
+            b = d.getBridges(randomIPString(), "x", 1,
                     bridgeFilterRules=[
                         filterBridgesByNotBlockedIn("cn", methodname='obfs2'),
                         filterBridgesByTransport('obfs2'),
@@ -364,7 +364,7 @@ class IPBridgeDistTests(unittest.TestCase):
             try: assert len(b) > 0
             except AssertionError:
                 print("epic fail")
-            b = d.getBridgesForIP(randomIPString(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPString(), "x", 1, bridgeFilterRules=[
                 filterBridgesByNotBlockedIn("us")])
             assert len(b) > 0
 
diff --git a/lib/bridgedb/test/test_HTTPServer.py b/lib/bridgedb/test/test_HTTPServer.py
index d597c55..415fb8d 100644
--- a/lib/bridgedb/test/test_HTTPServer.py
+++ b/lib/bridgedb/test/test_HTTPServer.py
@@ -448,22 +448,22 @@ class DummyBridge(object):
         self.or_addresses = {ipaddr.IPv6Address(self._returnSix()):
                              self._randORPort()}
 
-    def getConfigLine(self, includeFingerprint=True,
-                      addressClass=ipaddr.IPv4Address,
-                      transport=None,
-                      request=None):
+    def getBridgeLine(self, bridgeRequest, includeFingerprint=True):
         """Get a "torrc" bridge config line to give to a client."""
+        if not bridgeRequest.isValid():
+            return
         line = []
-        if transport is not None:
-            #print("getConfigLine got transport=%r" % transport)
-            line.append(str(transport))
-        line.append("%s:%s" % (self.ip, self.orport))
-        if includeFingerprint is True: line.append(self.fingerprint)
+        if bridgeRequest.transports:
+            line.append(bridgeRequest.transports[-1])  # Just the last PT
+        if bridgeRequest.addressClass is ipaddr.IPv6Address:
+            line.append("[%s]:%s" % self.or_addresses.items()[0])
+        else:
+            line.append("%s:%s" % (self.ip, self.orport))
+        if includeFingerprint is True:
+            line.append(self.fingerprint)
         if self.ptArgs:
             line.append(','.join(['='.join(x) for x in self.ptArgs.items()]))
-        bridgeLine = " ".join([item for item in line])
-        #print "Created config line: %r" % bridgeLine
-        return bridgeLine
+        return " ".join([item for item in line])
 
 
 class DummyMaliciousBridge(DummyBridge):
@@ -499,8 +499,7 @@ class DummyIPBasedDistributor(object):
         self.ipCategories = ipCategories
         self.answerParameters = answerParameters
 
-    def getBridgesForIP(self, ip=None, epoch=None, N=1,
-                        countyCode=None, bridgeFilterRules=None):
+    def getBridges(self, bridgeRequest=None, epoch=None, N=1):
         """Needed because it's called in
         :meth:`WebResourceBridges.getBridgesForIP`.
         """
diff --git a/setup.py b/setup.py
index b788a7e..c75239a 100644
--- a/setup.py
+++ b/setup.py
@@ -392,6 +392,7 @@ setuptools.setup(
     package_dir={'': 'lib'},
     packages=['bridgedb',
               'bridgedb.email',
+              'bridgedb.https',
               'bridgedb.parse',
               'bridgedb.test'],
     scripts=['scripts/bridgedb',





More information about the tor-commits mailing list