# Path: txtorcon/util.py
# def find_keywords(args, key_filter=lambda x: not x.startswith("$")):
#     """
#     This splits up strings like name=value, foo=bar into a dict. Does NOT deal
#     with quotes in value (e.g. key="value with space" will not work
# 
#     By default, note that it takes OUT any key which starts with $ (i.e. a
#     single dollar sign) since for many use-cases the way Tor encodes nodes
#     with "$hash=name" looks like a keyword argument (but it isn't). If you
#     don't want this, override the "key_filter" argument to this method.
# 
#     :param args: a list of strings, each with one key=value pair
# 
#     :return:
#         a dict of key->value (both strings) of all name=value type
#         keywords found in args.
#     """
#     filtered = [x for x in args if '=' in x and key_filter(x.split('=')[0])]
#     return dict(x.split('=', 1) for x in filtered)
# 
# def version_at_least(version_string, major, minor, micro, patch):
#     """
#     This returns True if the version_string represents a Tor version
#     of at least ``major``.``minor``.``micro``.``patch`` version,
#     ignoring any trailing specifiers.
#     """
#     parts = re.match(
#         r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+).*$',
#         version_string,
#     )
#     for ver, gold in zip(parts.group(1, 2, 3, 4), (major, minor, micro, patch)):
#         if int(ver) < int(gold):
#             return False
#         elif int(ver) > int(gold):
#             return True
#     return True
# 
# Path: txtorcon/util.py
# def _is_non_public_numeric_address(host):
#     """
#     returns True if 'host' is not public
#     """
#     # for numeric hostnames, skip RFC1918 addresses, since no Tor exit
#     # node will be able to reach those. Likewise ignore IPv6 addresses.
#     try:
#         a = ipaddress.ip_address(six.text_type(host))
#     except ValueError:
#         return False        # non-numeric, let Tor try it
#     if a.is_loopback or a.is_multicast or a.is_private or a.is_reserved \
#        or a.is_unspecified:
#         return True         # too weird, don't connect
#     return False
# 
# Path: txtorcon/util.py
# @defer.inlineCallbacks
# def available_tcp_port(reactor):
#     """
#     Returns a Deferred firing an available TCP port on localhost.
#     It does so by listening on port 0; then stopListening and fires the
#     assigned port number.
#     """
# 
#     endpoint = serverFromString(reactor, 'tcp:0:interface=127.0.0.1')
#     port = yield endpoint.listen(NoOpProtocolFactory())
#     address = port.getHost()
#     yield port.stopListening()
#     defer.returnValue(address.port)


import os
import re
import six
import base64
import hashlib
import functools
import warnings
from os.path import isabs, abspath
from zope.interface import Interface, Attribute, implementer
from twisted.internet import defer
from twisted.python import log
from txtorcon.util import find_keywords, version_at_least
from txtorcon.util import _is_non_public_numeric_address
from txtorcon.util import available_tcp_port
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
        from .torconfig import _ListWrapper
        from .torconfig import _ListWrapper
    from txtorcon.spaghetti import FSM, State, Transition
    if not isinstance(ports, (list, tuple)):
        raise ValueError("'ports' must be a list of strings")
    if any([not isinstance(x, (six.text_type, str)) for x in ports]):
        raise ValueError("'ports' must be a list of strings")
    for port in ports:
        _validate_single_port_string(port)


def _validate_single_port_string(port):
    """
    Validate a single string specifying ports for Onion
    services. These look like: "80 127.0.0.1:4321"
    """
    if ' ' not in port or len(port.split(' ')) != 2:
        raise ValueError(
            "Port '{}' should have exactly one space in it".format(port)
        )
    (external, internal) = port.split(' ')
    try:
        external = int(external)
    except ValueError:
        raise ValueError(
            "Port '{}' external port isn't an int".format(port)
        )
    if ':' not in internal:
        raise ValueError(
            "Port '{}' local address should be 'IP:port'".format(port)
        )
    if not internal.startswith('unix:'):
        ip, localport = internal.split(':')

# Path: txtorcon/util.py
# def find_keywords(args, key_filter=lambda x: not x.startswith("$")):
#     """
#     This splits up strings like name=value, foo=bar into a dict. Does NOT deal
#     with quotes in value (e.g. key="value with space" will not work
# 
#     By default, note that it takes OUT any key which starts with $ (i.e. a
#     single dollar sign) since for many use-cases the way Tor encodes nodes
#     with "$hash=name" looks like a keyword argument (but it isn't). If you
#     don't want this, override the "key_filter" argument to this method.
# 
#     :param args: a list of strings, each with one key=value pair
# 
#     :return:
#         a dict of key->value (both strings) of all name=value type
#         keywords found in args.
#     """
#     filtered = [x for x in args if '=' in x and key_filter(x.split('=')[0])]
#     return dict(x.split('=', 1) for x in filtered)
# 
# def version_at_least(version_string, major, minor, micro, patch):
#     """
#     This returns True if the version_string represents a Tor version
#     of at least ``major``.``minor``.``micro``.``patch`` version,
#     ignoring any trailing specifiers.
#     """
#     parts = re.match(
#         r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+).*$',
#         version_string,
#     )
#     for ver, gold in zip(parts.group(1, 2, 3, 4), (major, minor, micro, patch)):
#         if int(ver) < int(gold):
#             return False
#         elif int(ver) > int(gold):
#             return True
#     return True
# 
# Path: txtorcon/util.py
# def _is_non_public_numeric_address(host):
#     """
#     returns True if 'host' is not public
#     """
#     # for numeric hostnames, skip RFC1918 addresses, since no Tor exit
#     # node will be able to reach those. Likewise ignore IPv6 addresses.
#     try:
#         a = ipaddress.ip_address(six.text_type(host))
#     except ValueError:
#         return False        # non-numeric, let Tor try it
#     if a.is_loopback or a.is_multicast or a.is_private or a.is_reserved \
#        or a.is_unspecified:
#         return True         # too weird, don't connect
#     return False
# 
# Path: txtorcon/util.py
# @defer.inlineCallbacks
# def available_tcp_port(reactor):
#     """
#     Returns a Deferred firing an available TCP port on localhost.
#     It does so by listening on port 0; then stopListening and fires the
#     assigned port number.
#     """
# 
#     endpoint = serverFromString(reactor, 'tcp:0:interface=127.0.0.1')
#     port = yield endpoint.listen(NoOpProtocolFactory())
#     address = port.getHost()
#     yield port.stopListening()
#     defer.returnValue(address.port)


# Path: txtorcon/onion.py
import os
import re
import six
import base64
import hashlib
import functools
import warnings
from os.path import isabs, abspath
from zope.interface import Interface, Attribute, implementer
from twisted.internet import defer
from twisted.python import log
from txtorcon.util import find_keywords, version_at_least
from txtorcon.util import _is_non_public_numeric_address
from txtorcon.util import available_tcp_port
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
        from .torconfig import _ListWrapper
        from .torconfig import _ListWrapper
    from txtorcon.spaghetti import FSM, State, Transition

    if not isinstance(ports, (list, tuple)):
        raise ValueError("'ports' must be a list of strings")
    if any([not isinstance(x, (six.text_type, str)) for x in ports]):
        raise ValueError("'ports' must be a list of strings")
    for port in ports:
        _validate_single_port_string(port)


def _validate_single_port_string(port):
    """
    Validate a single string specifying ports for Onion
    services. These look like: "80 127.0.0.1:4321"
    """
    if ' ' not in port or len(port.split(' ')) != 2:
        raise ValueError(
            "Port '{}' should have exactly one space in it".format(port)
        )
    (external, internal) = port.split(' ')
    try:
        external = int(external)
    except ValueError:
        raise ValueError(
            "Port '{}' external port isn't an int".format(port)
        )
    if ':' not in internal:
        raise ValueError(
            "Port '{}' local address should be 'IP:port'".format(port)
        )
    if not internal.startswith('unix:'):
        ip, localport = internal.split(':')

        if ip != 'localhost' and not _is_non_public_numeric_address(ip):