@@ -232,45 +232,94 @@ def set_tunnel(
232
232
super ().set_tunnel (host , port = port , headers = headers )
233
233
self ._tunnel_scheme = scheme
234
234
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]
269
310
270
311
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 ()
274
323
275
324
def connect (self ) -> None :
276
325
self .sock = self ._new_conn ()
0 commit comments