@@ -384,7 +384,10 @@ enum header_states
384384 , h_transfer_encoding
385385 , h_upgrade
386386
387+ , h_matching_transfer_encoding_token_start
387388 , h_matching_transfer_encoding_chunked
389+ , h_matching_transfer_encoding_token
390+
388391 , h_matching_connection_token_start
389392 , h_matching_connection_keep_alive
390393 , h_matching_connection_close
@@ -1260,9 +1263,9 @@ size_t c_nio_http_parser_execute (http_parser *parser,
12601263
12611264 switch (parser -> header_state ) {
12621265 case h_general : {
1263- size_t limit = data + len - p ;
1264- limit = MIN (limit , max_header_size );
1265- while (p + 1 < data + limit && TOKEN (p [1 ])) {
1266+ size_t left = data + len - p ;
1267+ const char * pe = p + MIN (left , max_header_size );
1268+ while (p + 1 < pe && TOKEN (p [1 ])) {
12661269 p ++ ;
12671270 }
12681271 break ;
@@ -1338,6 +1341,7 @@ size_t c_nio_http_parser_execute (http_parser *parser,
13381341 parser -> header_state = h_general ;
13391342 } else if (parser -> index == sizeof (TRANSFER_ENCODING )- 2 ) {
13401343 parser -> header_state = h_transfer_encoding ;
1344+ parser -> flags |= F_TRANSFER_ENCODING ;
13411345 }
13421346 break ;
13431347
@@ -1419,10 +1423,14 @@ size_t c_nio_http_parser_execute (http_parser *parser,
14191423 if ('c' == c ) {
14201424 parser -> header_state = h_matching_transfer_encoding_chunked ;
14211425 } else {
1422- parser -> header_state = h_general ;
1426+ parser -> header_state = h_matching_transfer_encoding_token ;
14231427 }
14241428 break ;
14251429
1430+ /* Multi-value `Transfer-Encoding` header */
1431+ case h_matching_transfer_encoding_token_start :
1432+ break ;
1433+
14261434 case h_content_length :
14271435 if (UNLIKELY (!IS_NUM (ch ))) {
14281436 SET_ERRNO (HPE_INVALID_CONTENT_LENGTH );
@@ -1499,28 +1507,25 @@ size_t c_nio_http_parser_execute (http_parser *parser,
14991507
15001508 switch (h_state ) {
15011509 case h_general :
1502- {
1503- const char * p_cr ;
1504- const char * p_lf ;
1505- size_t limit = data + len - p ;
1506-
1507- limit = MIN ( limit , max_header_size ) ;
1508-
1509- p_cr = ( const char * ) memchr ( p , CR , limit ) ;
1510- p_lf = ( const char * ) memchr ( p , LF , limit ) ;
1511- if ( p_cr != NULL ) {
1512- if (p_lf != NULL && p_cr >= p_lf )
1513- p = p_lf ;
1514- else
1515- p = p_cr ;
1516- } else if ( UNLIKELY ( p_lf != NULL )) {
1517- p = p_lf ;
1518- } else {
1519- p = data + len ;
1510+ {
1511+ size_t left = data + len - p ;
1512+ const char * pe = p + MIN ( left , max_header_size ) ;
1513+
1514+ for (; p != pe ; p ++ ) {
1515+ ch = * p ;
1516+ if ( ch == CR || ch == LF ) {
1517+ -- p ;
1518+ break ;
1519+ }
1520+ if (! lenient && ! IS_HEADER_CHAR ( ch )) {
1521+ SET_ERRNO ( HPE_INVALID_HEADER_TOKEN ) ;
1522+ goto error ;
1523+ }
1524+ }
1525+ if ( p == data + len )
1526+ -- p ;
1527+ break ;
15201528 }
1521- -- p ;
1522- break ;
1523- }
15241529
15251530 case h_connection :
15261531 case h_transfer_encoding :
@@ -1569,16 +1574,41 @@ size_t c_nio_http_parser_execute (http_parser *parser,
15691574 goto error ;
15701575
15711576 /* Transfer-Encoding: chunked */
1577+ case h_matching_transfer_encoding_token_start :
1578+ /* looking for 'Transfer-Encoding: chunked' */
1579+ if ('c' == c ) {
1580+ h_state = h_matching_transfer_encoding_chunked ;
1581+ } else if (STRICT_TOKEN (c )) {
1582+ /* TODO(indutny): similar code below does this, but why?
1583+ * At the very least it seems to be inconsistent given that
1584+ * h_matching_transfer_encoding_token does not check for
1585+ * `STRICT_TOKEN`
1586+ */
1587+ h_state = h_matching_transfer_encoding_token ;
1588+ } else if (c == ' ' || c == '\t' ) {
1589+ /* Skip lws */
1590+ } else {
1591+ h_state = h_general ;
1592+ }
1593+ break ;
1594+
15721595 case h_matching_transfer_encoding_chunked :
15731596 parser -> index ++ ;
15741597 if (parser -> index > sizeof (CHUNKED )- 1
15751598 || c != CHUNKED [parser -> index ]) {
1576- h_state = h_general ;
1599+ h_state = h_matching_transfer_encoding_token ;
15771600 } else if (parser -> index == sizeof (CHUNKED )- 2 ) {
15781601 h_state = h_transfer_encoding_chunked ;
15791602 }
15801603 break ;
15811604
1605+ case h_matching_transfer_encoding_token :
1606+ if (ch == ',' ) {
1607+ h_state = h_matching_transfer_encoding_token_start ;
1608+ parser -> index = 0 ;
1609+ }
1610+ break ;
1611+
15821612 case h_matching_connection_token_start :
15831613 /* looking for 'Connection: keep-alive' */
15841614 if (c == 'k' ) {
@@ -1637,7 +1667,7 @@ size_t c_nio_http_parser_execute (http_parser *parser,
16371667 break ;
16381668
16391669 case h_transfer_encoding_chunked :
1640- if (ch != ' ' ) h_state = h_general ;
1670+ if (ch != ' ' ) h_state = h_matching_transfer_encoding_token ;
16411671 break ;
16421672
16431673 case h_connection_keep_alive :
@@ -1771,12 +1801,17 @@ size_t c_nio_http_parser_execute (http_parser *parser,
17711801 REEXECUTE ();
17721802 }
17731803
1774- /* Cannot use chunked encoding and a content-length header together
1775- per the HTTP specification. */
1776- if ((parser -> flags & F_CHUNKED ) &&
1804+ /* Cannot us transfer- encoding and a content-length header together
1805+ per the HTTP specification. (RFC 7230 Section 3.3.3) */
1806+ if ((parser -> flags & F_TRANSFER_ENCODING ) &&
17771807 (parser -> flags & F_CONTENTLENGTH )) {
1778- SET_ERRNO (HPE_UNEXPECTED_CONTENT_LENGTH );
1779- goto error ;
1808+ /* Allow it for lenient parsing as long as `Transfer-Encoding` is
1809+ * not `chunked`
1810+ */
1811+ if (!lenient || (parser -> flags & F_CHUNKED )) {
1812+ SET_ERRNO (HPE_UNEXPECTED_CONTENT_LENGTH );
1813+ goto error ;
1814+ }
17801815 }
17811816
17821817 UPDATE_STATE (s_headers_done );
@@ -1851,8 +1886,31 @@ size_t c_nio_http_parser_execute (http_parser *parser,
18511886 UPDATE_STATE (NEW_MESSAGE ());
18521887 CALLBACK_NOTIFY (message_complete );
18531888 } else if (parser -> flags & F_CHUNKED ) {
1854- /* chunked encoding - ignore Content-Length header */
1889+ /* chunked encoding - ignore Content-Length header,
1890+ * prepare for a chunk */
18551891 UPDATE_STATE (s_chunk_size_start );
1892+ } else if (parser -> flags & F_TRANSFER_ENCODING ) {
1893+ if (parser -> type == HTTP_REQUEST && !lenient ) {
1894+ /* RFC 7230 3.3.3 */
1895+
1896+ /* If a Transfer-Encoding header field
1897+ * is present in a request and the chunked transfer coding is not
1898+ * the final encoding, the message body length cannot be determined
1899+ * reliably; the server MUST respond with the 400 (Bad Request)
1900+ * status code and then close the connection.
1901+ */
1902+ SET_ERRNO (HPE_INVALID_TRANSFER_ENCODING );
1903+ RETURN (p - data ); /* Error */
1904+ } else {
1905+ /* RFC 7230 3.3.3 */
1906+
1907+ /* If a Transfer-Encoding header field is present in a response and
1908+ * the chunked transfer coding is not the final encoding, the
1909+ * message body length is determined by reading the connection until
1910+ * it is closed by the server.
1911+ */
1912+ UPDATE_STATE (s_body_identity_eof );
1913+ }
18561914 } else {
18571915 if (parser -> content_length == 0 ) {
18581916 /* Content-Length header given but zero: Content-Length: 0\r\n */
@@ -2106,6 +2164,12 @@ c_nio_http_message_needs_eof (const http_parser *parser)
21062164 return 0 ;
21072165 }
21082166
2167+ /* RFC 7230 3.3.3, see `s_headers_almost_done` */
2168+ if ((parser -> flags & F_TRANSFER_ENCODING ) &&
2169+ (parser -> flags & F_CHUNKED ) == 0 ) {
2170+ return 1 ;
2171+ }
2172+
21092173 if ((parser -> flags & F_CHUNKED ) || parser -> content_length != ULLONG_MAX ) {
21102174 return 0 ;
21112175 }
0 commit comments