yhirose 1 year ago
parent
commit
e323374d2a
2 changed files with 82 additions and 15 deletions
  1. 44 12
      httplib.h
  2. 38 3
      test/test.cc

+ 44 - 12
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<ssize_t>(res.content_length_ ? res.content_length_
-                                                         : res.body.size());
+  ssize_t contant_len = static_cast<ssize_t>(
+      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 (first_pos == -1 && last_pos == -1) {
+        first_pos = 0;
+        last_pos = contant_len;
+      }
 
-      if (st == -1 && ed == -1) {
-        st = 0;
-        ed = len;
+      if (first_pos == -1) {
+        first_pos = contant_len - last_pos;
+        last_pos = contant_len - 1;
       }
 
-      if (st == -1) {
-        st = len - ed;
-        ed = len - 1;
+      if (last_pos == -1) { last_pos = contant_len - 1; }
+
+      // Range must be within content length
+      if (!(0 <= first_pos && first_pos <= last_pos &&
+            last_pos <= contant_len - 1)) {
+        return false;
       }
 
-      if (ed == -1) { ed = len - 1; }
+      // Ranges must be in ascending order
+      if (first_pos <= prev_first_pos) { return false; }
 
-      if (!(0 <= st && st <= ed && ed <= len - 1)) { 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;
 }
 

+ 38 - 3
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",