Fix chunk header parsing

fix #431, fix #430
This commit is contained in:
Vinnie Falco
2017-06-07 10:48:35 -07:00
parent 3c2681f02b
commit 6203074c92
5 changed files with 247 additions and 64 deletions

View File

@@ -12,6 +12,7 @@ Version 50
* Add missing handler_alloc nested types * Add missing handler_alloc nested types
* Fix chunk delimiter parsing * Fix chunk delimiter parsing
* Fix test::pipe read_size * Fix test::pipe read_size
* Fix chunk header parsing
API Changes: API Changes:

View File

@@ -85,19 +85,18 @@ class basic_parser
static unsigned constexpr flagHTTP11 = 1<< 4; static unsigned constexpr flagHTTP11 = 1<< 4;
static unsigned constexpr flagNeedEOF = 1<< 5; static unsigned constexpr flagNeedEOF = 1<< 5;
static unsigned constexpr flagExpectCRLF = 1<< 6; static unsigned constexpr flagExpectCRLF = 1<< 6;
static unsigned constexpr flagFinalChunk = 1<< 7; static unsigned constexpr flagConnectionClose = 1<< 7;
static unsigned constexpr flagConnectionClose = 1<< 8; static unsigned constexpr flagConnectionUpgrade = 1<< 8;
static unsigned constexpr flagConnectionUpgrade = 1<< 9; static unsigned constexpr flagConnectionKeepAlive = 1<< 9;
static unsigned constexpr flagConnectionKeepAlive = 1<< 10; static unsigned constexpr flagContentLength = 1<< 10;
static unsigned constexpr flagContentLength = 1<< 11; static unsigned constexpr flagChunked = 1<< 11;
static unsigned constexpr flagChunked = 1<< 12; static unsigned constexpr flagUpgrade = 1<< 12;
static unsigned constexpr flagUpgrade = 1<< 13; static unsigned constexpr flagFinalChunk = 1<< 13;
std::uint64_t len_; // size of chunk or body std::uint64_t len_; // size of chunk or body
std::unique_ptr<char[]> buf_; std::unique_ptr<char[]> buf_;
std::size_t buf_len_ = 0; std::size_t buf_len_ = 0;
std::size_t skip_ = 0; // search from here std::size_t skip_ = 0; // search from here
std::size_t x_; // scratch variable
state state_ = state::nothing_yet; state state_ = state::nothing_yet;
unsigned f_ = 0; // flags unsigned f_ = 0; // flags

View File

@@ -29,7 +29,6 @@ basic_parser(basic_parser<
, buf_(std::move(other.buf_)) , buf_(std::move(other.buf_))
, buf_len_(other.buf_len_) , buf_len_(other.buf_len_)
, skip_(other.skip_) , skip_(other.skip_)
, x_(other.x_)
, state_(other.state_) , state_(other.state_)
, f_(other.f_) , f_(other.f_)
{ {
@@ -152,7 +151,7 @@ loop:
case state::chunk_header0: case state::chunk_header0:
impl().on_body(content_length(), ec); impl().on_body(content_length(), ec);
if(ec) if(ec)
return 0; goto done;
state_ = state::chunk_header; state_ = state::chunk_header;
// [[fallthrough]] // [[fallthrough]]
@@ -487,7 +486,7 @@ parse_body_to_eof(char const*& p,
template<bool isRequest, class Derived> template<bool isRequest, class Derived>
void void
basic_parser<isRequest, Derived>:: basic_parser<isRequest, Derived>::
parse_chunk_header(char const*& p, parse_chunk_header(char const*& p0,
std::size_t n, error_code& ec) std::size_t n, error_code& ec)
{ {
/* /*
@@ -504,30 +503,9 @@ parse_chunk_header(char const*& p,
chunk-ext-val = token / quoted-string chunk-ext-val = token / quoted-string
*/ */
auto p = p0;
auto const pend = p + n; auto const pend = p + n;
auto pbegin = p; char const* eol;
// Treat the last CRLF in a chunk as
// part of the next chunk, so p can
// be parsed in one call instead of two.
if(f_ & flagExpectCRLF)
{
if(n < 2)
{
ec = error::need_more;
return;
}
if(! parse_crlf(p))
{
ec = error::bad_chunk;
return;
}
f_ &= ~flagExpectCRLF;
pbegin += 2;
n -= 2;
}
char const* term;
if(! (f_ & flagFinalChunk)) if(! (f_ & flagFinalChunk))
{ {
@@ -536,15 +514,29 @@ parse_chunk_header(char const*& p,
ec = error::need_more; ec = error::need_more;
return; return;
} }
term = find_eol(p + skip_, pend, ec); if(f_ & flagExpectCRLF)
{
// Treat the last CRLF in a chunk as
// part of the next chunk, so p can
// be parsed in one call instead of two.
if(! parse_crlf(p))
{
ec = error::bad_chunk;
return;
}
}
eol = find_eol(p0 + skip_, pend, ec);
if(ec) if(ec)
return; return;
if(! term) if(! eol)
{ {
ec = error::need_more; ec = error::need_more;
skip_ = n - 1; skip_ = n - 1;
return; return;
} }
skip_ = static_cast<
std::size_t>(eol - 2 - p0);
std::uint64_t v; std::uint64_t v;
if(! parse_hex(p, v)) if(! parse_hex(p, v))
{ {
@@ -555,64 +547,63 @@ parse_chunk_header(char const*& p,
{ {
if(*p == ';') if(*p == ';')
{ {
// VFALCO We need to parse the chunk // VFALCO TODO Validate extension
// extension to validate p here. impl().on_chunk(v, make_string(
auto const ext = make_string(p, term - 2); p, eol - 2), ec);
impl().on_chunk(v, ext, ec);
if(ec) if(ec)
return; return;
} }
else if(p != term - 2) else if(p != eol - 2)
{ {
ec = error::bad_chunk; ec = error::bad_chunk;
return; return;
} }
p = term;
len_ = v; len_ = v;
skip_ = 0; skip_ = 2;
p0 = eol;
f_ |= flagExpectCRLF; f_ |= flagExpectCRLF;
state_ = state::chunk_body; state_ = state::chunk_body;
return; return;
} }
pbegin = p;
n = static_cast<std::size_t>(pend - pbegin);
x_ = term - 2 - pbegin; // start of first '\r\n'
skip_ = x_;
f_ |= flagFinalChunk; f_ |= flagFinalChunk;
} }
else
{
BOOST_ASSERT(n >= 5);
if(f_ & flagExpectCRLF)
BOOST_VERIFY(parse_crlf(p));
std::uint64_t v;
BOOST_VERIFY(parse_hex(p, v));
eol = find_eol(p, pend, ec);
BOOST_ASSERT(! ec);
}
term = find_eom( auto eom = find_eom(p0 + skip_, pend, ec);
pbegin + skip_, pend, ec);
if(ec) if(ec)
return; return;
if(! term) if(! eom)
{ {
// VFALCO Is this right? BOOST_ASSERT(n >= 3);
if(n > 3) skip_ = n - 3;
skip_ = (pend - pbegin) - 3;
ec = error::need_more; ec = error::need_more;
return; return;
} }
if(*p == ';') if(*p == ';')
{ {
auto const ext = make_string(p, pbegin + x_); // VFALCO TODO Validate extension
impl().on_chunk(0, ext, ec); impl().on_chunk(0, make_string(
p, eol - 2), ec);
if(ec) if(ec)
return; return;
p = pbegin + x_;
} }
if(! parse_crlf(p)) p = eol;
{ parse_fields(p, eom, ec);
ec = error::bad_chunk;
return;
}
parse_fields(p, term, ec);
if(ec) if(ec)
return; return;
BOOST_ASSERT(p == term); BOOST_ASSERT(p == eom);
p0 = eom;
impl().on_complete(ec); impl().on_complete(ec);
if(ec) if(ec)

View File

@@ -942,6 +942,116 @@ public:
#endif #endif
} }
//--------------------------------------------------------------------------
template<bool isRequest, class Derived>
void
put(basic_parser<isRequest, Derived>& p,
string_view s)
{
error_code ec;
auto const bytes_used = p.put(
boost::asio::buffer(s.data(), s.size()), ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(bytes_used == s.size());
}
// https://github.com/vinniefalco/Beast/issues/430
void
testRegression430()
{
{
test_parser<false> p;
p.eager(true);
put(p,
"HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "Content-Type: application/octet-stream\r\n" "\r\n"
"4\r\nabcd\r\n"
"0\r\n\r\n"
);
}
{
test_parser<false> p;
p.eager(true);
put(p,
"HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "Content-Type: application/octet-stream\r\n" "\r\n"
"4\r\nabcd");
put(p,
"\r\n"
"0\r\n\r\n"
);
}
}
//--------------------------------------------------------------------------
template<class Parser, class Pred>
void
bufgrind(string_view s, Pred&& pred)
{
using boost::asio::buffer;
for(std::size_t n = 1; n < s.size() - 1; ++n)
{
Parser p;
p.eager(true);
error_code ec;
std::size_t used;
used = p.put(buffer(s.data(), n), ec);
if(ec == error::need_more)
continue;
if(! BEAST_EXPECTS(! ec, ec.message()))
continue;
if(! BEAST_EXPECT(used == n))
continue;
used = p.put(buffer(
s.data() + n, s.size() - n), ec);
if(ec == error::need_more)
continue;
if(! BEAST_EXPECTS(! ec, ec.message()))
continue;
if(! BEAST_EXPECT(used == s.size() -n))
continue;
if(! BEAST_EXPECT(p.is_done()))
continue;
pred(p);
}
}
void
testBufGrind()
{
bufgrind<test_parser<false>>(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n"
"4\r\nabcd\r\n"
"0\r\n\r\n"
,[&](test_parser<false> const& p)
{
BEAST_EXPECT(p.body == "abcd");
});
bufgrind<test_parser<false>>(
"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"
,[&](test_parser<false> const& p)
{
BEAST_EXPECT(p.body == "*****--");
});
}
//--------------------------------------------------------------------------
void void
run() override run() override
{ {
@@ -956,6 +1066,8 @@ public:
testUpgradeField(); testUpgradeField();
testBody(); testBody();
testSplit(); testSplit();
testRegression430();
testBufGrind();
} }
}; };

View File

@@ -363,9 +363,86 @@ public:
} }
} }
// https://github.com/vinniefalco/Beast/issues/430
void
testRegression430()
{
test::pipe c{ios_};
c.server.read_size(1);
ostream(c.server.buffer) <<
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n"
"4\r\nabcd\r\n"
"0\r\n\r\n";
error_code ec;
flat_buffer fb;
parser<false, dynamic_body> p;
read(c.server, fb, p, ec);
BEAST_EXPECTS(! ec, ec.message());
}
//--------------------------------------------------------------------------
template<class Parser, class Pred>
void
readgrind(string_view s, Pred&& pred)
{
using boost::asio::buffer;
for(std::size_t n = 1; n < s.size() - 1; ++n)
{
Parser p;
error_code ec;
flat_buffer b;
test::pipe c{ios_};
ostream(c.server.buffer) << s;
c.server.read_size(n);
read(c.server, b, p, ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
continue;
pred(p);
}
}
void
testReadGrind()
{
readgrind<test_parser<false>>(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n"
"4\r\nabcd\r\n"
"0\r\n\r\n"
,[&](test_parser<false> const& p)
{
BEAST_EXPECT(p.body == "abcd");
});
readgrind<test_parser<false>>(
"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"
,[&](test_parser<false> const& p)
{
BEAST_EXPECT(p.body == "*****--");
});
}
void void
run() override run() override
{ {
#if 0
testThrow(); testThrow();
testBufferOverflow(); testBufferOverflow();
@@ -377,6 +454,9 @@ public:
testEof(yield); }); testEof(yield); });
testIoService(); testIoService();
testRegression430();
#endif
testReadGrind();
} }
}; };