From 8b8a28c4ec4258e60cf5b8ac5c36baa52f584e86 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 3 Jul 2023 16:27:36 +0200 Subject: [PATCH] Added some HTTP parser unit tests, fixed HTTP support for zero content length, added gcovr file for coverage reports --- gcovr.cfg | 3 +++ lib/http_parser.cpp | 9 ++++--- lib/http_parser.h | 1 + test/http_parser.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++ test/meson.build | 15 +++++++++++ 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 gcovr.cfg create mode 100644 test/http_parser.cpp diff --git a/gcovr.cfg b/gcovr.cfg new file mode 100644 index 00000000..385b37bd --- /dev/null +++ b/gcovr.cfg @@ -0,0 +1,3 @@ +filter = src/ +filter = lib/ + diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 913a8e22..98d591f7 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -45,6 +45,7 @@ void HTTP::Parser::CleanPreserveHeaders(){ protocol = "HTTP/1.1"; body.clear(); length = 0; + knownLength = false; vars.clear(); } @@ -619,11 +620,13 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer, Util::DataCallback &cb){ if (tmpA.size() == 0){ seenHeaders = true; body.clear(); + knownLength = false; if (GetHeader("Content-Length") != ""){ length = atoi(GetHeader("Content-Length").c_str()); if (!bodyCallback && (&cb == &Util::defaultDataCallback) && body.capacity() < length){ body.reserve(length); } + knownLength = true; } if (GetHeader("Transfer-Encoding") == "chunked"){ getChunks = true; @@ -645,7 +648,7 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer, Util::DataCallback &cb){ unsigned int code = atoi(url.data()); if ((code >= 100 && code < 200) || code == 204 || code == 304){return true;} } - if (length > 0 && !getChunks){ + if (knownLength && !getChunks){ unsigned int toappend = length - body.length(); // limit the amount of bytes that will be appended to the amount there @@ -673,8 +676,8 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer, Util::DataCallback &cb){ currentLength += toappend; } if (length == body.length()){ - // parse POST variables - if (method == "POST"){parseVars(body, vars);} + // parse POST body if the content type is URLEncoded + if (method == "POST" && GetHeader("Content-Type") == "application/x-www-form-urlencoded"){parseVars(body, vars);} return true; }else{ return false; diff --git a/lib/http_parser.h b/lib/http_parser.h index 35845aac..b45aedb9 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -57,6 +57,7 @@ namespace HTTP{ std::string url; std::string protocol; unsigned int length; + bool knownLength; unsigned int currentLength; bool headerOnly; ///< If true, do not parse body if the length is a known size. bool bufferChunks; diff --git a/test/http_parser.cpp b/test/http_parser.cpp new file mode 100644 index 00000000..26e9783f --- /dev/null +++ b/test/http_parser.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +int main(int argc, char ** argv){ + bool preMade = false; + Socket::Connection C(1, 0); // Open stdio by default + // If there is a T_HTTP environment variable, use that as input instead + if (getenv("T_HTTP")){ + preMade = true; + // Keep stdio open, only drop the reference to it + C.drop(); + // Create a pipe and reconnect the socket to it + int p[2]; + if (pipe(p)){ + FAIL_MSG("Could not open pipe!"); + return 1; + } + C.open(p[1], p[0]); + // Write the T_HTTP env contents into the pipe + C.SendNow(getenv("T_HTTP")); + // Close the write end if we're not lingering + if (!getenv("T_LINGER")){close(p[1]);} + } + + HTTP::Parser p; + int counter = 0; + C.setBlocking(false); + uint64_t lastData = Util::bootMS(); + do { + if (C.spool()){ + lastData = Util::bootMS(); + while (p.Read(C)){ + INFO_MSG("Read a HTTP message: %s %s %s (%zu bytes)", p.method.c_str(), p.url.c_str(), p.protocol.c_str(), p.body.size()); + ++counter; + p.Clean(); + } + }else{ + // premade requests will instantly time out, others after 10 seconds + if (preMade){break;} + if (Util::bootMS() > lastData + 10000){ + WARN_MSG("Read timeout, aborting"); + break; + } + Util::sleep(5); + } + }while(C); + while (p.Read(C)){ + INFO_MSG("Read a HTTP message: %s %s %s (%zu bytes)", p.method.c_str(), p.url.c_str(), p.protocol.c_str(), p.body.size()); + ++counter; + p.Clean(); + } + + INFO_MSG("Total messages: %d", counter); + + if (getenv("T_COUNT")){ + if (counter != atoi(getenv("T_COUNT"))){ + return 1; + } + } + + return 0; +} + diff --git a/test/meson.build b/test/meson.build index d127db1d..adc96929 100644 --- a/test/meson.build +++ b/test/meson.build @@ -148,6 +148,21 @@ test('DTSC Sizing Test', dtsc_sizing_test) bitwritertest = executable('bitwritertest', 'bitwriter.cpp', dependencies: libmist_dep) test('bitWriter Test', bitwritertest) +httpparsertest = executable('httpparsertest', 'http_parser.cpp', dependencies: libmist_dep) +test('GET request for /', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'GET / HTTP/1.1\n\n', 'T_COUNT':'1'}) +test('GET request for / with carriage returns', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'GET / HTTP/1.1\r\n\r\n', 'T_COUNT':'1'}) +test('POST request to /, raw body', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'POST / HTTP/1.1\nContent-Length: 4\nContent-Type: text/plain\n\ntest', 'T_COUNT':'1'}) +test('POST request to /, urlencoded body', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'POST / HTTP/1.1\nContent-Length: 28\nContent-Type: application/x-www-form-urlencoded\n\nfoo=bar&banana=sauce&cookies', 'T_COUNT':'1'}) +test('Blank HTTP response, closed connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nDate: Thu, 15 Jun 2023 21:34:06 GMT\nContent-Length: 0\n\n', 'T_COUNT':'1'}) +test('Blank HTTP response, lingering connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nDate: Thu, 15 Jun 2023 21:34:06 GMT\nContent-Length: 0\n\n', 'T_LINGER':'1', 'T_COUNT':'1'}) +test('Simple HTTP response, closed connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nDate: Thu, 15 Jun 2023 21:34:06 GMT\nContent-Length: 4\n\ntest', 'T_COUNT':'1'}) +test('Simple HTTP response, lingering connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nDate: Thu, 15 Jun 2023 21:34:06 GMT\nContent-Length: 4\n\ntest', 'T_LINGER':'1', 'T_COUNT':'1'}) +test('Simple HTTP response, no length, closed connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nDate: Thu, 15 Jun 2023 21:34:06 GMT\n\ntest', 'T_COUNT':'1'}) +test('Simple HTTP response, no length, lingering connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nDate: Thu, 15 Jun 2023 21:34:06 GMT\n\ntest', 'T_LINGER':'1', 'T_COUNT':'0'}) +test('Chunked HTTP response, closed connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\nt\n3\nest\n0\n\n', 'T_COUNT':'1'}) +test('Chunked HTTP response, lingering connection', httpparsertest, suite: 'HTTP parser', env: {'T_HTTP':'HTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\nt\n3\nest\n0\n\n', 'T_LINGER':'1', 'T_COUNT':'1'}) + + #abst_test = executable('abst_test', 'abst_test.cpp', dependencies: libmist_dep) #test('MP4::ABST Test', abst_test)