// Read component
auto beg = i;
while (i < path.size() && path[i] != '/') {
+ if (path[i] == '\0') {
+ return false;
+ } else if (path[i] == '\\') {
+ return false;
+ }
i++;
}
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));
}
void split(const char *b, const char *e, char d, size_t m,
size_t mmap::size() const { return size_; }
-const char *mmap::data() const { return (const char *)addr_; }
+const char *mmap::data() const {
+ return static_cast<const char *>(addr_);
+}
void mmap::close() {
#if defined(_WIN32)
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return 1; }
+ if (sock >= FD_SETSIZE) { return -1; }
#endif
fd_set fds;
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
#ifndef _WIN32
- if (sock >= FD_SETSIZE) { return 1; }
+ if (sock >= FD_SETSIZE) { return -1; }
#endif
fd_set fds;
}
bool is_chunked_transfer_encoding(const Headers &headers) {
- return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
- "chunked");
+ return compare_case_ignore(
+ get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
}
template <typename T, typename U>
return out;
}
-std::string make_multipart_data_boundary() {
+std::string random_string(size_t length) {
static const char data[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
// std::random_device might actually be deterministic on some
// platforms, but due to lack of support in the c++ standard library,
// doing better requires either some ugly hacks or breaking portability.
- std::random_device seed_gen;
+ static std::random_device seed_gen;
// Request 128 bits of entropy for initialization
- std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
- std::mt19937 engine(seed_sequence);
+ static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(),
+ seed_gen()};
- std::string result = "--cpp-httplib-multipart-data-";
+ static std::mt19937 engine(seed_sequence);
- for (auto i = 0; i < 16; i++) {
+ std::string result;
+ for (size_t i = 0; i < length; i++) {
result += data[engine() % (sizeof(data) - 1)];
}
-
return result;
}
+std::string make_multipart_data_boundary() {
+ return "--cpp-httplib-multipart-data-" + detail::random_string(16);
+}
+
bool is_multipart_boundary_chars_valid(const std::string &boundary) {
auto valid = true;
for (size_t i = 0; i < boundary.size(); i++) {
return body;
}
-std::pair<size_t, size_t>
-get_range_offset_and_length(const Request &req, size_t content_length,
- size_t index) {
- auto r = req.ranges[index];
+bool normalize_ranges(Request &req, Response &res) {
+ ssize_t contant_len = static_cast<ssize_t>(
+ res.content_length_ ? res.content_length_ : res.body.size());
- if (r.first == -1 && r.second == -1) {
- return std::make_pair(0, content_length);
- }
+ 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 &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 false;
+ }
+
+ // Ranges must be in ascending order
+ if (first_pos <= prev_first_pos) { return false; }
- auto slen = static_cast<ssize_t>(content_length);
+ // Request must not have more than two overlapping ranges
+ if (first_pos <= prev_last_pos) {
+ overwrapping_count++;
+ if (overwrapping_count > 2) { return false; }
+ }
- if (r.first == -1) {
- r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
- r.second = slen - 1;
+ prev_first_pos = (std::max)(prev_first_pos, first_pos);
+ prev_last_pos = (std::max)(prev_last_pos, last_pos);
+ }
}
- if (r.second == -1) { r.second = slen - 1; }
+ return true;
+}
+
+std::pair<size_t, size_t>
+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);
}
-std::string
-make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
- size_t content_length) {
+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 ";
- if (range.first != -1) { field += std::to_string(range.first); }
+ field += std::to_string(st);
field += "-";
- if (range.second != -1) { field += std::to_string(range.second); }
+ field += std::to_string(ed);
field += "/";
field += std::to_string(content_length);
return field;
}
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);
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 offsets = get_range_offset_and_length(req, res.content_length_, i);
- auto offset = offsets.first;
- auto length = offsets.second;
- if (!content(offset, length)) { return false; }
+ if (!content(offset_and_length.first, offset_and_length.second)) {
+ return false;
+ }
ctoken("\r\n");
}
return true;
}
-bool make_multipart_ranges_data(const Request &req, Response &res,
+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;
});
}
-size_t
-get_multipart_ranges_data_length(const Request &req, Response &res,
- const std::string &boundary,
- const std::string &content_type) {
+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) {
}
template <typename T>
-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) {
+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) {
});
}
-std::pair<size_t, size_t>
-get_range_offset_and_length(const Request &req, const Response &res,
- size_t index) {
- auto r = req.ranges[index];
-
- if (r.second == -1) {
- r.second = static_cast<ssize_t>(res.content_length_) - 1;
- }
-
- return std::make_pair(r.first, r.second - r.first + 1);
-}
-
bool expect_content(const Request &req) {
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
req.method == "PRI" || req.method == "DELETE") {
return false;
}
-// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
-std::string random_string(size_t length) {
- auto randchar = []() -> char {
- const char charset[] = "0123456789"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz";
- const size_t max_index = (sizeof(charset) - 1);
- return charset[static_cast<size_t>(std::rand()) % max_index];
- };
- std::string str(length, 0);
- std::generate_n(str.begin(), length, randchar);
- return str;
-}
-
class ContentProviderAdapter {
public:
explicit ContentProviderAdapter(
}
// Header utilities
-std::pair<std::string, std::string> make_range_header(Ranges ranges) {
+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 += '-';
set_content(s.data(), s.size(), content_type);
}
+void Response::set_content(std::string &&s,
+ const std::string &content_type) {
+ body = std::move(s);
+
+ auto rng = headers.equal_range("Content-Type");
+ headers.erase(rng.first, rng.second);
+ set_header("Content-Type", content_type);
+}
+
void Response::set_content_provider(
size_t in_length, const std::string &content_type, ContentProvider provider,
ContentProviderResourceReleaser resource_releaser) {
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;
}
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;
}
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;
}
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
res.content_provider_success_ = true;
} else {
- res.content_provider_success_ = false;
ret = false;
}
}
return detail::write_content(strm, res.content_provider_, 0,
res.content_length_, is_shutting_down);
} else if (req.ranges.size() == 1) {
- auto offsets =
- detail::get_range_offset_and_length(req, res.content_length_, 0);
- auto offset = offsets.first;
- auto length = offsets.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_) {
#endif
}
- task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
+ if (!task_queue->enqueue(
+ [this, sock]() { process_and_close_socket(sock); })) {
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ }
}
task_queue->shutdown();
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);
}
if (req.ranges.empty()) {
length = res.content_length_;
} else if (req.ranges.size() == 1) {
- auto offsets =
- detail::get_range_offset_and_length(req, res.content_length_, 0);
- length = offsets.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 {
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 offsets =
- detail::get_range_offset_and_length(req, res.body.size(), 0);
- auto offset = offsets.first;
- auto length = offsets.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) {
}
}
#endif
-
if (routed) {
- if (res.status == -1) {
- res.status = req.ranges.empty() ? StatusCode::OK_200
- : StatusCode::PartialContent_206;
+ if (detail::normalize_ranges(req, res)) {
+ if (res.status == -1) {
+ res.status = req.ranges.empty() ? StatusCode::OK_200
+ : StatusCode::PartialContent_206;
+ }
+ return write_response_with_content(strm, close_connection, req, res);
+ } else {
+ 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);
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));
}
Result ClientImpl::Get(const std::string &path, const Params ¶ms,
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));
}
Result ClientImpl::Get(const std::string &path, const Params ¶ms,
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));
}
Result ClientImpl::Head(const std::string &path) {
return true;
},
[&](SSL *ssl2) {
- // NOTE: With -Wold-style-cast, this can produce a warning, since
- // SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains
- // an old style cast. Short of doing compiler specific pragma's
- // here, we can't get rid of this warning. :'(
- SSL_set_tlsext_host_name(ssl2, host_.c_str());
+ // NOTE: Direct call instead of using the OpenSSL macro to suppress
+ // -Wold-style-cast warning
+ // SSL_set_tlsext_host_name(ssl2, host_.c_str());
+ SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
+ static_cast<void *>(const_cast<char *>(host_.c_str())));
return true;
});
}
Result Client::Get(const std::string &path, const Params ¶ms,
const Headers &headers, Progress progress) {
- return cli_->Get(path, params, headers, progress);
+ return cli_->Get(path, params, headers, std::move(progress));
}
Result Client::Get(const std::string &path, const Params ¶ms,
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));
}
Result Client::Get(const std::string &path, const Params ¶ms,
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));
}
Result Client::Head(const std::string &path) { return cli_->Head(path); }