// // Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Test that header file is self-contained. #include #include "fail_parser.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace beast { namespace http { class basic_parser_v1_test : public beast::unit_test::suite { public: struct cb_req_checker { bool method = false; bool uri = false; bool request = false; }; struct cb_res_checker { bool reason = false; bool response = false; }; template struct cb_checker : public basic_parser_v1> , std::conditional::type { bool start = false; bool field = false; bool value = false; bool headers = false; bool body = false; bool complete = false; private: friend class basic_parser_v1>; void on_start(error_code&) { this->start = true; } void on_method(boost::string_ref const&, error_code&) { this->method = true; } void on_uri(boost::string_ref const&, error_code&) { this->uri = true; } void on_reason(boost::string_ref const&, error_code&) { this->reason = true; } void on_request(error_code&) { this->request = true; } void on_response(error_code&) { this->response = true; } void on_field(boost::string_ref const&, error_code&) { field = true; } void on_value(boost::string_ref const&, error_code&) { value = true; } int on_headers(std::uint64_t, error_code&) { headers = true; return 0; } void on_body(boost::string_ref const&, error_code&) { body = true; } void on_complete(error_code&) { complete = true; } }; // Check that all callbacks are invoked void testCallbacks() { using boost::asio::buffer; { cb_checker p; error_code ec; std::string const s = "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "Content-Length: 1\r\n" "\r\n" "*"; p.write(buffer(s), ec); if(expect(! ec)) { expect(p.start); expect(p.method); expect(p.uri); expect(p.request); expect(p.field); expect(p.value); expect(p.headers); expect(p.body); expect(p.complete); } } { cb_checker p; error_code ec; std::string const s = "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Content-Length: 1\r\n" "\r\n" "*"; p.write(buffer(s), ec); if(expect(! ec)) { expect(p.start); expect(p.reason); expect(p.response); expect(p.field); expect(p.value); expect(p.headers); expect(p.body); expect(p.complete); } } } //-------------------------------------------------------------------------- template static void for_split(boost::string_ref const& s, F const& f) { using boost::asio::buffer; using boost::asio::buffer_copy; for(std::size_t i = 0; i < s.size(); ++i) { // Use separately allocated buffers so // address sanitizer has something to chew on. // auto const n1 = s.size() - i; auto const n2 = i; std::unique_ptr p1(new char[n1]); std::unique_ptr p2(new char[n2]); buffer_copy(buffer(p1.get(), n1), buffer(s.data(), n1)); buffer_copy(buffer(p2.get(), n2), buffer(s.data() + n1, n2)); f( boost::string_ref{p1.get(), n1}, boost::string_ref{p2.get(), n2}); } } struct none { template void operator()(Parser const&) const { } }; template void good(int onBodyRv, std::string const& s, F const& f) { using boost::asio::buffer; for_split(s, [&](boost::string_ref const& s1, boost::string_ref const& s2) { static std::size_t constexpr Limit = 200; std::size_t n; for(n = 0; n < Limit; ++n) { test::fail_counter fc(n); fail_parser p(fc); p.on_body_rv(onBodyRv); error_code ec; p.write(buffer(s1.data(), s1.size()), ec); if(ec == test::fail_error) continue; if(! expect(! ec)) break; if(! expect(s2.empty() || ! p.complete())) break; p.write(buffer(s2.data(), s2.size()), ec); if(ec == test::fail_error) continue; if(! expect(! ec)) break; p.write_eof(ec); if(ec == test::fail_error) continue; if(! expect(! ec)) break; expect(p.complete()); f(p); break; } expect(n < Limit); }); } template void good(std::string const& s, F const& f = {}) { return good(0, s, f); } template void bad(int onBodyRv, std::string const& s, error_code ev) { using boost::asio::buffer; for_split(s, [&](boost::string_ref const& s1, boost::string_ref const& s2) { static std::size_t constexpr Limit = 200; std::size_t n; for(n = 0; n < Limit; ++n) { test::fail_counter fc(n); fail_parser p(fc); p.on_body_rv(onBodyRv); error_code ec; p.write(buffer(s1.data(), s1.size()), ec); if(ec == test::fail_error) continue; if(ec) { expect((ec && ! ev) || ec == ev); break; } if(! expect(! p.complete())) break; if(! s2.empty()) { p.write(buffer(s2.data(), s2.size()), ec); if(ec == test::fail_error) continue; if(ec) { expect((ec && ! ev) || ec == ev); break; } if(! expect(! p.complete())) break; } p.write_eof(ec); if(ec == test::fail_error) continue; expect(! p.complete()); expect((ec && ! ev) || ec == ev); break; } expect(n < Limit); }); } template void bad(std::string const& s, error_code ev = {}) { return bad(0, s, ev); } //-------------------------------------------------------------------------- class version { suite& s_; unsigned major_; unsigned minor_; public: version(suite& s, unsigned major, unsigned minor) : s_(s) , major_(major) , minor_(minor) { } template void operator()(Parser const& p) const { s_.expect(p.http_major() == major_); s_.expect(p.http_minor() == minor_); } }; class status { suite& s_; unsigned code_; public: status(suite& s, int code) : s_(s) , code_(code) { } template void operator()(Parser const& p) const { s_.expect(p.status_code() == code_); } }; void testRequestLine() { /* request-line = method SP request-target SP HTTP-version CRLF method = token request-target = origin-form / absolute-form / authority-form / asterisk-form HTTP-version = "HTTP/" DIGIT "." DIGIT */ good("GET /x HTTP/1.0\r\n\r\n"); good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); good("GET / HTTP/1.0\r\n\r\n", version{*this, 1, 0}); good("G / HTTP/1.1\r\n\r\n", version{*this, 1, 1}); // VFALCO TODO various forms of good request-target (uri) good("GET / HTTP/0.1\r\n\r\n", version{*this, 0, 1}); good("GET / HTTP/2.3\r\n\r\n", version{*this, 2, 3}); good("GET / HTTP/4.5\r\n\r\n", version{*this, 4, 5}); good("GET / HTTP/6.7\r\n\r\n", version{*this, 6, 7}); good("GET / HTTP/8.9\r\n\r\n", version{*this, 8, 9}); bad("\tGET / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); bad("GET\x01 / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); bad("GET \x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); bad("GET /\x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); // VFALCO TODO various forms of bad request-target (uri) bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_version); bad("GET / _TTP/1.0\r\n" "\r\n", parse_error::bad_version); bad("GET / H_TP/1.0\r\n" "\r\n", parse_error::bad_version); bad("GET / HT_P/1.0\r\n" "\r\n", parse_error::bad_version); bad("GET / HTT_/1.0\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP_1.0\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/01.2\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/3.45\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/67.89\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/x.0\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/1.x\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/1.0 \r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/1_0\r\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/1.0\n" "\r\n", parse_error::bad_version); bad("GET / HTTP/1.0\n\r" "\r\n", parse_error::bad_version); bad("GET / HTTP/1.0\r\r\n" "\r\n", parse_error::bad_crlf); // write a bad request line in 2 pieces { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buffer_cat( buf("GET / "), buf("_TTP/1.1\r\n"), buf("\r\n") ), ec); expect(ec == parse_error::bad_version); } } void testStatusLine() { /* status-line = HTTP-version SP status-code SP reason-phrase CRLF HTTP-version = "HTTP/" DIGIT "." DIGIT status-code = 3DIGIT reason-phrase = *( HTAB / SP / VCHAR / obs-text ) */ good("HTTP/0.1 200 OK\r\n" "\r\n", version{*this, 0, 1}); good("HTTP/2.3 200 OK\r\n" "\r\n", version{*this, 2, 3}); good("HTTP/4.5 200 OK\r\n" "\r\n", version{*this, 4, 5}); good("HTTP/6.7 200 OK\r\n" "\r\n", version{*this, 6, 7}); good("HTTP/8.9 200 OK\r\n" "\r\n", version{*this, 8, 9}); good("HTTP/1.0 000 OK\r\n" "\r\n", status{*this, 0}); good("HTTP/1.1 012 OK\r\n" "\r\n", status{*this, 12}); good("HTTP/1.0 345 OK\r\n" "\r\n", status{*this, 345}); good("HTTP/1.0 678 OK\r\n" "\r\n", status{*this, 678}); good("HTTP/1.0 999 OK\r\n" "\r\n", status{*this, 999}); good("HTTP/1.0 200 \tX\r\n" "\r\n", version{*this, 1, 0}); good("HTTP/1.1 200 X\r\n" "\r\n", version{*this, 1, 1}); good("HTTP/1.0 200 \r\n" "\r\n"); good("HTTP/1.1 200 X \r\n" "\r\n"); good("HTTP/1.1 200 X\t\r\n" "\r\n"); good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); bad("\rHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("\nHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad(" HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("_TTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("H_TP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HT_P/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTT_/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP_1.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/01.2 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/3.45 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/67.89 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/x.0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/1.x 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/1_0 200 OK\r\n" "\r\n", parse_error::bad_version); bad("HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_status); bad("HTTP/1.0 0 OK\r\n" "\r\n", parse_error::bad_status); bad("HTTP/1.0 12 OK\r\n" "\r\n", parse_error::bad_status); bad("HTTP/1.0 3456 OK\r\n" "\r\n", parse_error::bad_status); bad("HTTP/1.0 200\r\n" "\r\n", parse_error::bad_status); bad("HTTP/1.0 200 \n" "\r\n", parse_error::bad_reason); bad("HTTP/1.0 200 \x01\r\n" "\r\n", parse_error::bad_reason); bad("HTTP/1.0 200 \x7f\r\n" "\r\n", parse_error::bad_reason); bad("HTTP/1.0 200 OK\n" "\r\n", parse_error::bad_reason); bad("HTTP/1.0 200 OK\r\r\n" "\r\n", parse_error::bad_crlf); } //-------------------------------------------------------------------------- void testHeaders() { /* header-field = field-name ":" OWS field-value OWS field-name = token field-value = *( field-content / obs-fold ) field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] field-vchar = VCHAR / obs-text obs-fold = CRLF 1*( SP / HTAB ) ; obsolete line folding */ auto const m = [](std::string const& s) { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; good(m("f:\r\n")); good(m("f: \r\n")); good(m("f:\t\r\n")); good(m("f: \t\r\n")); good(m("f: v\r\n")); good(m("f:\tv\r\n")); good(m("f:\tv \r\n")); good(m("f:\tv\t\r\n")); good(m("f:\tv\t \r\n")); good(m("f:\r\n \r\n")); good(m("f:v\r\n")); good(m("f: v\r\n u\r\n")); good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); bad(m(" f: v\r\n"), parse_error::bad_field); bad(m("\tf: v\r\n"), parse_error::bad_field); bad(m("f : v\r\n"), parse_error::bad_field); bad(m("f\t: v\r\n"), parse_error::bad_field); bad(m("f: \n\r\n"), parse_error::bad_value); bad(m("f: v\r \r\n"), parse_error::bad_crlf); bad(m("f: \r v\r\n"), parse_error::bad_crlf); bad("GET / HTTP/1.1\r\n\r \n", parse_error::bad_crlf); } //-------------------------------------------------------------------------- class flags { suite& s_; std::size_t value_; public: flags(suite& s, std::size_t value) : s_(s) , value_(value) { } template void operator()(Parser const& p) const { s_.expect(p.flags() == value_); } }; class keepalive_f { suite& s_; bool value_; public: keepalive_f(suite& s, bool value) : s_(s) , value_(value) { } template void operator()(Parser const& p) const { s_.expect(p.keep_alive() == value_); } }; void testConnectionHeader() { auto const m = [](std::string const& s) { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; auto const cn = [](std::string const& s) { return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; }; auto const keepalive = [&](bool v) { return keepalive_f{*this, v}; }; good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); good(cn(",close\r\n"), flags{*this, parse_flag::connection_close}); good(cn(" close\r\n"), flags{*this, parse_flag::connection_close}); good(cn("\tclose\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close,\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close\t\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); good(cn(" ,\t,,close,, ,\t,,\r\n"), flags{*this, parse_flag::connection_close}); good(cn("\r\n close\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close\r\n \r\n"), flags{*this, parse_flag::connection_close}); good(cn("any,close\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close,any\r\n"), flags{*this, parse_flag::connection_close}); good(cn("any\r\n ,close\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close\r\n ,any\r\n"), flags{*this, parse_flag::connection_close}); good(cn("close,close\r\n"), flags{*this, parse_flag::connection_close}); // weird but allowed good(cn("keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("keep-alive \r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("keep-alive\t \r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("keep-alive\t ,x\r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("\r\n keep-alive \t\r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("keep-alive \r\n \t \r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("keep-alive\r\n \r\n"), flags{*this, parse_flag::connection_keep_alive}); good(cn("upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("upgrade \r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("upgrade\t \r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("upgrade\t ,x\r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("\r\n upgrade \t\r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("upgrade \r\n \t \r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("upgrade\r\n \r\n"), flags{*this, parse_flag::connection_upgrade}); good(cn("close,keep-alive\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); good(cn("upgrade,keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); good(cn("upgrade,\r\n keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); good(cn("close,keep-alive,upgrade\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); good("GET / HTTP/1.1\r\n\r\n", keepalive(true)); good("GET / HTTP/1.0\r\n\r\n", keepalive(false)); good("GET / HTTP/1.0\r\n" "Connection: keep-alive\r\n\r\n", keepalive(true)); good("GET / HTTP/1.1\r\n" "Connection: close\r\n\r\n", keepalive(false)); good(cn("x\r\n"), flags{*this, 0}); good(cn("x,y\r\n"), flags{*this, 0}); good(cn("x ,y\r\n"), flags{*this, 0}); good(cn("x\t,y\r\n"), flags{*this, 0}); good(cn("keep\r\n"), flags{*this, 0}); good(cn(",keep\r\n"), flags{*this, 0}); good(cn(" keep\r\n"), flags{*this, 0}); good(cn("\tnone\r\n"), flags{*this, 0}); good(cn("keep,\r\n"), flags{*this, 0}); good(cn("keep\t\r\n"), flags{*this, 0}); good(cn("keep\r\n"), flags{*this, 0}); good(cn(" ,\t,,keep,, ,\t,,\r\n"), flags{*this, 0}); good(cn("\r\n keep\r\n"), flags{*this, 0}); good(cn("keep\r\n \r\n"), flags{*this, 0}); good(cn("closet\r\n"), flags{*this, 0}); good(cn(",closet\r\n"), flags{*this, 0}); good(cn(" closet\r\n"), flags{*this, 0}); good(cn("\tcloset\r\n"), flags{*this, 0}); good(cn("closet,\r\n"), flags{*this, 0}); good(cn("closet\t\r\n"), flags{*this, 0}); good(cn("closet\r\n"), flags{*this, 0}); good(cn(" ,\t,,closet,, ,\t,,\r\n"), flags{*this, 0}); good(cn("\r\n closet\r\n"), flags{*this, 0}); good(cn("closet\r\n \r\n"), flags{*this, 0}); good(cn("clog\r\n"), flags{*this, 0}); good(cn("key\r\n"), flags{*this, 0}); good(cn("uptown\r\n"), flags{*this, 0}); good(cn("keeper\r\n \r\n"), flags{*this, 0}); good(cn("keep-alively\r\n \r\n"), flags{*this, 0}); good(cn("up\r\n \r\n"), flags{*this, 0}); good(cn("upgrader\r\n \r\n"), flags{*this, 0}); good(cn("none\r\n"), flags{*this, 0}); good(cn("\r\n none\r\n"), flags{*this, 0}); good(m("ConnectioX: close\r\n"), flags{*this, 0}); good(m("Condor: close\r\n"), flags{*this, 0}); good(m("Connect: close\r\n"), flags{*this, 0}); good(m("Connections: close\r\n"), flags{*this, 0}); good(m("Proxy-Connection: close\r\n"), flags{*this, parse_flag::connection_close}); good(m("Proxy-Connection: keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); good(m("Proxy-Connection: upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); good(m("Proxy-ConnectioX: none\r\n"), flags{*this, 0}); good(m("Proxy-Connections: 1\r\n"), flags{*this, 0}); good(m("Proxy-Connotes: see-also\r\n"), flags{*this, 0}); bad(cn("["), parse_error::bad_value); bad(cn("\"\r\n"), parse_error::bad_value); bad(cn("close[\r\n"), parse_error::bad_value); bad(cn("close [\r\n"), parse_error::bad_value); bad(cn("close, upgrade [\r\n"), parse_error::bad_value); bad(cn("upgrade[]\r\n"), parse_error::bad_value); bad(cn("keep\r\n -alive\r\n"), parse_error::bad_value); bad(cn("keep-alive[\r\n"), parse_error::bad_value); bad(cn("keep-alive []\r\n"), parse_error::bad_value); bad(cn("no[ne]\r\n"), parse_error::bad_value); } void testContentLengthHeader() { auto const length = [&](std::string const& s, std::uint64_t v) { good(1, s, [&](fail_parser const& p) { expect(p.content_length() == v); if(v != no_content_length) expect(p.flags() & parse_flag::contentlength); }); }; auto const c = [](std::string const& s) { return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; }; auto const m = [](std::string const& s) { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; length(c("0\r\n"), 0); length(c("00\r\n"), 0); length(c("1\r\n"), 1); length(c("01\r\n"), 1); length(c("9\r\n"), 9); length(c("123456789\r\n"), 123456789); length(c("42 \r\n"), 42); length(c("42\t\r\n"), 42); length(c("42 \t \r\n"), 42); length(c("42\r\n \t \r\n"), 42); good(m("Content-LengtX: 0\r\n"), flags{*this, 0}); good(m("Content-Lengths: many\r\n"), flags{*this, 0}); good(m("Content: full\r\n"), flags{*this, 0}); bad(c("\r\n"), parse_error::bad_content_length); bad(c("18446744073709551616\r\n"), parse_error::bad_content_length); bad(c("0 0\r\n"), parse_error::bad_content_length); bad(c("0 1\r\n"), parse_error::bad_content_length); bad(c(",\r\n"), parse_error::bad_content_length); bad(c("0,\r\n"), parse_error::bad_content_length); bad(m( "Content-Length: 0\r\nContent-Length: 0\r\n"), parse_error::bad_content_length); } void testTransferEncodingHeader() { auto const m = [](std::string const& s) { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; auto const ce = [](std::string const& s) { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; }; auto const te = [](std::string const& s) { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; }; good(ce("chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked\t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked \t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce(" chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("\tchunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked ,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked, \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce(",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce(", chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce(" ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("chunked\r\n \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("\r\n ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("gzip, chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("gzip, chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(ce("gzip, \r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); // Technically invalid but beyond the parser's scope to detect good(ce("custom;key=\",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); good(te("gzip\r\n"), flags{*this, 0}); good(te("chunked, gzip\r\n"), flags{*this, 0}); good(te("chunked\r\n , gzip\r\n"), flags{*this, 0}); good(te("chunked,\r\n gzip\r\n"), flags{*this, 0}); good(te("chunked,\r\n ,gzip\r\n"), flags{*this, 0}); good(te("bigchunked\r\n"), flags{*this, 0}); good(te("chunk\r\n ked\r\n"), flags{*this, 0}); good(te("bar\r\n ley chunked\r\n"), flags{*this, 0}); good(te("barley\r\n chunked\r\n"), flags{*this, 0}); good(m("Transfer-EncodinX: none\r\n"), flags{*this, 0}); good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); bad(1, "HTTP/1.1 200 OK\r\n" "Content-Length: 1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n", parse_error::illegal_content_length); } void testUpgradeHeader() { auto const m = [](std::string const& s) { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; good(m("Upgrade:\r\n"), flags{*this, parse_flag::upgrade}); good(m("Upgrade: \r\n"), flags{*this, parse_flag::upgrade}); good(m("Upgrade: yes\r\n"), flags{*this, parse_flag::upgrade}); good(m("Up: yes\r\n"), flags{*this, 0}); good(m("UpgradX: none\r\n"), flags{*this, 0}); good(m("Upgrades: 2\r\n"), flags{*this, 0}); good(m("Upsample: 4x\r\n"), flags{*this, 0}); good( "GET / HTTP/1.1\r\n" "Connection: upgrade\r\n" "Upgrade: WebSocket\r\n" "\r\n", [&](fail_parser const& p) { expect(p.upgrade()); }); } //-------------------------------------------------------------------------- class body_f { suite& s_; std::string const& body_; public: body_f(body_f&&) = default; body_f(suite& s, std::string const& v) : s_(s) , body_(v) { } template void operator()(Parser const& p) const { s_.expect(p.body == body_); } }; template static boost::asio::const_buffers_1 buf(char const (&s)[N]) { return { s, N-1 }; } void testBody() { using boost::asio::buffer; auto const body = [&](std::string const& s) { return body_f{*this, s}; }; good( "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" "\r\n" "1", body("1")); good( "HTTP/1.0 200 OK\r\n" "\r\n" "hello", body("hello")); // on_body returns 2, meaning upgrade { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.on_body_rv(2); p.write(buf( "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" "\r\n" ), ec); expect(! ec); expect(p.complete()); } // write the body in 3 pieces { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buffer_cat( buf("GET / HTTP/1.1\r\n" "Content-Length: 10\r\n" "\r\n"), buf("12"), buf("345"), buf("67890")), ec); expect(! ec); expect(p.complete()); expect(! p.needs_eof()); p.write_eof(ec); expect(! ec); p.write_eof(ec); expect(! ec); p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); expect(ec == parse_error::connection_closed); } // request without Content-Length or // Transfer-Encoding: chunked has no body. { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "GET / HTTP/1.0\r\n" "\r\n" ), ec); expect(! ec); expect(! p.needs_eof()); expect(p.complete()); } { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "GET / HTTP/1.1\r\n" "\r\n" ), ec); expect(! ec); expect(! p.needs_eof()); expect(p.complete()); } // response without Content-Length or // Transfer-Encoding: chunked requires eof. { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "HTTP/1.0 200 OK\r\n" "\r\n" ), ec); expect(! ec); expect(! p.complete()); expect(p.needs_eof()); p.write(buf( "hello" ), ec); expect(! ec); expect(! p.complete()); expect(p.needs_eof()); p.write_eof(ec); expect(! ec); expect(p.complete()); p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); expect(ec == parse_error::connection_closed); } // 304 "Not Modified" response does not require eof { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "HTTP/1.0 304 Not Modified\r\n" "\r\n" ), ec); expect(! ec); expect(! p.needs_eof()); expect(p.complete()); } // Chunked response does not require eof { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" ), ec); expect(! ec); expect(! p.needs_eof()); expect(! p.complete()); p.write(buf( "0\r\n\r\n" ), ec); expect(! ec); expect(! p.needs_eof()); expect(p.complete()); } // restart: 1.0 assumes Connection: close { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "GET / HTTP/1.0\r\n" "\r\n" ), ec); expect(! ec); expect(p.complete()); p.write(buf( "GET / HTTP/1.0\r\n" "\r\n" ), ec); expect(ec == parse_error::connection_closed); } // restart: 1.1 assumes Connection: keep-alive { error_code ec; test::fail_counter fc(1000); fail_parser p(fc); p.write(buf( "GET / HTTP/1.1\r\n" "\r\n" ), ec); expect(! ec); expect(p.complete()); p.write(buf( "GET / HTTP/1.0\r\n" "\r\n" ), ec); expect(! ec); expect(p.complete()); } bad(3, "HTTP/1.1 200 OK\r\n" "Content-Length: 1\r\n" "\r\n*", parse_error::bad_on_headers_rv); bad(0, "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" "\r\n", parse_error::short_read); } void testChunkedBody() { auto const body = [&](std::string const& s) { return body_f{*this, s}; }; auto const ce = [](std::string const& s) { return "GET / HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" + s; }; /* chunked-body = *chunk last-chunk trailer-part CRLF chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF chunk-size = 1*HEXDIG last-chunk = 1*("0") [ chunk-ext ] CRLF chunk-data = 1*OCTET ; a sequence of chunk-size octets chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) chunk-ext-name = token chunk-ext-val = token / quoted-string trailer-part = *( header-field CRLF ) */ good(ce( "1;xy\r\n*\r\n" "0\r\n\r\n" ), body("*")); good(ce( "1;x\r\n*\r\n" "0\r\n\r\n" ), body("*")); good(ce( "1;x;y\r\n*\r\n" "0\r\n\r\n" ), body("*")); good(ce( "1;i;j=2;k=\"3\"\r\n*\r\n" "0\r\n\r\n" ), body("*")); good(ce( "1\r\n" "a\r\n" "0\r\n" "\r\n" ), body("a")); good(ce( "2\r\n" "ab\r\n" "0\r\n" "\r\n" ), body("ab")); good(ce( "2\r\n" "ab\r\n" "1\r\n" "c\r\n" "0\r\n" "\r\n" ), body("abc")); good(ce( "10\r\n" "1234567890123456\r\n" "0\r\n" "\r\n" ), body("1234567890123456")); bad(ce("ffffffffffffffff0\r\n0\r\n\r\n"), parse_error::bad_content_length); bad(ce("g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); bad(ce("0g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); bad(ce("0\r_\n"), parse_error::bad_crlf); bad(ce("1\r\n*_\r\n"), parse_error::bad_crlf); bad(ce("1\r\n*\r_\n"), parse_error::bad_crlf); bad(ce("1;,x\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); bad(ce("1;x,\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); } void testLimits() { std::size_t n; static std::size_t constexpr Limit = 100; { for(n = 1; n < Limit; ++n) { test::fail_counter fc(1000); fail_parser p(fc); p.set_option(headers_max_size{n}); error_code ec; p.write(buf( "GET / HTTP/1.1\r\n" "User-Agent: beast\r\n" "\r\n" ), ec); if(! ec) break; expect(ec == parse_error::headers_too_big); } expect(n < Limit); } { for(n = 1; n < Limit; ++n) { test::fail_counter fc(1000); fail_parser p(fc); p.set_option(headers_max_size{n}); error_code ec; p.write(buf( "HTTP/1.1 200 OK\r\n" "Server: beast\r\n" "Content-Length: 4\r\n" "\r\n" "****" ), ec); if(! ec) break; expect(ec == parse_error::headers_too_big); } expect(n < Limit); } { test::fail_counter fc(1000); fail_parser p(fc); p.set_option(body_max_size{2}); error_code ec; p.write(buf( "HTTP/1.1 200 OK\r\n" "Server: beast\r\n" "Content-Length: 4\r\n" "\r\n" "****" ), ec); expect(ec == parse_error::body_too_big); } } void run() override { testCallbacks(); testRequestLine(); testStatusLine(); testHeaders(); testConnectionHeader(); testContentLengthHeader(); testTransferEncodingHeader(); testUpgradeHeader(); testBody(); testChunkedBody(); testLimits(); } }; BEAST_DEFINE_TESTSUITE(basic_parser_v1,http,beast); } // http } // beast