Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ability to Stop Parsing After HTTP Headers #172

Merged
merged 11 commits into from
Jan 29, 2024
4 changes: 2 additions & 2 deletions docs/doxygen/include/size_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</tr>
<tr>
<td>core_http_client.c</td>
<td><center>3.2K</center></td>
<td><center>3.3K</center></td>
<td><center>2.6K</center></td>
</tr>
<tr>
Expand All @@ -29,7 +29,7 @@
</tr>
<tr>
<td><b>Total estimates</b></td>
<td><b><center>24.0K</center></b></td>
<td><b><center>24.1K</center></b></td>
<td><b><center>20.8K</center></b></td>
</tr>
</table>
41 changes: 38 additions & 3 deletions source/core_http_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,13 @@ static int httpParserOnHeadersCompleteCallback( llhttp_t * pHttpParser )

LogDebug( ( "Response parsing: Found the end of the headers." ) );

/* If there is HTTP_RESPONSE_DO_NOT_PARSE_BODY_FLAG opt-in we should stop
* parsing here. */
if( ( pResponse->respOptionFlags & HTTP_RESPONSE_DO_NOT_PARSE_BODY_FLAG ) != 0U )
{
shouldContinueParse = LLHTTP_PAUSE_PARSING;
}

return shouldContinueParse;
}

Expand Down Expand Up @@ -1145,6 +1152,11 @@ static HTTPStatus_t processLlhttpError( const llhttp_t * pHttpParser )
returnStatus = HTTPSecurityAlertInvalidContentLength;
break;

case HPE_PAUSED:
LogInfo( ( "User intervention: Parsing stopped by user." ) );
returnStatus = HTTPParserPaused;
break;

/* All other error cases cannot be triggered and indicate an error in the
* third-party parsing library if found. */
default:
Expand Down Expand Up @@ -1223,9 +1235,17 @@ static HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext,
llhttp_resume_after_upgrade( &( pParsingContext->llhttpParser ) );
}

/* The next location to parse will always be after what has already
* been parsed. */
pParsingContext->pBufferCur = parsingStartLoc + parseLen;
if( eReturn == HPE_PAUSED )
{
/* The next location to parse is where the parser was paused. */
pParsingContext->pBufferCur = pParsingContext->llhttpParser.error_pos;
}
else
{
/* The next location to parse is after what has already been parsed. */
pParsingContext->pBufferCur = parsingStartLoc + parseLen;
}

returnStatus = processLlhttpError( &( pParsingContext->llhttpParser ) );

return returnStatus;
Expand Down Expand Up @@ -2129,6 +2149,17 @@ HTTPStatus_t HTTPClient_ReceiveAndParseHttpResponse( const TransportInterface_t
( totalReceived < pResponse->bufferLen ) ) ? 1U : 0U;
}

if( ( returnStatus == HTTPParserPaused ) &&
( ( pResponse->respOptionFlags & HTTP_RESPONSE_DO_NOT_PARSE_BODY_FLAG ) != 0U ) )
{
returnStatus = HTTPSuccess;

/* There may be dangling data if we parse with do not parse body flag.
* We expose this data through body to let the applications access it. */
pResponse->pBody = ( const uint8_t * ) parsingContext.pBufferCur;
pResponse->bodyLen = totalReceived - ( size_t ) ( ( ( uintptr_t ) pResponse->pBody ) - ( ( uintptr_t ) pResponse->pBuffer ) );
}

if( returnStatus == HTTPSuccess )
{
/* If there are errors in receiving from the network or during parsing,
Expand Down Expand Up @@ -2650,6 +2681,10 @@ const char * HTTPClient_strerror( HTTPStatus_t status )
str = "HTTPSecurityAlertInvalidContentLength";
break;

case HTTPParserPaused:
str = "HTTPParserPaused";
break;

case HTTPParserInternalError:
str = "HTTPParserInternalError";
break;
Expand Down
41 changes: 40 additions & 1 deletion source/include/core_http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@
*/
#define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U

/**
* @defgroup http_response_option_flags HTTPResponse_t Flags
* @brief Flags for #HTTPResponse_t.respOptionFlags.
* These flags control the behavior of response parsing.
*
* Flags should be bitwise-ORed with each other to change the behavior of
* #HTTPClient_ReceiveAndParseHttpResponse and #HTTPClient_Send.
*/

/**
* @ingroup http_response_option_flags
* @brief Set this flag to indicate that the response body should not be parsed.
*
* Setting this will cause parser to stop after parsing the headers. Portion of
* the raw body may be available in #HTTPResponse_t.pBody and
* #HTTPResponse_t.bodyLen.
*
* This flag is valid only for #HTTPResponse_t.respOptionFlags.
*/
#define HTTP_RESPONSE_DO_NOT_PARSE_BODY_FLAG 0x1U

/**
* @ingroup http_constants
* @brief Flag that represents End of File byte in the range specification of
Expand All @@ -165,7 +186,7 @@
* - When the requested range is for the last N bytes of the file.
* In both cases, this value should be used for the "rangeEnd" parameter.
*/
#define HTTP_RANGE_REQUEST_END_OF_FILE -1
#define HTTP_RANGE_REQUEST_END_OF_FILE -1

/**
* @ingroup http_enum_types
Expand Down Expand Up @@ -291,6 +312,17 @@ typedef enum HTTPStatus
*/
HTTPSecurityAlertInvalidContentLength,

/**
* @brief Represents the paused state of the HTTP parser.
*
* This state indicates that the parser has encountered a pause condition
* and is waiting for further input.
*
* @see HTTPClient_Send
* @see HTTPClient_ReceiveAndParseHttpResponse
*/
HTTPParserPaused,

/**
* @brief An error occurred in the third-party parsing library.
*
Expand Down Expand Up @@ -535,6 +567,13 @@ typedef struct HTTPResponse
*/
uint8_t areHeadersComplete;

/**
* @brief Flags to control the behavior of response parsing.
*
* Please see @ref http_response_option_flags for more information.
*/
uint32_t respOptionFlags;

/**
* @brief Flags of useful headers found in the response.
*
Expand Down
6 changes: 6 additions & 0 deletions source/include/core_http_client_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@
*/
#define LLHTTP_STOP_PARSING HPE_USER

/**
* @brief Return value for llhttp registered callback to signal to pause
* further execution.
*/
#define LLHTTP_PAUSE_PARSING HPE_PAUSED

/**
* @brief Return value for llhttp_t.on_headers_complete to signal
* that the HTTP response has no body and to halt further execution.
Expand Down
18 changes: 18 additions & 0 deletions test/cbmc/stubs/HTTPClient_Send_llhttp_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,23 @@ llhttp_errno_t llhttp_execute( llhttp_t * parser,
pParsingContext->lastHeaderValueLen = 0U;
}

/* The body pointer is set by the httpParserOnBodyCallback. But since we are
* removing that from CBMC proof execution, the body has to be set here. */
size_t bodyOffset;

if( pParsingContext->pResponse->bufferLen == 0U )
{
bodyOffset = 0U;
}
else
{
/* Body offset can be anything as long as it doesn't exceed the buffer length
* and the length of the current data packet. */
__CPROVER_assume( bodyOffset < pParsingContext->pResponse->bufferLen );
__CPROVER_assume( bodyOffset < len );
}

pParsingContext->pResponse->pBody = pParsingContext->pBufferCur + bodyOffset;

return parser->error;
}
4 changes: 4 additions & 0 deletions test/unit-test/core_http_utest.c
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,10 @@ void test_HTTPClient_strerror( void )
str = HTTPClient_strerror( status );
TEST_ASSERT_EQUAL_STRING( "HTTPSecurityAlertInvalidContentLength", str );

status = HTTPParserPaused;
str = HTTPClient_strerror( status );
TEST_ASSERT_EQUAL_STRING( "HTTPParserPaused", str );

status = HTTPParserInternalError;
str = HTTPClient_strerror( status );
TEST_ASSERT_EQUAL_STRING( "HTTPParserInternalError", str );
Expand Down
Loading