@@ -389,14 +389,11 @@ def write_headers(
389389 self ._request_start_line = start_line
390390 lines .append (utf8 ("%s %s HTTP/1.1" % (start_line [0 ], start_line [1 ])))
391391 # 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.
393394 self ._chunking_output = (
394395 start_line .method in ("POST" , "PUT" , "PATCH" )
395396 and "Content-Length" not in headers
396- and (
397- "Transfer-Encoding" not in headers
398- or headers ["Transfer-Encoding" ] == "chunked"
399- )
400397 )
401398 else :
402399 assert isinstance (start_line , httputil .ResponseStartLine )
@@ -418,9 +415,6 @@ def write_headers(
418415 and (start_line .code < 100 or start_line .code >= 200 )
419416 # No need to chunk the output if a Content-Length is specified.
420417 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
424418 )
425419 # If connection to a 1.1 client will be closed, inform client
426420 if (
@@ -560,7 +554,7 @@ def _can_keep_alive(
560554 return connection_header != "close"
561555 elif (
562556 "Content-Length" in headers
563- or headers . get ( "Transfer-Encoding" , "" ). lower () == "chunked"
557+ or is_transfer_encoding_chunked ( headers )
564558 or getattr (start_line , "method" , None ) in ("HEAD" , "GET" )
565559 ):
566560 # start_line may be a request or response start line; only
@@ -598,13 +592,6 @@ def _read_body(
598592 delegate : httputil .HTTPMessageDelegate ,
599593 ) -> Optional [Awaitable [None ]]:
600594 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- )
608595 if "," in headers ["Content-Length" ]:
609596 # Proxies sometimes cause Content-Length headers to get
610597 # duplicated. If all the values are identical then we can
@@ -631,20 +618,22 @@ def _read_body(
631618 else :
632619 content_length = None
633620
621+ is_chunked = is_transfer_encoding_chunked (headers )
622+
634623 if code == 204 :
635624 # This response code is not allowed to have a non-empty body,
636625 # and has an implicit length of zero instead of read-until-close.
637626 # 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 ):
639628 raise httputil .HTTPInputError (
640629 "Response with code %d should not have body" % code
641630 )
642631 content_length = 0
643632
633+ if is_chunked :
634+ return self ._read_chunked_body (delegate )
644635 if content_length is not None :
645636 return self ._read_fixed_body (content_length , delegate )
646- if headers .get ("Transfer-Encoding" , "" ).lower () == "chunked" :
647- return self ._read_chunked_body (delegate )
648637 if self .is_client :
649638 return self ._read_body_until_close (delegate )
650639 return None
@@ -863,3 +852,33 @@ def parse_hex_int(s: str) -> int:
863852 if HEXDIGITS .fullmatch (s ) is None :
864853 raise ValueError ("not a hexadecimal integer: %r" % s )
865854 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