forked from boostorg/beast
Refactor chunked parsing (API Change):
* parser now has a callback feature for intercepting chunk headers and chunk bodies * The names for basic_parser derived class callbacks have been refactored * basic_parser offers an additional callback for distinguishing chunk body data. Actions Required: * Adjust signatures for required members of user-defined subclasses of basic_parser * Use the new basic_parser chunk callbacks for accessing chunk extensions and chunk bodies.
This commit is contained in:
+18
-10
@@ -166,14 +166,14 @@ public:
|
||||
boost::asio::mutable_buffers_1;
|
||||
|
||||
void
|
||||
on_request(verb, string_view,
|
||||
on_request_impl(verb, string_view,
|
||||
string_view, int, error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int,
|
||||
on_response_impl(int,
|
||||
string_view,
|
||||
int, error_code& ec)
|
||||
{
|
||||
@@ -181,42 +181,50 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_field(field,
|
||||
on_field_impl(field,
|
||||
string_view, string_view, error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code& ec)
|
||||
on_header_impl(error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
void
|
||||
on_body(boost::optional<std::uint64_t> const&,
|
||||
on_body_init_impl(
|
||||
boost::optional<std::uint64_t> const&,
|
||||
error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
on_data(string_view s, error_code& ec)
|
||||
on_body_impl(string_view s, error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
return s.size();
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t,
|
||||
string_view,
|
||||
error_code& ec)
|
||||
on_chunk_header_impl(std::uint64_t,
|
||||
string_view, error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
on_chunk_body_impl(std::uint64_t,
|
||||
string_view s, error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
return s.size();
|
||||
}
|
||||
|
||||
void
|
||||
on_complete(error_code& ec)
|
||||
on_finish_impl(error_code& ec)
|
||||
{
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ add_executable (core-tests
|
||||
base64.cpp
|
||||
empty_base_optimization.cpp
|
||||
sha1.cpp
|
||||
detail/varint.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(core-tests
|
||||
|
||||
@@ -38,4 +38,5 @@ unit-test core-tests :
|
||||
base64.cpp
|
||||
empty_base_optimization.cpp
|
||||
sha1.cpp
|
||||
detail/varint.cpp
|
||||
;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// 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 <beast/core/detail/varint.hpp>
|
||||
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
|
||||
namespace beast {
|
||||
|
||||
class varint_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testVarint()
|
||||
{
|
||||
using beast::detail::varint_read;
|
||||
using beast::detail::varint_size;
|
||||
using beast::detail::varint_write;
|
||||
std::size_t n0 = 0;
|
||||
std::size_t n1 = 1;
|
||||
for(;;)
|
||||
{
|
||||
char buf[16];
|
||||
BOOST_ASSERT(sizeof(buf) >= varint_size(n0));
|
||||
auto it = &buf[0];
|
||||
varint_write(it, n0);
|
||||
it = &buf[0];
|
||||
auto n = varint_read(it);
|
||||
BEAST_EXPECT(n == n0);
|
||||
n = n0 + n1;
|
||||
if(n < n1)
|
||||
break;
|
||||
n0 = n1;
|
||||
n1 = n;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
testVarint();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(varint,core,beast);
|
||||
|
||||
} // beast
|
||||
@@ -8,6 +8,7 @@
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/basic_parser.hpp>
|
||||
|
||||
#include "message_fuzz.hpp"
|
||||
#include "test_parser.hpp"
|
||||
|
||||
#include <beast/core/buffer_cat.hpp>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <beast/core/ostream.hpp>
|
||||
#include <beast/http/parser.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/test/fuzz.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
|
||||
namespace beast {
|
||||
@@ -399,7 +401,7 @@ public:
|
||||
BEAST_EXPECT(p.got_on_field == 2);
|
||||
BEAST_EXPECT(p.got_on_header == 1);
|
||||
BEAST_EXPECT(p.got_on_body == 1);
|
||||
BEAST_EXPECT(p.got_on_chunk == 1);
|
||||
BEAST_EXPECT(p.got_on_chunk == 2);
|
||||
BEAST_EXPECT(p.got_on_complete == 1);
|
||||
});
|
||||
parsegrind<test_parser<false>>(
|
||||
@@ -415,7 +417,7 @@ public:
|
||||
BEAST_EXPECT(p.got_on_field == 2);
|
||||
BEAST_EXPECT(p.got_on_header == 1);
|
||||
BEAST_EXPECT(p.got_on_body == 1);
|
||||
BEAST_EXPECT(p.got_on_chunk == 1);
|
||||
BEAST_EXPECT(p.got_on_chunk == 2);
|
||||
BEAST_EXPECT(p.got_on_complete == 1);
|
||||
});
|
||||
}
|
||||
@@ -1104,7 +1106,64 @@ public:
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
testFuzz1()
|
||||
testFuzz()
|
||||
{
|
||||
auto const grind =
|
||||
[&](string_view s)
|
||||
{
|
||||
static_string<100> ss{s};
|
||||
test::fuzz_rand r;
|
||||
test::fuzz(ss, 4, 5, r,
|
||||
[&](string_view s)
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<false> p;
|
||||
p.eager(true);
|
||||
p.put(boost::asio::const_buffers_1{
|
||||
s.data(), s.size()}, ec);
|
||||
});
|
||||
};
|
||||
auto const good =
|
||||
[&](string_view s)
|
||||
{
|
||||
std::string msg =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"0" + s.to_string() + "\r\n"
|
||||
"\r\n";
|
||||
error_code ec;
|
||||
test_parser<false> p;
|
||||
p.eager(true);
|
||||
p.put(boost::asio::const_buffers_1{
|
||||
msg.data(), msg.size()}, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
grind(msg);
|
||||
};
|
||||
auto const bad =
|
||||
[&](string_view s)
|
||||
{
|
||||
std::string msg =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"0" + s.to_string() + "\r\n"
|
||||
"\r\n";
|
||||
error_code ec;
|
||||
test_parser<false> p;
|
||||
p.eager(true);
|
||||
p.put(boost::asio::const_buffers_1{
|
||||
msg.data(), msg.size()}, ec);
|
||||
BEAST_EXPECT(ec);
|
||||
grind(msg);
|
||||
};
|
||||
chunkExtensionsTest(good, bad);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
testRegression1()
|
||||
{
|
||||
// crash_00cda0b02d5166bd1039ddb3b12618cd80da75f3
|
||||
unsigned char buf[] ={
|
||||
@@ -1154,7 +1213,8 @@ public:
|
||||
testIssue430();
|
||||
testIssue452();
|
||||
testIssue496();
|
||||
testFuzz1();
|
||||
testFuzz();
|
||||
testRegression1();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,9 +8,14 @@
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/chunk_encode.hpp>
|
||||
|
||||
#include "message_fuzz.hpp"
|
||||
|
||||
#include <beast/core/static_string.hpp>
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/test/fuzz.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <random>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
@@ -194,17 +199,94 @@ public:
|
||||
void
|
||||
testChunkExtensions()
|
||||
{
|
||||
auto const str =
|
||||
[](chunk_extensions const& ce)
|
||||
{
|
||||
std::string s;
|
||||
for(auto const& v : ce)
|
||||
{
|
||||
s.append(v.first.to_string());
|
||||
s.push_back(',');
|
||||
if(! v.second.empty())
|
||||
{
|
||||
s.append(v.second.to_string());
|
||||
s.push_back(',');
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
chunk_extensions ce;
|
||||
ce.insert("x");
|
||||
BEAST_EXPECT(ce.str() == ";x");
|
||||
BEAST_EXPECT(str(ce) == "x,");
|
||||
ce.insert("y", "z");
|
||||
BEAST_EXPECT(ce.str() == ";x;y=z");
|
||||
BEAST_EXPECT(str(ce) == "x,y,z,");
|
||||
ce.insert("z", R"(")");
|
||||
BEAST_EXPECT(ce.str() == R"(;x;y=z;z="\"")");
|
||||
BEAST_EXPECT(str(ce) == R"(x,y,z,z,",)");
|
||||
ce.insert("p", R"(\)");
|
||||
BEAST_EXPECT(ce.str() == R"(;x;y=z;z="\"";p="\\")");
|
||||
BEAST_EXPECT(str(ce) == R"(x,y,z,z,",p,\,)");
|
||||
ce.insert("q", R"(1"2\)");
|
||||
BEAST_EXPECT(ce.str() == R"(;x;y=z;z="\"";p="\\";q="1\"2\\")");
|
||||
BEAST_EXPECT(str(ce) == R"(x,y,z,z,",p,\,q,1"2\,)");
|
||||
}
|
||||
|
||||
void
|
||||
testParseChunkExtensions()
|
||||
{
|
||||
auto const grind =
|
||||
[&](string_view s)
|
||||
{
|
||||
error_code ec;
|
||||
static_string<200> ss{s};
|
||||
test::fuzz_rand r;
|
||||
for(auto i = 3; i--;)
|
||||
{
|
||||
test::fuzz(ss, 5, 5, r,
|
||||
[&](string_view s)
|
||||
{
|
||||
chunk_extensions c1;
|
||||
c1.parse(s, ec);
|
||||
if(ec)
|
||||
{
|
||||
pass();
|
||||
return;
|
||||
}
|
||||
chunk_extensions c2;
|
||||
c2.parse(c1.str(), ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
chunk_extensions c3;
|
||||
for(auto const& v : c2)
|
||||
if(v.second.empty())
|
||||
c3.insert(v.first);
|
||||
else
|
||||
c3.insert(v.first, v.second);
|
||||
BEAST_EXPECTS(c2.str() == c3.str(), c3.str());
|
||||
});
|
||||
}
|
||||
};
|
||||
auto const good =
|
||||
[&](string_view s)
|
||||
{
|
||||
error_code ec;
|
||||
chunk_extensions ce;
|
||||
ce.parse(s, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
grind(s);
|
||||
};
|
||||
auto const bad =
|
||||
[&](string_view s)
|
||||
{
|
||||
error_code ec;
|
||||
chunk_extensions ce;
|
||||
ce.parse(s, ec);
|
||||
BEAST_EXPECT(ec);
|
||||
grind(s);
|
||||
};
|
||||
chunkExtensionsTest(good, bad);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -215,6 +297,7 @@ public:
|
||||
testChunkBody();
|
||||
testChunkFinal();
|
||||
testChunkExtensions();
|
||||
testParseChunkExtensions();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+19
-11
@@ -22,8 +22,9 @@
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
@@ -369,7 +370,7 @@ public:
|
||||
ostream(c.client.buffer) <<
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Accept: Expires, Content-MD5\r\n"
|
||||
"Trailer: Expires, Content-MD5\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"5\r\n"
|
||||
@@ -385,16 +386,23 @@ public:
|
||||
"Content-MD5: f4a5c16584f03d90\r\n"
|
||||
"\r\n";
|
||||
|
||||
flat_buffer b;
|
||||
response_parser<empty_body> p;
|
||||
read_header(c.client, b, p);
|
||||
BOOST_ASSERT(p.is_chunked());
|
||||
//while(! p.is_done())
|
||||
{
|
||||
// read the chunk header?
|
||||
// read the next chunk?
|
||||
|
||||
}
|
||||
error_code ec;
|
||||
flat_buffer b;
|
||||
std::stringstream ss;
|
||||
print_chunked_body<false>(ss, c.client, b, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(ss.str() ==
|
||||
"Chunk Body: First\n"
|
||||
"Extension: quality = 1.0\n"
|
||||
"Chunk Body: Hello, world!\n"
|
||||
"Extension: file = abc.txt\n"
|
||||
"Extension: quality = 0.7\n"
|
||||
"Chunk Body: The Next Chunk\n"
|
||||
"Extension: last\n"
|
||||
"Chunk Body: Last one\n"
|
||||
"Expires: never\n"
|
||||
"Content-MD5: f4a5c16584f03d90\n");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
check("beast.http", error::need_more);
|
||||
check("beast.http", error::unexpected_body);
|
||||
check("beast.http", error::need_buffer);
|
||||
check("beast.http", error::end_of_chunk);
|
||||
check("beast.http", error::buffer_overflow);
|
||||
check("beast.http", error::body_limit);
|
||||
check("beast.http", error::bad_alloc);
|
||||
@@ -56,6 +57,7 @@ public:
|
||||
check("beast.http", error::bad_content_length);
|
||||
check("beast.http", error::bad_transfer_encoding);
|
||||
check("beast.http", error::bad_chunk);
|
||||
check("beast.http", error::bad_chunk_extension);
|
||||
check("beast.http", error::bad_obs_fold);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -562,6 +562,42 @@ public:
|
||||
|
||||
using message_fuzz = message_fuzz_t<>;
|
||||
|
||||
template<class Good, class Bad>
|
||||
void
|
||||
chunkExtensionsTest(
|
||||
Good const& good, Bad const& bad)
|
||||
{
|
||||
good("");
|
||||
good(";x");
|
||||
good(";x;y");
|
||||
good(";x=y");
|
||||
good(";x;y=z");
|
||||
good(" ;x");
|
||||
good("\t;x");
|
||||
good(" \t;x");
|
||||
good("\t ;x");
|
||||
good(" ; x");
|
||||
good(" ;\tx");
|
||||
good("\t ; \tx");
|
||||
good(";x= y");
|
||||
good(" ;x= y");
|
||||
good(" ; x= y");
|
||||
good(R"(;x="\"")");
|
||||
good(R"(;x="\\")");
|
||||
good(R"(;x;y=z;z="\"";p="\\";q="1\"2\\")");
|
||||
|
||||
bad(" ");
|
||||
bad(";");
|
||||
bad("=");
|
||||
bad(" ;");
|
||||
bad("; ");
|
||||
bad(" ; ");
|
||||
bad(" ; x ");
|
||||
bad(";x =");
|
||||
bad(";x = ");
|
||||
bad(";x==");
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
|
||||
@@ -328,49 +328,6 @@ public:
|
||||
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<true, string_body> p;
|
||||
p.eager(true);
|
||||
p.put(b.data(), ec);
|
||||
p.on_header(
|
||||
[this](parser<true, string_body>& 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<true, string_body> p;
|
||||
p.eager(true);
|
||||
p.put(b.data(), ec);
|
||||
p.on_header(
|
||||
[this](parser<true, string_body>&, error_code& ec)
|
||||
{
|
||||
ec.assign(errc::bad_message,
|
||||
generic_category());
|
||||
});
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -378,7 +335,6 @@ public:
|
||||
testNeedMore<flat_buffer>();
|
||||
testNeedMore<multi_buffer>();
|
||||
testGotSome();
|
||||
testCallback();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+28
-11
@@ -51,7 +51,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_request(verb, string_view method_str_,
|
||||
on_request_impl(verb, string_view method_str_,
|
||||
string_view path_, int version_, error_code& ec)
|
||||
{
|
||||
method = std::string(
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int code,
|
||||
on_response_impl(int code,
|
||||
string_view reason_,
|
||||
int version_, error_code& ec)
|
||||
{
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_field(field, string_view name,
|
||||
on_field_impl(field, string_view name,
|
||||
string_view value, error_code& ec)
|
||||
{
|
||||
++got_on_field;
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code& ec)
|
||||
on_header_impl(error_code& ec)
|
||||
{
|
||||
++got_on_header;
|
||||
if(fc_)
|
||||
@@ -105,9 +105,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_body(boost::optional<
|
||||
std::uint64_t> const& content_length_,
|
||||
error_code& ec)
|
||||
on_body_init_impl(
|
||||
boost::optional<std::uint64_t> const& content_length_,
|
||||
error_code& ec)
|
||||
{
|
||||
++got_on_body;
|
||||
got_content_length =
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
}
|
||||
|
||||
std::size_t
|
||||
on_data(string_view s,
|
||||
on_body_impl(string_view s,
|
||||
error_code& ec)
|
||||
{
|
||||
body.append(s.data(), s.size());
|
||||
@@ -131,8 +131,10 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t,
|
||||
string_view, error_code& ec)
|
||||
on_chunk_header_impl(
|
||||
std::uint64_t,
|
||||
string_view,
|
||||
error_code& ec)
|
||||
{
|
||||
++got_on_chunk;
|
||||
if(fc_)
|
||||
@@ -141,8 +143,23 @@ public:
|
||||
ec.assign(0, ec.category());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
on_chunk_body_impl(
|
||||
std::uint64_t,
|
||||
string_view s,
|
||||
error_code& ec)
|
||||
{
|
||||
body.append(s.data(), s.size());
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
else
|
||||
ec.assign(0, ec.category());
|
||||
return s.size();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
on_complete(error_code& ec)
|
||||
on_finish_impl(error_code& ec)
|
||||
{
|
||||
++got_on_complete;
|
||||
if(fc_)
|
||||
|
||||
Reference in New Issue
Block a user