Skip to content

Commit

Permalink
Add Ability to Stop Parsing After HTTP Headers (#172)
Browse files Browse the repository at this point in the history
* ability to pause parsing after headers

* Fix CBMC proof of Send function

Co-authored-by: Soren Ptak <[email protected]>

Signed-off-by: Gaurav Aggarwal <[email protected]>

Co-authored-by: Aniruddha Kanhere <[email protected]>
Co-authored-by: Soren Ptak <[email protected]>
Co-authored-by: Gaurav Aggarwal <[email protected]>
  • Loading branch information
4 people authored Jan 29, 2024
1 parent dc94df0 commit 65eba45
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 6 deletions.
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
30 changes: 30 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,35 @@ 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;

if( parser->error == HPE_PAUSED )
{
/* When the parser is paused ensure that the error_pos member points to
* a valid location in the response buffer. */
size_t errorPosOffset;

__CPROVER_assume( errorPosOffset < pParsingContext->pResponse->bufferLen );
__CPROVER_assume( errorPosOffset < len );

parser->error_pos = pParsingContext->pResponse->pBuffer + errorPosOffset;
}

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

0 comments on commit 65eba45

Please sign in to comment.