--- urllib/request.py.orig 2015-07-02 16:14:45.845985526 -0400 +++ urllib/request.py 2015-07-03 00:24:05.074113059 -0400 @@ -138,27 +138,74 @@ def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *, cafile=None, capath=None, cadefault=False, context=None): global _opener - if cafile or capath or cadefault: - if context is not None: - raise ValueError( - "You can't pass both context and any of cafile, capath, and " - "cadefault" - ) + + if url is None: + raise ValueError('URL must be string or instance of Request()') + + add_https_handler = (cafile or capath or cadefault or context) is not None + + _type = isinstance(url, Request) and url.type or url[:5] + add_https_handler |= _type.lower() == 'https' + + if add_https_handler: if not _have_ssl: raise ValueError('SSL support not available') - context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, - cafile=cafile, - capath=capath) - https_handler = HTTPSHandler(context=context) - opener = build_opener(https_handler) - elif context: - https_handler = HTTPSHandler(context=context) - opener = build_opener(https_handler) - elif _opener is None: - _opener = opener = build_opener() - else: - opener = _opener - return opener.open(url, data, timeout) + if cafile or capath or cadefault: + if context is not None: + raise ValueError( + "You can't pass both context and any of cafile, capath, and " + "cadefault" + ) + + if add_https_handler and _opener: + # if there's already an https handler registered, make sure we don't + # try to add another, there can be only one HTTPS handler + ssl_handler = [ x for x in _opener.handlers if hasattr(x, 'https_open')] + if len(ssl_handler) > 1: + raise ValueError('More than one HTTPS handler defined') + + if ssl_handler: + ssl_handler = ssl_handler[0] + + # check that the ssl_handler has this context, if yes, raise conflict + if context: + context_props = [ p for p in dir(ssl_handler) if isinstance(getattr(ssl_handler, p), ssl.SSLContext)] + + if context_props: + conflict_context = [ p for p in context_props if not getattr(ssl_handler, p) == context ] + my_context = [ p for p in context_props if getattr(ssl_handler, p) == context ] + + if conflict_context: + raise ValueError('{} already exists with different SSL context'.format(ssl_handler)) + + if my_context: + add_https_handler = False + + # we have an https handler without a context, assign it + if add_https_handler: + ssl_handler._context = context + add_https_handler = False + + else: + # no context but we have a handler, so build a default context and attach it to the handler + add_https_handler = False + ssl_handler._context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, + cafile=cafile, + capath=capath) + if add_https_handler: + if not context: + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, + cafile=cafile, + capath=capath) + if _opener: + _opener.add_handler(HTTPSHandler(context=context)) + else: + _opener = build_opener(HTTPSHandler(context=context)) + + if not _opener: + _opener = build_opener() + + return _opener.open(url, data, timeout) def install_opener(opener): global _opener @@ -385,6 +432,11 @@ raise TypeError("expected BaseHandler instance, got %r" % type(handler)) + # make sure more than one HTTPSHandler does not exist + if hasattr(handler, 'https_open'): + if [ h for h in self.handlers if hasattr(h, 'https_open') ]: + raise ValueError('Attempt to install second HTTPSHandler') + added = False for meth in dir(handler): if meth in ["redirect_request", "do_open", "proxy_open"]: @@ -524,8 +576,12 @@ HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor, DataHandler] - if hasattr(http.client, "HTTPSConnection"): - default_classes.append(HTTPSHandler) + + # don't add a default HTTPSHandler, it breaks things when SSL certificates don't verify + # with a default context + #if hasattr(http.client, "HTTPSConnection"): + # default_classes.append(HTTPSHandler) + skip = set() for klass in default_classes: for check in handlers: