nghttp: Support non-final response and check pseudo headers

This commit is contained in:
Tatsuhiro Tsujikawa 2014-08-08 23:03:12 +09:00
parent d4d56e1846
commit e217e789de
6 changed files with 147 additions and 15 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",