diff --git a/Lib/http/client.py b/Lib/http/client.py --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -746,26 +746,33 @@ self.__response = None self.__state = _CS_IDLE self._method = None - self._tunnel_host = None - self._tunnel_port = None - self._tunnel_headers = {} + self._tunnel_host = [] + self._tunnel_port = [] + self._tunnel_headers = [] - self._set_hostport(host, port) + (self.host, self.port) = self._get_hostport(host, port) def set_tunnel(self, host, port=None, headers=None): """ Sets up the host and the port for the HTTP CONNECT Tunnelling. + This method sends a CONNECT request to remote side. If this succeeds, + all further data is exchanged with *host*, with the original host acting + as an invisible tunnel only. + The headers argument should be a mapping of extra HTTP headers to send with the CONNECT request. + + This method may be called repeatedly to set up a chain of tunnels. """ - self._tunnel_host = host - self._tunnel_port = port + + self._tunnel_host.append(host) + self._tunnel_port.append(port) if headers: - self._tunnel_headers = headers + self._tunnel_headers.append(headers) else: - self._tunnel_headers.clear() + self._tunnel_headers.append({}) - 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 [...] @@ -782,18 +789,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) @@ -820,8 +827,11 @@ """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() + + for (host, port, headers) in zip(self._tunnel_host, + self._tunnel_port, + self._tunnel_headers): + self._tunnel(host, port, headers) def close(self): """Close the connection to the HTTP server.""" @@ -985,22 +995,29 @@ netloc_enc = netloc.encode("idna") self.putheader('Host', netloc_enc) else: + if self._tunnel_host: + host = self._tunnel_host[-1] + port = self._tunnel_port[-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 diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -195,6 +195,9 @@ - Issue #19286: Directories in ``package_data`` are no longer added to the filelist, preventing failure outlined in the ticket. +- Issue #7776: Fix ``Host:'' header and reconnection when using + http.client.HTTPConnection.set_tunnel(). Patch by Nikolaus Rath. + IDLE ----