diff --git a/MISRA.md b/MISRA.md index 49fd1208..296b961f 100644 --- a/MISRA.md +++ b/MISRA.md @@ -7,7 +7,7 @@ The specific deviations, suppressed inline, are listed below. Additionally, [MISRA configuration file](https://github.com/FreeRTOS/coreHTTP/blob/main/tools/coverity/misra.config) contains the project wide deviations. ### Suppressed with Coverity Comments -To find the violation references in the source files run grep on the source code +To find the deviation references in the source files run grep on the source code with ( Assuming rule 5.4 violation; with justification in point 2 ): ``` grep 'MISRA Ref 5.4.2' . -rI diff --git a/source/core_http_client.c b/source/core_http_client.c index 99cc545d..a2b09963 100644 --- a/source/core_http_client.c +++ b/source/core_http_client.c @@ -555,8 +555,8 @@ static HTTPStatus_t processLlhttpError( const llhttp_t * pHttpParser ); * 0 if str1 is equal to str2 * 1 if str1 is not equal to str2. */ -static int8_t caseInsensitiveStringCmp( const unsigned char * str1, - const unsigned char * str2, +static int8_t caseInsensitiveStringCmp( const char * str1, + const char * str2, size_t n ); /*-----------------------------------------------------------*/ @@ -567,20 +567,32 @@ static uint32_t getZeroTimestampMs( void ) } /*-----------------------------------------------------------*/ - -static int8_t caseInsensitiveStringCmp( const unsigned char * str1, - const unsigned char * str2, +static int8_t caseInsensitiveStringCmp( const char * str1, + const char * str2, size_t n ) { size_t i = 0U; - /* Inclusion of inbetween variables for coverity rule 13.2 compliance */ - int32_t firstChar; - int32_t secondChar; + /* Inclusion of inbetween variables for MISRA rule 13.2 compliance */ + char firstChar; + char secondChar; + /* Get the offset from a lowercase to capital character in a MISRA compliant way */ + int8_t offset = 'a' - 'A'; for( i = 0U; i < n; i++ ) { - firstChar = toupper( ( int32_t ) ( str1[ i ] ) ); - secondChar = toupper( ( ( int32_t ) str2[ i ] ) ); + firstChar = str1[ i ]; + secondChar = str2[ i ]; + + /* Subtract offset to go from lowercase to uppercase ASCII character */ + if( ( firstChar >= 'a' ) && ( firstChar <= 'z' ) ) + { + firstChar = firstChar - offset; + } + + if( ( secondChar >= 'a' ) && ( secondChar <= 'z' ) ) + { + secondChar = secondChar - offset; + } if( ( firstChar ) != ( secondChar ) ) { diff --git a/source/include/core_http_client_private.h b/source/include/core_http_client_private.h index 777f99da..b2b146da 100644 --- a/source/include/core_http_client_private.h +++ b/source/include/core_http_client_private.h @@ -213,12 +213,12 @@ typedef enum HTTPParsingState_t */ typedef struct findHeaderContext { - const unsigned char * pField; /**< The field that is being searched for. */ - size_t fieldLen; /**< The length of pField. */ - const char ** pValueLoc; /**< The location of the value found in the buffer. */ - size_t * pValueLen; /**< the length of the value found. */ - uint8_t fieldFound; /**< Indicates that the header field was found during parsing. */ - uint8_t valueFound; /**< Indicates that the header value was found during parsing. */ + const char * pField; /**< The field that is being searched for. */ + size_t fieldLen; /**< The length of pField. */ + const char ** pValueLoc; /**< The location of the value found in the buffer. */ + size_t * pValueLen; /**< the length of the value found. */ + uint8_t fieldFound; /**< Indicates that the header field was found during parsing. */ + uint8_t valueFound; /**< Indicates that the header value was found during parsing. */ } findHeaderContext_t; /** diff --git a/test/unit-test/core_http_utest.c b/test/unit-test/core_http_utest.c index d2cc932a..59aa5df1 100644 --- a/test/unit-test/core_http_utest.c +++ b/test/unit-test/core_http_utest.c @@ -149,15 +149,26 @@ static const char * pTestResponseEmptyValue = "HTTP/1.1 200 OK\r\n" "test-header2: test-value2\r\n" "\r\n"; +/* Template HTTP response for testing HTTPClient_ReadHeader API + * with special characters. */ +static const char * pTestResponseSpecialCharacter = "HTTP/1.1 200 OK\r\n" + "test-header0: test-value0\r\n" + "test-header1: test-value1\r\n" + "test-header2: test|value2\r\n" + "header{_not_in|buff}: test-value3\r\n" + "\r\n"; -#define HEADER_INVALID_PARAMS "Header" -#define HEADER_INVALID_PARAMS_LEN ( sizeof( HEADER_INVALID_PARAMS ) - 1 ) +#define HEADER_INVALID_PARAMS "Header" +#define HEADER_INVALID_PARAMS_LEN ( sizeof( HEADER_INVALID_PARAMS ) - 1 ) -#define HEADER_IN_BUFFER "test-header1" -#define HEADER_IN_BUFFER_LEN ( sizeof( HEADER_IN_BUFFER ) - 1 ) +#define HEADER_IN_BUFFER "teSt-hEader1" +#define HEADER_IN_BUFFER_LEN ( sizeof( HEADER_IN_BUFFER ) - 1 ) -#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" -#define HEADER_NOT_IN_BUFFER_LEN ( sizeof( HEADER_NOT_IN_BUFFER ) - 1 ) +#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" +#define HEADER_NOT_IN_BUFFER_LEN ( sizeof( HEADER_NOT_IN_BUFFER ) - 1 ) + +#define HEADER_WITH_SPECIAL_CHARACTERS "header{_not-in|buff}" +#define HEADER_WITH_SPECIAL_CHARACTERS_LEN ( sizeof( HEADER_WITH_SPECIAL_CHARACTERS ) - 1 ) /* File-scoped Global variables */ static HTTPStatus_t retCode = HTTPSuccess; @@ -1395,6 +1406,72 @@ void test_Http_ReadHeader_Invalid_Response_Only_Header_Field_Found() TEST_ASSERT_EQUAL( HTTPInvalidResponse, retCode ); } + +/** + * @brief Test happy path with zero-initialized requestHeaders and requestInfo. + * Use characters in the header with ASCII values higher than 'z'. + */ +void test_caseInsensitiveStringCmp() +{ + /* Add expectations for llhttp dependencies. */ + llhttp_settings_init_ExpectAnyArgs(); + llhttp_init_ExpectAnyArgs(); + + /* Configure the llhttp_execute mock. */ + invokeHeaderFieldCallback = 1U; + invokeHeaderValueCallback = 1U; + pFieldLocToReturn = &pTestResponseSpecialCharacter[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + pValueLocToReturn = &pTestResponseSpecialCharacter[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + expectedValCbRetVal = LLHTTP_CONTINUE_PARSING; + invokeHeaderCompleteCallback = 1U; + parserErrNo = HPE_OK; + llhttp_execute_ExpectAnyArgsAndReturn( HPE_OK ); + + /* Call the function under test. */ + testResponse.bufferLen = strlen( pTestResponseSpecialCharacter ); + retCode = HTTPClient_ReadHeader( &testResponse, + HEADER_NOT_IN_BUFFER, + HEADER_NOT_IN_BUFFER_LEN, + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTPHeaderNotFound, retCode ); + + /* Repeat the test above but with fieldLenToReturn == HEADER_NOT_IN_BUFFER_LEN. + * Doing this allows us to take the branch where the actual contents + * of the fields are compared rather than just the length. */ + setUp(); + /* Add expectations for llhttp dependencies. */ + llhttp_settings_init_ExpectAnyArgs(); + llhttp_init_ExpectAnyArgs(); + /* Ensure that the header field does NOT match what we're searching. */ + TEST_ASSERT_EQUAL( otherHeaderFieldInRespLen, HEADER_NOT_IN_BUFFER_LEN ); + TEST_ASSERT_TRUE( memcmp( &pTestResponseSpecialCharacter[ otherHeaderFieldInRespLoc ], + HEADER_WITH_SPECIAL_CHARACTERS, + HEADER_WITH_SPECIAL_CHARACTERS_LEN ) != 0 ); + /* Configure the llhttp_execute mock. */ + invokeHeaderFieldCallback = 1U; + invokeHeaderValueCallback = 1U; + pFieldLocToReturn = &pTestResponseSpecialCharacter[ otherHeaderFieldInRespLoc ]; + fieldLenToReturn = otherHeaderFieldInRespLen; + pValueLocToReturn = &pTestResponseSpecialCharacter[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + expectedValCbRetVal = LLHTTP_CONTINUE_PARSING; + invokeHeaderCompleteCallback = 1U; + parserErrNo = HPE_OK; + llhttp_execute_ExpectAnyArgsAndReturn( HPE_OK ); + + /* Call the function under test. */ + testResponse.bufferLen = strlen( pTestResponseSpecialCharacter ); + retCode = HTTPClient_ReadHeader( &testResponse, + HEADER_WITH_SPECIAL_CHARACTERS, + HEADER_WITH_SPECIAL_CHARACTERS_LEN, + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTPHeaderNotFound, retCode ); +} + /** * @brief Test with an invalid HTTP response that does not contain terminating * characters ("\r\n\r\n") that represent the end of headers in the response.