nghttpd, nghttpx: Check that pseudo headers come before normal headers
This commit is contained in:
parent
d496c42dc9
commit
d4d56e1846
|
@ -1168,6 +1168,18 @@ int on_header_callback(nghttp2_session *session,
|
||||||
if(!http2::check_nv(name, namelen, value, valuelen)) {
|
if(!http2::check_nv(name, namelen, value, valuelen)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(namelen > 0 && name[0] == ':') {
|
||||||
|
if((!stream->headers.empty() &&
|
||||||
|
stream->headers.back().name.c_str()[0] != ':') ||
|
||||||
|
!http2::check_http2_request_pseudo_header(name, namelen)) {
|
||||||
|
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http2::add_header(stream->headers, name, namelen, value, valuelen,
|
http2::add_header(stream->headers, name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
71
src/http2.cc
71
src/http2.cc
|
@ -249,58 +249,41 @@ bool check_http2_headers(const Headers& nva)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
template<typename InputIterator>
|
|
||||||
bool check_pseudo_headers(const Headers& nva,
|
|
||||||
InputIterator allowed_first,
|
|
||||||
InputIterator allowed_last)
|
|
||||||
{
|
|
||||||
// strict checking for pseudo headers.
|
|
||||||
for(auto& hd : nva) {
|
|
||||||
auto c = hd.name.c_str()[0];
|
|
||||||
|
|
||||||
if(c < ':') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(c > ':') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto i = allowed_first;
|
|
||||||
|
|
||||||
for(; i != allowed_last; ++i) {
|
|
||||||
if(hd.name == *i) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i == allowed_last) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool check_http2_request_headers(const Headers& nva)
|
bool check_http2_request_headers(const Headers& nva)
|
||||||
{
|
{
|
||||||
if(!check_http2_headers(nva)) {
|
return check_http2_headers(nva);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return check_pseudo_headers(nva, REQUEST_PSEUDO_HD,
|
|
||||||
REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_http2_response_headers(const Headers& nva)
|
bool check_http2_response_headers(const Headers& nva)
|
||||||
{
|
{
|
||||||
if(!check_http2_headers(nva)) {
|
return check_http2_headers(nva);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return check_pseudo_headers(nva, RESPONSE_PSEUDO_HD,
|
namespace {
|
||||||
|
template<typename InputIterator>
|
||||||
|
bool check_pseudo_header(const uint8_t *name, size_t namelen,
|
||||||
|
InputIterator allowed_first,
|
||||||
|
InputIterator allowed_last)
|
||||||
|
{
|
||||||
|
for(auto i = allowed_first; i != allowed_last; ++i) {
|
||||||
|
if(util::streq(*i, name, namelen)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen)
|
||||||
|
{
|
||||||
|
return check_pseudo_header(name, namelen, REQUEST_PSEUDO_HD,
|
||||||
|
REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen)
|
||||||
|
{
|
||||||
|
return check_pseudo_header(name, namelen, RESPONSE_PSEUDO_HD,
|
||||||
RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN);
|
RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/http2.h
14
src/http2.h
|
@ -96,16 +96,18 @@ bool check_http2_allowed_header(const char *name);
|
||||||
// contains such headers.
|
// contains such headers.
|
||||||
bool check_http2_headers(const Headers& nva);
|
bool check_http2_headers(const Headers& nva);
|
||||||
|
|
||||||
// Calls check_http2_headers() and also checks that |nva| only
|
// Calls check_http2_headers()
|
||||||
// contains pseudo headers allowed in request. Returns true if all
|
|
||||||
// checks passed.
|
|
||||||
bool check_http2_request_headers(const Headers& nva);
|
bool check_http2_request_headers(const Headers& nva);
|
||||||
|
|
||||||
// Calls check_http2_headers() and also checks that |nva| only
|
// Calls check_http2_headers()
|
||||||
// contains pseudo headers allowed in response. Returns true if all
|
|
||||||
// checks passed.
|
|
||||||
bool check_http2_response_headers(const Headers& nva);
|
bool check_http2_response_headers(const Headers& nva);
|
||||||
|
|
||||||
|
// Returns true if |name| is allowed pusedo header for request.
|
||||||
|
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen);
|
||||||
|
|
||||||
|
// Returns true if |name| is allowed pusedo header for response.
|
||||||
|
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen);
|
||||||
|
|
||||||
bool name_less(const Headers::value_type& lhs, const Headers::value_type& rhs);
|
bool name_less(const Headers::value_type& lhs, const Headers::value_type& rhs);
|
||||||
|
|
||||||
void normalize_headers(Headers& nva);
|
void normalize_headers(Headers& nva);
|
||||||
|
|
|
@ -93,20 +93,17 @@ void test_http2_check_http2_headers(void)
|
||||||
};
|
};
|
||||||
CU_ASSERT(http2::check_http2_headers(nva3));
|
CU_ASSERT(http2::check_http2_headers(nva3));
|
||||||
|
|
||||||
auto nva4 = Headers{
|
auto n1 = ":authority";
|
||||||
{ ":authority", "1" },
|
auto n1u8 = reinterpret_cast<const uint8_t*>(n1);
|
||||||
{ ":method", "2" },
|
|
||||||
{ ":path", "3" },
|
|
||||||
{ ":scheme", "4" }
|
|
||||||
};
|
|
||||||
CU_ASSERT(http2::check_http2_request_headers(nva4));
|
|
||||||
CU_ASSERT(!http2::check_http2_response_headers(nva4));
|
|
||||||
|
|
||||||
auto nva5 = Headers{
|
CU_ASSERT(http2::check_http2_request_pseudo_header(n1u8, strlen(n1)));
|
||||||
{ ":status", "1" }
|
CU_ASSERT(!http2::check_http2_response_pseudo_header(n1u8, strlen(n1)));
|
||||||
};
|
|
||||||
CU_ASSERT(!http2::check_http2_request_headers(nva5));
|
auto n2 = ":status";
|
||||||
CU_ASSERT(http2::check_http2_response_headers(nva5));
|
auto n2u8 = reinterpret_cast<const uint8_t*>(n2);
|
||||||
|
|
||||||
|
CU_ASSERT(!http2::check_http2_request_pseudo_header(n2u8, strlen(n2)));
|
||||||
|
CU_ASSERT(http2::check_http2_response_pseudo_header(n2u8, strlen(n2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_http2_get_unique_header(void)
|
void test_http2_get_unique_header(void)
|
||||||
|
|
|
@ -859,4 +859,25 @@ bool Downstream::expect_response_body() const
|
||||||
response_http_status_ != 204;
|
response_http_status_ != 204;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool pseudo_header_allowed(const Headers& headers)
|
||||||
|
{
|
||||||
|
if(headers.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers.back().name.c_str()[0] == ':';
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool Downstream::request_pseudo_header_allowed() const
|
||||||
|
{
|
||||||
|
return pseudo_header_allowed(request_headers_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Downstream::response_pseudo_header_allowed() const
|
||||||
|
{
|
||||||
|
return pseudo_header_allowed(response_headers_);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -144,6 +144,7 @@ public:
|
||||||
int end_upload_data();
|
int end_upload_data();
|
||||||
size_t get_request_datalen() const;
|
size_t get_request_datalen() const;
|
||||||
void reset_request_datalen();
|
void reset_request_datalen();
|
||||||
|
bool request_pseudo_header_allowed() const;
|
||||||
bool expect_response_body() const;
|
bool expect_response_body() const;
|
||||||
enum {
|
enum {
|
||||||
INITIAL,
|
INITIAL,
|
||||||
|
@ -217,6 +218,7 @@ public:
|
||||||
void add_response_datalen(size_t len);
|
void add_response_datalen(size_t len);
|
||||||
size_t get_response_datalen() const;
|
size_t get_response_datalen() const;
|
||||||
void reset_response_datalen();
|
void reset_response_datalen();
|
||||||
|
bool response_pseudo_header_allowed() const;
|
||||||
|
|
||||||
// Call this method when there is incoming data in downstream
|
// Call this method when there is incoming data in downstream
|
||||||
// connection.
|
// connection.
|
||||||
|
|
|
@ -820,6 +820,7 @@ int on_header_callback(nghttp2_session *session,
|
||||||
uint8_t flags,
|
uint8_t flags,
|
||||||
void *user_data)
|
void *user_data)
|
||||||
{
|
{
|
||||||
|
auto http2session = static_cast<Http2Session*>(user_data);
|
||||||
auto sd = static_cast<StreamData*>
|
auto sd = static_cast<StreamData*>
|
||||||
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||||
if(!sd || !sd->dconn) {
|
if(!sd || !sd->dconn) {
|
||||||
|
@ -846,6 +847,17 @@ int on_header_callback(nghttp2_session *session,
|
||||||
if(!http2::check_nv(name, namelen, value, valuelen)) {
|
if(!http2::check_nv(name, namelen, value, valuelen)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(namelen > 0 && name[0] == ':') {
|
||||||
|
if(!downstream->response_pseudo_header_allowed() ||
|
||||||
|
!http2::check_http2_response_pseudo_header(name, namelen)) {
|
||||||
|
|
||||||
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downstream->split_add_response_header(name, namelen, value, valuelen,
|
downstream->split_add_response_header(name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -233,8 +233,19 @@ int on_header_callback(nghttp2_session *session,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(!http2::check_nv(name, namelen, value, valuelen)) {
|
if(!http2::check_nv(name, namelen, value, valuelen)) {
|
||||||
|
// Simply discard name/value, as if it never happen.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(namelen > 0 && name[0] == ':') {
|
||||||
|
if(!downstream->request_pseudo_header_allowed() ||
|
||||||
|
!http2::check_http2_request_pseudo_header(name, namelen)) {
|
||||||
|
|
||||||
|
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downstream->split_add_request_header(name, namelen, value, valuelen,
|
downstream->split_add_request_header(name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue