@@ -391,14 +391,11 @@ def write_headers(
391
391
self ._request_start_line = start_line
392
392
lines .append (utf8 ("%s %s HTTP/1.1" % (start_line [0 ], start_line [1 ])))
393
393
# Client requests with a non-empty body must have either a
394
- # Content-Length or a Transfer-Encoding.
394
+ # Content-Length or a Transfer-Encoding. If Content-Length is not
395
+ # present we'll add our Transfer-Encoding below.
395
396
self ._chunking_output = (
396
397
start_line .method in ("POST" , "PUT" , "PATCH" )
397
398
and "Content-Length" not in headers
398
- and (
399
- "Transfer-Encoding" not in headers
400
- or headers ["Transfer-Encoding" ] == "chunked"
401
- )
402
399
)
403
400
else :
404
401
assert isinstance (start_line , httputil .ResponseStartLine )
@@ -420,9 +417,6 @@ def write_headers(
420
417
and (start_line .code < 100 or start_line .code >= 200 )
421
418
# No need to chunk the output if a Content-Length is specified.
422
419
and "Content-Length" not in headers
423
- # Applications are discouraged from touching Transfer-Encoding,
424
- # but if they do, leave it alone.
425
- and "Transfer-Encoding" not in headers
426
420
)
427
421
# If connection to a 1.1 client will be closed, inform client
428
422
if (
@@ -562,7 +556,7 @@ def _can_keep_alive(
562
556
return connection_header != "close"
563
557
elif (
564
558
"Content-Length" in headers
565
- or headers . get ( "Transfer-Encoding" , "" ). lower () == "chunked"
559
+ or is_transfer_encoding_chunked ( headers )
566
560
or getattr (start_line , "method" , None ) in ("HEAD" , "GET" )
567
561
):
568
562
# start_line may be a request or response start line; only
@@ -600,13 +594,6 @@ def _read_body(
600
594
delegate : httputil .HTTPMessageDelegate ,
601
595
) -> Optional [Awaitable [None ]]:
602
596
if "Content-Length" in headers :
603
- if "Transfer-Encoding" in headers :
604
- # Response cannot contain both Content-Length and
605
- # Transfer-Encoding headers.
606
- # http://tools.ietf.org/html/rfc7230#section-3.3.3
607
- raise httputil .HTTPInputError (
608
- "Response with both Transfer-Encoding and Content-Length"
609
- )
610
597
if "," in headers ["Content-Length" ]:
611
598
# Proxies sometimes cause Content-Length headers to get
612
599
# duplicated. If all the values are identical then we can
@@ -633,20 +620,22 @@ def _read_body(
633
620
else :
634
621
content_length = None
635
622
623
+ is_chunked = is_transfer_encoding_chunked (headers )
624
+
636
625
if code == 204 :
637
626
# This response code is not allowed to have a non-empty body,
638
627
# and has an implicit length of zero instead of read-until-close.
639
628
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
640
- if "Transfer-Encoding" in headers or content_length not in (None , 0 ):
629
+ if is_chunked or content_length not in (None , 0 ):
641
630
raise httputil .HTTPInputError (
642
631
"Response with code %d should not have body" % code
643
632
)
644
633
content_length = 0
645
634
635
+ if is_chunked :
636
+ return self ._read_chunked_body (delegate )
646
637
if content_length is not None :
647
638
return self ._read_fixed_body (content_length , delegate )
648
- if headers .get ("Transfer-Encoding" , "" ).lower () == "chunked" :
649
- return self ._read_chunked_body (delegate )
650
639
if self .is_client :
651
640
return self ._read_body_until_close (delegate )
652
641
return None
@@ -865,3 +854,33 @@ def parse_hex_int(s: str) -> int:
865
854
if HEXDIGITS .fullmatch (s ) is None :
866
855
raise ValueError ("not a hexadecimal integer: %r" % s )
867
856
return int (s , 16 )
857
+
858
+
859
+ def is_transfer_encoding_chunked (headers : httputil .HTTPHeaders ) -> bool :
860
+ """Returns true if the headers specify Transfer-Encoding: chunked.
861
+
862
+ Raise httputil.HTTPInputError if any other transfer encoding is used.
863
+ """
864
+ # Note that transfer-encoding is an area in which postel's law can lead
865
+ # us astray. If a proxy and a backend server are liberal in what they accept,
866
+ # but accept slightly different things, this can lead to mismatched framing
867
+ # and request smuggling issues. Therefore we are as strict as possible here
868
+ # (even technically going beyond the requirements of the RFCs: a value of
869
+ # ",chunked" is legal but doesn't appear in practice for legitimate traffic)
870
+ if "Transfer-Encoding" not in headers :
871
+ return False
872
+ if "Content-Length" in headers :
873
+ # Message cannot contain both Content-Length and
874
+ # Transfer-Encoding headers.
875
+ # http://tools.ietf.org/html/rfc7230#section-3.3.3
876
+ raise httputil .HTTPInputError (
877
+ "Message with both Transfer-Encoding and Content-Length"
878
+ )
879
+ if headers ["Transfer-Encoding" ].lower () == "chunked" :
880
+ return True
881
+ # We do not support any transfer-encodings other than chunked, and we do not
882
+ # expect to add any support because the concept of transfer-encoding has
883
+ # been removed in HTTP/2.
884
+ raise httputil .HTTPInputError (
885
+ "Unsupported Transfer-Encoding %s" % headers ["Transfer-Encoding" ]
886
+ )
0 commit comments