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