# Source code for certbot_nginx.obj

    """Module contains classes used by the Nginx Configurator."""
    import re

    import six

    from certbot.plugins import common

    ADD_HEADER_DIRECTIVE = 'add_header'

    class Addr(common.Addr):
        r"""Represents an Nginx address, i.e. what comes after the 'listen'
        directive.

        According to the `documentation`_, this may be address[:port], port,
        or unix:path. The latter is ignored here.

        The default value if no directive is specified is \*:80 (superuser)
        or \*:8000 (otherwise). If no port is specified, the default is
        80. If no address is specified, listen on all addresses.

        .. _documentation:
           http://nginx.org/en/docs/http/ngx_http_core_module.html#listen

        .. todo:: Old-style nginx configs define SSL vhosts in a separate
                  block instead of using 'ssl' in the listen directive.

        :param str addr: addr part of vhost address, may be hostname, IPv4, IPv6,
            "", or "\*"
        :param str port: port number or "\*" or ""
        :param bool ssl: Whether the directive includes 'ssl'
        :param bool default: Whether the directive includes 'default_server'
        :param bool default: Whether this is an IPv6 address
        :param bool ipv6only: Whether the directive includes 'ipv6only=on'

        """
        UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0')
        CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0]

        def __init__(self, host, port, ssl, default, ipv6, ipv6only):
            # pylint: disable=too-many-arguments
            super(Addr, self).__init__((host, port))
            self.ssl = ssl
            self.default = default
            self.ipv6 = ipv6
            self.ipv6only = ipv6only
            self.unspecified_address = host in self.UNSPECIFIED_IPV4_ADDRESSES

        @classmethod
        def fromstring(cls, str_addr):
            """Initialize Addr from string."""
            parts = str_addr.split(' ')
            ssl = False
            default = False
            ipv6 = False
            ipv6only = False
            host = ''
            port = ''

            # The first part must be the address
            addr = parts.pop(0)

            # Ignore UNIX-domain sockets
            if addr.startswith('unix:'):
                return None

            # IPv6 check
            ipv6_match = re.match(r'\[.*\]', addr)
            if ipv6_match:
                ipv6 = True
                # IPv6 handling
                host = ipv6_match.group()
                # The rest of the addr string will be the port, if any
                port = addr[ipv6_match.end()+1:]
            else:
                # IPv4 handling
                tup = addr.partition(':')
                if re.match(r'^\d+$', tup[0]):
                    # This is a bare port, not a hostname. E.g. listen 80
                    host = ''
                    port = tup[0]
                else:
                    # This is a host-port tuple. E.g. listen 127.0.0.1:*
                    host = tup[0]
                    port = tup[2]

            # The rest of the parts are options; we only care about ssl and default
            while parts:
                nextpart = parts.pop()
                if nextpart == 'ssl':
                    ssl = True
                elif nextpart == 'default_server':
                    default = True
                elif nextpart == 'default':
                    default = True
                elif nextpart == "ipv6only=on":
                    ipv6only = True

            return cls(host, port, ssl, default, ipv6, ipv6only)

        def to_string(self, include_default=True):
            """Return string representation of Addr"""
            parts = ''
            if self.tup[0] and self.tup[1]:
                parts = "%s:%s" % self.tup
            elif self.tup[0]:
                parts = self.tup[0]
            else:
                parts = self.tup[1]

            if self.default and include_default:
                parts += ' default_server'
            if self.ssl:
                parts += ' ssl'

            return parts

        def __str__(self):
            return self.to_string()

        def __repr__(self):
            return "Addr(" + self.__str__() + ")"

        def __hash__(self):  # pylint: disable=useless-super-delegation
            # Python 3 requires explicit overridden for __hash__
            # See certbot-apache/certbot_apache/obj.py for more information
            return super(Addr, self).__hash__()

        def super_eq(self, other):
            """Check ip/port equality, with IPv6 support.
            """
            # If both addresses got an unspecified address, then make sure the
            # host representation in each match when doing the comparison.
            if self.unspecified_address and other.unspecified_address:
                return common.Addr((self.CANONICAL_UNSPECIFIED_ADDRESS,
                                    self.tup[1]), self.ipv6) == \
                       common.Addr((other.CANONICAL_UNSPECIFIED_ADDRESS,
                                    other.tup[1]), other.ipv6)
            return super(Addr, self).__eq__(other)

        def __eq__(self, other):
            if isinstance(other, self.__class__):
                return (self.super_eq(other) and
                        self.ssl == other.ssl and
                        self.default == other.default)
            return False

    class VirtualHost(object):  # pylint: disable=too-few-public-methods
        """Represents an Nginx Virtualhost.

        :ivar str filep: file path of VH
        :ivar set addrs: Virtual Host addresses (:class:`set` of :class:`Addr`)
        :ivar set names: Server names/aliases of vhost
            (:class:`list` of :class:`str`)
        :ivar list raw: The raw form of the parsed server block

        :ivar bool ssl: SSLEngine on in vhost
        :ivar bool enabled: Virtual host is enabled
        :ivar list path: The indices into the parsed file used to access
            the server block defining the vhost

        """

        def __init__(self, filep, addrs, ssl, enabled, names, raw, path):
            # pylint: disable=too-many-arguments
            """Initialize a VH."""
            self.filep = filep
            self.addrs = addrs
            self.names = names
            self.ssl = ssl
            self.enabled = enabled
            self.raw = raw
            self.path = path

        def __str__(self):
            addr_str = ", ".join(str(addr) for addr in sorted(self.addrs, key=str))
            # names might be a set, and it has different representations in Python
            # 2 and 3. Force it to be a list here for consistent outputs
            return ("file: %s\n"
                    "addrs: %s\n"
                    "names: %s\n"
                    "ssl: %s\n"
                    "enabled: %s" % (self.filep, addr_str,
                                     list(self.names), self.ssl, self.enabled))

        def __repr__(self):
            return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n"

        def __eq__(self, other):
            if isinstance(other, self.__class__):
                return (self.filep == other.filep and
                        sorted(self.addrs, key=str) == sorted(other.addrs, key=str) and
                        self.names == other.names and
                        self.ssl == other.ssl and
                        self.enabled == other.enabled and
                        self.path == other.path)

            return False

        def __hash__(self):
            return hash((self.filep, tuple(self.path),
                         tuple(self.addrs), tuple(self.names),
                         self.ssl, self.enabled))

        def has_header(self, header_name):
            """Determine if this server block has a particular header set.
            :param str header_name: The name of the header to check for, e.g.
                'Strict-Transport-Security'
            """
            found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name)
            return found is not None

        def contains_list(self, test):
            """Determine if raw server block contains test list at top level
            """
            for i in six.moves.range(0, len(self.raw) - len(test) + 1):
                if self.raw[i:i + len(test)] == test:
                    return True
            return False

        def ipv6_enabled(self):
            """Return true if one or more of the listen directives in vhost supports
            IPv6"""
            for a in self.addrs:
                if a.ipv6:
                    return True
            return False

        def ipv4_enabled(self):
            """Return true if one or more of the listen directives in vhost are IPv4
            only"""
            if not self.addrs:
                return True
            for a in self.addrs:
                if not a.ipv6:
                    return True
            return False

        def display_repr(self):
            """Return a representation of VHost to be used in dialog"""
            return (
                "File: {filename}\n"
                "Addresses: {addrs}\n"
                "Names: {names}\n"
                "HTTPS: {https}\n".format(
                    filename=self.filep,
                    addrs=", ".join(str(addr) for addr in self.addrs),
                    names=", ".join(self.names),
                    https="Yes" if self.ssl else "No"))

    def _find_directive(directives, directive_name, match_content=None):
        """Find a directive of type directive_name in directives. If match_content is given,
           Searches for `match_content` in the directive arguments.
        """
        if not directives or isinstance(directives, six.string_types):
            return None

        # If match_content is None, just match on directive type. Otherwise, match on
        # both directive type -and- the content!
        if directives[0] == directive_name and \
                (match_content is None or match_content in directives):
            return directives

        matches = (_find_directive(line, directive_name, match_content) for line in directives)
        return next((m for m in matches if m is not None), None)