From f44ab9b3da7bfc830c230d3fe2f50db7b818b66b Mon Sep 17 00:00:00 2001
From: Jiwoo Park <jiwoo_90@naver.com>
Date: Sun, 7 Apr 2024 23:06:16 +0900
Subject: [PATCH] Fix range parser when parsing too many ranges (#1812)

* Implement range parser without std::regex

* Add test cases for invalid ranges
---
 httplib.h    | 55 ++++++++++++++++++++++++++++++----------------------
 test/test.cc | 19 ++++++++++++++++++
 2 files changed, 51 insertions(+), 23 deletions(-)

diff --git a/httplib.h b/httplib.h
index a77d6d9..b4363f6 100644
--- a/httplib.h
+++ b/httplib.h
@@ -4365,35 +4365,44 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
 #else
 inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
 #endif
-  static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
-  std::smatch m;
-  if (std::regex_match(s, m, re_first_range)) {
-    auto pos = static_cast<size_t>(m.position(1));
-    auto len = static_cast<size_t>(m.length(1));
+  auto is_valid = [](const std::string &str) {
+    return std::all_of(str.cbegin(), str.cend(),
+                       [](unsigned char c) { return std::isdigit(c); });
+  };
+
+  if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
+    const auto pos = static_cast<size_t>(6);
+    const auto len = static_cast<size_t>(s.size() - 6);
     auto all_valid_ranges = true;
     split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
       if (!all_valid_ranges) { return; }
-      static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
-      std::cmatch cm;
-      if (std::regex_match(b, e, cm, re_another_range)) {
-        ssize_t first = -1;
-        if (!cm.str(1).empty()) {
-          first = static_cast<ssize_t>(std::stoll(cm.str(1)));
-        }
 
-        ssize_t last = -1;
-        if (!cm.str(2).empty()) {
-          last = static_cast<ssize_t>(std::stoll(cm.str(2)));
-        }
-
-        if (first != -1 && last != -1 && first > last) {
-          all_valid_ranges = false;
-          return;
-        }
-        ranges.emplace_back(std::make_pair(first, last));
+      const auto it = std::find(b, e, '-');
+      if (it == e) {
+        all_valid_ranges = false;
+        return;
       }
+
+      const auto lhs = std::string(b, it);
+      const auto rhs = std::string(it + 1, e);
+      if (!is_valid(lhs) || !is_valid(rhs)) {
+        all_valid_ranges = false;
+        return;
+      }
+
+      const auto first =
+          static_cast<ssize_t>(lhs.empty() ? -1 : std::stoll(lhs));
+      const auto last =
+          static_cast<ssize_t>(rhs.empty() ? -1 : std::stoll(rhs));
+      if ((first == -1 && last == -1) ||
+          (first != -1 && last != -1 && first > last)) {
+        all_valid_ranges = false;
+        return;
+      }
+
+      ranges.emplace_back(first, last);
     });
-    return all_valid_ranges;
+    return all_valid_ranges && !ranges.empty();
   }
   return false;
 #ifdef CPPHTTPLIB_NO_EXCEPTIONS
diff --git a/test/test.cc b/test/test.cc
index 801673a..71023f3 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -352,6 +352,25 @@ TEST(ParseHeaderValueTest, Range) {
     EXPECT_EQ(300u, ranges[2].first);
     EXPECT_EQ(400u, ranges[2].second);
   }
+
+  {
+    Ranges ranges;
+
+    EXPECT_FALSE(detail::parse_range_header("bytes", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=0", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=-", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes= ", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=,", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=,,", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=,,,", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=a-b", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=1-0", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=0--1", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=0- 1", ranges));
+    EXPECT_FALSE(detail::parse_range_header("bytes=0 -1", ranges));
+    EXPECT_TRUE(ranges.empty());
+  }
 }
 
 TEST(ParseAcceptEncoding1, AcceptEncoding) {