Skip to content

Commit 09d51b1

Browse files
committed
http_{request,response}_parser: Limit header length
1 parent d860911 commit 09d51b1

9 files changed

Lines changed: 84 additions & 2 deletions

etc/poseidon/main.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ network
9898
// null ::= use zlib default value
9999
default_compression_level = 8
100100

101+
// max_header_length:
102+
// [bytes] ::= maximum number of bytes that the headers of a message,
103+
// including the request URI if any, is allowed to
104+
// contain
105+
// null ::= default value: 256 KiB
106+
max_header_length = 262144
107+
101108
// max_request_content_length:
102109
// [bytes] ::= maximum number of bytes that the body of a request
103110
// message from a client is allowed to contain (used by

poseidon/http/http_c_headers.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ HTTP_C_Headers::
1212
{
1313
}
1414

15+
size_t
16+
HTTP_C_Headers::
17+
estimate_size()
18+
const
19+
{
20+
size_t tlen = this->raw_host.size() + this->raw_userinfo.size()
21+
+ this->raw_path.size() + this->raw_query.size();
22+
for(const auto& hr : this->headers)
23+
tlen += 4 + hr.first.length() + hr.second.as_string_length();
24+
return tlen;
25+
}
26+
1527
void
1628
HTTP_C_Headers::
1729
encode_and_set_path(chars_view path)

poseidon/http/http_c_headers.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ struct HTTP_C_Headers
6464
this->headers.clear();
6565
}
6666

67+
// Estimates the number of bytes of all headers.
68+
size_t
69+
estimate_size()
70+
const;
71+
6772
// Encodes an arbitrary path and assigns it to `raw_path`.
6873
void
6974
encode_and_set_path(chars_view path);

poseidon/http/http_request_parser.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ HTTP_Request_Parser::s_settings[1] =
1919
// on_url
2020
+[](::http_parser* ps, const char* str, size_t len)
2121
{
22+
// Append to the the URI.
23+
if(this->m_headers.estimate_size() + len > this->m_max_header_length)
24+
POSEIDON_THROW((
25+
"HTTP header length limit exceeded: `$1` > `$2`"),
26+
this->m_headers.estimate_size() + len, this->m_max_content_length);
27+
2228
this->m_headers.raw_host.append(str, len);
2329
return 0;
2430
},
@@ -36,6 +42,11 @@ HTTP_Request_Parser::s_settings[1] =
3642

3743
// Append the header name to the last key, as this callback might be
3844
// invoked repeatedly.
45+
if(this->m_headers.estimate_size() + len > this->m_max_header_length)
46+
POSEIDON_THROW((
47+
"HTTP header length limit exceeded: `$1` > `$2`"),
48+
this->m_headers.estimate_size() + len, this->m_max_content_length);
49+
3950
this->m_headers.headers.mut_back().first.mut_str().append(str, len);
4051
return 0;
4152
},
@@ -44,6 +55,11 @@ HTTP_Request_Parser::s_settings[1] =
4455
+[](::http_parser* ps, const char* str, size_t len)
4556
{
4657
// Append the header value, as this callback might be invoked repeatedly.
58+
if(this->m_headers.estimate_size() + len > this->m_max_header_length)
59+
POSEIDON_THROW((
60+
"HTTP header length limit exceeded: `$1` > `$2`"),
61+
this->m_headers.estimate_size() + len, this->m_max_content_length);
62+
4763
cow_string value = this->m_headers.headers.back().second.as_string();
4864
value.append(str, len);
4965
this->m_headers.headers.mut_back().second = move(value);
@@ -195,8 +211,10 @@ HTTP_Request_Parser()
195211
auto conf_file = main_config.copy();
196212
this->m_default_compression_level = static_cast<int>(conf_file.get_integer_opt(
197213
&"network.http.default_compression_level", 0, 9).value_or(6));
214+
this->m_max_header_length = static_cast<uint32_t>(conf_file.get_integer_opt(
215+
&"network.http.max_header_length", 256, 16777216).value_or(262144));
198216
this->m_max_content_length = static_cast<uint32_t>(conf_file.get_integer_opt(
199-
&"network.http.max_request_content_length", 256, 16777216).value_or(1048576));
217+
&"network.http.max_request_content_length", 256, 16777216).value_or(1048576));
200218
}
201219

202220
HTTP_Request_Parser::

poseidon/http/http_request_parser.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class HTTP_Request_Parser
1313
{
1414
private:
1515
int m_default_compression_level;
16+
uint32_t m_max_header_length;
1617
uint32_t m_max_content_length;
1718

1819
static const ::http_parser_settings s_settings[1];
@@ -45,6 +46,11 @@ class HTTP_Request_Parser
4546
const noexcept
4647
{ return this->m_default_compression_level; }
4748

49+
uint32_t
50+
max_header_length()
51+
const noexcept
52+
{ return this->m_max_header_length; }
53+
4854
uint32_t
4955
max_content_length()
5056
const noexcept

poseidon/http/http_response_parser.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ HTTP_Response_Parser::s_settings[1] =
3737

3838
// Append the header name to the last key, as this callback might be
3939
// invoked repeatedly.
40+
if(this->m_headers.estimate_size() + len > this->m_max_header_length)
41+
POSEIDON_THROW((
42+
"HTTP header length limit exceeded: `$1` > `$2`"),
43+
this->m_headers.estimate_size() + len, this->m_max_content_length);
44+
4045
this->m_headers.headers.mut_back().first.mut_str().append(str, len);
4146
return 0;
4247
},
@@ -45,6 +50,11 @@ HTTP_Response_Parser::s_settings[1] =
4550
+[](::http_parser* ps, const char* str, size_t len)
4651
{
4752
// Append the header value, as this callback might be invoked repeatedly.
53+
if(this->m_headers.estimate_size() + len > this->m_max_header_length)
54+
POSEIDON_THROW((
55+
"HTTP header length limit exceeded: `$1` > `$2`"),
56+
this->m_headers.estimate_size() + len, this->m_max_content_length);
57+
4858
cow_string value = this->m_headers.headers.back().second.as_string();
4959
value.append(str, len);
5060
this->m_headers.headers.mut_back().second = move(value);
@@ -101,8 +111,10 @@ HTTP_Response_Parser()
101111
auto conf_file = main_config.copy();
102112
this->m_default_compression_level = static_cast<int>(conf_file.get_integer_opt(
103113
&"network.http.default_compression_level", 0, 9).value_or(6));
114+
this->m_max_header_length = static_cast<uint32_t>(conf_file.get_integer_opt(
115+
&"network.http.max_header_length", 256, 16777216).value_or(262144));
104116
this->m_max_content_length = static_cast<uint32_t>(conf_file.get_integer_opt(
105-
&"network.http.max_response_content_length", 256, 16777216).value_or(1048576));
117+
&"network.http.max_response_content_length", 256, 16777216).value_or(1048576));
106118
}
107119

108120
HTTP_Response_Parser::

poseidon/http/http_response_parser.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class HTTP_Response_Parser
1313
{
1414
private:
1515
int m_default_compression_level;
16+
uint32_t m_max_header_length;
1617
uint32_t m_max_content_length;
1718

1819
static const ::http_parser_settings s_settings[1];
@@ -45,6 +46,11 @@ class HTTP_Response_Parser
4546
const noexcept
4647
{ return this->m_default_compression_level; }
4748

49+
uint32_t
50+
max_header_length()
51+
const noexcept
52+
{ return this->m_max_header_length; }
53+
4854
uint32_t
4955
max_content_length()
5056
const noexcept

poseidon/http/http_s_headers.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ HTTP_S_Headers::
1212
{
1313
}
1414

15+
size_t
16+
HTTP_S_Headers::
17+
estimate_size()
18+
const
19+
{
20+
size_t tlen = 12 + this->reason.size();
21+
for(const auto& hr : this->headers)
22+
tlen += 4 + hr.first.length() + hr.second.as_string_length();
23+
return tlen;
24+
}
25+
1526
void
1627
HTTP_S_Headers::
1728
encode(tinyfmt& fmt)

poseidon/http/http_s_headers.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ struct HTTP_S_Headers
4545
this->headers.clear();
4646
}
4747

48+
// Estimates the number of bytes of all headers.
49+
size_t
50+
estimate_size()
51+
const;
52+
4853
// Encodes headers in wire format. Lines are separated by CR LF pairs. The
4954
// output will be suitable for sending through a stream socket.
5055
void

0 commit comments

Comments
 (0)