#include "httpparser.h"
#include <library/cpp/testing/unittest/registar.h>
#define ENUM_OUT(arg) \
case type ::arg: { \
out << #arg; \
return; \
}
template <>
void Out<THttpParserBase::States>(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<TSomethingLikeFakeCheck>;
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<const char*>(buf), len);
Cout << "State = " << static_cast<TTestHttpParser*>(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);
}
}