nghttp: Support non-final response and check pseudo headers
This commit is contained in:
parent
d4d56e1846
commit
e217e789de
22
src/http2.cc
22
src/http2.cc
|
@ -556,6 +556,28 @@ int check_nv(const uint8_t *name, size_t namelen,
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int parse_http_status_code(const std::string& src)
|
||||||
|
{
|
||||||
|
if(src.size() != 3) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
for(auto c : src) {
|
||||||
|
if(!isdigit(c)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
status *= 10;
|
||||||
|
status += c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status < 100) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace http2
|
} // namespace http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -230,6 +230,9 @@ std::string rewrite_location_uri(const std::string& uri,
|
||||||
int check_nv(const uint8_t *name, size_t namelen,
|
int check_nv(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen);
|
const uint8_t *value, size_t valuelen);
|
||||||
|
|
||||||
|
// Returns parsed HTTP status code. Returns -1 on failure.
|
||||||
|
int parse_http_status_code(const std::string& src);
|
||||||
|
|
||||||
} // namespace http2
|
} // namespace http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -280,4 +280,15 @@ void test_http2_rewrite_location_uri(void)
|
||||||
"localhost", "https", 3000);
|
"localhost", "https", 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_http2_parse_http_status_code(void)
|
||||||
|
{
|
||||||
|
CU_ASSERT(200 == http2::parse_http_status_code("200"));
|
||||||
|
CU_ASSERT(102 == http2::parse_http_status_code("102"));
|
||||||
|
CU_ASSERT(-1 == http2::parse_http_status_code("099"));
|
||||||
|
CU_ASSERT(-1 == http2::parse_http_status_code("99"));
|
||||||
|
CU_ASSERT(-1 == http2::parse_http_status_code("-1"));
|
||||||
|
CU_ASSERT(-1 == http2::parse_http_status_code("20a"));
|
||||||
|
CU_ASSERT(-1 == http2::parse_http_status_code(""));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -36,6 +36,7 @@ void test_http2_copy_norm_headers_to_nva(void);
|
||||||
void test_http2_build_http1_headers_from_norm_headers(void);
|
void test_http2_build_http1_headers_from_norm_headers(void);
|
||||||
void test_http2_lws(void);
|
void test_http2_lws(void);
|
||||||
void test_http2_rewrite_location_uri(void);
|
void test_http2_rewrite_location_uri(void);
|
||||||
|
void test_http2_parse_http_status_code(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
123
src/nghttp.cc
123
src/nghttp.cc
|
@ -178,7 +178,6 @@ struct Request {
|
||||||
Headers push_req_nva;
|
Headers push_req_nva;
|
||||||
// URI without fragment
|
// URI without fragment
|
||||||
std::string uri;
|
std::string uri;
|
||||||
std::string status;
|
|
||||||
http_parser_url u;
|
http_parser_url u;
|
||||||
std::shared_ptr<Dependency> dep;
|
std::shared_ptr<Dependency> dep;
|
||||||
nghttp2_priority_spec pri_spec;
|
nghttp2_priority_spec pri_spec;
|
||||||
|
@ -189,10 +188,12 @@ struct Request {
|
||||||
HtmlParser *html_parser;
|
HtmlParser *html_parser;
|
||||||
const nghttp2_data_provider *data_prd;
|
const nghttp2_data_provider *data_prd;
|
||||||
int32_t stream_id;
|
int32_t stream_id;
|
||||||
|
int status;
|
||||||
// Recursion level: 0: first entity, 1: entity linked from first entity
|
// Recursion level: 0: first entity, 1: entity linked from first entity
|
||||||
int level;
|
int level;
|
||||||
// RequestPriority value defined in HtmlParser.h
|
// RequestPriority value defined in HtmlParser.h
|
||||||
int pri;
|
int pri;
|
||||||
|
bool expect_final_response;
|
||||||
|
|
||||||
// For pushed request, |uri| is empty and |u| is zero-cleared.
|
// For pushed request, |uri| is empty and |u| is zero-cleared.
|
||||||
Request(const std::string& uri, const http_parser_url &u,
|
Request(const std::string& uri, const http_parser_url &u,
|
||||||
|
@ -209,8 +210,10 @@ struct Request {
|
||||||
html_parser(nullptr),
|
html_parser(nullptr),
|
||||||
data_prd(data_prd),
|
data_prd(data_prd),
|
||||||
stream_id(-1),
|
stream_id(-1),
|
||||||
|
status(0),
|
||||||
level(level),
|
level(level),
|
||||||
pri(pri)
|
pri(pri),
|
||||||
|
expect_final_response(false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~Request()
|
~Request()
|
||||||
|
@ -316,6 +319,16 @@ struct Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool response_pseudo_header_allowed() const
|
||||||
|
{
|
||||||
|
return res_nva.empty() || res_nva.back().name.c_str()[0] == ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool push_request_pseudo_header_allowed() const
|
||||||
|
{
|
||||||
|
return res_nva.empty() || push_req_nva.back().name.c_str()[0] == ':';
|
||||||
|
}
|
||||||
|
|
||||||
void record_request_time()
|
void record_request_time()
|
||||||
{
|
{
|
||||||
stat.stage = STAT_ON_REQUEST;
|
stat.stage = STAT_ON_REQUEST;
|
||||||
|
@ -1122,6 +1135,12 @@ int on_data_chunk_recv_callback
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(req->status == 0) {
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
stream_id, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if(req->inflater) {
|
if(req->inflater) {
|
||||||
while(len > 0) {
|
while(len > 0) {
|
||||||
const size_t MAX_OUTLEN = 4096;
|
const size_t MAX_OUTLEN = 4096;
|
||||||
|
@ -1181,6 +1200,9 @@ namespace {
|
||||||
void check_response_header(nghttp2_session *session, Request* req)
|
void check_response_header(nghttp2_session *session, Request* req)
|
||||||
{
|
{
|
||||||
bool gzip = false;
|
bool gzip = false;
|
||||||
|
|
||||||
|
req->expect_final_response = false;
|
||||||
|
|
||||||
for(auto& nv : req->res_nva) {
|
for(auto& nv : req->res_nva) {
|
||||||
if("content-encoding" == nv.name) {
|
if("content-encoding" == nv.name) {
|
||||||
gzip = util::strieq("gzip", nv.value) ||
|
gzip = util::strieq("gzip", nv.value) ||
|
||||||
|
@ -1188,9 +1210,32 @@ void check_response_header(nghttp2_session *session, Request* req)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(":status" == nv.name) {
|
if(":status" == nv.name) {
|
||||||
req->status.assign(nv.value);
|
int status;
|
||||||
|
if(req->status != 0 ||
|
||||||
|
(status = http2::parse_http_status_code(nv.value)) == -1) {
|
||||||
|
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
req->stream_id, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
req->status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(req->status == 0) {
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req->status / 100 == 1) {
|
||||||
|
req->expect_final_response = true;
|
||||||
|
req->status = 0;
|
||||||
|
req->res_nva.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(gzip) {
|
if(gzip) {
|
||||||
if(!req->inflater) {
|
if(!req->inflater) {
|
||||||
req->init_inflater();
|
req->init_inflater();
|
||||||
|
@ -1257,15 +1302,30 @@ int on_header_callback(nghttp2_session *session,
|
||||||
|
|
||||||
switch(frame->hd.type) {
|
switch(frame->hd.type) {
|
||||||
case NGHTTP2_HEADERS: {
|
case NGHTTP2_HEADERS: {
|
||||||
if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
|
||||||
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto req = (Request*)nghttp2_session_get_stream_user_data
|
auto req = (Request*)nghttp2_session_get_stream_user_data
|
||||||
(session, frame->hd.stream_id);
|
(session, frame->hd.stream_id);
|
||||||
|
|
||||||
if(!req) {
|
if(!req) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
||||||
|
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE &&
|
||||||
|
(frame->headers.cat != NGHTTP2_HCAT_HEADERS ||
|
||||||
|
!req->expect_final_response)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(namelen > 0 && name[0] == ':') {
|
||||||
|
if(!req->response_pseudo_header_allowed() ||
|
||||||
|
!http2::check_http2_response_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(req->res_nva, name, namelen, value, valuelen,
|
http2::add_header(req->res_nva, name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
||||||
break;
|
break;
|
||||||
|
@ -1273,9 +1333,21 @@ int on_header_callback(nghttp2_session *session,
|
||||||
case NGHTTP2_PUSH_PROMISE: {
|
case NGHTTP2_PUSH_PROMISE: {
|
||||||
auto req = (Request*)nghttp2_session_get_stream_user_data
|
auto req = (Request*)nghttp2_session_get_stream_user_data
|
||||||
(session, frame->push_promise.promised_stream_id);
|
(session, frame->push_promise.promised_stream_id);
|
||||||
|
|
||||||
if(!req) {
|
if(!req) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(namelen > 0 && name[0] == ':') {
|
||||||
|
if(!req->push_request_pseudo_header_allowed() ||
|
||||||
|
!http2::check_http2_request_pseudo_header(name, namelen)) {
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->push_promise.promised_stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http2::add_header(req->push_req_nva, name, namelen, value, valuelen,
|
http2::add_header(req->push_req_nva, name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
||||||
break;
|
break;
|
||||||
|
@ -1291,21 +1363,45 @@ int on_frame_recv_callback2
|
||||||
{
|
{
|
||||||
int rv = 0;
|
int rv = 0;
|
||||||
|
|
||||||
|
if(config.verbose) {
|
||||||
|
verbose_on_frame_recv_callback(session, frame, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
auto client = get_session(user_data);
|
auto client = get_session(user_data);
|
||||||
switch(frame->hd.type) {
|
switch(frame->hd.type) {
|
||||||
case NGHTTP2_HEADERS: {
|
case NGHTTP2_HEADERS: {
|
||||||
if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
|
||||||
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto req = (Request*)nghttp2_session_get_stream_user_data
|
auto req = (Request*)nghttp2_session_get_stream_user_data
|
||||||
(session, frame->hd.stream_id);
|
(session, frame->hd.stream_id);
|
||||||
// If this is the HTTP Upgrade with OPTIONS method to avoid POST,
|
// If this is the HTTP Upgrade with OPTIONS method to avoid POST,
|
||||||
// req is nullptr.
|
// req is nullptr.
|
||||||
if(req) {
|
if(!req) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
|
||||||
|
frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
|
||||||
req->record_response_time();
|
req->record_response_time();
|
||||||
check_response_header(session, req);
|
check_response_header(session, req);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
|
||||||
|
if(req->expect_final_response) {
|
||||||
|
check_response_header(session, req);
|
||||||
|
} else {
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req->status == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
||||||
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NGHTTP2_SETTINGS:
|
case NGHTTP2_SETTINGS:
|
||||||
|
@ -1367,9 +1463,6 @@ int on_frame_recv_callback2
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(config.verbose) {
|
|
||||||
verbose_on_frame_recv_callback(session, frame, user_data);
|
|
||||||
}
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -88,6 +88,8 @@ int main(int argc, char* argv[])
|
||||||
shrpx::test_http2_lws) ||
|
shrpx::test_http2_lws) ||
|
||||||
!CU_add_test(pSuite, "http2_rewrite_location_uri",
|
!CU_add_test(pSuite, "http2_rewrite_location_uri",
|
||||||
shrpx::test_http2_rewrite_location_uri) ||
|
shrpx::test_http2_rewrite_location_uri) ||
|
||||||
|
!CU_add_test(pSuite, "http2_parse_http_status_code",
|
||||||
|
shrpx::test_http2_parse_http_status_code) ||
|
||||||
!CU_add_test(pSuite, "downstream_normalize_request_headers",
|
!CU_add_test(pSuite, "downstream_normalize_request_headers",
|
||||||
shrpx::test_downstream_normalize_request_headers) ||
|
shrpx::test_downstream_normalize_request_headers) ||
|
||||||
!CU_add_test(pSuite, "downstream_normalize_response_headers",
|
!CU_add_test(pSuite, "downstream_normalize_response_headers",
|
||||||
|
|
Loading…
Reference in New Issue