Skip to content

Commit e323374

Browse files
committed
1 parent ffc294d commit e323374

File tree

2 files changed

+82
-15
lines changed

2 files changed

+82
-15
lines changed

httplib.h

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@
8282
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
8383
#endif
8484

85+
#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
86+
#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
87+
#endif
88+
8589
#ifndef CPPHTTPLIB_TCP_NODELAY
8690
#define CPPHTTPLIB_TCP_NODELAY false
8791
#endif
@@ -4721,29 +4725,57 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
47214725
}
47224726

47234727
inline bool normalize_ranges(Request &req, Response &res) {
4724-
ssize_t len = static_cast<ssize_t>(res.content_length_ ? res.content_length_
4725-
: res.body.size());
4728+
ssize_t contant_len = static_cast<ssize_t>(
4729+
res.content_length_ ? res.content_length_ : res.body.size());
4730+
4731+
ssize_t prev_first_pos = -1;
4732+
ssize_t prev_last_pos = -1;
4733+
size_t overwrapping_count = 0;
47264734

47274735
if (!req.ranges.empty()) {
4736+
// NOTE: The following Range check is based on '14.2. Range' in RFC 9110
4737+
// 'HTTP Semantics' to avoid potential denial-of-service attacks.
4738+
// https://www.rfc-editor.org/rfc/rfc9110#section-14.2
4739+
4740+
// Too many ranges
4741+
if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return false; }
4742+
47284743
for (auto &r : req.ranges) {
4729-
auto &st = r.first;
4730-
auto &ed = r.second;
4744+
auto &first_pos = r.first;
4745+
auto &last_pos = r.second;
4746+
4747+
if (first_pos == -1 && last_pos == -1) {
4748+
first_pos = 0;
4749+
last_pos = contant_len;
4750+
}
47314751

4732-
if (st == -1 && ed == -1) {
4733-
st = 0;
4734-
ed = len;
4752+
if (first_pos == -1) {
4753+
first_pos = contant_len - last_pos;
4754+
last_pos = contant_len - 1;
47354755
}
47364756

4737-
if (st == -1) {
4738-
st = len - ed;
4739-
ed = len - 1;
4757+
if (last_pos == -1) { last_pos = contant_len - 1; }
4758+
4759+
// Range must be within content length
4760+
if (!(0 <= first_pos && first_pos <= last_pos &&
4761+
last_pos <= contant_len - 1)) {
4762+
return false;
47404763
}
47414764

4742-
if (ed == -1) { ed = len - 1; }
4765+
// Ranges must be in ascending order
4766+
if (first_pos <= prev_first_pos) { return false; }
47434767

4744-
if (!(0 <= st && st <= ed && ed <= len - 1)) { return false; }
4768+
// Request must not have more than two overlapping ranges
4769+
if (first_pos <= prev_last_pos) {
4770+
overwrapping_count++;
4771+
if (overwrapping_count > 2) { return false; }
4772+
}
4773+
4774+
prev_first_pos = (std::max)(prev_first_pos, first_pos);
4775+
prev_last_pos = (std::max)(prev_last_pos, last_pos);
47454776
}
47464777
}
4778+
47474779
return true;
47484780
}
47494781

test/test.cc

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2962,7 +2962,7 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
29622962
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
29632963
EXPECT_EQ("0", res->get_header_value("Content-Length"));
29642964
EXPECT_EQ(false, res->has_header("Content-Range"));
2965-
EXPECT_EQ(0, res->body.size());
2965+
EXPECT_EQ(0U, res->body.size());
29662966
}
29672967

29682968
TEST_F(ServerTest, GetStreamedWithRangeError) {
@@ -2973,7 +2973,7 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
29732973
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
29742974
EXPECT_EQ("0", res->get_header_value("Content-Length"));
29752975
EXPECT_EQ(false, res->has_header("Content-Range"));
2976-
EXPECT_EQ(0, res->body.size());
2976+
EXPECT_EQ(0U, res->body.size());
29772977
}
29782978

29792979
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
@@ -2982,7 +2982,7 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) {
29822982
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
29832983
EXPECT_EQ("0", res->get_header_value("Content-Length"));
29842984
EXPECT_EQ(false, res->has_header("Content-Range"));
2985-
EXPECT_EQ(0, res->body.size());
2985+
EXPECT_EQ(0U, res->body.size());
29862986
}
29872987

29882988
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
@@ -2995,6 +2995,41 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
29952995
EXPECT_EQ(267U, res->body.size());
29962996
}
29972997

2998+
TEST_F(ServerTest, GetStreamedWithTooManyRanges) {
2999+
Ranges ranges;
3000+
for (size_t i = 0; i < CPPHTTPLIB_RANGE_MAX_COUNT + 1; i++) {
3001+
ranges.emplace_back(0, -1);
3002+
}
3003+
3004+
auto res =
3005+
cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}});
3006+
ASSERT_TRUE(res);
3007+
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
3008+
EXPECT_EQ("0", res->get_header_value("Content-Length"));
3009+
EXPECT_EQ(false, res->has_header("Content-Range"));
3010+
EXPECT_EQ(0U, res->body.size());
3011+
}
3012+
3013+
TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) {
3014+
auto res = cli_.Get("/streamed-with-range?error",
3015+
{{make_range_header({{0, -1}, {0, -1}})}});
3016+
ASSERT_TRUE(res);
3017+
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
3018+
EXPECT_EQ("0", res->get_header_value("Content-Length"));
3019+
EXPECT_EQ(false, res->has_header("Content-Range"));
3020+
EXPECT_EQ(0U, res->body.size());
3021+
}
3022+
3023+
TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) {
3024+
auto res = cli_.Get("/streamed-with-range?error",
3025+
{{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}});
3026+
ASSERT_TRUE(res);
3027+
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
3028+
EXPECT_EQ("0", res->get_header_value("Content-Length"));
3029+
EXPECT_EQ(false, res->has_header("Content-Range"));
3030+
EXPECT_EQ(0U, res->body.size());
3031+
}
3032+
29983033
TEST_F(ServerTest, GetStreamedEndless) {
29993034
uint64_t offset = 0;
30003035
auto res = cli_.Get("/streamed-cancel",

0 commit comments

Comments
 (0)