Fix Content-Length parsing

Fix #1880
This commit is contained in:
Richard Hodges
2020-03-18 16:45:05 +01:00
parent 5e9e517dc5
commit d12dcef52c
6 changed files with 113 additions and 20 deletions

View File

@ -1,5 +1,6 @@
Version 288:
* Fix Content-Length parsing
--------------------------------------------------------------------------------

View File

@ -624,6 +624,10 @@ protected:
on_finish_impl(error_code& ec) = 0;
private:
boost::optional<std::uint64_t>
content_length_unchecked() const;
template<class ConstBufferSequence>
std::size_t
put_from_stack(

View File

@ -56,6 +56,16 @@ put(ConstBufferSequence const& buffers,
buf_.get(), size}, ec);
}
template<bool isRequest>
boost::optional<std::uint64_t>
basic_parser<isRequest>::
content_length_unchecked() const
{
if(f_ & flagContentLength)
return len0_;
return boost::none;
}
template<bool isRequest>
template<class ConstBufferSequence>
std::size_t

View File

@ -50,9 +50,7 @@ basic_parser<isRequest>::
content_length() const
{
BOOST_ASSERT(is_header_done());
if(! (f_ & flagContentLength))
return boost::none;
return len0_;
return content_length_unchecked();
}
template<bool isRequest>
@ -786,30 +784,48 @@ do_field(field f,
// Content-Length
if(f == field::content_length)
{
if(f_ & flagContentLength)
auto bad_content_length = [&ec]
{
// duplicate
ec = error::bad_content_length;
return;
}
};
if(f_ & flagChunked)
{
// conflicting field
ec = error::bad_content_length;
return;
}
if(f_ & flagChunked)
return bad_content_length();
std::uint64_t v;
if(! parse_dec(value, v))
// Content-length may be a comma-separated list of integers
auto tokens_unprocessed = 1 +
std::count(value.begin(), value.end(), ',');
auto tokens = opt_token_list(value);
if (tokens.begin() == tokens.end() ||
!validate_list(tokens))
return bad_content_length();
auto existing = this->content_length_unchecked();
for (auto tok : tokens)
{
ec = error::bad_content_length;
return;
std::uint64_t v;
if (!parse_dec(tok, v))
return bad_content_length();
--tokens_unprocessed;
if (existing.has_value())
{
if (v != *existing)
return bad_content_length();
}
else
{
existing = v;
}
}
if (tokens_unprocessed)
return bad_content_length();
BOOST_ASSERT(existing.has_value());
ec = {};
len_ = v;
len0_ = v;
len_ = *existing;
len0_ = *existing;
f_ |= flagContentLength;
return;
}

View File

@ -692,6 +692,8 @@ public:
parsegrind<P>(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0});
parsegrind<P>(m("Content-Lengths: many\r\n"), expect_flags{*this, 0});
parsegrind<P>(m("Content: full\r\n"), expect_flags{*this, 0});
parsegrind<P>(m("Content-Length: 0\r\n"
"Content-Length: 0\r\n"), expect_flags{*this, 0});
failgrind<P>(c("\r\n"), error::bad_content_length);
failgrind<P>(c("18446744073709551616\r\n"), error::bad_content_length);
@ -699,8 +701,8 @@ public:
failgrind<P>(c("0 1\r\n"), error::bad_content_length);
failgrind<P>(c(",\r\n"), error::bad_content_length);
failgrind<P>(c("0,\r\n"), error::bad_content_length);
failgrind<P>(m(
"Content-Length: 0\r\nContent-Length: 0\r\n"), error::bad_content_length);
failgrind<P>(m("Content-Length: 0\r\n"
"Content-Length: 100\r\n"), error::bad_content_length);
}
void

View File

@ -357,6 +357,65 @@ public:
BEAST_EXPECT(p.need_eof());
}
void
testIssue1880()
{
// A user raised the issue that multiple Content-Length fields and
// values are permissible provided all values are the same.
// See rfc7230 section-3.3.2
// https://tools.ietf.org/html/rfc7230#section-3.3.2
// Credit: Dimitry Bulsunov
auto checkPass = [&](std::string const& message)
{
response_parser<string_body> parser;
error_code ec;
parser.put(net::buffer(message), ec);
BEAST_EXPECTS(!ec.failed(), ec.message());
};
auto checkFail = [&](std::string const& message)
{
response_parser<string_body> parser;
error_code ec;
parser.put(net::buffer(message), ec);
BEAST_EXPECTS(ec == error::bad_content_length, ec.message());
};
// multiple contents lengths the same
checkPass(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0\r\n"
"Content-Length: 0\r\n"
"\r\n");
// multiple contents lengths different
checkFail(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0\r\n"
"Content-Length: 1\r\n"
"\r\n");
// multiple content in same header
checkPass(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0, 0, 0\r\n"
"\r\n");
// multiple content in same header but mismatch (case 1)
checkFail(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0, 0, 1\r\n"
"\r\n");
// multiple content in same header but mismatch (case 2)
checkFail(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0, 0, 0\r\n"
"Content-Length: 1\r\n"
"\r\n");
}
void
run() override
{
@ -366,6 +425,7 @@ public:
testGotSome();
testIssue818();
testIssue1187();
testIssue1880();
}
};