nghttpx: Response 502 if HTTP/2 backend receives invalid Content-Length

This commit is contained in:
Tatsuhiro Tsujikawa 2015-01-19 23:44:23 +09:00
parent 8004ea9726
commit a440bdf15e
7 changed files with 73 additions and 24 deletions

View File

@ -664,6 +664,10 @@ int64_t Downstream::get_response_sent_bodylen() const {
return response_sent_bodylen_; return response_sent_bodylen_;
} }
int64_t Downstream::get_response_content_length() const {
return response_content_length_;
}
void Downstream::set_response_content_length(int64_t len) { void Downstream::set_response_content_length(int64_t len) {
response_content_length_ = len; response_content_length_ = len;
} }

View File

@ -172,7 +172,10 @@ public:
STREAM_CLOSED, STREAM_CLOSED,
CONNECT_FAIL, CONNECT_FAIL,
IDLE, IDLE,
MSG_RESET MSG_RESET,
// header contains invalid header field. We can safely send error
// response (502) to a client.
MSG_BAD_HEADER,
}; };
void set_request_state(int state); void set_request_state(int state);
int get_request_state() const; int get_request_state() const;
@ -223,6 +226,7 @@ public:
int64_t get_response_bodylen() const; int64_t get_response_bodylen() const;
void add_response_sent_bodylen(size_t amount); void add_response_sent_bodylen(size_t amount);
int64_t get_response_sent_bodylen() const; int64_t get_response_sent_bodylen() const;
int64_t get_response_content_length() const;
void set_response_content_length(int64_t len); void set_response_content_length(int64_t len);
// Validates that received response body length and content-length // Validates that received response body length and content-length
// matches. // matches.

View File

@ -139,6 +139,7 @@ int Http2DownstreamConnection::submit_rst_stream(Downstream *downstream,
downstream->get_downstream_stream_id() != -1) { downstream->get_downstream_stream_id() != -1) {
switch (downstream->get_response_state()) { switch (downstream->get_response_state()) {
case Downstream::MSG_RESET: case Downstream::MSG_RESET:
case Downstream::MSG_BAD_HEADER:
case Downstream::MSG_COMPLETE: case Downstream::MSG_COMPLETE:
break; break;
default: default:

View File

@ -654,10 +654,15 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
downstream->get_upstream()->on_downstream_body_complete(downstream); downstream->get_upstream()->on_downstream_body_complete(downstream);
downstream->set_response_state(Downstream::MSG_COMPLETE); downstream->set_response_state(Downstream::MSG_COMPLETE);
} else if (error_code == NGHTTP2_NO_ERROR) { } else if (error_code == NGHTTP2_NO_ERROR) {
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) { switch (downstream->get_response_state()) {
case Downstream::MSG_COMPLETE:
case Downstream::MSG_BAD_HEADER:
break;
default:
downstream->set_response_state(Downstream::MSG_RESET); downstream->set_response_state(Downstream::MSG_RESET);
} }
} else { } else if (downstream->get_response_state() !=
Downstream::MSG_BAD_HEADER) {
downstream->set_response_state(Downstream::MSG_RESET); downstream->set_response_state(Downstream::MSG_RESET);
} }
call_downstream_readcb(http2session, downstream); call_downstream_readcb(http2session, downstream);
@ -727,6 +732,24 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
if (token == http2::HD_CONTENT_LENGTH) {
auto len = util::parse_uint(value, valuelen);
if (len == -1) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_BAD_HEADER);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto cl = downstream->get_response_content_length();
if (cl != -1 && cl != len) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_BAD_HEADER);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
downstream->set_response_content_length(len);
}
downstream->add_response_header(name, namelen, value, valuelen, downstream->add_response_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token); flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0; return 0;
@ -817,27 +840,21 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
return 0; return 0;
} }
auto content_length_hd = if (downstream->get_response_content_length() == -1 &&
downstream->get_response_header(http2::HD_CONTENT_LENGTH); downstream->expect_response_body()) {
// Here we have response body but Content-Length is not known in
if (content_length_hd) { // advance.
auto content_length = util::parse_uint(content_length_hd->value); if (downstream->get_request_major() <= 0 ||
if (content_length != -1) { (downstream->get_request_major() <= 1 &&
downstream->set_response_content_length(content_length); downstream->get_request_minor() <= 0)) {
} else if (downstream->expect_response_body()) { // We simply close connection for pre-HTTP/1.1 in this case.
// Here we have response body but Content-Length is not known in downstream->set_response_connection_close(true);
// advance. } else if (downstream->get_request_method() != "CONNECT") {
if (downstream->get_request_major() <= 0 || // Otherwise, use chunked encoding to keep upstream connection
downstream->get_request_minor() <= 0) { // open. In HTTP2, we are supporsed not to receive
// We simply close connection for pre-HTTP/1.1 in this case. // transfer-encoding.
downstream->set_response_connection_close(true); downstream->add_response_header("transfer-encoding", "chunked");
} else if (downstream->get_request_method() != "CONNECT") { downstream->set_chunked_response(true);
// Otherwise, use chunked encoding to keep upstream connection
// open. In HTTP2, we are supporsed not to receive
// transfer-encoding.
downstream->add_response_header("transfer-encoding", "chunked");
downstream->set_chunked_response(true);
}
} }
} }

View File

@ -823,6 +823,13 @@ int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();
// dconn was deleted // dconn was deleted
dconn = nullptr; dconn = nullptr;
} else if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) {
if (error_reply(downstream, 502) != 0) {
return -1;
}
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
} else { } else {
auto rv = downstream->on_read(); auto rv = downstream->on_read();
if (rv == DownstreamConnection::ERR_EOF) { if (rv == DownstreamConnection::ERR_EOF) {

View File

@ -460,6 +460,14 @@ int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
return -1; return -1;
} }
if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) {
if (error_reply(502) != 0) {
return -1;
}
downstream->pop_downstream_connection();
goto end;
}
if (rv == DownstreamConnection::ERR_EOF) { if (rv == DownstreamConnection::ERR_EOF) {
return downstream_eof(dconn); return downstream_eof(dconn);
} }
@ -472,6 +480,7 @@ int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
return -1; return -1;
} }
end:
handler_->signal_write(); handler_->signal_write();
return 0; return 0;

View File

@ -531,6 +531,13 @@ int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
downstream->get_response_rst_stream_error_code())); downstream->get_response_rst_stream_error_code()));
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();
dconn = nullptr; dconn = nullptr;
} else if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) {
if (error_reply(downstream, 502) != 0) {
return -1;
}
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
} else { } else {
auto rv = downstream->on_read(); auto rv = downstream->on_read();
if (rv == DownstreamConnection::ERR_EOF) { if (rv == DownstreamConnection::ERR_EOF) {