Skip to content

Commit 0ac1f83

Browse files
committed
Backport the changes made in revision 74198, fixing the issue 1424152
1 parent 36613e0 commit 0ac1f83

4 files changed

Lines changed: 63 additions & 3 deletions

File tree

Lib/http/client.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,17 @@ def __init__(self, host, port=None, strict=None,
644644
self.__response = None
645645
self.__state = _CS_IDLE
646646
self._method = None
647+
self._tunnel_host = None
648+
self._tunnel_port = None
647649

648650
self._set_hostport(host, port)
649651
if strict is not None:
650652
self.strict = strict
651653

654+
def _set_tunnel(self, host, port=None):
655+
self._tunnel_host = host
656+
self._tunnel_port = port
657+
652658
def _set_hostport(self, host, port):
653659
if port is None:
654660
i = host.rfind(':')
@@ -669,10 +675,29 @@ def _set_hostport(self, host, port):
669675
def set_debuglevel(self, level):
670676
self.debuglevel = level
671677

678+
def _tunnel(self):
679+
self._set_hostport(self._tunnel_host, self._tunnel_port)
680+
connect_str = "CONNECT %s:%d HTTP/1.0\r\n\r\n" %(self.host, self.port)
681+
connect_bytes = connect_str.encode("ascii")
682+
self.send(connect_bytes)
683+
response = self.response_class(self.sock, strict = self.strict,
684+
method= self._method)
685+
(version, code, message) = response._read_status()
686+
if code != 200:
687+
self.close()
688+
raise socket.error("Tunnel connection failed: %d %s" % (code,
689+
message.strip()))
690+
while True:
691+
line = response.fp.readline()
692+
if line == b'\r\n':
693+
break
694+
672695
def connect(self):
673696
"""Connect to the host and port specified in __init__."""
674697
self.sock = socket.create_connection((self.host,self.port),
675698
self.timeout)
699+
if self._tunnel_host:
700+
self._tunnel()
676701

677702
def close(self):
678703
"""Close the connection to the HTTP server."""
@@ -1008,6 +1033,11 @@ def connect(self):
10081033

10091034
sock = socket.create_connection((self.host, self.port),
10101035
self.timeout)
1036+
1037+
if self._tunnel_host:
1038+
self.sock = sock
1039+
self._tunnel()
1040+
10111041
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
10121042

10131043

Lib/test/test_urllib2.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,23 @@ def test_proxy(self):
947947
self.assertEqual([(handlers[0], "http_open")],
948948
[tup[0:2] for tup in o.calls])
949949

950+
def test_proxy_https(self):
951+
o = OpenerDirector()
952+
ph = urllib.request.ProxyHandler(dict(https="proxy.example.com:3128"))
953+
o.add_handler(ph)
954+
meth_spec = [
955+
[("https_open", "return response")]
956+
]
957+
handlers = add_ordered_mock_handlers(o, meth_spec)
958+
959+
req = Request("https://www.example.com/")
960+
self.assertEqual(req.get_host(), "www.example.com")
961+
r = o.open(req)
962+
self.assertEqual(req.get_host(), "proxy.example.com:3128")
963+
self.assertEqual([(handlers[0], "https_open")],
964+
[tup[0:2] for tup in o.calls])
965+
966+
950967
def test_basic_auth(self, quote_char='"'):
951968
opener = OpenerDirector()
952969
password_manager = MockPasswordManager()

Lib/urllib/request.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def __init__(self, url, data=None, headers={},
163163
self.full_url = unwrap(url)
164164
self.data = data
165165
self.headers = {}
166+
self._tunnel_host = None
166167
for key, value in headers.items():
167168
self.add_header(key, value)
168169
self.unredirected_hdrs = {}
@@ -218,8 +219,12 @@ def get_origin_req_host(self):
218219
# End deprecated methods
219220

220221
def set_proxy(self, host, type):
221-
self.host, self.type = host, type
222-
self.selector = self.full_url
222+
if self.type == 'https' and not self._tunnel_host:
223+
self._tunnel_host = self.host
224+
else:
225+
self.type= type
226+
self.selector = self.full_url
227+
self.host = host
223228

224229
def has_proxy(self):
225230
return self.selector == self.full_url
@@ -659,7 +664,7 @@ def proxy_open(self, req, proxy, type):
659664
req.add_header('Proxy-authorization', 'Basic ' + creds)
660665
hostport = unquote(hostport)
661666
req.set_proxy(hostport, proxy_type)
662-
if orig_type == proxy_type:
667+
if orig_type == proxy_type or orig_type == 'https':
663668
# let other handlers take care of it
664669
return None
665670
else:
@@ -1041,6 +1046,10 @@ def do_open(self, http_class, req):
10411046
# request.
10421047
headers["Connection"] = "close"
10431048
headers = dict((name.title(), val) for name, val in headers.items())
1049+
1050+
if req._tunnel_host:
1051+
h._set_tunnel(req._tunnel_host)
1052+
10441053
try:
10451054
h.request(req.get_method(), req.selector, req.data, headers)
10461055
r = h.getresponse() # an HTTPResponse instance

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ C-API
3939
Library
4040
-------
4141

42+
- Issue #1424152: Fix for http.client, urllib.request to support SSL while
43+
working through proxy. Original patch by Christopher Li, changes made by
44+
Senthil Kumaran
45+
4246
- importlib.abc.PyLoader did not inherit from importlib.abc.ResourceLoader like
4347
the documentation said it did even though the code in PyLoader relied on the
4448
abstract method required by ResourceLoader.

0 commit comments

Comments
 (0)