#include "httpfsm.h" #include "library-htfetch_ut_hreflang_in.h" #include "library-htfetch_ut_hreflang_out.h" #include #include #include class THttpHeaderParserTestSuite: public TTestBase { UNIT_TEST_SUITE(THttpHeaderParserTestSuite); UNIT_TEST(TestRequestHeader); UNIT_TEST(TestSplitRequestHeader); UNIT_TEST(TestTrailingData); UNIT_TEST(TestProxyRequestHeader); UNIT_TEST(TestIncorrectRequestHeader); UNIT_TEST(TestLastModified); UNIT_TEST(TestLastModifiedCorrupted); UNIT_TEST(TestResponseHeaderOnRequest); UNIT_TEST(TestRequestHeaderOnResponse); UNIT_TEST(TestXRobotsTagUnknownTags); UNIT_TEST(TestXRobotsTagMyBot); UNIT_TEST(TestXRobotsTagOtherBot); UNIT_TEST(TestXRobotsTagUnavailableAfterAware); UNIT_TEST(TestXRobotsTagUnavailableAfterWorks); UNIT_TEST(TestXRobotsTagOverridePriority); UNIT_TEST(TestXRobotsTagDoesNotBreakCharset); UNIT_TEST(TestXRobotsTagAllowsMultiline); UNIT_TEST(TestRelCanonical); UNIT_TEST(TestHreflang); UNIT_TEST(TestHreflangOnLongInput); UNIT_TEST(TestMimeType); UNIT_TEST(TestRepeatedContentEncoding); UNIT_TEST_SUITE_END(); private: THolder httpHeaderParser; private: void TestStart(); void TestFinish(); public: void TestRequestHeader(); void TestSplitRequestHeader(); void TestTrailingData(); void TestProxyRequestHeader(); void TestIncorrectRequestHeader(); void TestLastModified(); void TestLastModifiedCorrupted(); void TestResponseHeaderOnRequest(); void TestRequestHeaderOnResponse(); void TestXRobotsTagUnknownTags(); void TestXRobotsTagMyBot(); void TestXRobotsTagOtherBot(); void TestXRobotsTagUnavailableAfterAware(); void TestXRobotsTagUnavailableAfterWorks(); void TestXRobotsTagOverridePriority(); void TestXRobotsTagDoesNotBreakCharset(); void TestXRobotsTagAllowsMultiline(); void TestRelCanonical(); void TestHreflang(); void TestHreflangOnLongInput(); void TestMimeType(); void TestRepeatedContentEncoding(); }; void THttpHeaderParserTestSuite::TestStart() { httpHeaderParser.Reset(new THttpHeaderParser()); } void THttpHeaderParserTestSuite::TestFinish() { httpHeaderParser.Reset(); } void THttpHeaderParserTestSuite::TestRequestHeader() { TestStart(); THttpRequestHeader httpRequestHeader; httpHeaderParser->Init(&httpRequestHeader); const char* request = "GET /search?q=hi HTTP/1.1\r\n" "Host: www.google.ru:8080\r\n\r\n"; i32 result = httpHeaderParser->Execute(request, strlen(request)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, "www.google.ru:8080"), 0); UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "/search?q=hi"); UNIT_ASSERT_EQUAL(httpRequestHeader.GetUrl(), "http://www.google.ru:8080/search?q=hi"); UNIT_ASSERT_EQUAL(httpHeaderParser->lastchar - request + 1, (i32)strlen(request)); UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_response_timeout, DEFAULT_RESPONSE_TIMEOUT); UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_request_priority, DEFAULT_REQUEST_PRIORITY); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_sourcename, ""), 0); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_requesttype, ""), 0); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_fetchoptions, ""), 0); TestFinish(); UNIT_ASSERT_EQUAL(httpRequestHeader.max_age, DEFAULT_MAX_AGE); } void THttpHeaderParserTestSuite::TestSplitRequestHeader() { TestStart(); const char* request = "GET /search?q=hi HTTP/1.1\r\n" "Host: www.google.ru:8080 \r\n" "\r\n"; const size_t rlen = strlen(request); for (size_t n1 = 0; n1 < rlen; n1++) { for (size_t n2 = n1; n2 < rlen; n2++) { TString s1{request, 0, n1}; TString s2{request, n1, n2 - n1}; TString s3{request, n2, rlen - n2}; UNIT_ASSERT_EQUAL(s1 + s2 + s3, request); THttpRequestHeader httpRequestHeader; UNIT_ASSERT(0 == httpHeaderParser->Init(&httpRequestHeader)); i32 result = httpHeaderParser->Execute(s1); UNIT_ASSERT_EQUAL(result, 1); result = httpHeaderParser->Execute(s2); UNIT_ASSERT_EQUAL(result, 1); result = httpHeaderParser->Execute(s3); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, "www.google.ru:8080"), 0); UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "/search?q=hi"); } } TestFinish(); } void THttpHeaderParserTestSuite::TestTrailingData() { TestStart(); THttpRequestHeader httpRequestHeader; UNIT_ASSERT(0 == httpHeaderParser->Init(&httpRequestHeader)); const char* request = "GET /search?q=hi HTTP/1.1\r\n" "Host: www.google.ru:8080\r\n" "\r\n" "high.ru"; i32 result = httpHeaderParser->Execute(request, strlen(request)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, "www.google.ru:8080"), 0); UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "/search?q=hi"); UNIT_ASSERT_EQUAL(TString(httpHeaderParser->lastchar + 1), "high.ru"); UNIT_ASSERT_EQUAL(httpRequestHeader.http_minor, 1); UNIT_ASSERT_EQUAL(httpRequestHeader.transfer_chunked, -1); UNIT_ASSERT_EQUAL(httpRequestHeader.content_length, -1); UNIT_ASSERT_EQUAL(httpRequestHeader.connection_closed, -1); TestFinish(); } void THttpHeaderParserTestSuite::TestProxyRequestHeader() { TestStart(); THttpRequestHeader httpRequestHeader; httpHeaderParser->Init(&httpRequestHeader); const char* request = "GET http://www.google.ru:8080/search?q=hi HTTP/1.1\r\n" "X-Yandex-Response-Timeout: 1000\r\n" "X-Yandex-Request-Priority: 2\r\n" "X-Yandex-Sourcename: orange\r\n" "X-Yandex-Requesttype: userproxy\r\n" "X-Yandex-FetchOptions: d;c\r\n" "Cache-control: max-age=100\r\n" "If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT\r\n" "User-Agent: Yandex/1.01.001 (compatible; Win16; I)\r\n" "From: webadmin@yandex.ru\r\n\r\n"; i32 result = httpHeaderParser->Execute(request, strlen(request)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpRequestHeader.http_method, HTTP_METHOD_GET); UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_response_timeout, 1000); UNIT_ASSERT_EQUAL(httpRequestHeader.x_yandex_request_priority, 2); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_sourcename, "orange"), 0); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_requesttype, "userproxy"), 0); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.x_yandex_fetchoptions, "d;c"), 0); UNIT_ASSERT_EQUAL(httpRequestHeader.max_age, 100); UNIT_ASSERT_VALUES_EQUAL(httpRequestHeader.if_modified_since, TInstant::ParseIso8601Deprecated("1994-10-29 19:43:31Z").TimeT()); UNIT_ASSERT_EQUAL(httpRequestHeader.request_uri, "http://www.google.ru:8080/search?q=hi"); UNIT_ASSERT(httpRequestHeader.GetUrl() == "http://www.google.ru:8080/search?q=hi"); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.host, ""), 0); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.from, "webadmin@yandex.ru"), 0); UNIT_ASSERT_EQUAL(strcmp(httpRequestHeader.user_agent, "Yandex/1.01.001 (compatible; Win16; I)"), 0); UNIT_ASSERT_EQUAL(httpHeaderParser->lastchar - request + 1, (i32)strlen(request)); TestFinish(); } void THttpHeaderParserTestSuite::TestIncorrectRequestHeader() { TestStart(); THttpRequestHeader httpRequestHeader; httpHeaderParser->Init(&httpRequestHeader); const char* request = "GET /search?q=hi HTP/1.1\r\n" "Host: www.google.ru:8080\r\n\r\n"; i32 result = httpHeaderParser->Execute(request, strlen(request)); UNIT_ASSERT(result != 2); TestFinish(); } void THttpHeaderParserTestSuite::TestLastModified() { TestStart(); THttpHeader h; UNIT_ASSERT(0 == httpHeaderParser->Init(&h)); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Last-Modified: Thu, 13 Aug 2009 14:27:08 GMT\r\n\r\n"; UNIT_ASSERT(2 == httpHeaderParser->Execute(headers, strlen(headers))); UNIT_ASSERT_VALUES_EQUAL( TInstant::ParseIso8601Deprecated("2009-08-13 14:27:08Z").TimeT(), h.http_time); TestFinish(); } void THttpHeaderParserTestSuite::TestLastModifiedCorrupted() { TestStart(); THttpHeader h; UNIT_ASSERT(0 == httpHeaderParser->Init(&h)); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Last-Modified: Thu, 13 Aug 2009 14:\r\n\r\n"; UNIT_ASSERT(2 == httpHeaderParser->Execute(headers, strlen(headers))); UNIT_ASSERT(h.http_time < 0); // XXX: don't understand what is the proper value TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagUnknownTags() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "x-robots-tag: asdfasdf asdf asdf,,, , noindex,noodpXXX , NOFOLLOW ,noodpnofollow\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 3); UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "00xxx"); TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagMyBot() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "x-robots-tag: yandex: noindex, nofollow\r\n" "x-robots-tag: yandexbot: noarchive, noodp\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 15); UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "0000x"); TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagOtherBot() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "x-robots-tag: google: noindex, nofollow\r\n" "x-robots-tag: googlebot: noarchive, noodp\r\n" "x-robots-tag: !still(-other) bot_: foo, noyaca\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 0); UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "xxxxx"); TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagUnavailableAfterAware() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); // проверяем только что unavailable_after ничего не ломает const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "x-robots-tag: unavailable_after: 01 Jan 2999 00:00 UTC, noindex, nofollow\r\n" "x-robots-tag: yandex: unavailable_after: 01 Jan 2999 00:00 UTC, noarchive, noodp\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 15); UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "0000x"); TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagUnavailableAfterWorks() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); // пока не поддерживается const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "x-robots-tag: unavailable_after: 01 Jan 2000 00:00 UTC\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); //UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 1); //UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "0xxxx"); TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagOverridePriority() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "x-robots-tag: all, none\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "11xxx"); UNIT_ASSERT_EQUAL(httpHeader.x_robots_tag, 3); // NOTE legacy behavior, should be 0 as `all` overrides TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagDoesNotBreakCharset() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "X-Robots-Tag: noarchive\r\n" "Content-Type: application/json; charset=utf-8\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.mime_type, static_cast(MIME_JSON)); UNIT_ASSERT_EQUAL(httpHeader.charset, static_cast(CODES_UTF8)); TestFinish(); } void THttpHeaderParserTestSuite::TestXRobotsTagAllowsMultiline() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "X-Robots-Tag\r\n" " :\r\n" " unavailable_since\r\n" " :\r\n" " ,\r\n" " unavailable_since\r\n" " :\r\n" " 01 Jan 2000\r\n" " 00:00 UTC\r\n" " ,\r\n" " yandexbot\r\n" " :\r\n" " noindex\r\n" " ,\r\n" " garbage\r\n" " ,\r\n" " nofollow\r\n" " ,\r\n" " other\r\n" " bot\r\n" " :\r\n" " noarchive\r\n" " ,\r\n" "Content-Type: application/json; charset=utf-8\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.x_robots_state, "00xxx"); UNIT_ASSERT_EQUAL(httpHeader.mime_type, static_cast(MIME_JSON)); UNIT_ASSERT_EQUAL(httpHeader.charset, static_cast(CODES_UTF8)); TestFinish(); } void THttpHeaderParserTestSuite::TestHreflang() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "link: ; rel='alternate'; hreflang='x-default'\r\n" "link: ;rel = 'alternate' ;hreflang = en_GB \r\n" "link: ;hreflang = ru_RU.KOI8-r ;rel = 'alternate' \r\n" "\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_VALUES_EQUAL(result, 2); // UNIT_ASSERT_VALUES_EQUAL(strcmp(httpHeader.hreflangs, "x-default http://www.high.ru/;"), 0); UNIT_ASSERT_VALUES_EQUAL(httpHeader.hreflangs, "x-default http://www.high.ru/\ten_GB http://www.high.ru/en.html\tru_RU.KOI8-r http://www.high.ru/ru.html"); TestFinish(); } void THttpHeaderParserTestSuite::TestHreflangOnLongInput() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); TStringBuf testInput(hreflang_ut_in); TStringBuf testOut(hreflang_ut_out); i32 result = httpHeaderParser->Execute(testInput.data(), testInput.size()); UNIT_ASSERT_VALUES_EQUAL(result, 2); UNIT_ASSERT_VALUES_EQUAL(httpHeader.hreflangs, testOut); TestFinish(); } void THttpHeaderParserTestSuite::TestRelCanonical() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Link: ; rel = \"canonical\"\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.rel_canonical, "http://yandex.ru"); TestFinish(); } void THttpHeaderParserTestSuite::TestResponseHeaderOnRequest() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* request = "GET /search?q=hi HTP/1.1\r\n" "Host: www.google.ru:8080\r\n\r\n"; i32 result = httpHeaderParser->Execute(request, strlen(request)); UNIT_ASSERT_EQUAL(result, -3); TestFinish(); } void THttpHeaderParserTestSuite::TestRequestHeaderOnResponse() { TestStart(); THttpRequestHeader httpRequestHeader; httpHeaderParser->Init(&httpRequestHeader); const char* response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Last-Modified: Thu, 13 Aug 2009 14:\r\n\r\n"; i32 result = httpHeaderParser->Execute(response, strlen(response)); UNIT_ASSERT_EQUAL(result, -3); TestFinish(); } void THttpHeaderParserTestSuite::TestMimeType() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char* headers = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=utf-8\r\n\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.mime_type, static_cast(MIME_JSON)); UNIT_ASSERT_EQUAL(httpHeader.charset, static_cast(CODES_UTF8)); TestFinish(); } void THttpHeaderParserTestSuite::TestRepeatedContentEncoding() { TestStart(); THttpHeader httpHeader; httpHeaderParser->Init(&httpHeader); const char *headers = "HTTP/1.1 200 OK\r\n" "Server: nginx\r\n" "Date: Mon, 15 Oct 2018 10:40:44 GMT\r\n" "Content-Type: text/plain\r\n" "Transfer-Encoding: chunked\r\n" "Connection: keep-alive\r\n" "Last-Modified: Mon, 15 Oct 2018 03:48:54 GMT\r\n" "ETag: W/\"5bc40e26-a956d\"\r\n" "X-Autoru-LB: lb-03-sas.prod.vertis.yandex.net\r\n" "Content-Encoding: gzip\r\n" "Content-Encoding: gzip\r\n" "X-UA-Bot: 1\r\n" "\r\n"; i32 result = httpHeaderParser->Execute(headers, strlen(headers)); UNIT_ASSERT_EQUAL(result, 2); UNIT_ASSERT_EQUAL(httpHeader.error, 0); UNIT_ASSERT_EQUAL(httpHeader.compression_method, 3); TestFinish(); } UNIT_TEST_SUITE_REGISTRATION(THttpHeaderParserTestSuite); Y_UNIT_TEST_SUITE(TestHttpChunkParser) { static THttpChunkParser initParser() { THttpChunkParser parser; parser.Init(); return parser; } static THttpChunkParser parseByteByByte(const TStringBuf& blob, const TVector& states) { UNIT_ASSERT(states.size() <= blob.size()); THttpChunkParser parser{initParser()}; for (size_t n = 0; n < states.size(); n++) { const TStringBuf d{blob, n, 1}; int code = parser.Execute(d.data(), d.size()); Cout << TString(d).Quote() << " " << code << Endl; UNIT_ASSERT_EQUAL(code, states[n]); } return parser; } static THttpChunkParser parseBytesWithLastState(const TStringBuf& blob, const int last_state) { TVector states(blob.size() - 1, 1); states.push_back(last_state); return parseByteByByte(blob, states); } Y_UNIT_TEST(TestWithoutEolHead) { const TStringBuf blob{ "4\r\n" "____\r\n"}; TVector states{ -1, /* 1, -1, 1, -1, 1, -1, 1, -1 */}; // as soon as error happens parser state should be considered // undefined, state is meaningless after the very first `-1` // moreover, testenv produces `states[1] == -1` for this input and // my local build produces `states[1] == 1`. parseByteByByte(blob, states); } Y_UNIT_TEST(TestTrivialChunk) { const TStringBuf blob{ "\r\n" "4\r\n"}; THttpChunkParser parser(parseBytesWithLastState(blob, 2)); UNIT_ASSERT_EQUAL(parser.chunk_length, 4); UNIT_ASSERT_EQUAL(parser.cnt64, 4); } Y_UNIT_TEST(TestNegative) { const TStringBuf blob{ "\r\n" "-1"}; TVector states{ 1, 1, -1, /* 1 */}; parseByteByByte(blob, states); } Y_UNIT_TEST(TestLeadingZero) { const TStringBuf blob{ "\r\n" "042\r\n"}; THttpChunkParser parser(parseBytesWithLastState(blob, 2)); UNIT_ASSERT_EQUAL(parser.chunk_length, 0x42); } Y_UNIT_TEST(TestIntOverflow) { const TStringBuf blob{ "\r\n" "deadbeef"}; THttpChunkParser parser(parseBytesWithLastState(blob, -2)); UNIT_ASSERT_EQUAL(parser.chunk_length, 0); UNIT_ASSERT_EQUAL(parser.cnt64, 0xdeadbeef); } Y_UNIT_TEST(TestTrivialChunkWithTail) { const TStringBuf blob{ "\r\n" "4\r\n" "_" // first byte of the chunk }; TVector states{ 1, 1, 1, 1, 2, -1}; parseByteByByte(blob, states); } Y_UNIT_TEST(TestLastChunk) { // NB: current parser does not permit whitespace before `foo`, // but I've never seen the feature in real-life traffic const TStringBuf blob{ "\r\n" "000 ;foo = bar \r\n" "Trailer: bar\r\n" "\r\n"}; THttpChunkParser parser(parseBytesWithLastState(blob, 2)); UNIT_ASSERT_EQUAL(parser.chunk_length, 0); } }