Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(13)

Unified Diff: Lib/http/client.py

Issue 7776: http.client.HTTPConnection tunneling is broken
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | Lib/test/test_httplib.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -74,6 +74,7 @@
import collections
from urllib.parse import urlsplit
import warnings
+from collections import namedtuple
__all__ = ["HTTPResponse", "HTTPConnection",
"HTTPException", "NotConnected", "UnknownProtocol",
@@ -717,6 +718,10 @@
def getcode(self):
return self.status
+
+# To store tunneling information
+TunnelInfo = namedtuple('TunnelInfo', ('host', 'port', 'headers'))
+
class HTTPConnection:
_http_vsn = 11
@@ -744,26 +749,42 @@
self.__response = None
self.__state = _CS_IDLE
self._method = None
- self._tunnel_host = None
- self._tunnel_port = None
- self._tunnel_headers = {}
- self._set_hostport(host, port)
+ # Will hold TunnelInfo tuples
+ self._tunnel_info = []
+
+ (self.host, self.port) = self._get_hostport(host, port)
+
+ # This is stored as an instance variable to allow unit
+ # tests to replace it with a suitable mockup
+ self._create_connection = socket.create_connection
def set_tunnel(self, host, port=None, headers=None):
- """ Sets up the host and the port for the HTTP CONNECT Tunnelling.
+ """Prepare connection for HTTP CONNECT tunneling
- The headers argument should be a mapping of extra HTTP headers
- to send with the CONNECT request.
+ This method must be called before the connection has been established,
+ and may be called repeatedly to set up a chain of tunnels.
+
+ When establishing a connection that uses tunnels, HTTP CONNECT requests
+ are used to tunnel through all intermediate hosts (starting from the
+ host passed to the constructor). Once the connection is established, all
+ further communication is exchanged with the last host corresponding to
+ the most recent call to set_tunnel(), with the remaining hosts acting as
+ invisible gateways.
+
+ The headers argument should be a mapping of extra HTTP headers to send
+ with the CONNECT request.
"""
- self._tunnel_host = host
- self._tunnel_port = port
+
+ if self.sock:
+ raise RuntimeError("Can't set up tunnel for established connection")
+
if headers:
- self._tunnel_headers = headers
+ self._tunnel_info.append(TunnelInfo(host, port, headers))
else:
- self._tunnel_headers.clear()
+ self._tunnel_info.append(TunnelInfo(host, port, {}))
- def _set_hostport(self, host, port):
+ def _get_hostport(self, host, port):
if port is None:
i = host.rfind(':')
j = host.rfind(']') # ipv6 addresses have [...]
@@ -780,18 +801,18 @@
port = self.default_port
if host and host[0] == '[' and host[-1] == ']':
host = host[1:-1]
- self.host = host
- self.port = port
+
+ return (host, port)
def set_debuglevel(self, level):
self.debuglevel = level
- def _tunnel(self):
- self._set_hostport(self._tunnel_host, self._tunnel_port)
- connect_str = "CONNECT %s:%d HTTP/1.0\r\n" % (self.host, self.port)
+ def _tunnel(self, host, port, headers):
+ (host, port) = self._get_hostport(host, port)
+ connect_str = "CONNECT %s:%d HTTP/1.0\r\n" % (host, port)
connect_bytes = connect_str.encode("ascii")
self.send(connect_bytes)
- for header, value in self._tunnel_headers.items():
+ for header, value in headers.items():
header_str = "%s: %s\r\n" % (header, value)
header_bytes = header_str.encode("latin-1")
self.send(header_bytes)
@@ -816,10 +837,11 @@
def connect(self):
"""Connect to the host and port specified in __init__."""
- self.sock = socket.create_connection((self.host,self.port),
- self.timeout, self.source_address)
- if self._tunnel_host:
- self._tunnel()
+ self.sock = self._create_connection((self.host,self.port),
+ self.timeout, self.source_address)
+
+ for (host, port, headers) in self._tunnel_info:
+ self._tunnel(host, port, headers)
def close(self):
"""Close the connection to the HTTP server."""
@@ -986,22 +1008,28 @@
netloc_enc = netloc.encode("idna")
self.putheader('Host', netloc_enc)
else:
+ if self._tunnel_info:
+ (host, port, _) = self._tunnel_info[-1]
+ else:
+ host = self.host
+ port = self.port
+
try:
- host_enc = self.host.encode("ascii")
+ host_enc = host.encode("ascii")
except UnicodeEncodeError:
- host_enc = self.host.encode("idna")
+ host_enc = host.encode("idna")
# As per RFC 273, IPv6 address should be wrapped with []
# when used as Host header
- if self.host.find(':') >= 0:
+ if host.find(':') >= 0:
host_enc = b'[' + host_enc + b']'
- if self.port == self.default_port:
+ if port == self.default_port:
self.putheader('Host', host_enc)
else:
host_enc = host_enc.decode("ascii")
- self.putheader('Host', "%s:%s" % (host_enc, self.port))
+ self.putheader('Host', "%s:%s" % (host_enc, port))
# note: we are assuming that clients will not attempt to set these
# headers since *this* library must deal with the
@@ -1194,19 +1222,19 @@
def connect(self):
"Connect to a host on a given (SSL) port."
- sock = socket.create_connection((self.host, self.port),
- self.timeout, self.source_address)
+ super().connect()
- if self._tunnel_host:
- self.sock = sock
- self._tunnel()
+ if self._tunnel_info:
+ server_hostname = self._tunnel_info[-1].host
+ else:
+ server_hostname = self.host
+ sni_hostname = server_hostname if ssl.HAS_SNI else None
- server_hostname = self.host if ssl.HAS_SNI else None
- self.sock = self._context.wrap_socket(sock,
- server_hostname=server_hostname)
+ self.sock = self._context.wrap_socket(self.sock,
+ server_hostname=sni_hostname)
if not self._context.check_hostname and self._check_hostname:
try:
- ssl.match_hostname(self.sock.getpeercert(), self.host)
+ ssl.match_hostname(self.sock.getpeercert(), server_hostname)
except Exception:
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
« no previous file with comments | « no previous file | Lib/test/test_httplib.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+