3748fee5333ffd1cec6309f48966ede019049538
[sdc/sdc-distribution-client.git] /
1 # -*- coding: utf-8 -*-
2 """
3 SOCKS support for urllib3
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 This contrib module contains provisional support for SOCKS proxies from within
7 urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and
8 SOCKS5. To enable its functionality, either install PySocks or install this
9 module with the ``socks`` extra.
10
11 Known Limitations:
12
13 - Currently PySocks does not support contacting remote websites via literal
14   IPv6 addresses. Any such connection attempt will fail.
15 - Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any
16   such connection attempt will fail.
17 """
18 from __future__ import absolute_import
19
20 try:
21     import socks
22 except ImportError:
23     import warnings
24     from ..exceptions import DependencyWarning
25
26     warnings.warn((
27         'SOCKS support in urllib3 requires the installation of optional '
28         'dependencies: specifically, PySocks.  For more information, see '
29         'https://urllib3.readthedocs.org/en/latest/contrib.html#socks-proxies'
30         ),
31         DependencyWarning
32     )
33     raise
34
35 from socket import error as SocketError, timeout as SocketTimeout
36
37 from ..connection import (
38     HTTPConnection, HTTPSConnection
39 )
40 from ..connectionpool import (
41     HTTPConnectionPool, HTTPSConnectionPool
42 )
43 from ..exceptions import ConnectTimeoutError, NewConnectionError
44 from ..poolmanager import PoolManager
45 from ..util.url import parse_url
46
47 try:
48     import ssl
49 except ImportError:
50     ssl = None
51
52
53 class SOCKSConnection(HTTPConnection):
54     """
55     A plain-text HTTP connection that connects via a SOCKS proxy.
56     """
57     def __init__(self, *args, **kwargs):
58         self._socks_options = kwargs.pop('_socks_options')
59         super(SOCKSConnection, self).__init__(*args, **kwargs)
60
61     def _new_conn(self):
62         """
63         Establish a new connection via the SOCKS proxy.
64         """
65         extra_kw = {}
66         if self.source_address:
67             extra_kw['source_address'] = self.source_address
68
69         if self.socket_options:
70             extra_kw['socket_options'] = self.socket_options
71
72         try:
73             conn = socks.create_connection(
74                 (self.host, self.port),
75                 proxy_type=self._socks_options['socks_version'],
76                 proxy_addr=self._socks_options['proxy_host'],
77                 proxy_port=self._socks_options['proxy_port'],
78                 proxy_username=self._socks_options['username'],
79                 proxy_password=self._socks_options['password'],
80                 timeout=self.timeout,
81                 **extra_kw
82             )
83
84         except SocketTimeout as e:
85             raise ConnectTimeoutError(
86                 self, "Connection to %s timed out. (connect timeout=%s)" %
87                 (self.host, self.timeout))
88
89         except socks.ProxyError as e:
90             # This is fragile as hell, but it seems to be the only way to raise
91             # useful errors here.
92             if e.socket_err:
93                 error = e.socket_err
94                 if isinstance(error, SocketTimeout):
95                     raise ConnectTimeoutError(
96                         self,
97                         "Connection to %s timed out. (connect timeout=%s)" %
98                         (self.host, self.timeout)
99                     )
100                 else:
101                     raise NewConnectionError(
102                         self,
103                         "Failed to establish a new connection: %s" % error
104                     )
105             else:
106                 raise NewConnectionError(
107                     self,
108                     "Failed to establish a new connection: %s" % e
109                 )
110
111         except SocketError as e:  # Defensive: PySocks should catch all these.
112             raise NewConnectionError(
113                 self, "Failed to establish a new connection: %s" % e)
114
115         return conn
116
117
118 # We don't need to duplicate the Verified/Unverified distinction from
119 # urllib3/connection.py here because the HTTPSConnection will already have been
120 # correctly set to either the Verified or Unverified form by that module. This
121 # means the SOCKSHTTPSConnection will automatically be the correct type.
122 class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection):
123     pass
124
125
126 class SOCKSHTTPConnectionPool(HTTPConnectionPool):
127     ConnectionCls = SOCKSConnection
128
129
130 class SOCKSHTTPSConnectionPool(HTTPSConnectionPool):
131     ConnectionCls = SOCKSHTTPSConnection
132
133
134 class SOCKSProxyManager(PoolManager):
135     """
136     A version of the urllib3 ProxyManager that routes connections via the
137     defined SOCKS proxy.
138     """
139     pool_classes_by_scheme = {
140         'http': SOCKSHTTPConnectionPool,
141         'https': SOCKSHTTPSConnectionPool,
142     }
143
144     def __init__(self, proxy_url, username=None, password=None,
145                  num_pools=10, headers=None, **connection_pool_kw):
146         parsed = parse_url(proxy_url)
147
148         if parsed.scheme == 'socks5':
149             socks_version = socks.PROXY_TYPE_SOCKS5
150         elif parsed.scheme == 'socks4':
151             socks_version = socks.PROXY_TYPE_SOCKS4
152         else:
153             raise ValueError(
154                 "Unable to determine SOCKS version from %s" % proxy_url
155             )
156
157         self.proxy_url = proxy_url
158
159         socks_options = {
160             'socks_version': socks_version,
161             'proxy_host': parsed.host,
162             'proxy_port': parsed.port,
163             'username': username,
164             'password': password,
165         }
166         connection_pool_kw['_socks_options'] = socks_options
167
168         super(SOCKSProxyManager, self).__init__(
169             num_pools, headers, **connection_pool_kw
170         )
171
172         self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme