from __future__ import absolute_import, print_function, division

import base64
import collections
import os
import re
from netlib import strutils

import six
from OpenSSL import SSL, crypto

from mitmproxy import exceptions
from netlib import certutils
from netlib import tcp
from netlib.http import authentication
from netlib.http import url

CONF_BASENAME = "mitmproxy"


class HostMatcher(object):

    def __init__(self, patterns=tuple()):
        self.patterns = list(patterns)
        self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns]

    def __call__(self, address):
        if not address:
            return False
        address = tcp.Address.wrap(address)
        host = "%s:%s" % (address.host, address.port)
        if any(rex.search(host) for rex in self.regexes):
            return True
        else:
            return False

    def __bool__(self):
        return bool(self.patterns)

    if six.PY2:
        __nonzero__ = __bool__


ServerSpec = collections.namedtuple("ServerSpec", "scheme address")


def parse_server_spec(spec):
    try:
        p = url.parse(spec)
        if p[0] not in (b"http", b"https"):
            raise ValueError()
    except ValueError:
        raise exceptions.OptionsError(
            "Invalid server specification: %s" % spec
        )
    host, port = p[1:3]
    address = tcp.Address((host.decode("ascii"), port))
    scheme = p[0].decode("ascii").lower()
    return ServerSpec(scheme, address)


def parse_upstream_auth(auth):
    pattern = re.compile(".+:")
    if pattern.search(auth) is None:
        raise exceptions.OptionsError(
            "Invalid upstream auth specification: %s" % auth
        )
    return b"Basic" + b" " + base64.b64encode(strutils.always_bytes(auth))


class ProxyConfig:

    def __init__(self, options):
        self.options = options

        self.authenticator = None
        self.check_ignore = None
        self.check_tcp = None
        self.certstore = None
        self.clientcerts = None
        self.openssl_verification_mode_server = None
        self.configure(options, set(options.keys()))
        options.changed.connect(self.configure)

    def configure(self, options, updated):
        # type: (mitmproxy.options.Options, Any) -> None
        if options.add_upstream_certs_to_client_chain and not options.ssl_insecure:
            raise exceptions.OptionsError(
                "The verify-upstream-cert requires certificate verification to be disabled. "
                "If upstream certificates are verified then extra upstream certificates are "
                "not available for inclusion to the client chain."
            )

        if options.ssl_insecure:
            self.openssl_verification_mode_server = SSL.VERIFY_NONE
        else:
            self.openssl_verification_mode_server = SSL.VERIFY_PEER

        self.check_ignore = HostMatcher(options.ignore_hosts)
        self.check_tcp = HostMatcher(options.tcp_hosts)

        self.openssl_method_client, self.openssl_options_client = \
            tcp.sslversion_choices[options.ssl_version_client]
        self.openssl_method_server, self.openssl_options_server = \
            tcp.sslversion_choices[options.ssl_version_server]

        certstore_path = os.path.expanduser(options.cadir)
        if not os.path.exists(os.path.dirname(certstore_path)):
            raise exceptions.OptionsError(
                "Certificate Authority parent directory does not exist: %s" %
                os.path.dirname(options.cadir)
            )
        self.certstore = certutils.CertStore.from_store(
            certstore_path,
            CONF_BASENAME
        )

        if options.clientcerts:
            clientcerts = os.path.expanduser(options.clientcerts)
            if not os.path.exists(clientcerts):
                raise exceptions.OptionsError(
                    "Client certificate path does not exist: %s" %
                    options.clientcerts
                )
            self.clientcerts = clientcerts

        for spec, cert in options.certs:
            cert = os.path.expanduser(cert)
            if not os.path.exists(cert):
                raise exceptions.OptionsError(
                    "Certificate file does not exist: %s" % cert
                )
            try:
                self.certstore.add_cert_file(spec, cert)
            except crypto.Error:
                raise exceptions.OptionsError(
                    "Invalid certificate format: %s" % cert
                )

        self.upstream_server = None
        self.upstream_auth = None
        if options.upstream_server:
            self.upstream_server = parse_server_spec(options.upstream_server)
        if options.upstream_auth:
            self.upstream_auth = parse_upstream_auth(options.upstream_auth)

        self.authenticator = authentication.NullProxyAuth(None)
        needsauth = any(
            [
                options.auth_nonanonymous,
                options.auth_singleuser,
                options.auth_htpasswd
            ]
        )
        if needsauth:
            if options.mode == "transparent":
                raise exceptions.OptionsError(
                    "Proxy Authentication not supported in transparent mode."
                )
            elif options.mode == "socks5":
                raise exceptions.OptionsError(
                    "Proxy Authentication not supported in SOCKS mode. "
                    "https://github.com/mitmproxy/mitmproxy/issues/738"
                )
            elif options.auth_singleuser:
                parts = options.auth_singleuser.split(':')
                if len(parts) != 2:
                    raise exceptions.OptionsError(
                        "Invalid single-user specification. "
                        "Please use the format username:password"
                    )
                password_manager = authentication.PassManSingleUser(*parts)
            elif options.auth_nonanonymous:
                password_manager = authentication.PassManNonAnon()
            elif options.auth_htpasswd:
                try:
                    password_manager = authentication.PassManHtpasswd(
                        options.auth_htpasswd
                    )
                except ValueError as v:
                    raise exceptions.OptionsError(str(v))
            self.authenticator = authentication.BasicProxyAuth(
                password_manager,
                "mitmproxy"
            )

mitmproxy/proxy/config.py
from __future__ import absolute_import, print_function, division

import base64
import collections
import os
import re
from netlib import strutils

import six
from OpenSSL import SSL, crypto

from mitmproxy import exceptions
from netlib import certutils
from netlib import tcp
from netlib.http import authentication
from netlib.http import url

CONF_BASENAME = "mitmproxy"


class HostMatcher(object):

    def __init__(self, patterns=tuple()):
        self.patterns = list(patterns)
        self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns]

    def __call__(self, address):
        if not address:
            return False
        address = tcp.Address.wrap(address)
        host = "%s:%s" % (address.host, address.port)
        if any(rex.search(host) for rex in self.regexes):
            return True
        else:
            return False

    def __bool__(self):
        return bool(self.patterns)

    if six.PY2:
        __nonzero__ = __bool__


ServerSpec = collections.namedtuple("ServerSpec", "scheme address")


def parse_server_spec(spec):
    try:
        p = url.parse(spec)
        if p[0] not in (b"http", b"https"):
            raise ValueError()
    except ValueError:
        raise exceptions.OptionsError(
            "Invalid server specification: %s" % spec
        )
    host, port = p[1:3]
    address = tcp.Address((host.decode("ascii"), port))
    scheme = p[0].decode("ascii").lower()
    return ServerSpec(scheme, address)


def parse_upstream_auth(auth):
    pattern = re.compile(".+:")
    if pattern.search(auth) is None:
        raise exceptions.OptionsError(
            "Invalid upstream auth specification: %s" % auth
        )
    return b"Basic" + b" " + base64.b64encode(strutils.always_bytes(auth))


class ProxyConfig:

    def __init__(self, options):
        self.options = options

        self.authenticator = None
        self.check_ignore = None
        self.check_tcp = None
        self.certstore = None
        self.clientcerts = None
        self.ssl_insecure = False
        self.openssl_verification_mode_server = None
        self.configure(options, set(options.keys()))
        options.changed.connect(self.configure)

    def configure(self, options, updated):
        # type: (mitmproxy.options.Options, Any) -> None
        if options.add_upstream_certs_to_client_chain and not options.ssl_insecure:
            raise exceptions.OptionsError(
                "The verify-upstream-cert requires certificate verification to be disabled. "
                "If upstream certificates are verified then extra upstream certificates are "
                "not available for inclusion to the client chain."
            )

        if options.ssl_insecure:
            self.openssl_verification_mode_server = SSL.VERIFY_NONE
        else:
            self.openssl_verification_mode_server = SSL.VERIFY_PEER

        self.check_ignore = HostMatcher(options.ignore_hosts)
        self.check_tcp = HostMatcher(options.tcp_hosts)

        self.openssl_method_client, self.openssl_options_client = \
            tcp.sslversion_choices[options.ssl_version_client]
        self.openssl_method_server, self.openssl_options_server = \
            tcp.sslversion_choices[options.ssl_version_server]

        certstore_path = os.path.expanduser(options.cadir)
        if not os.path.exists(os.path.dirname(certstore_path)):
            raise exceptions.OptionsError(
                "Certificate Authority parent directory does not exist: %s" %
                os.path.dirname(options.cadir)
            )
        self.certstore = certutils.CertStore.from_store(
            certstore_path,
            CONF_BASENAME
        )

        if options.clientcerts:
            clientcerts = os.path.expanduser(options.clientcerts)
            if not os.path.exists(clientcerts):
                raise exceptions.OptionsError(
                    "Client certificate path does not exist: %s" %
                    options.clientcerts
                )
            self.clientcerts = clientcerts

        for spec, cert in options.certs:
            cert = os.path.expanduser(cert)
            if not os.path.exists(cert):
                raise exceptions.OptionsError(
                    "Certificate file does not exist: %s" % cert
                )
            try:
                self.certstore.add_cert_file(spec, cert)
            except crypto.Error:
                raise exceptions.OptionsError(
                    "Invalid certificate format: %s" % cert
                )

        self.upstream_server = None
        self.upstream_auth = None
        if options.upstream_server:
            self.upstream_server = parse_server_spec(options.upstream_server)
        if options.upstream_auth:
            self.upstream_auth = parse_upstream_auth(options.upstream_auth)

        self.authenticator = authentication.NullProxyAuth(None)
        needsauth = any(
            [
                options.auth_nonanonymous,
                options.auth_singleuser,
                options.auth_htpasswd
            ]
        )
        if needsauth:
            if options.mode == "transparent":
                raise exceptions.OptionsError(
                    "Proxy Authentication not supported in transparent mode."
                )
            elif options.mode == "socks5":
                raise exceptions.OptionsError(
                    "Proxy Authentication not supported in SOCKS mode. "
                    "https://github.com/mitmproxy/mitmproxy/issues/738"
                )
            elif options.auth_singleuser:
                parts = options.auth_singleuser.split(':')
                if len(parts) != 2:
                    raise exceptions.OptionsError(
                        "Invalid single-user specification. "
                        "Please use the format username:password"
                    )
                password_manager = authentication.PassManSingleUser(*parts)
            elif options.auth_nonanonymous:
                password_manager = authentication.PassManNonAnon()
            elif options.auth_htpasswd:
                try:
                    password_manager = authentication.PassManHtpasswd(
                        options.auth_htpasswd
                    )
                except ValueError as v:
                    raise exceptions.OptionsError(str(v))
            self.authenticator = authentication.BasicProxyAuth(
                password_manager,
                "mitmproxy"
            )

mitmproxy/proxy/config.py
diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py
index cf75830a5e..604045a4f3 100644
--- a/mitmproxy/proxy/config.py
+++ b/mitmproxy/proxy/config.py
@@ -78,6 +78,7 @@ def __init__(self, options):
         self.check_tcp = None
         self.certstore = None
         self.clientcerts = None
+        self.ssl_insecure = False
         self.openssl_verification_mode_server = None
         self.configure(options, set(options.keys()))
         options.changed.connect(self.configure)

Can't view options
##### Steps to reproduce the problem:
1. `mitmproxy`
2. Press `o`
##### What is the expected behavior?

No Crash!
##### What went wrong?

mitmproxy crashed!

```
~/dev/mitmproxy (master) > env/bin/mitmproxy                                                                                                                                          03:29:44
Traceback (most recent call last):
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/master.py", line 537, in run
    self.loop.run()
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 278, in run
    self._run()
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 376, in _run
    self.event_loop.run()
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 682, in run
    self._loop()
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 719, in _loop
    self._watch_files[fd]()
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/raw_display.py", line 393, in <lambda>
    event_loop, callback, self.get_available_raw_input())
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/raw_display.py", line 493, in parse_input
    callback(processed, processed_codes)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 403, in _update
    self.process_input(keys)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/window.py", line 89, in keypress
    self.master.view_options()
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/master.py", line 572, in view_options
    options.help_context,
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/blinker/base.py", line 267, in send
    for receiver in self.receivers_for(sender)]
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/master.py", line 333, in sig_push_view_state
    self.loop.draw_screen()
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/main_loop.py", line 578, in draw_screen
    canvas = self._topmost_widget.render(self.screen_size, focus=True)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/container.py", line 1083, in render
    focus and self.focus_part == 'body')
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/decoration.py", line 225, in render
    canv = self._original_widget.render(size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/widget.py", line 1751, in render
    canv = get_delegate(self).render(size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/container.py", line 1083, in render
    focus and self.focus_part == 'body')
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/listbox.py", line 457, in render
    (maxcol, maxrow), focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/listbox.py", line 339, in calculate_visible
    self._set_focus_complete( (maxcol, maxrow), focus )
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/listbox.py", line 704, in _set_focus_complete
    (maxcol,maxrow), focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/listbox.py", line 674, in _set_focus_first_selectable
    (maxcol, maxrow), focus=focus)
  File "/home/dufferzafar/dev/mitmproxy/env/lib/python2.7/site-packages/urwid/listbox.py", line 402, in calculate_visible
    next, pos = self.body.get_next( pos )
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/select.py", line 57, in get_next
    return self.options[pos + 1].render(False), pos + 1
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/select.py", line 94, in render
    self.getstate(),
  File "/home/dufferzafar/dev/mitmproxy/mitmproxy/console/options.py", line 97, in <lambda>
    lambda: master.server.config.ssl_insecure,
AttributeError: ProxyConfig instance has no attribute 'ssl_insecure'

mitmproxy has crashed!
Please lodge a bug report at:
    https://github.com/mitmproxy/mitmproxy
Shutting down...
```

