From d14283010934d51b5ceb8b811d4bb2fbbe27ca9b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 15 Jan 2015 23:07:25 +0900 Subject: [PATCH] nghttpd: Issue RST_STREAM if content-length does not match uploaded bytes --- src/HttpServer.cc | 27 ++++++++++++++++++++++++++- src/HttpServer.h | 1 + src/shrpx-unittest.cc | 1 + src/util.cc | 27 +++++++++++++++++++++++++++ src/util.h | 5 +++++ src/util_test.cc | 16 ++++++++++++++++ src/util_test.h | 1 + 7 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 711d6c6d..7256bd66 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -246,7 +246,8 @@ private: }; Stream::Stream(Http2Handler *handler, int32_t stream_id) - : handler(handler), body_left(0), stream_id(stream_id), file(-1) { + : handler(handler), body_left(0), upload_left(-1), stream_id(stream_id), + file(-1) { auto config = handler->get_config(); ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout); ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout); @@ -1048,6 +1049,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } + if (token == http2::HD_CONTENT_LENGTH) { + auto upload_left = util::parse_uint(value, valuelen); + if (upload_left != -1) { + stream->upload_left = upload_left; + } + } + http2::index_header(stream->hdidx, token, stream->headers.size()); http2::add_header(stream->headers, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); @@ -1093,6 +1101,10 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { remove_stream_read_timeout(stream); + if (stream->upload_left > 0) { + hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); + return 0; + } if (!hd->get_config()->early_response) { prepare_response(stream, hd); } @@ -1129,6 +1141,10 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { remove_stream_read_timeout(stream); + if (stream->upload_left > 0) { + hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); + return 0; + } if (!hd->get_config()->early_response) { prepare_response(stream, hd); } @@ -1230,6 +1246,15 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, // TODO Handle POST + if (stream->upload_left != -1) { + if (stream->upload_left < len) { + stream->upload_left = -1; + hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); + return 0; + } + stream->upload_left -= len; + } + add_stream_read_timeout(stream); return 0; diff --git a/src/HttpServer.h b/src/HttpServer.h index 87d32964..02802a9c 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -81,6 +81,7 @@ struct Stream { ev_timer rtimer; ev_timer wtimer; int64_t body_left; + int64_t upload_left; int32_t stream_id; int file; int hdidx[http2::HD_MAXIDX]; diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 8262d345..59c900f3 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -133,6 +133,7 @@ int main(int argc, char *argv[]) { shrpx::test_util_utos_with_unit) || !CU_add_test(pSuite, "util_parse_uint_with_unit", shrpx::test_util_parse_uint_with_unit) || + !CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || !CU_add_test(pSuite, "ringbuf_write", nghttp2::test_ringbuf_write) || !CU_add_test(pSuite, "ringbuf_iovec", nghttp2::test_ringbuf_iovec) || diff --git a/src/util.cc b/src/util.cc index 75dddff7..8e1c558d 100644 --- a/src/util.cc +++ b/src/util.cc @@ -950,6 +950,33 @@ int64_t parse_uint_with_unit(const char *s) { return n * mul; } +int64_t parse_uint(const char *s) { + return parse_uint(reinterpret_cast(s), strlen(s)); +} + +int64_t parse_uint(const uint8_t *s, size_t len) { + if (len == 0) { + return -1; + } + constexpr int64_t max = std::numeric_limits::max(); + int64_t n = 0; + for (size_t i = 0; i < len; ++i) { + if ('0' <= s[i] && s[i] <= '9') { + if (n > max / 10) { + return -1; + } + n *= 10; + if (n > max - (s[i] - '0')) { + return -1; + } + n += s[i] - '0'; + continue; + } + return -1; + } + return n; +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index 7d869f95..89d0f42b 100644 --- a/src/util.h +++ b/src/util.h @@ -502,6 +502,11 @@ bool ipv6_numeric_addr(const char *host); // -1. int64_t parse_uint_with_unit(const char *s); +// Parses NULL terminated string |s| as unsigned integer and returns +// the parsed integer. If there is an error, returns -1. +int64_t parse_uint(const char *s); +int64_t parse_uint(const uint8_t *s, size_t len); + } // namespace util } // namespace nghttp2 diff --git a/src/util_test.cc b/src/util_test.cc index 9743ffe3..a61fba3b 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -207,4 +207,20 @@ void test_util_parse_uint_with_unit(void) { CU_ASSERT(-1 == util::parse_uint_with_unit("")); } +void test_util_parse_uint(void) { + CU_ASSERT(0 == util::parse_uint("0")); + CU_ASSERT(1023 == util::parse_uint("1023")); + CU_ASSERT(-1 == util::parse_uint("1k")); + CU_ASSERT(9223372036854775807LL == util::parse_uint("9223372036854775807")); + // check overflow case + CU_ASSERT(-1 == util::parse_uint("9223372036854775808")); + CU_ASSERT(-1 == util::parse_uint("10000000000000000000")); + // bad characters + CU_ASSERT(-1 == util::parse_uint("1.1")); + CU_ASSERT(-1 == util::parse_uint("1a")); + CU_ASSERT(-1 == util::parse_uint("a1")); + CU_ASSERT(-1 == util::parse_uint("1T")); + CU_ASSERT(-1 == util::parse_uint("")); +} + } // namespace shrpx diff --git a/src/util_test.h b/src/util_test.h index 4c7b79c5..7f6acb1d 100644 --- a/src/util_test.h +++ b/src/util_test.h @@ -39,6 +39,7 @@ void test_util_select_h2(void); void test_util_ipv6_numeric_addr(void); void test_util_utos_with_unit(void); void test_util_parse_uint_with_unit(void); +void test_util_parse_uint(void); } // namespace shrpx