diff --git a/docs/doxygen/include/size_table.md b/docs/doxygen/include/size_table.md index 2170e484..4f22c250 100644 --- a/docs/doxygen/include/size_table.md +++ b/docs/doxygen/include/size_table.md @@ -9,7 +9,7 @@ core_http_client.c -
3.2K
+
3.3K
2.6K
@@ -29,7 +29,7 @@ Total estimates -
24.0K
+
24.1K
20.8K
diff --git a/source/core_http_client.c b/source/core_http_client.c index 84c87b72..c4a4533a 100644 --- a/source/core_http_client.c +++ b/source/core_http_client.c @@ -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; } @@ -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: @@ -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; @@ -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, @@ -2650,6 +2681,10 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) str = "HTTPSecurityAlertInvalidContentLength"; break; + case HTTPParserPaused: + str = "HTTPParserPaused"; + break; + case HTTPParserInternalError: str = "HTTPParserInternalError"; break; diff --git a/source/include/core_http_client.h b/source/include/core_http_client.h index 8dc2a098..5239c3a1 100644 --- a/source/include/core_http_client.h +++ b/source/include/core_http_client.h @@ -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 @@ -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 @@ -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. * @@ -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. * diff --git a/source/include/core_http_client_private.h b/source/include/core_http_client_private.h index 680e1894..3ded00cc 100644 --- a/source/include/core_http_client_private.h +++ b/source/include/core_http_client_private.h @@ -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. diff --git a/test/cbmc/stubs/HTTPClient_Send_llhttp_execute.c b/test/cbmc/stubs/HTTPClient_Send_llhttp_execute.c index 8ceb72dd..74b29bb7 100644 --- a/test/cbmc/stubs/HTTPClient_Send_llhttp_execute.c +++ b/test/cbmc/stubs/HTTPClient_Send_llhttp_execute.c @@ -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; } diff --git a/test/unit-test/core_http_utest.c b/test/unit-test/core_http_utest.c index 04809c2f..ec22b1c2 100644 --- a/test/unit-test/core_http_utest.c +++ b/test/unit-test/core_http_utest.c @@ -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 );