@@ -232,45 +232,94 @@ def set_tunnel(
232232 super ().set_tunnel (host , port = port , headers = headers )
233233 self ._tunnel_scheme = scheme
234234
235- if sys .version_info < (3 , 11 , 4 ):
236-
237- def _tunnel (self ) -> None :
238- _MAXLINE = http .client ._MAXLINE # type: ignore[attr-defined]
239- connect = b"CONNECT %s:%d HTTP/1.0\r \n " % ( # type: ignore[str-format]
240- self ._tunnel_host .encode ("ascii" ), # type: ignore[union-attr]
241- self ._tunnel_port ,
242- )
243- headers = [connect ]
244- for header , value in self ._tunnel_headers .items (): # type: ignore[attr-defined]
245- headers .append (f"{ header } : { value } \r \n " .encode ("latin-1" ))
246- headers .append (b"\r \n " )
247- # Making a single send() call instead of one per line encourages
248- # the host OS to use a more optimal packet size instead of
249- # potentially emitting a series of small packets.
250- self .send (b"" .join (headers ))
251- del headers
252-
253- response = self .response_class (self .sock , method = self ._method ) # type: ignore[attr-defined]
254- try :
255- (version , code , message ) = response ._read_status () # type: ignore[attr-defined]
256-
257- if code != http .HTTPStatus .OK :
258- self .close ()
259- raise OSError (f"Tunnel connection failed: { code } { message .strip ()} " )
260- while True :
261- line = response .fp .readline (_MAXLINE + 1 )
262- if len (line ) > _MAXLINE :
263- raise http .client .LineTooLong ("header line" )
264- if not line :
265- # for sites which EOF without sending a trailer
266- break
267- if line in (b"\r \n " , b"\n " , b"" ):
268- break
235+ if sys .version_info < (3 , 11 , 9 ) or ((3 , 12 ) <= sys .version_info < (3 , 12 , 3 )):
236+ # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3.
237+ # When using connection_from_host, host will come without brackets.
238+ def _wrap_ipv6 (self , ip : bytes ) -> bytes :
239+ if b":" in ip and ip [0 ] != b"[" [0 ]:
240+ return b"[" + ip + b"]"
241+ return ip
242+
243+ if sys .version_info < (3 , 11 , 9 ):
244+ # `_tunnel` copied from 3.11.13 backporting
245+ # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261
246+ # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1
247+ def _tunnel (self ) -> None :
248+ _MAXLINE = http .client ._MAXLINE # type: ignore[attr-defined]
249+ connect = b"CONNECT %s:%d HTTP/1.0\r \n " % ( # type: ignore[str-format]
250+ self ._wrap_ipv6 (self ._tunnel_host .encode ("ascii" )), # type: ignore[union-attr]
251+ self ._tunnel_port ,
252+ )
253+ headers = [connect ]
254+ for header , value in self ._tunnel_headers .items (): # type: ignore[attr-defined]
255+ headers .append (f"{ header } : { value } \r \n " .encode ("latin-1" ))
256+ headers .append (b"\r \n " )
257+ # Making a single send() call instead of one per line encourages
258+ # the host OS to use a more optimal packet size instead of
259+ # potentially emitting a series of small packets.
260+ self .send (b"" .join (headers ))
261+ del headers
262+
263+ response = self .response_class (self .sock , method = self ._method ) # type: ignore[attr-defined]
264+ try :
265+ (version , code , message ) = response ._read_status () # type: ignore[attr-defined]
266+
267+ if code != http .HTTPStatus .OK :
268+ self .close ()
269+ raise OSError (
270+ f"Tunnel connection failed: { code } { message .strip ()} "
271+ )
272+ while True :
273+ line = response .fp .readline (_MAXLINE + 1 )
274+ if len (line ) > _MAXLINE :
275+ raise http .client .LineTooLong ("header line" )
276+ if not line :
277+ # for sites which EOF without sending a trailer
278+ break
279+ if line in (b"\r \n " , b"\n " , b"" ):
280+ break
281+
282+ if self .debuglevel > 0 :
283+ print ("header:" , line .decode ())
284+ finally :
285+ response .close ()
286+
287+ elif (3 , 12 ) <= sys .version_info < (3 , 12 , 3 ):
288+ # `_tunnel` copied from 3.12.11 backporting
289+ # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8
290+ def _tunnel (self ) -> None : # noqa: F811
291+ connect = b"CONNECT %s:%d HTTP/1.1\r \n " % ( # type: ignore[str-format]
292+ self ._wrap_ipv6 (self ._tunnel_host .encode ("idna" )), # type: ignore[union-attr]
293+ self ._tunnel_port ,
294+ )
295+ headers = [connect ]
296+ for header , value in self ._tunnel_headers .items (): # type: ignore[attr-defined]
297+ headers .append (f"{ header } : { value } \r \n " .encode ("latin-1" ))
298+ headers .append (b"\r \n " )
299+ # Making a single send() call instead of one per line encourages
300+ # the host OS to use a more optimal packet size instead of
301+ # potentially emitting a series of small packets.
302+ self .send (b"" .join (headers ))
303+ del headers
304+
305+ response = self .response_class (self .sock , method = self ._method ) # type: ignore[attr-defined]
306+ try :
307+ (version , code , message ) = response ._read_status () # type: ignore[attr-defined]
308+
309+ self ._raw_proxy_headers = http .client ._read_headers (response .fp ) # type: ignore[attr-defined]
269310
270311 if self .debuglevel > 0 :
271- print ("header:" , line .decode ())
272- finally :
273- response .close ()
312+ for header in self ._raw_proxy_headers :
313+ print ("header:" , header .decode ())
314+
315+ if code != http .HTTPStatus .OK :
316+ self .close ()
317+ raise OSError (
318+ f"Tunnel connection failed: { code } { message .strip ()} "
319+ )
320+
321+ finally :
322+ response .close ()
274323
275324 def connect (self ) -> None :
276325 self .sock = self ._new_conn ()
0 commit comments