1919from .request import RequestMethods
2020from .util .url import parse_url
2121from .util .retry import Retry
22+ from .util .connection import connection_requires_http_tunnel
23+ from .packages .six import PY3
2224
2325
2426__all__ = ["PoolManager" , "ProxyManager" , "proxy_from_url" ]
2527
2628
27- class InvalidProxyConfigurationWarning (HTTPWarning ):
28- """Raised when a user has an HTTPS proxy without enabling HTTPS proxies."""
29-
30- pass
31-
32-
3329log = logging .getLogger (__name__ )
3430
3531SSL_KEYWORDS = (
@@ -66,6 +62,7 @@ class InvalidProxyConfigurationWarning(HTTPWarning):
6662 "key_headers" , # dict
6763 "key__proxy" , # parsed proxy url
6864 "key__proxy_headers" , # dict
65+ "key__proxy_config" , # class
6966 "key_socket_options" , # list of (level (int), optname (int), value (int or str)) tuples
7067 "key__socks_options" , # dict
7168 "key_assert_hostname" , # bool or string
@@ -76,6 +73,18 @@ class InvalidProxyConfigurationWarning(HTTPWarning):
7673#: The namedtuple class used to construct keys for the connection pool.
7774#: All custom key schemes should include the fields in this key at a minimum.
7875PoolKey = collections .namedtuple ("PoolKey" , _key_fields )
76+ _proxy_config_fields = ("ssl_context" , "allow_insecure_proxy" , "destination_scheme" )
77+
78+
79+ class ProxyConfig :
80+ """
81+ Configuration class for proxies.
82+ """
83+
84+ def __init__ (self , ssl_context , allow_insecure_proxy , destination_scheme = None ):
85+ self .ssl_context = ssl_context
86+ self .allow_insecure_proxy = allow_insecure_proxy
87+ self .destination_scheme = destination_scheme
7988
8089
8190def _default_key_normalizer (key_class , request_context ):
@@ -168,6 +177,7 @@ class PoolManager(RequestMethods):
168177 """
169178
170179 proxy = None
180+ proxy_config = None
171181
172182 def __init__ (self , num_pools = 10 , headers = None , ** connection_pool_kw ):
173183 RequestMethods .__init__ (self , headers )
@@ -322,14 +332,26 @@ def _merge_pool_kwargs(self, override):
322332 def _proxy_requires_url_absolute_form (self , parsed_url ):
323333 """
324334 Indicates if the proxy requires the complete destination URL in the
325- request.
326-
327- Normally this is only needed when not using an HTTP CONNECT tunnel.
335+ request. Normally this is only needed when not using an HTTP CONNECT
336+ tunnel.
328337 """
329338 if self .proxy is None :
330339 return False
331340
332- return parsed_url .scheme == "http" or self .proxy .scheme == "https"
341+ return not connection_requires_http_tunnel (self .proxy , self .proxy_config )
342+
343+ def _validate_proxy_scheme_url_selection (self , url_scheme ):
344+ if (
345+ not PY3
346+ and url_scheme == "https"
347+ and self .proxy is not None
348+ and self .proxy .scheme == "https"
349+ and not self .proxy_config .allow_insecure_proxy
350+ ):
351+
352+ raise ProxySchemeUnsupported (
353+ "Contacting HTTPS destinations through HTTPS proxies is not supported on py2."
354+ )
333355
334356 def urlopen (self , method , url , redirect = True , ** kw ):
335357 """
@@ -341,6 +363,8 @@ def urlopen(self, method, url, redirect=True, **kw):
341363 :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
342364 """
343365 u = parse_url (url )
366+ self ._validate_proxy_scheme_url_selection (u .scheme )
367+
344368 conn = self .connection_from_host (u .host , port = u .port , scheme = u .scheme )
345369
346370 kw ["assert_same_host" ] = False
@@ -408,6 +432,10 @@ class ProxyManager(PoolManager):
408432 HTTPS/CONNECT case they are sent only once. Could be used for proxy
409433 authentication.
410434
435+ :param proxy_ssl_context:
436+ The proxy SSL context is used to establish the TLS connection to the
437+ proxy when using HTTPS proxies.
438+
411439 :param _allow_https_proxy_to_see_traffic:
412440 Allows forwarding of HTTPS requests to HTTPS proxies. The proxy will
413441 have visibility of all the traffic sent. ONLY USE IF YOU KNOW WHAT
@@ -433,6 +461,7 @@ def __init__(
433461 num_pools = 10 ,
434462 headers = None ,
435463 proxy_headers = None ,
464+ proxy_ssl_context = None ,
436465 _allow_https_proxy_to_see_traffic = False ,
437466 ** connection_pool_kw
438467 ):
@@ -454,15 +483,21 @@ def __init__(
454483
455484 self .proxy = proxy
456485 self .proxy_headers = proxy_headers or {}
486+ self .proxy_ssl_context = proxy_ssl_context
487+ self .proxy_config = ProxyConfig (
488+ proxy_ssl_context , _allow_https_proxy_to_see_traffic
489+ )
457490
458491 connection_pool_kw ["_proxy" ] = self .proxy
459492 connection_pool_kw ["_proxy_headers" ] = self .proxy_headers
460-
461- self .allow_insecure_proxy = _allow_https_proxy_to_see_traffic
493+ connection_pool_kw ["_proxy_config" ] = self .proxy_config
462494
463495 super (ProxyManager , self ).__init__ (num_pools , headers , ** connection_pool_kw )
464496
465497 def connection_from_host (self , host , port = None , scheme = "http" , pool_kwargs = None ):
498+ # Original destination is needed to identify if we need TLS within TLS.
499+ self .proxy_config .destination_scheme = scheme
500+
466501 if scheme == "https" :
467502 return super (ProxyManager , self ).connection_from_host (
468503 host , port , scheme , pool_kwargs = pool_kwargs
@@ -487,31 +522,14 @@ def _set_proxy_headers(self, url, headers=None):
487522 headers_ .update (headers )
488523 return headers_
489524
490- def _validate_proxy_scheme_url_selection (self , url_scheme ):
491- if (
492- url_scheme == "https"
493- and self .proxy .scheme == "https"
494- and not self .allow_insecure_proxy
495- ):
496- warnings .warn (
497- "Your proxy configuration specified an HTTPS scheme for the proxy. "
498- "Are you sure you want to use HTTPS to contact the proxy? "
499- "This most likely indicates an error in your configuration."
500- "If you are sure you want use HTTPS to contact the proxy, enable "
501- "the _allow_https_proxy_to_see_traffic." ,
502- InvalidProxyConfigurationWarning ,
503- )
504-
505- raise ProxySchemeUnsupported (
506- "Contacting HTTPS destinations through HTTPS proxies is not supported."
507- )
508-
509525 def urlopen (self , method , url , redirect = True , ** kw ):
510526 "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
511527 u = parse_url (url )
512- self ._validate_proxy_scheme_url_selection (u .scheme )
513528
514- if u .scheme == "http" or self .proxy .scheme == "https" :
529+ # Original destination is needed to identify if we need TLS within TLS.
530+ self .proxy_config .destination_scheme = u .scheme
531+
532+ if not connection_requires_http_tunnel (self .proxy , self .proxy_config ):
515533 # For connections using HTTP CONNECT, httplib sets the necessary
516534 # headers on the CONNECT to the proxy. For HTTP or when talking
517535 # HTTPS to the proxy, we'll definitely need to set 'Host' at the
0 commit comments