// // Copyright (c) 2013-2017 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 "test_parser.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace beast { namespace http { class parser_test : public beast::unit_test::suite , public beast::test::enable_yield_to { public: template using parser_type = parser; static boost::asio::const_buffers_1 buf(string_view s) { return {s.data(), s.size()}; } template static void put(ConstBufferSequence const& buffers, basic_parser& p, error_code& ec) { using boost::asio::buffer_size; consuming_buffers cb{buffers}; for(;;) { auto const used = p.put(cb, ec); cb.consume(used); if(ec) return; if(p.need_eof() && buffer_size(cb) == 0) { p.put_eof(ec); if(ec) return; } if(p.is_done()) break; } } template void doMatrix(string_view s0, F const& f) { using boost::asio::buffer; // parse a single buffer { auto s = s0; error_code ec; parser_type p; put(buffer(s.data(), s.size()), p, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; f(p); } // parse two buffers for(auto n = s0.size() - 1; n >= 1; --n) { auto s = s0; error_code ec; parser_type p; p.eager(true); auto used = p.put(buffer(s.data(), n), ec); s.remove_prefix(used); if(ec == error::need_more) ec.assign(0, ec.category()); if(! BEAST_EXPECTS(! ec, ec.message())) continue; BEAST_EXPECT(! p.is_done()); used = p.put( buffer(s.data(), s.size()), ec); s.remove_prefix(used); if(! BEAST_EXPECTS(! ec, ec.message())) continue; BEAST_EXPECT(s.empty()); if(p.need_eof()) { p.put_eof(ec); if(! BEAST_EXPECTS(! ec, ec.message())) continue; } if(BEAST_EXPECT(p.is_done())) f(p); } } void testParse() { doMatrix( "HTTP/1.0 200 OK\r\n" "Server: test\r\n" "\r\n" "Hello, world!", [&](parser_type const& p) { auto const& m = p.get(); BEAST_EXPECT(! p.is_chunked()); BEAST_EXPECT(p.need_eof()); BEAST_EXPECT(p.content_length() == boost::none); BEAST_EXPECT(m.version == 10); BEAST_EXPECT(m.result() == status::ok); BEAST_EXPECT(m.reason() == "OK"); BEAST_EXPECT(m["Server"] == "test"); BEAST_EXPECT(m.body == "Hello, world!"); } ); doMatrix( "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Expect: Expires, MD5-Fingerprint\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "5\r\n" "*****\r\n" "2;a;b=1;c=\"2\"\r\n" "--\r\n" "0;d;e=3;f=\"4\"\r\n" "Expires: never\r\n" "MD5-Fingerprint: -\r\n" "\r\n", [&](parser_type const& p) { auto const& m = p.get(); BEAST_EXPECT(! p.need_eof()); BEAST_EXPECT(p.is_chunked()); BEAST_EXPECT(p.content_length() == boost::none); BEAST_EXPECT(m.version == 11); BEAST_EXPECT(m.result() == status::ok); BEAST_EXPECT(m.reason() == "OK"); BEAST_EXPECT(m["Server"] == "test"); BEAST_EXPECT(m["Transfer-Encoding"] == "chunked"); BEAST_EXPECT(m["Expires"] == "never"); BEAST_EXPECT(m["MD5-Fingerprint"] == "-"); BEAST_EXPECT(m.body == "*****--"); } ); doMatrix( "HTTP/1.0 200 OK\r\n" "Server: test\r\n" "Content-Length: 5\r\n" "\r\n" "*****", [&](parser_type const& p) { auto const& m = p.get(); BEAST_EXPECT(m.body == "*****"); } ); doMatrix( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "\r\n", [&](parser_type const& p) { auto const& m = p.get(); BEAST_EXPECT(m.method() == verb::get); BEAST_EXPECT(m.target() == "/"); BEAST_EXPECT(m.version == 11); BEAST_EXPECT(! p.need_eof()); BEAST_EXPECT(! p.is_chunked()); BEAST_EXPECT(p.content_length() == boost::none); } ); doMatrix( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "X: \t x \t \r\n" "\r\n", [&](parser_type const& p) { auto const& m = p.get(); BEAST_EXPECT(m["X"] == "x"); } ); // test eager(true) { error_code ec; parser_type p; p.eager(true); p.put(buf( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "Content-Length: 1\r\n" "\r\n" "*") , ec); auto const& m = p.get(); BEAST_EXPECT(! ec); BEAST_EXPECT(p.is_done()); BEAST_EXPECT(p.is_header_done()); BEAST_EXPECT(! p.need_eof()); BEAST_EXPECT(m.method() == verb::get); BEAST_EXPECT(m.target() == "/"); BEAST_EXPECT(m.version == 11); BEAST_EXPECT(m["User-Agent"] == "test"); BEAST_EXPECT(m.body == "*"); } { // test partial parsing of final chunk // parse through the chunk body error_code ec; flat_buffer b; parser_type p; p.eager(true); ostream(b) << "PUT / HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "1\r\n" "*"; auto used = p.put(b.data(), ec); b.consume(used); BEAST_EXPECT(! ec); BEAST_EXPECT(! p.is_done()); BEAST_EXPECT(p.get().body == "*"); ostream(b) << "\r\n" "0;d;e=3;f=\"4\"\r\n" "Expires: never\r\n" "MD5-Fingerprint: -\r\n"; // incomplete parse, missing the final crlf used = p.put(b.data(), ec); b.consume(used); BEAST_EXPECT(ec == error::need_more); ec.assign(0, ec.category()); BEAST_EXPECT(! p.is_done()); ostream(b) << "\r\n"; // final crlf to end message used = p.put(b.data(), ec); b.consume(used); BEAST_EXPECTS(! ec, ec.message()); BEAST_EXPECT(p.is_done()); } // skip body { error_code ec; response_parser p; p.skip(true); p.put(buf( "HTTP/1.1 200 OK\r\n" "Content-Length: 5\r\n" "\r\n" "*****") , ec); BEAST_EXPECTS(! ec, ec.message()); BEAST_EXPECT(p.is_done()); BEAST_EXPECT(p.is_header_done()); BEAST_EXPECT(p.content_length() && *p.content_length() == 5); } } //-------------------------------------------------------------------------- template void testNeedMore() { error_code ec; std::size_t used; { DynamicBuffer b; parser_type p; ostream(b) << "GET / HTTP/1.1\r\n"; used = p.put(b.data(), ec); BEAST_EXPECTS(ec == error::need_more, ec.message()); b.consume(used); ec.assign(0, ec.category()); ostream(b) << "User-Agent: test\r\n" "\r\n"; used = p.put(b.data(), ec); BEAST_EXPECTS(! ec, ec.message()); b.consume(used); BEAST_EXPECT(p.is_done()); BEAST_EXPECT(p.is_header_done()); } } void testGotSome() { error_code ec; parser_type p; auto used = p.put(buf(""), ec); BEAST_EXPECT(ec == error::need_more); BEAST_EXPECT(! p.got_some()); BEAST_EXPECT(used == 0); ec.assign(0, ec.category()); used = p.put(buf("G"), ec); BEAST_EXPECT(ec == error::need_more); BEAST_EXPECT(p.got_some()); BEAST_EXPECT(used == 0); } void testCallback() { { multi_buffer b; ostream(b) << "POST / HTTP/1.1\r\n" "Content-Length: 2\r\n" "\r\n" "**"; error_code ec; parser p; p.eager(true); p.put(b.data(), ec); p.on_header( [this](parser& p, error_code& ec) { BEAST_EXPECT(p.is_header_done()); ec.assign(0, ec.category()); }); BEAST_EXPECTS(! ec, ec.message()); } { multi_buffer b; ostream(b) << "POST / HTTP/1.1\r\n" "Content-Length: 2\r\n" "\r\n" "**"; error_code ec; parser p; p.eager(true); p.put(b.data(), ec); p.on_header( [this](parser&, error_code& ec) { ec.assign(errc::bad_message, generic_category()); }); BEAST_EXPECTS(! ec, ec.message()); } } void run() override { testParse(); testNeedMore(); testNeedMore(); testGotSome(); testCallback(); } }; BEAST_DEFINE_TESTSUITE(parser,http,beast); } // http } // beast