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

Delta Between Two Patch Sets: Lib/http/client.py

Issue 7776: http.client.HTTPConnection tunneling is broken
Left Patch Set: Created 6 years ago
Right Patch Set: Created 6 years ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | Lib/test/test_httplib.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 """HTTP/1.1 client library 1 """HTTP/1.1 client library
2 2
3 <intro stuff goes here> 3 <intro stuff goes here>
4 <other stuff, too> 4 <other stuff, too>
5 5
6 HTTPConnection goes through a number of "states", which define when a client 6 HTTPConnection goes through a number of "states", which define when a client
7 may legally make another request or fetch the response for a particular 7 may legally make another request or fetch the response for a particular
8 request. This diagram details these state transitions: 8 request. This diagram details these state transitions:
9 9
10 (null) 10 (null)
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
67 """ 67 """
68 68
69 import email.parser 69 import email.parser
70 import email.message 70 import email.message
71 import io 71 import io
72 import os 72 import os
73 import socket 73 import socket
74 import collections 74 import collections
75 from urllib.parse import urlsplit 75 from urllib.parse import urlsplit
76 import warnings 76 import warnings
77 from collections import namedtuple
77 78
78 __all__ = ["HTTPResponse", "HTTPConnection", 79 __all__ = ["HTTPResponse", "HTTPConnection",
79 "HTTPException", "NotConnected", "UnknownProtocol", 80 "HTTPException", "NotConnected", "UnknownProtocol",
80 "UnknownTransferEncoding", "UnimplementedFileMode", 81 "UnknownTransferEncoding", "UnimplementedFileMode",
81 "IncompleteRead", "InvalidURL", "ImproperConnectionState", 82 "IncompleteRead", "InvalidURL", "ImproperConnectionState",
82 "CannotSendRequest", "CannotSendHeader", "ResponseNotReady", 83 "CannotSendRequest", "CannotSendHeader", "ResponseNotReady",
83 "BadStatusLine", "error", "responses"] 84 "BadStatusLine", "error", "responses"]
84 85
85 HTTP_PORT = 80 86 HTTP_PORT = 80
86 HTTPS_PORT = 443 87 HTTPS_PORT = 443
(...skipping 623 matching lines...) Expand 10 before | Expand all | Expand 10 after
710 711
711 def info(self): 712 def info(self):
712 return self.headers 713 return self.headers
713 714
714 def geturl(self): 715 def geturl(self):
715 return self.url 716 return self.url
716 717
717 def getcode(self): 718 def getcode(self):
718 return self.status 719 return self.status
719 720
721
722 # To store tunneling information
723 TunnelInfo = namedtuple('TunnelInfo', ('host', 'port', 'headers'))
724
720 class HTTPConnection: 725 class HTTPConnection:
721 726
722 _http_vsn = 11 727 _http_vsn = 11
723 _http_vsn_str = 'HTTP/1.1' 728 _http_vsn_str = 'HTTP/1.1'
724 729
725 response_class = HTTPResponse 730 response_class = HTTPResponse
726 default_port = HTTP_PORT 731 default_port = HTTP_PORT
727 auto_open = 1 732 auto_open = 1
728 debuglevel = 0 733 debuglevel = 0
729 # TCP Maximum Segment Size (MSS) is determined by the TCP stack on 734 # TCP Maximum Segment Size (MSS) is determined by the TCP stack on
730 # a per-connection basis. There is no simple and efficient 735 # a per-connection basis. There is no simple and efficient
731 # platform independent mechanism for determining the MSS, so 736 # platform independent mechanism for determining the MSS, so
732 # instead a reasonable estimate is chosen. The getsockopt() 737 # instead a reasonable estimate is chosen. The getsockopt()
733 # interface using the TCP_MAXSEG parameter may be a suitable 738 # interface using the TCP_MAXSEG parameter may be a suitable
734 # approach on some operating systems. A value of 16KiB is chosen 739 # approach on some operating systems. A value of 16KiB is chosen
735 # as a reasonable estimate of the maximum MSS. 740 # as a reasonable estimate of the maximum MSS.
736 mss = 16384 741 mss = 16384
737 742
738 def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 743 def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
739 source_address=None): 744 source_address=None):
740 self.timeout = timeout 745 self.timeout = timeout
741 self.source_address = source_address 746 self.source_address = source_address
742 self.sock = None 747 self.sock = None
743 self._buffer = [] 748 self._buffer = []
744 self.__response = None 749 self.__response = None
745 self.__state = _CS_IDLE 750 self.__state = _CS_IDLE
746 self._method = None 751 self._method = None
747 self._tunnel_host = [] 752
748 self._tunnel_port = [] 753 # Will hold TunnelInfo tuples
749 self._tunnel_headers = [] 754 self._tunnel_info = []
750 755
751 (self.host, self.port) = self._get_hostport(host, port) 756 (self.host, self.port) = self._get_hostport(host, port)
752 757
753 # This is stored as an instance variable to allow unit 758 # This is stored as an instance variable to allow unit
754 # tests to replace it with a suitable mockup 759 # tests to replace it with a suitable mockup
755 self._create_connection = socket.create_connection 760 self._create_connection = socket.create_connection
756 761
757 def set_tunnel(self, host, port=None, headers=None): 762 def set_tunnel(self, host, port=None, headers=None):
758 """ Sets up the host and the port for the HTTP CONNECT Tunnelling. 763 """Prepare connection for HTTP CONNECT tunneling
759 764
760 This method sends a CONNECT request to remote side. If this succeeds, 765 This method must be called before the connection has been established,
761 all further data is exchanged with *host*, with the original host acting 766 and may be called repeatedly to set up a chain of tunnels.
762 as an invisible tunnel only. 767
763 768 When establishing a connection that uses tunnels, HTTP CONNECT requests
764 The headers argument should be a mapping of extra HTTP headers 769 are used to tunnel through all intermediate hosts (starting from the
765 to send with the CONNECT request. 770 host passed to the constructor). Once the connection is established, all
766 771 further communication is exchanged with the last host corresponding to
767 This method may be called repeatedly to set up a chain of tunnels. 772 the most recent call to set_tunnel(), with the remaining hosts acting as
773 invisible gateways.
774
775 The headers argument should be a mapping of extra HTTP headers to send
776 with the CONNECT request.
768 """ 777 """
769 778
770 self._tunnel_host.append(host) 779 if self.sock:
771 self._tunnel_port.append(port) 780 raise RuntimeError("Can't set up tunnel for established connection")
781
772 if headers: 782 if headers:
773 self._tunnel_headers.append(headers) 783 self._tunnel_info.append(TunnelInfo(host, port, headers))
774 else: 784 else:
775 self._tunnel_headers.append({}) 785 self._tunnel_info.append(TunnelInfo(host, port, {}))
776 786
777 def _get_hostport(self, host, port): 787 def _get_hostport(self, host, port):
778 if port is None: 788 if port is None:
779 i = host.rfind(':') 789 i = host.rfind(':')
780 j = host.rfind(']') # ipv6 addresses have [...] 790 j = host.rfind(']') # ipv6 addresses have [...]
781 if i > j: 791 if i > j:
782 try: 792 try:
783 port = int(host[i+1:]) 793 port = int(host[i+1:])
784 except ValueError: 794 except ValueError:
785 if host[i+1:] == "": # http://foo.com:/ == http://foo.com/ 795 if host[i+1:] == "": # http://foo.com:/ == http://foo.com/
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
823 # for sites which EOF without sending a trailer 833 # for sites which EOF without sending a trailer
824 break 834 break
825 if line in (b'\r\n', b'\n', b''): 835 if line in (b'\r\n', b'\n', b''):
826 break 836 break
827 837
828 def connect(self): 838 def connect(self):
829 """Connect to the host and port specified in __init__.""" 839 """Connect to the host and port specified in __init__."""
830 self.sock = self._create_connection((self.host,self.port), 840 self.sock = self._create_connection((self.host,self.port),
831 self.timeout, self.source_address) 841 self.timeout, self.source_address)
832 842
833 for (host, port, headers) in zip(self._tunnel_host, 843 for (host, port, headers) in self._tunnel_info:
834 self._tunnel_port,
835 self._tunnel_headers):
836 self._tunnel(host, port, headers) 844 self._tunnel(host, port, headers)
837 845
838 def close(self): 846 def close(self):
839 """Close the connection to the HTTP server.""" 847 """Close the connection to the HTTP server."""
840 if self.sock: 848 if self.sock:
841 self.sock.close() # close it manually... there may be other refs 849 self.sock.close() # close it manually... there may be other refs
842 self.sock = None 850 self.sock = None
843 if self.__response: 851 if self.__response:
844 self.__response.close() 852 self.__response.close()
845 self.__response = None 853 self.__response = None
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
993 if url.startswith('http'): 1001 if url.startswith('http'):
994 nil, netloc, nil, nil, nil = urlsplit(url) 1002 nil, netloc, nil, nil, nil = urlsplit(url)
995 1003
996 if netloc: 1004 if netloc:
997 try: 1005 try:
998 netloc_enc = netloc.encode("ascii") 1006 netloc_enc = netloc.encode("ascii")
999 except UnicodeEncodeError: 1007 except UnicodeEncodeError:
1000 netloc_enc = netloc.encode("idna") 1008 netloc_enc = netloc.encode("idna")
1001 self.putheader('Host', netloc_enc) 1009 self.putheader('Host', netloc_enc)
1002 else: 1010 else:
1003 if self._tunnel_host: 1011 if self._tunnel_info:
1004 host = self._tunnel_host[-1] 1012 (host, port, _) = self._tunnel_info[-1]
1005 port = self._tunnel_port[-1]
1006 else: 1013 else:
1007 host = self.host 1014 host = self.host
1008 port = self.port 1015 port = self.port
1009 1016
1010 try: 1017 try:
1011 host_enc = host.encode("ascii") 1018 host_enc = host.encode("ascii")
1012 except UnicodeEncodeError: 1019 except UnicodeEncodeError:
1013 host_enc = host.encode("idna") 1020 host_enc = host.encode("idna")
1014 1021
1015 # As per RFC 273, IPv6 address should be wrapped with [] 1022 # As per RFC 273, IPv6 address should be wrapped with []
1016 # when used as Host header 1023 # when used as Host header
1017 1024
1018 if host.find(':') >= 0: 1025 if host.find(':') >= 0:
1019 host_enc = b'[' + host_enc + b']' 1026 host_enc = b'[' + host_enc + b']'
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after
1210 if key_file or cert_file: 1217 if key_file or cert_file:
1211 context.load_cert_chain(cert_file, key_file) 1218 context.load_cert_chain(cert_file, key_file)
1212 self._context = context 1219 self._context = context
1213 self._check_hostname = check_hostname 1220 self._check_hostname = check_hostname
1214 1221
1215 def connect(self): 1222 def connect(self):
1216 "Connect to a host on a given (SSL) port." 1223 "Connect to a host on a given (SSL) port."
1217 1224
1218 super().connect() 1225 super().connect()
1219 1226
1220 server_hostname = self._tunnel_host[-1] if self._tunnel_host else se lf.host 1227 if self._tunnel_info:
1228 server_hostname = self._tunnel_info[-1].host
1229 else:
1230 server_hostname = self.host
1221 sni_hostname = server_hostname if ssl.HAS_SNI else None 1231 sni_hostname = server_hostname if ssl.HAS_SNI else None
1222 1232
1223 self.sock = self._context.wrap_socket(self.sock, 1233 self.sock = self._context.wrap_socket(self.sock,
1224 server_hostname=sni_hostname) 1234 server_hostname=sni_hostname)
1225 if not self._context.check_hostname and self._check_hostname: 1235 if not self._context.check_hostname and self._check_hostname:
1226 try: 1236 try:
1227 ssl.match_hostname(self.sock.getpeercert(), server_hostname) 1237 ssl.match_hostname(self.sock.getpeercert(), server_hostname)
1228 except Exception: 1238 except Exception:
1229 self.sock.shutdown(socket.SHUT_RDWR) 1239 self.sock.shutdown(socket.SHUT_RDWR)
1230 self.sock.close() 1240 self.sock.close()
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
1287 self.args = line, 1297 self.args = line,
1288 self.line = line 1298 self.line = line
1289 1299
1290 class LineTooLong(HTTPException): 1300 class LineTooLong(HTTPException):
1291 def __init__(self, line_type): 1301 def __init__(self, line_type):
1292 HTTPException.__init__(self, "got more than %d bytes when reading %s" 1302 HTTPException.__init__(self, "got more than %d bytes when reading %s"
1293 % (_MAXLINE, line_type)) 1303 % (_MAXLINE, line_type))
1294 1304
1295 # for backwards compatibility 1305 # for backwards compatibility
1296 error = HTTPException 1306 error = HTTPException
LEFTRIGHT

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