Compare commits

..

11 Commits

Author SHA1 Message Date
yhirose 5c00bbf36b Release v0.15.3 2024-02-05 22:12:43 -05:00
yhirose 9d6f5372a3 Fix #1772 2024-02-05 22:11:53 -05:00
Ikko Eltociear Ashimine f06fd934f6 Fix typo in gtest-all.cc (#1770)
synthetic -> synthetic
2024-02-05 15:35:33 -05:00
yhirose 80c0cc445e Release v0.15.2 2024-02-02 23:29:30 -05:00
yhirose 762024b890 Fix #1768 2024-02-02 23:17:32 -05:00
yhirose 82a90a2325 Update year 2024-01-29 08:53:01 -05:00
yhirose b7cac4f4b8 Release v0.15.1 2024-01-29 07:40:56 -05:00
yhirose e323374d2a Fix #1766 2024-01-28 17:43:51 -05:00
Jiwoo Park ffc294d37e Reduce object copy (#1767) 2024-01-28 08:18:29 -05:00
yhirose fceada9ef4 Changed to return 416 for a request with an invalid range 2024-01-28 08:13:19 -05:00
yhirose 5f0f73fad9 Reduce duplicate computation for ranges 2024-01-27 19:07:52 -05:00
4 changed files with 234 additions and 125 deletions
+1 -1
View File
@@ -866,7 +866,7 @@ NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 incl
License
-------
MIT license (© 2023 Yuji Hirose)
MIT license (© 2024 Yuji Hirose)
Special Thanks To
-----------------
+165 -109
View File
@@ -1,14 +1,14 @@
//
// httplib.h
//
// Copyright (c) 2023 Yuji Hirose. All rights reserved.
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License
//
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.15.0"
#define CPPHTTPLIB_VERSION "0.15.3"
/*
* Configuration
@@ -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
@@ -957,7 +961,7 @@ private:
bool parse_request_line(const char *s, Request &req) const;
void apply_ranges(const Request &req, Response &res,
std::string &content_type, std::string &boundary) const;
bool write_response(Stream &strm, bool close_connection, const Request &req,
bool write_response(Stream &strm, bool close_connection, Request &req,
Response &res);
bool write_response_with_content(Stream &strm, bool close_connection,
const Request &req, Response &res);
@@ -2065,7 +2069,7 @@ void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
std::string append_query_params(const std::string &path, const Params &params);
std::pair<std::string, std::string> make_range_header(Ranges ranges);
std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
std::pair<std::string, std::string>
make_basic_authentication_header(const std::string &username,
@@ -2413,7 +2417,11 @@ inline bool is_valid_path(const std::string &path) {
// Read component
auto beg = i;
while (i < path.size() && path[i] != '/') {
if (path[i] == '\0') { return false; }
if (path[i] == '\0') {
return false;
} else if (path[i] == '\\') {
return false;
}
i++;
}
@@ -2571,7 +2579,7 @@ inline std::string trim_double_quotes_copy(const std::string &s) {
inline void split(const char *b, const char *e, char d,
std::function<void(const char *, const char *)> fn) {
return split(b, e, d, (std::numeric_limits<size_t>::max)(), fn);
return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
}
inline void split(const char *b, const char *e, char d, size_t m,
@@ -4720,37 +4728,75 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
return body;
}
inline std::pair<size_t, size_t>
get_range_offset_and_length(Range range, size_t content_length) {
if (range.first == -1 && range.second == -1) {
return std::make_pair(0, content_length);
inline bool range_error(Request &req, Response &res) {
if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
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;
// 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 true; }
for (auto &r : req.ranges) {
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 (first_pos == -1) {
first_pos = contant_len - last_pos;
last_pos = contant_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 true;
}
// Ranges must be in ascending order
if (first_pos <= prev_first_pos) { return true; }
// Request must not have more than two overlapping ranges
if (first_pos <= prev_last_pos) {
overwrapping_count++;
if (overwrapping_count > 2) { return true; }
}
prev_first_pos = (std::max)(prev_first_pos, first_pos);
prev_last_pos = (std::max)(prev_last_pos, last_pos);
}
}
auto slen = static_cast<ssize_t>(content_length);
if (range.first == -1) {
range.first = (std::max)(static_cast<ssize_t>(0), slen - range.second);
range.second = slen - 1;
}
if (range.second == -1) { range.second = slen - 1; }
return std::make_pair(range.first,
static_cast<size_t>(range.second - range.first) + 1);
return false;
}
inline std::pair<size_t, size_t>
get_range_offset_and_length(const Request &req, size_t content_length,
size_t index) {
return get_range_offset_and_length(req.ranges[index], content_length);
get_range_offset_and_length(Range r, size_t content_length) {
assert(r.first != -1 && r.second != -1);
assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
assert(r.first <= r.second &&
r.second < static_cast<ssize_t>(content_length));
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
}
inline std::string
make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
size_t content_length) {
auto ret = get_range_offset_and_length(range, content_length);
auto st = ret.first;
auto ed = (std::min)(st + ret.second - 1, content_length - 1);
inline std::string make_content_range_header_field(
const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
auto st = offset_and_length.first;
auto ed = st + offset_and_length.second - 1;
std::string field = "bytes ";
field += std::to_string(st);
@@ -4762,11 +4808,11 @@ make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
}
template <typename SToken, typename CToken, typename Content>
bool process_multipart_ranges_data(const Request &req, Response &res,
bool process_multipart_ranges_data(const Request &req,
const std::string &boundary,
const std::string &content_type,
SToken stoken, CToken ctoken,
Content content) {
size_t content_length, SToken stoken,
CToken ctoken, Content content) {
for (size_t i = 0; i < req.ranges.size(); i++) {
ctoken("--");
stoken(boundary);
@@ -4777,16 +4823,17 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
ctoken("\r\n");
}
auto offset_and_length =
get_range_offset_and_length(req.ranges[i], content_length);
ctoken("Content-Range: ");
const auto &range = req.ranges[i];
stoken(make_content_range_header_field(range, res.content_length_));
stoken(make_content_range_header_field(offset_and_length, content_length));
ctoken("\r\n");
ctoken("\r\n");
auto ret = get_range_offset_and_length(req, res.content_length_, i);
auto offset = ret.first;
auto length = ret.second;
if (!content(offset, length)) { return false; }
if (!content(offset_and_length.first, offset_and_length.second)) {
return false;
}
ctoken("\r\n");
}
@@ -4797,31 +4844,30 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
return true;
}
inline bool make_multipart_ranges_data(const Request &req, Response &res,
inline void make_multipart_ranges_data(const Request &req, Response &res,
const std::string &boundary,
const std::string &content_type,
size_t content_length,
std::string &data) {
return process_multipart_ranges_data(
req, res, boundary, content_type,
process_multipart_ranges_data(
req, boundary, content_type, content_length,
[&](const std::string &token) { data += token; },
[&](const std::string &token) { data += token; },
[&](size_t offset, size_t length) {
if (offset < res.body.size()) {
data += res.body.substr(offset, length);
return true;
}
return false;
assert(offset + length <= content_length);
data += res.body.substr(offset, length);
return true;
});
}
inline size_t
get_multipart_ranges_data_length(const Request &req, Response &res,
const std::string &boundary,
const std::string &content_type) {
inline size_t get_multipart_ranges_data_length(const Request &req,
const std::string &boundary,
const std::string &content_type,
size_t content_length) {
size_t data_length = 0;
process_multipart_ranges_data(
req, res, boundary, content_type,
req, boundary, content_type, content_length,
[&](const std::string &token) { data_length += token.size(); },
[&](const std::string &token) { data_length += token.size(); },
[&](size_t /*offset*/, size_t length) {
@@ -4833,13 +4879,13 @@ get_multipart_ranges_data_length(const Request &req, Response &res,
}
template <typename T>
inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
Response &res,
const std::string &boundary,
const std::string &content_type,
const T &is_shutting_down) {
inline bool
write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
const std::string &boundary,
const std::string &content_type,
size_t content_length, const T &is_shutting_down) {
return process_multipart_ranges_data(
req, res, boundary, content_type,
req, boundary, content_type, content_length,
[&](const std::string &token) { strm.write(token); },
[&](const std::string &token) { strm.write(token); },
[&](size_t offset, size_t length) {
@@ -5198,10 +5244,11 @@ inline std::string append_query_params(const std::string &path,
}
// Header utilities
inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
inline std::pair<std::string, std::string>
make_range_header(const Ranges &ranges) {
std::string field = "bytes=";
auto i = 0;
for (auto r : ranges) {
for (const auto &r : ranges) {
if (i != 0) { field += ", "; }
if (r.first != -1) { field += std::to_string(r.first); }
field += '-';
@@ -5354,7 +5401,7 @@ inline void Response::set_content_provider(
set_header("Content-Type", content_type);
content_length_ = in_length;
if (in_length > 0) { content_provider_ = std::move(provider); }
content_provider_resource_releaser_ = resource_releaser;
content_provider_resource_releaser_ = std::move(resource_releaser);
is_chunked_content_provider_ = false;
}
@@ -5364,7 +5411,7 @@ inline void Response::set_content_provider(
set_header("Content-Type", content_type);
content_length_ = 0;
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
content_provider_resource_releaser_ = resource_releaser;
content_provider_resource_releaser_ = std::move(resource_releaser);
is_chunked_content_provider_ = false;
}
@@ -5374,7 +5421,7 @@ inline void Response::set_chunked_content_provider(
set_header("Content-Type", content_type);
content_length_ = 0;
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
content_provider_resource_releaser_ = resource_releaser;
content_provider_resource_releaser_ = std::move(resource_releaser);
is_chunked_content_provider_ = true;
}
@@ -5940,7 +5987,10 @@ inline bool Server::parse_request_line(const char *s, Request &req) const {
}
inline bool Server::write_response(Stream &strm, bool close_connection,
const Request &req, Response &res) {
Request &req, Response &res) {
// NOTE: `req.ranges` should be empty, otherwise it will be applied
// incorrectly to the error content.
req.ranges.clear();
return write_response_core(strm, close_connection, req, res, false);
}
@@ -6018,7 +6068,6 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
res.content_provider_success_ = true;
} else {
res.content_provider_success_ = false;
ret = false;
}
}
@@ -6043,15 +6092,16 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
return detail::write_content(strm, res.content_provider_, 0,
res.content_length_, is_shutting_down);
} else if (req.ranges.size() == 1) {
auto ret =
detail::get_range_offset_and_length(req, res.content_length_, 0);
auto offset = ret.first;
auto length = ret.second;
return detail::write_content(strm, res.content_provider_, offset, length,
is_shutting_down);
auto offset_and_length = detail::get_range_offset_and_length(
req.ranges[0], res.content_length_);
return detail::write_content(strm, res.content_provider_,
offset_and_length.first,
offset_and_length.second, is_shutting_down);
} else {
return detail::write_multipart_ranges_data(
strm, req, res, boundary, content_type, is_shutting_down);
strm, req, res, boundary, content_type, res.content_length_,
is_shutting_down);
}
} else {
if (res.is_chunked_content_provider_) {
@@ -6443,14 +6493,14 @@ inline void Server::apply_ranges(const Request &req, Response &res,
std::string &content_type,
std::string &boundary) const {
if (req.ranges.size() > 1) {
boundary = detail::make_multipart_data_boundary();
auto it = res.headers.find("Content-Type");
if (it != res.headers.end()) {
content_type = it->second;
res.headers.erase(it);
}
boundary = detail::make_multipart_data_boundary();
res.set_header("Content-Type",
"multipart/byteranges; boundary=" + boundary);
}
@@ -6463,16 +6513,17 @@ inline void Server::apply_ranges(const Request &req, Response &res,
if (req.ranges.empty()) {
length = res.content_length_;
} else if (req.ranges.size() == 1) {
auto ret =
detail::get_range_offset_and_length(req, res.content_length_, 0);
length = ret.second;
auto offset_and_length = detail::get_range_offset_and_length(
req.ranges[0], res.content_length_);
length = offset_and_length.second;
auto content_range = detail::make_content_range_header_field(
req.ranges[0], res.content_length_);
offset_and_length, res.content_length_);
res.set_header("Content-Range", content_range);
} else {
length = detail::get_multipart_ranges_data_length(req, res, boundary,
content_type);
length = detail::get_multipart_ranges_data_length(
req, boundary, content_type, res.content_length_);
}
res.set_header("Content-Length", std::to_string(length));
} else {
@@ -6491,29 +6542,22 @@ inline void Server::apply_ranges(const Request &req, Response &res,
if (req.ranges.empty()) {
;
} else if (req.ranges.size() == 1) {
auto offset_and_length =
detail::get_range_offset_and_length(req.ranges[0], res.body.size());
auto offset = offset_and_length.first;
auto length = offset_and_length.second;
auto content_range = detail::make_content_range_header_field(
req.ranges[0], res.body.size());
offset_and_length, res.body.size());
res.set_header("Content-Range", content_range);
auto ret = detail::get_range_offset_and_length(req, res.body.size(), 0);
auto offset = ret.first;
auto length = ret.second;
if (offset < res.body.size()) {
res.body = res.body.substr(offset, length);
} else {
res.body.clear();
res.status = StatusCode::RangeNotSatisfiable_416;
}
assert(offset + length <= res.body.size());
res.body = res.body.substr(offset, length);
} else {
std::string data;
if (detail::make_multipart_ranges_data(req, res, boundary, content_type,
data)) {
res.body.swap(data);
} else {
res.body.clear();
res.status = StatusCode::RangeNotSatisfiable_416;
}
detail::make_multipart_ranges_data(req, res, boundary, content_type,
res.body.size(), data);
res.body.swap(data);
}
if (type != detail::EncodingType::None) {
@@ -6653,7 +6697,7 @@ Server::process_request(Stream &strm, bool close_connection,
}
}
// Rounting
// Routing
auto routed = false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
routed = routing(req, res, strm);
@@ -6689,15 +6733,24 @@ Server::process_request(Stream &strm, bool close_connection,
}
}
#endif
if (routed) {
if (res.status == -1) {
res.status = req.ranges.empty() ? StatusCode::OK_200
: StatusCode::PartialContent_206;
}
if (detail::range_error(req, res)) {
res.body.clear();
res.content_length_ = 0;
res.content_provider_ = nullptr;
res.status = StatusCode::RangeNotSatisfiable_416;
return write_response(strm, close_connection, req, res);
}
return write_response_with_content(strm, close_connection, req, res);
} else {
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
return write_response(strm, close_connection, req, res);
}
}
@@ -7601,14 +7654,15 @@ inline Result ClientImpl::Get(const std::string &path, const Params &params,
if (params.empty()) { return Get(path, headers); }
std::string path_with_query = append_query_params(path, params);
return Get(path_with_query, headers, progress);
return Get(path_with_query, headers, std::move(progress));
}
inline Result ClientImpl::Get(const std::string &path, const Params &params,
const Headers &headers,
ContentReceiver content_receiver,
Progress progress) {
return Get(path, params, headers, nullptr, content_receiver, progress);
return Get(path, params, headers, nullptr, std::move(content_receiver),
std::move(progress));
}
inline Result ClientImpl::Get(const std::string &path, const Params &params,
@@ -7617,12 +7671,13 @@ inline Result ClientImpl::Get(const std::string &path, const Params &params,
ContentReceiver content_receiver,
Progress progress) {
if (params.empty()) {
return Get(path, headers, response_handler, content_receiver, progress);
return Get(path, headers, std::move(response_handler),
std::move(content_receiver), std::move(progress));
}
std::string path_with_query = append_query_params(path, params);
return Get(path_with_query, headers, response_handler, content_receiver,
progress);
return Get(path_with_query, headers, std::move(response_handler),
std::move(content_receiver), std::move(progress));
}
inline Result ClientImpl::Head(const std::string &path) {
@@ -8997,19 +9052,20 @@ inline Result Client::Get(const std::string &path, const Headers &headers,
}
inline Result Client::Get(const std::string &path, const Params &params,
const Headers &headers, Progress progress) {
return cli_->Get(path, params, headers, progress);
return cli_->Get(path, params, headers, std::move(progress));
}
inline Result Client::Get(const std::string &path, const Params &params,
const Headers &headers,
ContentReceiver content_receiver, Progress progress) {
return cli_->Get(path, params, headers, content_receiver, progress);
return cli_->Get(path, params, headers, std::move(content_receiver),
std::move(progress));
}
inline Result Client::Get(const std::string &path, const Params &params,
const Headers &headers,
ResponseHandler response_handler,
ContentReceiver content_receiver, Progress progress) {
return cli_->Get(path, params, headers, response_handler, content_receiver,
progress);
return cli_->Get(path, params, headers, std::move(response_handler),
std::move(content_receiver), std::move(progress));
}
inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
+1 -1
View File
@@ -1912,7 +1912,7 @@ void AssertHelper::operator=(const Message& message) const {
namespace {
// When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P
// to creates test cases for it, a syntetic test case is
// to creates test cases for it, a synthetic test case is
// inserted to report ether an error or a log message.
//
// This configuration bit will likely be removed at some point.
+67 -14
View File
@@ -1831,7 +1831,7 @@ protected:
});
})
.Get("/streamed-with-range",
[&](const Request & /*req*/, Response &res) {
[&](const Request &req, Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), "text/plain",
@@ -1845,8 +1845,8 @@ protected:
EXPECT_TRUE(ret);
return true;
},
[data](bool success) {
EXPECT_TRUE(success);
[data, &req](bool success) {
EXPECT_EQ(success, !req.has_param("error"));
delete data;
});
})
@@ -2166,6 +2166,11 @@ protected:
EXPECT_EQ("4", req.get_header_value("Content-Length"));
res.set_content(req.body, "application/octet-stream");
})
.Get("/issue1772",
[&](const Request & /*req*/, Response &res) {
res.status = 401;
res.set_header("WWW-Authenticate", "Basic realm=123456");
})
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
.Get("/compress",
[&](const Request & /*req*/, Response &res) {
@@ -2509,6 +2514,12 @@ TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) {
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, GetMethodOutOfBaseDirMountWithBackslash) {
auto res = cli_.Get("/mount/%2e%2e%5c/www2/dir/test.html");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, PostMethod303) {
auto res = cli_.Post("/1", "body", "text/plain");
ASSERT_TRUE(res);
@@ -2957,13 +2968,12 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
}
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-9999"}});
auto res = cli_.Get("/streamed-with-range?error", {{"Range", "bytes=-9999"}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
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, GetStreamedWithRangeError) {
@@ -2972,16 +2982,18 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
"92233720368547758079223372036854775807"}});
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, GetRangeWithMaxLongLength) {
auto res =
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
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, GetStreamedWithRangeMultipart) {
@@ -2994,6 +3006,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",
@@ -3097,6 +3144,12 @@ TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
}
TEST_F(ServerTest, Issue1772) {
auto res = cli_.Get("/issue1772", {{make_range_header({{1000, -1}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res);