diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 31909d9..5344513 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -779,7 +779,8 @@ int HttpClient::readHeader() case eStatusCodeRead: // We're at the start of a line, or somewhere in the middle of reading // the Content-Length prefix - if (*iContentLengthPtr == c) + if ((iTransferEncodingChunkedPtr == kTransferEncodingChunked) && + (tolower((unsigned char)*iContentLengthPtr) == tolower((unsigned char)c))) { // This character matches, just move along iContentLengthPtr++; @@ -793,7 +794,8 @@ int HttpClient::readHeader() iBodyLengthConsumed = 0; } } - else if (*iTransferEncodingChunkedPtr == c) + else if ((iContentLengthPtr == kContentLengthPrefix) && + (tolower((unsigned char)*iTransferEncodingChunkedPtr) == tolower((unsigned char)c))) { // This character matches, just move along iTransferEncodingChunkedPtr++; diff --git a/tests/host/Arduino.h b/tests/host/Arduino.h new file mode 100644 index 0000000..ec89a6b --- /dev/null +++ b/tests/host/Arduino.h @@ -0,0 +1,4 @@ +#pragma once +#include +#include +using namespace arduino; diff --git a/tests/host/README.md b/tests/host/README.md new file mode 100644 index 0000000..da860fe --- /dev/null +++ b/tests/host/README.md @@ -0,0 +1,22 @@ +# Response parser regression + +Use a local checkout of [ArduinoCore-API](https://github.com/arduino/ArduinoCore-API) +for the production String/Stream/Client interfaces and its host conversion helpers. +The test transport supplies in-memory HTTP response bytes; it does not emulate a +Wi-Fi chip or prove behavior on physical hardware. + +From this repository root, set `CORE_API` to that checkout and run: + +```sh +c++ -std=c++11 -fsanitize=address,undefined -Itests/host -I"$CORE_API" \ + -idirafter "$CORE_API/api" -Isrc src/HttpClient.cpp src/b64.cpp \ + tests/host/response_headers.cpp "$CORE_API"/api/String.cpp \ + "$CORE_API"/api/Print.cpp "$CORE_API"/api/Stream.cpp \ + "$CORE_API"/api/IPAddress.cpp "$CORE_API"/test/src/itoa.cpp \ + "$CORE_API"/test/src/dtostrf.cpp -o /tmp/http-response-headers +/tmp/http-response-headers +``` + +`-idirafter` avoids shadowing the system `string.h` with Arduino's `String.h` +on case-insensitive filesystems. The Core API emits existing weak-main warnings +with Clang on macOS. diff --git a/tests/host/response_headers.cpp b/tests/host/response_headers.cpp new file mode 100644 index 0000000..50f8cb2 --- /dev/null +++ b/tests/host/response_headers.cpp @@ -0,0 +1,50 @@ +#include "HttpClient.h" +#include +#include +#include + +unsigned long millis() { static unsigned long clock = 0; return ++clock; } +void delay(unsigned long) {} + +class ResponseClient : public Client { + std::string response; + size_t position = 0; +public: + explicit ResponseClient(const std::string& value) : response(value) {} + int connect(IPAddress, uint16_t) override { return 1; } + int connect(const char*, uint16_t) override { return 1; } + size_t write(uint8_t) override { return 1; } + size_t write(const uint8_t*, size_t size) override { return size; } + int available() override { return response.size() - position; } + int read() override { return available() ? (unsigned char)response[position++] : -1; } + int read(uint8_t* data, size_t size) override { + size_t count = 0; + while (count < size && available()) data[count++] = read(); + return count; + } + int peek() override { return available() ? (unsigned char)response[position] : -1; } + void flush() override {} + void stop() override {} + uint8_t connected() override { return available() > 0; } + operator bool() override { return true; } +}; +int main() { + for (const char* name : {"Content-Length", "content-length", "CONTENT-LENGTH", "cOnTeNt-LeNgTh"}) { + ResponseClient transport(std::string("HTTP/1.1 200 OK\r\nX-Test: ignored\r\n") + name + ": 5\r\n\r\nhello"); + HttpClient http(transport, "example.test"); + assert(http.get("/") == 0); + assert(http.responseStatusCode() == 200); + assert(http.contentLength() == 5); + assert(http.responseBody() == "hello"); + } + for (const char* field : {"Transfer-Encoding: chunked", "transfer-encoding: chunked", "TRANSFER-ENCODING: CHUNKED", "tRaNsFeR-EnCoDiNg: ChUnKeD"}) { + ResponseClient transport(std::string("HTTP/1.1 200 OK\r\n") + field + "\r\n\r\n5\r\nhello\r\n0\r\n\r\n"); + HttpClient http(transport, "example.test"); + assert(http.get("/") == 0); + assert(http.responseStatusCode() == 200); + assert(http.skipResponseHeaders() == 0); + assert(http.isResponseChunked()); + assert(http.responseBody() == "hello"); + } + puts("eight response-header casing cases passed"); +}