@ -381,7 +381,10 @@ enum header_states
, h_transfer_encoding
, h_transfer_encoding
, h_upgrade
, h_upgrade
, h_matching_transfer_encoding_token_start
, h_matching_transfer_encoding_chunked
, h_matching_transfer_encoding_chunked
, h_matching_transfer_encoding_token
, h_matching_connection_token_start
, h_matching_connection_token_start
, h_matching_connection_keep_alive
, h_matching_connection_keep_alive
, h_matching_connection_close
, h_matching_connection_close
@ -1335,6 +1338,7 @@ reexecute:
parser - > header_state = h_general ;
parser - > header_state = h_general ;
} else if ( parser - > index = = sizeof ( TRANSFER_ENCODING ) - 2 ) {
} else if ( parser - > index = = sizeof ( TRANSFER_ENCODING ) - 2 ) {
parser - > header_state = h_transfer_encoding ;
parser - > header_state = h_transfer_encoding ;
parser - > flags | = F_TRANSFER_ENCODING ;
}
}
break ;
break ;
@ -1416,10 +1420,14 @@ reexecute:
if ( ' c ' = = c ) {
if ( ' c ' = = c ) {
parser - > header_state = h_matching_transfer_encoding_chunked ;
parser - > header_state = h_matching_transfer_encoding_chunked ;
} else {
} else {
parser - > header_state = h_ general ;
parser - > header_state = h_ matching_transfer_encoding_token ;
}
}
break ;
break ;
/* Multi-value `Transfer-Encoding` header */
case h_matching_transfer_encoding_token_start :
break ;
case h_content_length :
case h_content_length :
if ( UNLIKELY ( ! IS_NUM ( ch ) ) ) {
if ( UNLIKELY ( ! IS_NUM ( ch ) ) ) {
SET_ERRNO ( HPE_INVALID_CONTENT_LENGTH ) ;
SET_ERRNO ( HPE_INVALID_CONTENT_LENGTH ) ;
@ -1563,16 +1571,41 @@ reexecute:
goto error ;
goto error ;
/* Transfer-Encoding: chunked */
/* Transfer-Encoding: chunked */
case h_matching_transfer_encoding_token_start :
/* looking for 'Transfer-Encoding: chunked' */
if ( ' c ' = = c ) {
h_state = h_matching_transfer_encoding_chunked ;
} else if ( STRICT_TOKEN ( c ) ) {
/* TODO(indutny): similar code below does this, but why?
* At the very least it seems to be inconsistent given that
* h_matching_transfer_encoding_token does not check for
* ` STRICT_TOKEN `
*/
h_state = h_matching_transfer_encoding_token ;
} else if ( c = = ' ' | | c = = ' \t ' ) {
/* Skip lws */
} else {
h_state = h_general ;
}
break ;
case h_matching_transfer_encoding_chunked :
case h_matching_transfer_encoding_chunked :
parser - > index + + ;
parser - > index + + ;
if ( parser - > index > sizeof ( CHUNKED ) - 1
if ( parser - > index > sizeof ( CHUNKED ) - 1
| | c ! = CHUNKED [ parser - > index ] ) {
| | c ! = CHUNKED [ parser - > index ] ) {
h_state = h_general ;
h_state = h_ matching_transfer_encoding_token ;
} else if ( parser - > index = = sizeof ( CHUNKED ) - 2 ) {
} else if ( parser - > index = = sizeof ( CHUNKED ) - 2 ) {
h_state = h_transfer_encoding_chunked ;
h_state = h_transfer_encoding_chunked ;
}
}
break ;
break ;
case h_matching_transfer_encoding_token :
if ( ch = = ' , ' ) {
h_state = h_matching_transfer_encoding_token_start ;
parser - > index = 0 ;
}
break ;
case h_matching_connection_token_start :
case h_matching_connection_token_start :
/* looking for 'Connection: keep-alive' */
/* looking for 'Connection: keep-alive' */
if ( c = = ' k ' ) {
if ( c = = ' k ' ) {
@ -1631,7 +1664,7 @@ reexecute:
break ;
break ;
case h_transfer_encoding_chunked :
case h_transfer_encoding_chunked :
if ( ch ! = ' ' ) h_state = h_ general ;
if ( ch ! = ' ' ) h_state = h_ matching_transfer_encoding_token ;
break ;
break ;
case h_connection_keep_alive :
case h_connection_keep_alive :
@ -1765,12 +1798,17 @@ reexecute:
REEXECUTE ( ) ;
REEXECUTE ( ) ;
}
}
/* Cannot us e chunked encoding and a content-length header together
/* Cannot us transfer- encoding and a content-length header together
per the HTTP specification . */
per the HTTP specification . ( RFC 7230 Section 3.3 .3 ) */
if ( ( parser - > flags & F_ CHUNKED ) & &
if ( ( parser - > flags & F_ TRANSFER_ENCODING ) & &
( parser - > flags & F_CONTENTLENGTH ) ) {
( parser - > flags & F_CONTENTLENGTH ) ) {
SET_ERRNO ( HPE_UNEXPECTED_CONTENT_LENGTH ) ;
/* Allow it for lenient parsing as long as `Transfer-Encoding` is
goto error ;
* not ` chunked `
*/
if ( ! lenient | | ( parser - > flags & F_CHUNKED ) ) {
SET_ERRNO ( HPE_UNEXPECTED_CONTENT_LENGTH ) ;
goto error ;
}
}
}
UPDATE_STATE ( s_headers_done ) ;
UPDATE_STATE ( s_headers_done ) ;
@ -1845,8 +1883,31 @@ reexecute:
UPDATE_STATE ( NEW_MESSAGE ( ) ) ;
UPDATE_STATE ( NEW_MESSAGE ( ) ) ;
CALLBACK_NOTIFY ( message_complete ) ;
CALLBACK_NOTIFY ( message_complete ) ;
} else if ( parser - > flags & F_CHUNKED ) {
} else if ( parser - > flags & F_CHUNKED ) {
/* chunked encoding - ignore Content-Length header */
/* chunked encoding - ignore Content-Length header,
* prepare for a chunk */
UPDATE_STATE ( s_chunk_size_start ) ;
UPDATE_STATE ( s_chunk_size_start ) ;
} else if ( parser - > flags & F_TRANSFER_ENCODING ) {
if ( parser - > type = = HTTP_REQUEST & & ! lenient ) {
/* RFC 7230 3.3.3 */
/* If a Transfer-Encoding header field
* is present in a request and the chunked transfer coding is not
* the final encoding , the message body length cannot be determined
* reliably ; the server MUST respond with the 400 ( Bad Request )
* status code and then close the connection .
*/
SET_ERRNO ( HPE_INVALID_TRANSFER_ENCODING ) ;
RETURN ( p - data ) ; /* Error */
} else {
/* RFC 7230 3.3.3 */
/* If a Transfer-Encoding header field is present in a response and
* the chunked transfer coding is not the final encoding , the
* message body length is determined by reading the connection until
* it is closed by the server .
*/
UPDATE_STATE ( s_body_identity_eof ) ;
}
} else {
} else {
if ( parser - > content_length = = 0 ) {
if ( parser - > content_length = = 0 ) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
/* Content-Length header given but zero: Content-Length: 0\r\n */
@ -2100,6 +2161,12 @@ http_message_needs_eof (const http_parser *parser)
return 0 ;
return 0 ;
}
}
/* RFC 7230 3.3.3, see `s_headers_almost_done` */
if ( ( parser - > flags & F_TRANSFER_ENCODING ) & &
( parser - > flags & F_CHUNKED ) = = 0 ) {
return 1 ;
}
if ( ( parser - > flags & F_CHUNKED ) | | parser - > content_length ! = ULLONG_MAX ) {
if ( ( parser - > flags & F_CHUNKED ) | | parser - > content_length ! = ULLONG_MAX ) {
return 0 ;
return 0 ;
}
}