@@ -381,7 +381,10 @@ enum header_states
381
381
, h_transfer_encoding
382
382
, h_upgrade
383
383
384
+ , h_matching_transfer_encoding_token_start
384
385
, h_matching_transfer_encoding_chunked
386
+ , h_matching_transfer_encoding_token
387
+
385
388
, h_matching_connection_token_start
386
389
, h_matching_connection_keep_alive
387
390
, h_matching_connection_close
@@ -1257,9 +1260,9 @@ size_t http_parser_execute (http_parser *parser,
1257
1260
1258
1261
switch (parser -> header_state ) {
1259
1262
case h_general : {
1260
- size_t limit = data + len - p ;
1261
- limit = MIN (limit , max_header_size );
1262
- while (p + 1 < data + limit && TOKEN (p [1 ])) {
1263
+ size_t left = data + len - p ;
1264
+ const char * pe = p + MIN (left , max_header_size );
1265
+ while (p + 1 < pe && TOKEN (p [1 ])) {
1263
1266
p ++ ;
1264
1267
}
1265
1268
break ;
@@ -1335,6 +1338,7 @@ size_t http_parser_execute (http_parser *parser,
1335
1338
parser -> header_state = h_general ;
1336
1339
} else if (parser -> index == sizeof (TRANSFER_ENCODING )- 2 ) {
1337
1340
parser -> header_state = h_transfer_encoding ;
1341
+ parser -> flags |= F_TRANSFER_ENCODING ;
1338
1342
}
1339
1343
break ;
1340
1344
@@ -1416,10 +1420,14 @@ size_t http_parser_execute (http_parser *parser,
1416
1420
if ('c' == c ) {
1417
1421
parser -> header_state = h_matching_transfer_encoding_chunked ;
1418
1422
} else {
1419
- parser -> header_state = h_general ;
1423
+ parser -> header_state = h_matching_transfer_encoding_token ;
1420
1424
}
1421
1425
break ;
1422
1426
1427
+ /* Multi-value `Transfer-Encoding` header */
1428
+ case h_matching_transfer_encoding_token_start :
1429
+ break ;
1430
+
1423
1431
case h_content_length :
1424
1432
if (UNLIKELY (!IS_NUM (ch ))) {
1425
1433
SET_ERRNO (HPE_INVALID_CONTENT_LENGTH );
@@ -1497,9 +1505,10 @@ size_t http_parser_execute (http_parser *parser,
1497
1505
switch (h_state ) {
1498
1506
case h_general :
1499
1507
{
1500
- const char * limit = p + MIN (data + len - p , max_header_size );
1508
+ size_t left = data + len - p ;
1509
+ const char * pe = p + MIN (left , max_header_size );
1501
1510
1502
- for (; p != limit ; p ++ ) {
1511
+ for (; p != pe ; p ++ ) {
1503
1512
ch = * p ;
1504
1513
if (ch == CR || ch == LF ) {
1505
1514
-- p ;
@@ -1562,16 +1571,41 @@ size_t http_parser_execute (http_parser *parser,
1562
1571
goto error ;
1563
1572
1564
1573
/* Transfer-Encoding: chunked */
1574
+ case h_matching_transfer_encoding_token_start :
1575
+ /* looking for 'Transfer-Encoding: chunked' */
1576
+ if ('c' == c ) {
1577
+ h_state = h_matching_transfer_encoding_chunked ;
1578
+ } else if (STRICT_TOKEN (c )) {
1579
+ /* TODO(indutny): similar code below does this, but why?
1580
+ * At the very least it seems to be inconsistent given that
1581
+ * h_matching_transfer_encoding_token does not check for
1582
+ * `STRICT_TOKEN`
1583
+ */
1584
+ h_state = h_matching_transfer_encoding_token ;
1585
+ } else if (c == ' ' || c == '\t' ) {
1586
+ /* Skip lws */
1587
+ } else {
1588
+ h_state = h_general ;
1589
+ }
1590
+ break ;
1591
+
1565
1592
case h_matching_transfer_encoding_chunked :
1566
1593
parser -> index ++ ;
1567
1594
if (parser -> index > sizeof (CHUNKED )- 1
1568
1595
|| c != CHUNKED [parser -> index ]) {
1569
- h_state = h_general ;
1596
+ h_state = h_matching_transfer_encoding_token ;
1570
1597
} else if (parser -> index == sizeof (CHUNKED )- 2 ) {
1571
1598
h_state = h_transfer_encoding_chunked ;
1572
1599
}
1573
1600
break ;
1574
1601
1602
+ case h_matching_transfer_encoding_token :
1603
+ if (ch == ',' ) {
1604
+ h_state = h_matching_transfer_encoding_token_start ;
1605
+ parser -> index = 0 ;
1606
+ }
1607
+ break ;
1608
+
1575
1609
case h_matching_connection_token_start :
1576
1610
/* looking for 'Connection: keep-alive' */
1577
1611
if (c == 'k' ) {
@@ -1630,7 +1664,7 @@ size_t http_parser_execute (http_parser *parser,
1630
1664
break ;
1631
1665
1632
1666
case h_transfer_encoding_chunked :
1633
- if (ch != ' ' ) h_state = h_general ;
1667
+ if (ch != ' ' ) h_state = h_matching_transfer_encoding_token ;
1634
1668
break ;
1635
1669
1636
1670
case h_connection_keep_alive :
@@ -1764,12 +1798,17 @@ size_t http_parser_execute (http_parser *parser,
1764
1798
REEXECUTE ();
1765
1799
}
1766
1800
1767
- /* Cannot use chunked encoding and a content-length header together
1768
- per the HTTP specification. */
1769
- if ((parser -> flags & F_CHUNKED ) &&
1801
+ /* Cannot us transfer- encoding and a content-length header together
1802
+ per the HTTP specification. (RFC 7230 Section 3.3.3) */
1803
+ if ((parser -> flags & F_TRANSFER_ENCODING ) &&
1770
1804
(parser -> flags & F_CONTENTLENGTH )) {
1771
- SET_ERRNO (HPE_UNEXPECTED_CONTENT_LENGTH );
1772
- goto error ;
1805
+ /* Allow it for lenient parsing as long as `Transfer-Encoding` is
1806
+ * not `chunked`
1807
+ */
1808
+ if (!lenient || (parser -> flags & F_CHUNKED )) {
1809
+ SET_ERRNO (HPE_UNEXPECTED_CONTENT_LENGTH );
1810
+ goto error ;
1811
+ }
1773
1812
}
1774
1813
1775
1814
UPDATE_STATE (s_headers_done );
@@ -1844,8 +1883,31 @@ size_t http_parser_execute (http_parser *parser,
1844
1883
UPDATE_STATE (NEW_MESSAGE ());
1845
1884
CALLBACK_NOTIFY (message_complete );
1846
1885
} else if (parser -> flags & F_CHUNKED ) {
1847
- /* chunked encoding - ignore Content-Length header */
1886
+ /* chunked encoding - ignore Content-Length header,
1887
+ * prepare for a chunk */
1848
1888
UPDATE_STATE (s_chunk_size_start );
1889
+ } else if (parser -> flags & F_TRANSFER_ENCODING ) {
1890
+ if (parser -> type == HTTP_REQUEST && !lenient ) {
1891
+ /* RFC 7230 3.3.3 */
1892
+
1893
+ /* If a Transfer-Encoding header field
1894
+ * is present in a request and the chunked transfer coding is not
1895
+ * the final encoding, the message body length cannot be determined
1896
+ * reliably; the server MUST respond with the 400 (Bad Request)
1897
+ * status code and then close the connection.
1898
+ */
1899
+ SET_ERRNO (HPE_INVALID_TRANSFER_ENCODING );
1900
+ RETURN (p - data ); /* Error */
1901
+ } else {
1902
+ /* RFC 7230 3.3.3 */
1903
+
1904
+ /* If a Transfer-Encoding header field is present in a response and
1905
+ * the chunked transfer coding is not the final encoding, the
1906
+ * message body length is determined by reading the connection until
1907
+ * it is closed by the server.
1908
+ */
1909
+ UPDATE_STATE (s_body_identity_eof );
1910
+ }
1849
1911
} else {
1850
1912
if (parser -> content_length == 0 ) {
1851
1913
/* Content-Length header given but zero: Content-Length: 0\r\n */
@@ -2099,6 +2161,12 @@ http_message_needs_eof (const http_parser *parser)
2099
2161
return 0 ;
2100
2162
}
2101
2163
2164
+ /* RFC 7230 3.3.3, see `s_headers_almost_done` */
2165
+ if ((parser -> flags & F_TRANSFER_ENCODING ) &&
2166
+ (parser -> flags & F_CHUNKED ) == 0 ) {
2167
+ return 1 ;
2168
+ }
2169
+
2102
2170
if ((parser -> flags & F_CHUNKED ) || parser -> content_length != ULLONG_MAX ) {
2103
2171
return 0 ;
2104
2172
}
0 commit comments