mirror of
https://github.com/boostorg/beast.git
synced 2025-07-30 21:07:26 +02:00
Add basic_parser header and body limits:
fix #550 * default body limits are now 1MB/8MB * default header limit is 8KB Actions Required: * Call body_limit and/or header_limit as needed to adjust the limits to suitable values if the defaults are insufficient.
This commit is contained in:
@ -1,15 +1,20 @@
|
|||||||
Version 70:
|
Version 70:
|
||||||
|
|
||||||
* Serialize in one step when possible
|
* Serialize in one step when possible
|
||||||
|
* Add basic_parser header and body limits
|
||||||
|
|
||||||
API Changes:
|
API Changes:
|
||||||
|
|
||||||
* Rename to message::base
|
* Rename to message::base
|
||||||
|
* basic_parser default limits are now 1MB/8MB
|
||||||
|
|
||||||
Actions Required:
|
Actions Required:
|
||||||
|
|
||||||
* Change calls to message::header_part() with message::base()
|
* Change calls to message::header_part() with message::base()
|
||||||
|
|
||||||
|
* Call body_limit and/or header_limit as needed to adjust the
|
||||||
|
limits to suitable values if the defaults are insufficient.
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Version 69:
|
Version 69:
|
||||||
|
@ -23,7 +23,7 @@ if the underlying connection is closed),
|
|||||||
|
|
||||||
[heading Parser Options]
|
[heading Parser Options]
|
||||||
|
|
||||||
The parser provides two options which may be set before parsing begins:
|
The parser provides a few options which may be set before parsing begins:
|
||||||
|
|
||||||
[table Parser Options
|
[table Parser Options
|
||||||
[[Name][Default][Description]]
|
[[Name][Default][Description]]
|
||||||
@ -58,6 +58,27 @@ The parser provides two options which may be set before parsing begins:
|
|||||||
no body is expected. The parser will consider the message complete
|
no body is expected. The parser will consider the message complete
|
||||||
after the header has been received.
|
after the header has been received.
|
||||||
]]
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.beast__http__basic_parser.body_limit `body_limit`]
|
||||||
|
][
|
||||||
|
1MB/8MB
|
||||||
|
][
|
||||||
|
This function sets the maximum allowed size of the content body.
|
||||||
|
When a body larger than the specified size is detected, an error
|
||||||
|
is generated and parsing terminates. This setting helps protect
|
||||||
|
servers from resource exhaustion attacks. The default limit when
|
||||||
|
parsing requests is 1MB, and for parsing responses 8MB.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.beast__http__basic_parser.header_limit `header_limit`]
|
||||||
|
][
|
||||||
|
8KB
|
||||||
|
][
|
||||||
|
This function sets the maximum allowed size of the header
|
||||||
|
including all field name, value, and delimiter characters
|
||||||
|
and also including the CRLF sequences in the serialized
|
||||||
|
input.
|
||||||
|
]]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/assert.hpp>
|
#include <boost/assert.hpp>
|
||||||
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -105,30 +106,50 @@ class basic_parser
|
|||||||
static unsigned constexpr flagUpgrade = 1<< 12;
|
static unsigned constexpr flagUpgrade = 1<< 12;
|
||||||
static unsigned constexpr flagFinalChunk = 1<< 13;
|
static unsigned constexpr flagFinalChunk = 1<< 13;
|
||||||
|
|
||||||
std::uint64_t len_; // size of chunk or body
|
static
|
||||||
std::unique_ptr<char[]> buf_;
|
std::uint64_t
|
||||||
std::size_t buf_len_ = 0;
|
default_body_limit(std::true_type)
|
||||||
std::size_t skip_ = 0; // search from here
|
{
|
||||||
state state_ = state::nothing_yet;
|
// limit for requests
|
||||||
unsigned f_ = 0; // flags
|
return 1 * 1024 * 1024; // 1MB
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::uint64_t
|
||||||
|
default_body_limit(std::false_type)
|
||||||
|
{
|
||||||
|
// limit for responses
|
||||||
|
return 8 * 1024 * 1024; // 8MB
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t body_limit_; // max payload body
|
||||||
|
std::uint64_t len_; // size of chunk or body
|
||||||
|
std::unique_ptr<char[]> buf_; // temp storage
|
||||||
|
std::size_t buf_len_ = 0; // size of buf_
|
||||||
|
std::size_t skip_ = 0; // resume search here
|
||||||
|
std::uint32_t
|
||||||
|
header_limit_ = 8192; // max header size
|
||||||
|
state state_ = // initial state
|
||||||
|
state::nothing_yet;
|
||||||
|
unsigned f_ = 0; // flags
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// `true` if this parser parses requests, `false` for responses.
|
||||||
|
using is_request =
|
||||||
|
std::integral_constant<bool, isRequest>;
|
||||||
|
|
||||||
/// Copy constructor (disallowed)
|
/// Copy constructor (disallowed)
|
||||||
basic_parser(basic_parser const&) = delete;
|
basic_parser(basic_parser const&) = delete;
|
||||||
|
|
||||||
/// Copy assignment (disallowed)
|
/// Copy assignment (disallowed)
|
||||||
basic_parser& operator=(basic_parser const&) = delete;
|
basic_parser& operator=(basic_parser const&) = delete;
|
||||||
|
|
||||||
/// Default constructor
|
|
||||||
basic_parser() = default;
|
|
||||||
|
|
||||||
/// `true` if this parser parses requests, `false` for responses.
|
|
||||||
using is_request =
|
|
||||||
std::integral_constant<bool, isRequest>;
|
|
||||||
|
|
||||||
/// Destructor
|
/// Destructor
|
||||||
~basic_parser() = default;
|
~basic_parser() = default;
|
||||||
|
|
||||||
|
/// Default constructor
|
||||||
|
basic_parser();;
|
||||||
|
|
||||||
/** Move constructor
|
/** Move constructor
|
||||||
|
|
||||||
After the move, the only valid operation on the
|
After the move, the only valid operation on the
|
||||||
@ -246,6 +267,58 @@ public:
|
|||||||
return (f_ & flagNeedEOF) != 0;
|
return (f_ & flagNeedEOF) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set the limit on the payload body.
|
||||||
|
|
||||||
|
This function sets the maximum allowed size of the payload body,
|
||||||
|
before any encodings except chunked have been removed. Depending
|
||||||
|
on the message semantics, one of these cases will apply:
|
||||||
|
|
||||||
|
@li The Content-Length is specified and exceeds the limit. In
|
||||||
|
this case the result @ref error::body_limit is returned
|
||||||
|
immediately after the header is parsed.
|
||||||
|
|
||||||
|
@li The Content-Length is unspecified and the chunked encoding
|
||||||
|
is not specified as the last encoding. In this case the end of
|
||||||
|
message is determined by the end of file indicator on the
|
||||||
|
associated stream or input source. If a sufficient number of
|
||||||
|
body payload octets are presented to the parser to exceed the
|
||||||
|
configured limit, the parse fails with the result
|
||||||
|
@ref error::body_limit
|
||||||
|
|
||||||
|
@li The Transfer-Encoding specifies the chunked encoding as the
|
||||||
|
last encoding. In this case, when the number of payload body
|
||||||
|
octets produced by removing the chunked encoding exceeds
|
||||||
|
the configured limit, the parse fails with the result
|
||||||
|
@ref error::body_limit.
|
||||||
|
|
||||||
|
Setting the limit after any body octets have been parsed
|
||||||
|
results in undefined behavior.
|
||||||
|
|
||||||
|
The default limit is 1MB for requests and 8MB for responses.
|
||||||
|
|
||||||
|
@param v The payload body limit to set
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
body_limit(std::uint64_t v)
|
||||||
|
{
|
||||||
|
body_limit_ = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set a limit on the total size of the header.
|
||||||
|
|
||||||
|
This function sets the maximum allowed size of the header
|
||||||
|
including all field name, value, and delimiter characters
|
||||||
|
and also including the CRLF sequences in the serialized
|
||||||
|
input. If the end of the header is not found within the
|
||||||
|
limit of the header size, the error @ref error::header_limit
|
||||||
|
is returned by @ref put.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
header_limit(std::uint32_t v)
|
||||||
|
{
|
||||||
|
header_limit_ = v;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if the eager parse option is set.
|
/// Returns `true` if the eager parse option is set.
|
||||||
bool
|
bool
|
||||||
eager() const
|
eager() const
|
||||||
|
@ -80,6 +80,20 @@ enum class error
|
|||||||
*/
|
*/
|
||||||
buffer_overflow,
|
buffer_overflow,
|
||||||
|
|
||||||
|
/** Header limit exceeded.
|
||||||
|
|
||||||
|
The parser detected an incoming message header which
|
||||||
|
exceeded a configured limit.
|
||||||
|
*/
|
||||||
|
header_limit,
|
||||||
|
|
||||||
|
/** Body limit exceeded.
|
||||||
|
|
||||||
|
The parser detected an incoming message body which
|
||||||
|
exceeded a configured limit.
|
||||||
|
*/
|
||||||
|
body_limit,
|
||||||
|
|
||||||
/** A memory allocation failed.
|
/** A memory allocation failed.
|
||||||
|
|
||||||
When basic_fields throws std::bad_alloc, it is
|
When basic_fields throws std::bad_alloc, it is
|
||||||
|
@ -55,12 +55,21 @@ skip_ows_rev2(
|
|||||||
|
|
||||||
} // detail
|
} // detail
|
||||||
|
|
||||||
|
template<bool isRequest, class Derived>
|
||||||
|
basic_parser<isRequest, Derived>::
|
||||||
|
basic_parser()
|
||||||
|
: body_limit_(
|
||||||
|
default_body_limit(is_request{}))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
template<bool isRequest, class Derived>
|
template<bool isRequest, class Derived>
|
||||||
template<class OtherDerived>
|
template<class OtherDerived>
|
||||||
basic_parser<isRequest, Derived>::
|
basic_parser<isRequest, Derived>::
|
||||||
basic_parser(basic_parser<
|
basic_parser(basic_parser<
|
||||||
isRequest, OtherDerived>&& other)
|
isRequest, OtherDerived>&& other)
|
||||||
: len_(other.len_)
|
: body_limit_(other.body_limit_)
|
||||||
|
, len_(other.len_)
|
||||||
, 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_)
|
||||||
@ -294,9 +303,11 @@ basic_parser<isRequest, Derived>::
|
|||||||
parse_header(char const*& p,
|
parse_header(char const*& p,
|
||||||
std::size_t n, error_code& ec)
|
std::size_t n, error_code& ec)
|
||||||
{
|
{
|
||||||
|
if( n > header_limit_)
|
||||||
|
n = header_limit_;
|
||||||
if(n < skip_ + 4)
|
if(n < skip_ + 4)
|
||||||
{
|
{
|
||||||
ec = http::error::need_more;
|
ec = error::need_more;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto const term =
|
auto const term =
|
||||||
@ -304,6 +315,11 @@ parse_header(char const*& p,
|
|||||||
if(! term)
|
if(! term)
|
||||||
{
|
{
|
||||||
skip_ = n - 3;
|
skip_ = n - 3;
|
||||||
|
if(skip_ + 4 > header_limit_)
|
||||||
|
{
|
||||||
|
ec = error::header_limit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
ec = http::error::need_more;
|
ec = http::error::need_more;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -528,6 +544,12 @@ basic_parser<isRequest, Derived>::
|
|||||||
parse_body_to_eof(char const*& p,
|
parse_body_to_eof(char const*& p,
|
||||||
std::size_t n, error_code& ec)
|
std::size_t n, error_code& ec)
|
||||||
{
|
{
|
||||||
|
if(n > body_limit_)
|
||||||
|
{
|
||||||
|
ec = error::body_limit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
body_limit_ = body_limit_ - n;
|
||||||
impl().on_data(string_view{p, n}, ec);
|
impl().on_data(string_view{p, n}, ec);
|
||||||
if(ec)
|
if(ec)
|
||||||
return;
|
return;
|
||||||
@ -596,6 +618,12 @@ parse_chunk_header(char const*& p0,
|
|||||||
}
|
}
|
||||||
if(v != 0)
|
if(v != 0)
|
||||||
{
|
{
|
||||||
|
if(v > body_limit_)
|
||||||
|
{
|
||||||
|
ec = error::body_limit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
body_limit_ -= v;
|
||||||
if(*p == ';')
|
if(*p == ';')
|
||||||
{
|
{
|
||||||
// VFALCO TODO Validate extension
|
// VFALCO TODO Validate extension
|
||||||
@ -868,6 +896,12 @@ do_field(field f,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(v > body_limit_)
|
||||||
|
{
|
||||||
|
ec = error::body_limit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ec.assign(0, ec.category());
|
ec.assign(0, ec.category());
|
||||||
len_ = v;
|
len_ = v;
|
||||||
f_ |= flagContentLength;
|
f_ |= flagContentLength;
|
||||||
|
@ -44,6 +44,8 @@ public:
|
|||||||
case error::unexpected_body: return "unexpected body";
|
case error::unexpected_body: return "unexpected body";
|
||||||
case error::need_buffer: return "need buffer";
|
case error::need_buffer: return "need buffer";
|
||||||
case error::buffer_overflow: return "buffer overflow";
|
case error::buffer_overflow: return "buffer overflow";
|
||||||
|
case error::header_limit: return "header limit exceeded";
|
||||||
|
case error::body_limit: return "body limit exceeded";
|
||||||
case error::bad_alloc: return "bad alloc";
|
case error::bad_alloc: return "bad alloc";
|
||||||
case error::bad_line_ending: return "bad line ending";
|
case error::bad_line_ending: return "bad line ending";
|
||||||
case error::bad_method: return "bad method";
|
case error::bad_method: return "bad method";
|
||||||
|
@ -615,7 +615,6 @@ public:
|
|||||||
length(c("1\r\n"), 1);
|
length(c("1\r\n"), 1);
|
||||||
length(c("01\r\n"), 1);
|
length(c("01\r\n"), 1);
|
||||||
length(c("9\r\n"), 9);
|
length(c("9\r\n"), 9);
|
||||||
length(c("123456789\r\n"), 123456789);
|
|
||||||
length(c("42 \r\n"), 42);
|
length(c("42 \r\n"), 42);
|
||||||
length(c("42\t\r\n"), 42);
|
length(c("42\t\r\n"), 42);
|
||||||
length(c("42 \t \r\n"), 42);
|
length(c("42 \t \r\n"), 42);
|
||||||
@ -923,6 +922,68 @@ public:
|
|||||||
BEAST_EXPECT(p.body == "*****");
|
BEAST_EXPECT(p.body == "*****");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testLimits()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
ostream(b) <<
|
||||||
|
"POST / HTTP/1.1\r\n"
|
||||||
|
"Content-Length: 2\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"**";
|
||||||
|
error_code ec;
|
||||||
|
test_parser<true> p;
|
||||||
|
p.header_limit(10);
|
||||||
|
p.eager(true);
|
||||||
|
p.put(b.data(), ec);
|
||||||
|
BEAST_EXPECTS(ec == error::header_limit, ec.message());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
ostream(b) <<
|
||||||
|
"POST / HTTP/1.1\r\n"
|
||||||
|
"Content-Length: 2\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"**";
|
||||||
|
error_code ec;
|
||||||
|
test_parser<true> p;
|
||||||
|
p.body_limit(1);
|
||||||
|
p.eager(true);
|
||||||
|
p.put(b.data(), ec);
|
||||||
|
BEAST_EXPECTS(ec == error::body_limit, ec.message());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
ostream(b) <<
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"**";
|
||||||
|
error_code ec;
|
||||||
|
test_parser<false> p;
|
||||||
|
p.body_limit(1);
|
||||||
|
p.eager(true);
|
||||||
|
p.put(b.data(), ec);
|
||||||
|
BEAST_EXPECTS(ec == error::body_limit, ec.message());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
ostream(b) <<
|
||||||
|
"POST / HTTP/1.1\r\n"
|
||||||
|
"Transfer-Encoding: chunked\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"2\r\n"
|
||||||
|
"**\r\n"
|
||||||
|
"0\r\n\r\n";
|
||||||
|
error_code ec;
|
||||||
|
test_parser<true> p;
|
||||||
|
p.body_limit(1);
|
||||||
|
p.eager(true);
|
||||||
|
p.put(b.data(), ec);
|
||||||
|
BEAST_EXPECTS(ec == error::body_limit, ec.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
template<bool isRequest, class Derived>
|
template<bool isRequest, class Derived>
|
||||||
@ -1088,6 +1149,7 @@ public:
|
|||||||
testUpgradeField();
|
testUpgradeField();
|
||||||
testBody();
|
testBody();
|
||||||
testSplit();
|
testSplit();
|
||||||
|
testLimits();
|
||||||
testIssue430();
|
testIssue430();
|
||||||
testIssue452();
|
testIssue452();
|
||||||
testIssue496();
|
testIssue496();
|
||||||
|
@ -42,6 +42,7 @@ public:
|
|||||||
check("beast.http", error::unexpected_body);
|
check("beast.http", error::unexpected_body);
|
||||||
check("beast.http", error::need_buffer);
|
check("beast.http", error::need_buffer);
|
||||||
check("beast.http", error::buffer_overflow);
|
check("beast.http", error::buffer_overflow);
|
||||||
|
check("beast.http", error::body_limit);
|
||||||
check("beast.http", error::bad_alloc);
|
check("beast.http", error::bad_alloc);
|
||||||
|
|
||||||
check("beast.http", error::bad_line_ending);
|
check("beast.http", error::bad_line_ending);
|
||||||
|
Reference in New Issue
Block a user