#include "httpparser.h" #include #define ENUM_OUT(arg) \ case type ::arg: { \ out << #arg; \ return; \ } template <> void Out(IOutputStream& out, THttpParserBase::States st) { using type = THttpParserBase::States; switch (st) { ENUM_OUT(hp_error) ENUM_OUT(hp_eof) ENUM_OUT(hp_in_header) ENUM_OUT(hp_read_alive) ENUM_OUT(hp_read_closed) ENUM_OUT(hp_begin_chunk_header) ENUM_OUT(hp_chunk_header) ENUM_OUT(hp_read_chunk) } } namespace { class TSomethingLikeFakeCheck; using TTestHttpParser = THttpParser; class TSomethingLikeFakeCheck { TString Body_; public: const TString& Body() const { return Body_; } // other functions are not really called by THttpParser void CheckDocPart(const void* buf, size_t len, THttpHeader* /* header */) { TString s(static_cast(buf), len); Cout << "State = " << static_cast(this)->GetState() << ", CheckDocPart(" << s.Quote() << ")\n"; Body_ += s; } }; } Y_UNIT_TEST_SUITE(TestHttpParser) { Y_UNIT_TEST(TestTrivialRequest) { const TString blob{ "GET /search?q=hi HTTP/1.1\r\n" "Host: www.google.ru:8080 \r\n" "\r\n"}; THttpHeader hdr; THttpParser<> parser; parser.Init(&hdr); parser.Parse((void*)blob.data(), blob.size()); UNIT_ASSERT_EQUAL(parser.GetState(), parser.hp_error); // can't parse request as response } // XXX: `entity_size` is i32 and `content_length` is i64! Y_UNIT_TEST(TestTrivialResponse) { const TString blob{ "HTTP/1.1 200 Ok\r\n" "Content-Length: 2\r\n" "\r\n" "OK"}; THttpHeader hdr; TTestHttpParser parser; parser.Init(&hdr); parser.Parse((void*)blob.data(), blob.size()); UNIT_ASSERT_EQUAL(parser.GetState(), parser.hp_eof); UNIT_ASSERT_EQUAL(parser.Body(), "OK"); UNIT_ASSERT_EQUAL(hdr.header_size, strlen( "HTTP/1.1 200 Ok\r\n" "Content-Length: 2\r\n" "\r\n")); UNIT_ASSERT_EQUAL(hdr.entity_size, strlen("OK")); } // XXX: `entity_size` is off by one in TE:chunked case. Y_UNIT_TEST(TestChunkedResponse) { const TString blob{ "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "2\r\n" "Ok\r\n" "8\r\n" "AllRight\r\n" "0\r\n" "\r\n"}; THttpHeader hdr; TTestHttpParser parser; parser.Init(&hdr); parser.Parse((void*)blob.data(), blob.size()); UNIT_ASSERT_EQUAL(parser.GetState(), parser.hp_eof); UNIT_ASSERT_EQUAL(parser.Body(), "OkAllRight"); UNIT_ASSERT_EQUAL(hdr.header_size, strlen( "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n")); const int off_by_one_err = -1; // XXX: it really looks so UNIT_ASSERT_EQUAL(hdr.entity_size + off_by_one_err, strlen( "2\r\n" "Ok\r\n" "8\r\n" "AllRight\r\n" "0\r\n" "\r\n")); } static const TString PipelineClenBlob_{ "HTTP/1.1 200 Ok\r\n" "Content-Length: 4\r\n" "\r\n" "OK\r\n" "HTTP/1.1 200 Zz\r\n" "Content-Length: 4\r\n" "\r\n" "ZZ\r\n"}; void AssertPipelineClen(TTestHttpParser & parser, const THttpHeader& hdr) { UNIT_ASSERT_EQUAL(parser.GetState(), parser.hp_eof); UNIT_ASSERT_EQUAL(4, hdr.content_length); UNIT_ASSERT_EQUAL(hdr.header_size, strlen( "HTTP/1.1 200 Ok\r\n" "Content-Length: 4\r\n" "\r\n")); } Y_UNIT_TEST(TestPipelineClenByteByByte) { const TString& blob = PipelineClenBlob_; THttpHeader hdr; TTestHttpParser parser; parser.Init(&hdr); for (size_t i = 0; i < blob.size(); ++i) { const TStringBuf d{blob, i, 1}; parser.Parse((void*)d.data(), d.size()); Cout << TString(d).Quote() << " -> " << parser.GetState() << Endl; } AssertPipelineClen(parser, hdr); UNIT_ASSERT_EQUAL(parser.Body(), "OK\r\n"); UNIT_ASSERT_EQUAL(hdr.entity_size, hdr.content_length); } // XXX: Content-Length is ignored, Body() looks unexpected! Y_UNIT_TEST(TestPipelineClenOneChunk) { const TString& blob = PipelineClenBlob_; THttpHeader hdr; TTestHttpParser parser; parser.Init(&hdr); parser.Parse((void*)blob.data(), blob.size()); AssertPipelineClen(parser, hdr); UNIT_ASSERT_EQUAL(parser.Body(), "OK\r\n" "HTTP/1.1 200 Zz\r\n" "Content-Length: 4\r\n" "\r\n" "ZZ\r\n"); UNIT_ASSERT_EQUAL(hdr.entity_size, strlen( "OK\r\n" "HTTP/1.1 200 Zz\r\n" "Content-Length: 4\r\n" "\r\n" "ZZ\r\n")); } static const TString PipelineChunkedBlob_{ "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "2\r\n" "Ok\r\n" "8\r\n" "AllRight\r\n" "0\r\n" "\r\n" "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "2\r\n" "Yo\r\n" "8\r\n" "uWin!Iam\r\n" "0\r\n" "\r\n"}; void AssertPipelineChunked(TTestHttpParser & parser, const THttpHeader& hdr) { UNIT_ASSERT_EQUAL(parser.GetState(), parser.hp_eof); UNIT_ASSERT_EQUAL(parser.Body(), "OkAllRight"); UNIT_ASSERT_EQUAL(-1, hdr.content_length); UNIT_ASSERT_EQUAL(hdr.header_size, strlen( "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n")); const int off_by_one_err = -1; UNIT_ASSERT_EQUAL(hdr.entity_size + off_by_one_err, strlen( "2\r\n" "Ok\r\n" "8\r\n" "AllRight\r\n" "0\r\n" "\r\n")); } Y_UNIT_TEST(TestPipelineChunkedByteByByte) { const TString& blob = PipelineChunkedBlob_; THttpHeader hdr; TTestHttpParser parser; parser.Init(&hdr); for (size_t i = 0; i < blob.size(); ++i) { const TStringBuf d{blob, i, 1}; parser.Parse((void*)d.data(), d.size()); Cout << TString(d).Quote() << " -> " << parser.GetState() << Endl; if (blob.size() / 2 - 1 <= i) // last \n sets EOF UNIT_ASSERT_EQUAL(parser.GetState(), parser.hp_eof); } AssertPipelineChunked(parser, hdr); } Y_UNIT_TEST(TestPipelineChunkedOneChunk) { const TString& blob = PipelineChunkedBlob_; THttpHeader hdr; TTestHttpParser parser; parser.Init(&hdr); parser.Parse((void*)blob.data(), blob.size()); AssertPipelineChunked(parser, hdr); } }