From e323374d2abbcfd548c236e767aa2dd7499dcb33 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 28 Jan 2024 17:43:51 -0500 Subject: [PATCH] Fix #1766 --- httplib.h | 56 +++++++++++++++++++++++++++++++++++++++++----------- test/test.cc | 41 +++++++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/httplib.h b/httplib.h index 464a421..036e819 100644 --- a/httplib.h +++ b/httplib.h @@ -82,6 +82,10 @@ #define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 #endif +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + #ifndef CPPHTTPLIB_TCP_NODELAY #define CPPHTTPLIB_TCP_NODELAY false #endif @@ -4721,29 +4725,57 @@ serialize_multipart_formdata(const MultipartFormDataItems &items, } inline bool normalize_ranges(Request &req, Response &res) { - ssize_t len = static_cast(res.content_length_ ? res.content_length_ - : res.body.size()); + ssize_t contant_len = static_cast( + res.content_length_ ? res.content_length_ : res.body.size()); + + ssize_t prev_first_pos = -1; + ssize_t prev_last_pos = -1; + size_t overwrapping_count = 0; if (!req.ranges.empty()) { + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return false; } + for (auto &r : req.ranges) { - auto &st = r.first; - auto &ed = r.second; + auto &first_pos = r.first; + auto &last_pos = r.second; - if (st == -1 && ed == -1) { - st = 0; - ed = len; + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = contant_len; } - if (st == -1) { - st = len - ed; - ed = len - 1; + if (first_pos == -1) { + first_pos = contant_len - last_pos; + last_pos = contant_len - 1; } - if (ed == -1) { ed = len - 1; } + if (last_pos == -1) { last_pos = contant_len - 1; } - if (!(0 <= st && st <= ed && ed <= len - 1)) { return false; } + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos <= contant_len - 1)) { + return false; + } + + // Ranges must be in ascending order + if (first_pos <= prev_first_pos) { return false; } + + // Request must not have more than two overlapping ranges + if (first_pos <= prev_last_pos) { + overwrapping_count++; + if (overwrapping_count > 2) { return false; } + } + + prev_first_pos = (std::max)(prev_first_pos, first_pos); + prev_last_pos = (std::max)(prev_last_pos, last_pos); } } + return true; } diff --git a/test/test.cc b/test/test.cc index 9ca1681..7b6e8d9 100644 --- a/test/test.cc +++ b/test/test.cc @@ -2962,7 +2962,7 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix2) { EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ(false, res->has_header("Content-Range")); - EXPECT_EQ(0, res->body.size()); + EXPECT_EQ(0U, res->body.size()); } TEST_F(ServerTest, GetStreamedWithRangeError) { @@ -2973,7 +2973,7 @@ TEST_F(ServerTest, GetStreamedWithRangeError) { EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ(false, res->has_header("Content-Range")); - EXPECT_EQ(0, res->body.size()); + EXPECT_EQ(0U, res->body.size()); } TEST_F(ServerTest, GetRangeWithMaxLongLength) { @@ -2982,7 +2982,7 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) { EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ(false, res->has_header("Content-Range")); - EXPECT_EQ(0, res->body.size()); + EXPECT_EQ(0U, res->body.size()); } TEST_F(ServerTest, GetStreamedWithRangeMultipart) { @@ -2995,6 +2995,41 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) { EXPECT_EQ(267U, res->body.size()); } +TEST_F(ServerTest, GetStreamedWithTooManyRanges) { + Ranges ranges; + for (size_t i = 0; i < CPPHTTPLIB_RANGE_MAX_COUNT + 1; i++) { + ranges.emplace_back(0, -1); + } + + auto res = + cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}}); + ASSERT_TRUE(res); + EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); + EXPECT_EQ("0", res->get_header_value("Content-Length")); + EXPECT_EQ(false, res->has_header("Content-Range")); + EXPECT_EQ(0U, res->body.size()); +} + +TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) { + auto res = cli_.Get("/streamed-with-range?error", + {{make_range_header({{0, -1}, {0, -1}})}}); + ASSERT_TRUE(res); + EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); + EXPECT_EQ("0", res->get_header_value("Content-Length")); + EXPECT_EQ(false, res->has_header("Content-Range")); + EXPECT_EQ(0U, res->body.size()); +} + +TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) { + auto res = cli_.Get("/streamed-with-range?error", + {{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}}); + ASSERT_TRUE(res); + EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); + EXPECT_EQ("0", res->get_header_value("Content-Length")); + EXPECT_EQ(false, res->has_header("Content-Range")); + EXPECT_EQ(0U, res->body.size()); +} + TEST_F(ServerTest, GetStreamedEndless) { uint64_t offset = 0; auto res = cli_.Get("/streamed-cancel",