#include #include #include "http.hpp" TEST(HttpRequestParserTest, SimpleGetRequest) { constexpr std::string_view http_request = "GET /hello/world HTTP/1.1\r\n" "Host: example.com\r\n" "User-Agent: TestClient/1.0\r\n" "\r\n"; HttpRequestParser parser; auto result = parser.parse( std::span(std::bit_cast(http_request.data()), http_request.size())); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 1); const HttpRequest &request = requests.front(); EXPECT_EQ(request.method, "GET"); EXPECT_EQ(request.url, "/hello/world"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 2); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "example.com"); EXPECT_EQ(request.headers[1].first, "User-Agent"); EXPECT_EQ(request.headers[1].second, "TestClient/1.0"); EXPECT_TRUE(request.body.empty()); } TEST(HttpRequestParserTest, SimpleGetRequest2) { constexpr std::string_view http_request = "GET / HTTP/1.1\r\n" "Host: localhost:8080\r\n" "Connection: keep-alive\r\n" "Cache-Control: max-age=0\r\n" "sec-ch-ua: \"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"\r\n" "sec-ch-ua-mobile: ?0\r\n" "sec-ch-ua-platform: \"Linux\"\r\n" "Upgrade-Insecure-Requests: 1\r\n" "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 " "Safari/537.36\r\n" "Accept: " "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/" "signed-exchange;v=b3;q=0.7\r\n" "Sec-Fetch-Site: none\r\n" "Sec-Fetch-Mode: navigate\r\n" "Sec-Fetch-User: ?1\r\n" "Sec-Fetch-Dest: document\r\n" "Accept-Encoding: gzip, deflate, br, zstd\r\n" "Accept-Language: en-US,en;q=0.9,am;q=0.8\r\n" "\r\n"; HttpRequestParser parser; auto input = std::span(std::bit_cast(http_request.data()), http_request.size()); auto result = parser.parse(input); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 1); const HttpRequest &request = requests.front(); EXPECT_EQ(request.method, "GET"); EXPECT_EQ(request.url, "/"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 15); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "localhost:8080"); EXPECT_EQ(request.headers[1].first, "Connection"); EXPECT_EQ(request.headers[1].second, "keep-alive"); EXPECT_EQ(request.headers[2].first, "Cache-Control"); EXPECT_EQ(request.headers[2].second, "max-age=0"); EXPECT_EQ(request.headers[3].first, "sec-ch-ua"); EXPECT_EQ(request.headers[3].second, "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\""); EXPECT_EQ(request.headers[4].first, "sec-ch-ua-mobile"); EXPECT_EQ(request.headers[4].second, "?0"); EXPECT_EQ(request.headers[5].first, "sec-ch-ua-platform"); EXPECT_EQ(request.headers[5].second, "\"Linux\""); EXPECT_EQ(request.headers[6].first, "Upgrade-Insecure-Requests"); EXPECT_EQ(request.headers[6].second, "1"); EXPECT_EQ(request.headers[7].first, "User-Agent"); EXPECT_EQ(request.headers[7].second, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 " "Safari/537.36"); EXPECT_EQ(request.headers[8].first, "Accept"); EXPECT_EQ(request.headers[8].second, "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8," "application/signed-exchange;v=b3;q=0.7"); EXPECT_EQ(request.headers[9].first, "Sec-Fetch-Site"); EXPECT_EQ(request.headers[9].second, "none"); EXPECT_EQ(request.headers[10].first, "Sec-Fetch-Mode"); EXPECT_EQ(request.headers[10].second, "navigate"); EXPECT_EQ(request.headers[11].first, "Sec-Fetch-User"); EXPECT_EQ(request.headers[11].second, "?1"); EXPECT_EQ(request.headers[12].first, "Sec-Fetch-Dest"); EXPECT_EQ(request.headers[12].second, "document"); EXPECT_EQ(request.headers[13].first, "Accept-Encoding"); EXPECT_EQ(request.headers[13].second, "gzip, deflate, br, zstd"); EXPECT_EQ(request.headers[14].first, "Accept-Language"); EXPECT_EQ(request.headers[14].second, "en-US,en;q=0.9,am;q=0.8"); EXPECT_TRUE(request.body.empty()); } TEST(HttpRequestParserTest, PartialInput) { constexpr std::string_view http_request = "POST /submit HTTP/1.1\r\n" "Host: example.com\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Content-Length: 81\r\n" "Connection: close\r\n" "\r\n" "

Hello World

This is a test HTML payload.

"; constexpr std::string_view part1 = http_request.substr(0, 50); constexpr std::string_view part2 = http_request.substr(50); HttpRequestParser parser; auto result1 = parser.parse(std::span(std::bit_cast(part1.data()), part1.size())); ASSERT_TRUE(result1.has_value()); RequestList requests; parser.get_completed_requests(requests); EXPECT_TRUE(requests.empty()); // No complete requests yet auto result2 = parser.parse(std::span(std::bit_cast(part2.data()), part2.size())); ASSERT_TRUE(result2.has_value()); parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 1); const HttpRequest &request = requests.front(); EXPECT_EQ(request.method, "POST"); EXPECT_EQ(request.url, "/submit"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 4); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "example.com"); EXPECT_EQ(request.headers[1].first, "Content-Type"); EXPECT_EQ(request.headers[1].second, "text/html; charset=UTF-8"); EXPECT_EQ(request.headers[2].first, "Content-Length"); EXPECT_EQ(request.headers[2].second, "81"); EXPECT_EQ(request.headers[3].first, "Connection"); EXPECT_EQ(request.headers[3].second, "close"); EXPECT_EQ(request.body.size(), 81); const std::string expected_body = "

Hello World

This is a test HTML payload.

"; EXPECT_EQ(std::string_view(reinterpret_cast(request.body.data()), request.body.size()), expected_body); } TEST(HttpRequestParserTest, MultipleCompleteRequests) { constexpr std::string_view http_requests = "GET /first HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n" "POST /second HTTP/1.1\r\n" "Host: example.com\r\n" "Content-Length: 11\r\n" "\r\n" "Hello World"; HttpRequestParser parser; auto result = parser.parse( std::span(std::bit_cast(http_requests.data()), http_requests.size())); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 2); const HttpRequest &first_request = requests[0]; EXPECT_EQ(first_request.method, "GET"); EXPECT_EQ(first_request.url, "/first"); EXPECT_EQ(first_request.http_major, 1); EXPECT_EQ(first_request.http_minor, 1); ASSERT_EQ(first_request.headers.size(), 1); EXPECT_EQ(first_request.headers[0].first, "Host"); EXPECT_EQ(first_request.headers[0].second, "example.com"); EXPECT_TRUE(first_request.body.empty()); const HttpRequest &second_request = requests[1]; EXPECT_EQ(second_request.method, "POST"); EXPECT_EQ(second_request.url, "/second"); EXPECT_EQ(second_request.http_major, 1); EXPECT_EQ(second_request.http_minor, 1); ASSERT_EQ(second_request.headers.size(), 2); EXPECT_EQ(second_request.headers[0].first, "Host"); EXPECT_EQ(second_request.headers[0].second, "example.com"); EXPECT_EQ(second_request.headers[1].first, "Content-Length"); EXPECT_EQ(second_request.headers[1].second, "11"); ASSERT_EQ(second_request.body.size(), 11); EXPECT_EQ(std::string_view(reinterpret_cast(second_request.body.data()), second_request.body.size()), "Hello World"); } TEST(HttpRequestParserTest, EmptyInput) { HttpRequestParser parser; auto result = parser.parse(std::span()); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); EXPECT_TRUE(requests.empty()); } TEST(HttpRequestParserTest, IncompleteHeaders) { constexpr std::string_view http_request = "GET /incomplete HTTP/1.1\r\n" "Host: example.com\r\n" "User-Agent: TestClient/1.0"; // Missing final CRLF HttpRequestParser parser; auto result = parser.parse( std::span(std::bit_cast(http_request.data()), http_request.size())); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); EXPECT_TRUE(requests.empty()); // No complete requests yet } TEST(HttpRequestParserTest, IncompleteBody) { constexpr std::string_view http_request = "POST /submit HTTP/1.1\r\n" "Host: example.com\r\n" "Content-Length: 20\r\n" "\r\n" "Partial body data"; // Only 17 bytes instead of 20 HttpRequestParser parser; auto result = parser.parse( std::span(std::bit_cast(http_request.data()), http_request.size())); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); EXPECT_TRUE(requests.empty()); // No complete requests yet // Now provide the remaining body data constexpr std::string_view remaining_body = "123"; // 3 more bytes to complete auto result2 = parser.parse( std::span(std::bit_cast(remaining_body.data()), remaining_body.size())); ASSERT_TRUE(result2.has_value()); parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 1); const HttpRequest &request = requests.front(); EXPECT_EQ(request.method, "POST"); EXPECT_EQ(request.url, "/submit"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 2); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "example.com"); EXPECT_EQ(request.headers[1].first, "Content-Length"); EXPECT_EQ(request.headers[1].second, "20"); ASSERT_EQ(request.body.size(), 20); EXPECT_EQ(std::string_view(reinterpret_cast(request.body.data()), request.body.size()), "Partial body data123"); } TEST(HttpRequestParserTest, MalformedRequest) { constexpr std::string_view http_request = "GET /incomplete HTTP/1.1\r\n" "Host: example.com\r\n" "Content-Length: abc\r\n" // Invalid Content-Length "\r\n"; HttpRequestParser parser; auto result = parser.parse( std::span(std::bit_cast(http_request.data()), http_request.size())); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error(), "Invalid character in Content-Length"); } TEST(HttpRequestParserTest, FeedCharByChar) { constexpr std::string_view http_request = "GET /charbychar HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n"; HttpRequestParser parser; constexpr std::string_view view = http_request.substr(0, http_request.size() - 1); for (char ch : view) { auto result = parser.parse(std::span(std::bit_cast(&ch), 1)); // Should not produce a complete request until the final character ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); EXPECT_TRUE(requests.empty()); } // Now feed the final character char final_ch = http_request.back(); auto result = parser.parse(std::span(std::bit_cast(&final_ch), 1)); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 1); const HttpRequest &request = requests.front(); EXPECT_EQ(request.method, "GET"); EXPECT_EQ(request.url, "/charbychar"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 1); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "example.com"); EXPECT_TRUE(request.body.empty()); } TEST(HttpRequestParserTest, ResetParser) { constexpr std::string_view http_request1 = "GET /first HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n"; HttpRequestParser parser; auto result1 = parser.parse( std::span(std::bit_cast(http_request1.data()), http_request1.size())); ASSERT_TRUE(result1.has_value()); RequestList requests1; parser.get_completed_requests(requests1); ASSERT_EQ(requests1.size(), 1); parser.reset(); constexpr std::string_view http_request2 = "POST /second HTTP/1.1\r\n" "Host: example.com\r\n" "Content-Length: 5\r\n" "\r\n" "Hello"; auto result2 = parser.parse( std::span(std::bit_cast(http_request2.data()), http_request2.size())); ASSERT_TRUE(result2.has_value()); RequestList requests2; parser.get_completed_requests(requests2); ASSERT_EQ(requests2.size(), 1); const HttpRequest &request2 = requests2.front(); EXPECT_EQ(request2.method, "POST"); EXPECT_EQ(request2.url, "/second"); EXPECT_EQ(request2.http_major, 1); EXPECT_EQ(request2.http_minor, 1); ASSERT_EQ(request2.headers.size(), 2); EXPECT_EQ(request2.headers[0].first, "Host"); EXPECT_EQ(request2.headers[0].second, "example.com"); EXPECT_EQ(request2.headers[1].first, "Content-Length"); EXPECT_EQ(request2.headers[1].second, "5"); ASSERT_EQ(request2.body.size(), 5); EXPECT_EQ(std::string_view(reinterpret_cast(request2.body.data()), request2.body.size()), "Hello"); } TEST(HttpRequestParserTest, MultipleRequestsWithLastOnePartial) { constexpr std::string_view http_requests = "GET /first HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n" "GET /first HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n" "GET /first HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n" "POST /second HTTP/1.1\r\n" "Host: example.com\r\n" "Content-Length: 11\r\n" "\r\n" "Hello"; // Incomplete body HttpRequestParser parser; auto result = parser.parse( std::span(std::bit_cast(http_requests.data()), http_requests.size())); ASSERT_TRUE(result.has_value()); RequestList requests; parser.get_completed_requests(requests); ASSERT_EQ(requests.size(), 3); // Only 3 complete requests for (int i = 0; i < 3; ++i) { const HttpRequest &request = requests[i]; EXPECT_EQ(request.method, "GET"); EXPECT_EQ(request.url, "/first"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 1); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "example.com"); EXPECT_TRUE(request.body.empty()); } // Now provide the remaining body data constexpr std::string_view remaining_body = " World"; // 6 more bytes to complete auto result2 = parser.parse( std::span(std::bit_cast(remaining_body.data()), remaining_body.size())); ASSERT_TRUE(result2.has_value()); RequestList requests2; parser.get_completed_requests(requests2); ASSERT_EQ(requests2.size(), 1); const HttpRequest &request = requests2.front(); EXPECT_EQ(request.method, "POST"); EXPECT_EQ(request.url, "/second"); EXPECT_EQ(request.http_major, 1); EXPECT_EQ(request.http_minor, 1); ASSERT_EQ(request.headers.size(), 2); EXPECT_EQ(request.headers[0].first, "Host"); EXPECT_EQ(request.headers[0].second, "example.com"); EXPECT_EQ(request.headers[1].first, "Content-Length"); EXPECT_EQ(request.headers[1].second, "11"); ASSERT_EQ(request.body.size(), 11); EXPECT_EQ(std::string_view(reinterpret_cast(request.body.data()), request.body.size()), "Hello World"); }