diff --git a/httplib.h b/httplib.h index d7582318..e6b2fceb 100644 --- a/httplib.h +++ b/httplib.h @@ -1141,8 +1141,7 @@ private: pointer operator->() const { return data_ + idx_; } iterator_t &operator++() { - // Saturating, so that advancing past the last entry of a key (which - // get_multimap_value() does when asked for an out-of-range id) stays at + // Saturating, so that advancing past the last entry of a key stays at // end() instead of running off the container. if (idx_ >= size_) { return *this; } ++idx_; @@ -3477,13 +3476,20 @@ inline bool is_numeric(const std::string &str) { return !str.empty() && std::all_of(str.cbegin(), str.cend(), is_ascii_digit); } +// std::ranges::next(first, n, last) for C++11 +template inline It bounded_next(It first, size_t n, It last) { + for (; n > 0 && first != last; --n) { + ++first; + } + return first; +} + inline size_t get_header_value_u64(const Headers &headers, const std::string &key, size_t def, size_t id, bool &is_invalid_value) { is_invalid_value = false; auto rng = headers.equal_range(key); - auto it = rng.first; - std::advance(it, static_cast(id)); + auto it = bounded_next(rng.first, id, rng.second); if (it != rng.second) { if (is_numeric(it->second)) { // Parse at size_t width so an out-of-range Content-Length is reported @@ -8146,8 +8152,7 @@ inline const char *get_header_value(const Headers &headers, } auto rng = headers.equal_range(key); - auto it = rng.first; - std::advance(it, static_cast(id)); + auto it = bounded_next(rng.first, id, rng.second); if (it != rng.second) { return it->second.c_str(); } return def; } @@ -8200,8 +8205,7 @@ template inline typename Map::mapped_type get_multimap_value(const Map &m, const std::string &key, size_t id) { auto rng = m.equal_range(key); - auto it = rng.first; - std::advance(it, static_cast(id)); + auto it = bounded_next(rng.first, id, rng.second); if (it != rng.second) { return it->second; } return typename Map::mapped_type(); } @@ -11728,8 +11732,7 @@ inline bool Request::is_multipart_form_data() const { inline std::string MultipartFormData::get_field(const std::string &key, size_t id) const { auto rng = fields.equal_range(key); - auto it = rng.first; - std::advance(it, static_cast(id)); + auto it = detail::bounded_next(rng.first, id, rng.second); if (it != rng.second) { return it->second.content; } return std::string(); } diff --git a/test/test.cc b/test/test.cc index 09b31296..ede009a0 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1923,6 +1923,28 @@ TEST(HeadersOrderTest, IdBeyondTheLastDuplicateYieldsDefault) { EXPECT_EQ("def", req.get_header_value("X-Missing", "def", 3)); } +TEST(HeadersOrderTest, HugeIdYieldsDefault) { + Request req; + req.set_header("X-First", "1"); + req.set_header("X-Test", "2"); + req.params.emplace("first", "1"); + req.params.emplace("test", "2"); + req.form.fields.emplace("first", FormField{"first", "1", {}}); + req.form.fields.emplace("test", FormField{"test", "2", {}}); + req.form.files.emplace("first", FormData{"first", "1", "", "", {}}); + req.form.files.emplace("test", FormData{"test", "2", "", "", {}}); + + // The second one is negative when read as a signed value + const auto max_id = (std::numeric_limits::max)(); + for (auto id : {max_id / 2, max_id}) { + EXPECT_EQ("def", req.get_header_value("X-Test", "def", id)); + EXPECT_EQ(7U, req.get_header_value_u64("X-Test", 7, id)); + EXPECT_EQ("", req.get_param_value("test", id)); + EXPECT_EQ("", req.form.get_field("test", id)); + EXPECT_EQ("", req.form.get_file("test", id).content); + } +} + TEST(HeadersOrderTest, TraversalFollowsInsertionOrder) { Headers headers; headers.emplace("Host", "example.com");