[tor-commits] [bridgedb/master] Add bridgedb.parse.addr module for parsing IPs and ports.

isis at torproject.org isis at torproject.org
Sun Jan 12 06:06:32 UTC 2014


commit 1f111e56428e89b731d832e20b7c5876af736943
Author: Isis Lovecruft <isis at torproject.org>
Date:   Fri Nov 15 15:26:32 2013 +0000

    Add bridgedb.parse.addr module for parsing IPs and ports.
    
     * MOVE class bridgedb.Bridges.PortList to bridgedb.parse.addr.
---
 lib/bridgedb/Bridges.py    |   64 --------------
 lib/bridgedb/parse/addr.py |  208 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 208 insertions(+), 64 deletions(-)

diff --git a/lib/bridgedb/Bridges.py b/lib/bridgedb/Bridges.py
index a11cd48..723d3c9 100644
--- a/lib/bridgedb/Bridges.py
+++ b/lib/bridgedb/Bridges.py
@@ -361,54 +361,6 @@ def parseDescFile(f, bridge_purpose='bridge'):
                 yield b
             nickname = ip = orport = fingerprint = purpose = None 
 
-class PortList:
-    """ container class for port ranges
-    """
-
-    def __init__(self, *args, **kwargs):
-        self.ports = set()
-        self.add(*args)
-
-    def _sanitycheck(self, val):
-        #XXX: if debug=False this is disabled. bad!
-        assert type(val) is int
-        assert(0 < val <= 65535) 
-
-    def __contains__(self, val1):
-        return val1 in self.ports
-
-    def add(self, *args):
-        for arg in args:
-            try:
-                if type(arg) is str:
-                    ports = set([int(p) for p in arg.split(',')][:PORTSPEC_LEN])
-                    [self._sanitycheck(p) for p in ports]
-                    self.ports.update(ports)
-                if type(arg) is int:
-                    self._sanitycheck(arg)
-                    self.ports.update([arg])
-                if type(arg) is PortList:
-                    self.add(list(arg.ports))
-            except AssertionError: raise ValueError
-            except ValueError: raise
-
-    def __iter__(self):
-        return self.ports.__iter__()
-
-    def __str__(self):
-        s = ""
-        for p in self.ports:
-            s += "".join(",%s"%p)
-        return s.lstrip(",")
-
-    def __repr__(self):
-        return "PortList('%s')" % self.__str__()
-
-    def __len__(self):
-        return len(self.ports)
-
-    def __getitem__(self, x):
-        return list(self.ports)[x]
 
 class ParseORAddressError(Exception):
     def __init__(self):
@@ -418,22 +370,6 @@ class ParseORAddressError(Exception):
 re_ipv6 = re.compile("\[([a-fA-F0-9:]+)\]:(.*$)")
 re_ipv4 = re.compile("((?:\d{1,3}\.?){4}):(.*$)")
 
-def parseORAddressLine(line):
-    address = None
-    portlist = None
-    # try regexp to discover ip version
-    for regex in [re_ipv4, re_ipv6]:
-        m = regex.match(line)
-        if m:
-            # get an address and portspec, or raise ParseError
-            try:
-                address  = ipaddr.IPAddress(m.group(1))
-                portlist = PortList(m.group(2))
-            except (IndexError, ValueError): raise ParseORAddressError
-
-    # return a valid address, portlist or raise ParseORAddressError
-    if address and portlist and len(portlist): return address,portlist
-    raise ParseORAddressError
 
 class PluggableTransport:
     """
diff --git a/lib/bridgedb/parse/addr.py b/lib/bridgedb/parse/addr.py
new file mode 100644
index 0000000..6e5daa6
--- /dev/null
+++ b/lib/bridgedb/parse/addr.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+#
+# 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) 2013 Isis Lovecruft
+#             (c) 2007-2013, The Tor Project, Inc.
+#             (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Utilities for parsing IP addresses.
+
+** Module Overview: **
+
+..
+  parse
+   ||_ parse.addr
+   |   |_ isIPAddress - Check if an arbitrary string is an IP address.
+   |   |_ isIPv4 - Check if an arbitrary string is an IPv4 address.
+   |   |_ isIPv6 - Check if an arbitrary string is an IPv6 address.
+   |   \_ isValidIP - Check that an IP address is valid.
+   |
+   |__ :mod:`bridgedbparse.headers`
+   |__ :mod:`bridgedb.parse.options`
+   \__ :mod:`bridgedb.parse.versions`
+"""
+
+import logging
+
+import ipaddr
+
+
+class InvalidPort(ValueError):
+    """Raised when a given port number is invalid."""
+
+
+def isIPAddress(ip, compressed=True):
+    """Check if an arbitrary string is an IP address, and that it's valid.
+
+    :type ip: basestring or int
+    :param ip: The IP address to check.
+    :param boolean compressed: If True, return a string representing the
+        compressed form of the address. Otherwise, return an
+        :class:`ipaddr.IPAddress` instance.
+    :rtype: A :class:`ipaddr.IPAddress`, or a string, or False
+    :returns: The IP, as a string or a class, if it passed the
+        checks. Otherwise, returns False.
+    """
+    try:
+        ip = ipaddr.IPAddress(ip)
+    except ValueError:
+        return False
+    except Exception as error:
+        logging.exception(error)
+    else:
+        if isValidIP(ip):
+            if compressed:
+                return ip.compressed
+            else:
+                return ip
+    return False
+
+def _isIPv(version, ip):
+    """Check if an address is a certain ``version``, either IPv4 or IPv6.
+
+    :param integer version: The IPv[4|6] version to check; must be either
+        ``4`` or ``6``.
+    :type ip: basestring or int
+    :param ip: The IP address to check.
+    :rtype: boolean
+    :returns: True if the address is an IPv4 address.
+    """
+    ip = isIPAddress(ip, compressed=False)
+    if ip and (ip.version == version):
+        return True
+    return False
+
+def isIPv4(ip):
+    """Check if an address is IPv4.
+
+    :type ip: basestring or int
+    :param ip: The IP address to check.
+    :rtype: boolean
+    :returns: True if the address is an IPv4 address.
+    """
+    return _isIPv(4, ip)
+
+def isIPv6(ip):
+    """Check if an address is IPv6.
+
+    :type ip: basestring or int
+    :param ip: The IP address to check.
+    :rtype: boolean
+    :returns: True if the address is an IPv6 address.
+    """
+    return _isIPv(6, ip)
+
+def isValidIP(ipaddress):
+    """Check that an IP (v4 or v6) is public and not reserved.
+
+    The IP address, ``ip``, must not be any of the following:
+      * A link-local address, such as ``169.254.0.0/16`` or ``fe80::/64``.
+      * The address of a loopback interface, i.e. ``127.0.0.1`` or ``::1``.
+      * A multicast address, for example, ``255.255.255.0``.
+      * An unspecified address, for example ``0.0.0.0/32`` in IPv4 or
+        ``::/128`` in IPv6.
+      * A default route address, for example ``0.0.0.0/0`` or ``::/0``.
+      * Any other address within a private networks, such as the IANA
+        reserved Shared Address Space, defined in RFC6598_ as
+        ``100.64.0.0/10``.
+
+    If it is an IPv4 address, it also must not be:
+      *  A reserved address vis-á-vis RFC1918_
+
+    If it is an IPv6 address, it also must not be:
+      *  A "site-local", or Unique Local Address (ULA_), address vis-á-vis
+         RFC4193_ (i.e. within the ``fc00::/7`` netblock)
+
+    .. _RFC6598: https://tools.ietf.org/htmłrfc6598
+    .. _RFC1918: https://tools.ietf.org/html/rfc1918
+    .. _ULA: https://en.wikipedia.org/wiki/Unique_local_address
+    .. _RFC4193: https://tools.ietf.org/html/rfc4193
+
+    :type ipaddress: An :class:`ipaddr.IPAddress`,
+        :class:`ipaddr.IPv4Address`, or :class:`ipaddr.IPv6Address`.
+    :param ipaddress: An IPAddress class.
+    :rtype: boolean
+    :returns: True if the address passes the checks, False otherwise.
+    """
+    if not (ipaddress.is_link_local or ipaddress.is_loopback
+            or ipaddress.is_multicast or ipaddress.is_private
+            or ipaddress.is_unspecified):
+        if (ipaddress.version == 6) and (not ipaddress.is_site_local):
+            return True
+        elif (ipaddress.version == 4) and (not ipaddress.is_reserved):
+            return True
+    return False
+
+
+class PortList(object):
+    """ container class for port ranges
+    """
+
+    #: The maximum number of allowed ports per IP address.
+    PORTSPEC_LEN = 16
+
+    def __init__(self, *args, **kwargs):
+        self.ports = set()
+        self.add(*args)
+
+    def _sanitycheck(self, port):
+        if (not isinstance(port, int)) or not (0 < port <= 65535):
+            raise InvalidPort("%s is not a valid port number!" % port)
+
+    def __contains__(self, port):
+        return port in self.ports
+
+    def add(self, *args):
+        """Add a port (or ports) to this PortList."""
+        for arg in args:
+            portlist = []
+            try:
+                if isinstance(arg, basestring):
+                    ports = set([int(p)
+                                 for p in arg.split(',')][:self.PORTSPEC_LEN])
+                    portlist.extend([p for p in ports])
+                if isinstance(arg, int):
+                    portlist.extend(arg)
+                if isinstance(arg, PortList):
+                    self.add(list(arg.ports))
+            except ValueError:
+                raise InvalidPort("%s is not a valid port number!" % arg)
+            except InvalidPort:
+                raise
+
+            [self._sanitycheck(port) for port in portlist]
+            self.ports.update(portlist)
+
+    def __iter__(self):
+        """Iterate through all ports in this PortList."""
+        return self.ports.__iter__()
+
+    def __str__(self):
+        """Returns a pretty string representation of this PortList."""
+        ret = []
+        for port in self.ports:
+            ret.append(',%s' % port)
+        ret = ''.join([piece for piece in ret])
+        return ret.lstrip(",")
+
+    def __repr__(self):
+        """Returns a raw depiction of this PortList."""
+        return "PortList('%s')" % self.__str__()
+
+    def __len__(self):
+        """Returns the total number of ports in this PortList."""
+        return len(self.ports)
+
+    def __getitem__(self, x):
+        """Get a port if it is in this PortList.
+
+        :raises: ValueError if ``x`` isn't in this PortList.
+        :rtype: integer
+        :returns: The port ``x``, if it is in this PortList.
+        """
+        portlist = list(self.ports)
+        return portlist[portlist.index(x)]





More information about the tor-commits mailing list