Compare commits

...

27 Commits

Author SHA1 Message Date
yhirose f086bf5310 Fix #738 2020-11-08 18:16:23 -05:00
yhirose 6613d7b7ad Fixed warnings 2020-11-08 17:59:11 -05:00
yhirose 6adf130bf3 Fix #739 2020-11-07 21:54:47 -05:00
miketsts b6b2eaf5bc Add unit test SSLConnectTimeout (#741)
Add unit test for issue #682 fixed in PR #728, which does not contain
the test of its own.

The test creates a fake SSL server, inherited from SSLServer, which
does not create an SSL context. When an SSL client attempts to send it
a request, it gets a timeout error. Prior to PR #728, the client would
wait indefinitely

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-07 09:41:20 -05:00
yhirose eb4b7c70a9 Fix #737 2020-11-07 09:33:22 -05:00
yhirose 84661ea6ed Refactoring 2020-11-05 07:06:53 -05:00
yhirose 041122908c Fix problem with invalid range 2020-11-05 00:20:59 -05:00
yhirose 401de608df Fixed debug option problem. 2020-11-05 00:20:59 -05:00
yhirose 726c64cf10 Code format 2020-11-05 00:20:59 -05:00
Omkar Jadhav e1f781a21a [oss-fuzz] Enable msan for fuzz tests (#734)
* Disable openssl as we're not using it in fuzz tests

* Increase timeout in ClientStop test
2020-11-04 11:30:56 -05:00
Omkar Jadhav 72b81badad Fix issues reported by oss-fuzz (#729)
* Fix oss-fuzz issue #26529

* Add test for oss-fuzz issue  #26598

* Fix oss-fuzz issue  #26632

* Revert change and add new test cases
2020-11-03 11:36:02 -05:00
yhirose 17428a8fbf Fixed warning 2020-11-03 09:16:28 -05:00
Daniel Ottiger 6e1879dfae ssl-verify-host: fix verifying ip addresses containing zero's (#732)
* ssl-verify-host: fix verifying ip addresses containing zero's

If the subject alternate name contained an ip address with an zero
(like 10.42.0.1) it could not successfully verify.
It is because in c++ strings are null-terminated
and therefore strlen(name) would return a wrong result.
As I can not see why we can not trust the length returned by openssl,
lets drop this check.

* ssl-verify-host: add test case

lets try to validate against 127.0.0.1

Co-authored-by: Daniel Ottiger <daniel.ottiger@ch.schindler.com>
2020-11-02 20:27:34 -05:00
miketsts eb1d2e04bc SSL_connect and SSL_accept in non-blocking mode (#728)
SSL connection is performed in two steps:
First, a regular socket connection is established.
Then, SSL_connect/SSL_accept is called to establish SSL handshake.

If a network problem occurs during the second stage, SSL_connect on
the client may hang indefinitely.

The non-blocking mode solves this problem.

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-02 17:05:08 -05:00
yhirose c909ffa758 Fix #731 2020-11-01 21:03:47 -05:00
yhirose ff5677ad19 Updated README 2020-10-27 20:35:56 -04:00
yhirose 8b1b31ac20 Fix #723 2020-10-27 20:32:19 -04:00
yhirose 953600c177 Fixed compiler error for old compiler. 2020-10-27 12:23:37 -04:00
yhirose 536e7eb7f2 Revert "Fix #697". (It broke unit test...)
This reverts commit 6d66721ba1.
2020-10-25 20:22:39 -04:00
yhirose 6d66721ba1 Fix #697 2020-10-25 16:55:54 -04:00
yhirose 3b29cd0bdc Fix #698 2020-10-25 12:14:54 -04:00
yhirose 109b624dfe Fix #708 (#713)
* Fix #708

* Rename ContentReceiver2 to ContentReceiverWithProgress
2020-10-22 11:48:43 -04:00
yhirose 0ed70c4d9f Fixed unit test errors 2020-10-21 14:34:27 -04:00
yhirose a50b7591ca Fix #714 2020-10-21 13:02:33 -04:00
yhirose bf8fc11b53 Code cleanup 2020-10-20 21:24:47 -04:00
Omkar Jadhav bc4a613b6d Fix suffix-byte-range issue (#711) 2020-10-20 11:11:27 -04:00
yhirose 4bb001351c Fix #705 2020-10-19 22:13:24 -04:00
6 changed files with 549 additions and 253 deletions
+21
View File
@@ -365,6 +365,27 @@ httplib::Client cli("http://localhost:8080");
httplib::Client cli("https://localhost");
```
### Error code
Here is the list of errors from `Result::error()`.
```c++
enum Error {
Success = 0,
Unknown,
Connection,
BindIPAddress,
Read,
Write,
ExceedRedirectCount,
Canceled,
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
UnsupportedMultipartBoundaryChars
};
```
### GET with HTTP headers
```c++
+343 -235
View File
@@ -87,6 +87,14 @@
: 0))
#endif
#ifndef CPPHTTPLIB_RECV_FLAGS
#define CPPHTTPLIB_RECV_FLAGS 0
#endif
#ifndef CPPHTTPLIB_SEND_FLAGS
#define CPPHTTPLIB_SEND_FLAGS 0
#endif
/*
* Headers
*/
@@ -173,6 +181,7 @@ using socket_t = int;
#define INVALID_SOCKET (-1)
#endif //_WIN32
#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
@@ -325,6 +334,10 @@ using ContentProvider =
using ContentProviderWithoutLength =
std::function<bool(size_t offset, DataSink &sink)>;
using ContentReceiverWithProgress =
std::function<bool(const char *data, size_t data_length, uint64_t offset,
uint64_t total_length)>;
using ContentReceiver =
std::function<bool(const char *data, size_t data_length)>;
@@ -377,7 +390,7 @@ struct Request {
// for client
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
ResponseHandler response_handler;
ContentReceiver content_receiver;
ContentReceiverWithProgress content_receiver;
size_t content_length = 0;
ContentProvider content_provider;
Progress progress;
@@ -425,7 +438,7 @@ struct Response {
void set_redirect(const char *url, int status = 302);
void set_redirect(const std::string &url, int status = 302);
void set_content(const char *s, size_t n, const char *content_type);
void set_content(std::string s, const char *content_type);
void set_content(const std::string &s, const char *content_type);
void set_content_provider(
size_t length, const char *content_type, ContentProvider provider,
@@ -671,8 +684,15 @@ private:
const HandlersForContentReader &handlers);
bool parse_request_line(const char *s, Request &req);
void apply_ranges(const Request &req, Response &res,
std::string &content_type, std::string &boundary);
bool write_response(Stream &strm, bool close_connection, const Request &req,
Response &res);
bool write_response_with_content(Stream &strm, bool close_connection,
const Request &req, Response &res);
bool write_response_core(Stream &strm, bool close_connection,
const Request &req, Response &res,
bool need_apply_ranges);
bool write_content_with_provider(Stream &strm, const Request &req,
Response &res, const std::string &boundary,
const std::string &content_type);
@@ -688,8 +708,8 @@ private:
ContentReceiver multipart_receiver);
virtual bool process_and_close_socket(socket_t sock);
struct MountPointEntry {
struct MountPointEntry {
std::string mount_point;
std::string base_dir;
Headers headers;
@@ -740,8 +760,11 @@ public:
bool operator==(std::nullptr_t) const { return res_ == nullptr; }
bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
const Response &value() const { return *res_; }
Response &value() { return *res_; }
const Response &operator*() const { return *res_; }
Response &operator*() { return *res_; }
const Response *operator->() const { return res_.get(); }
Response *operator->() { return res_.get(); }
Error error() const { return err_; }
private:
@@ -803,8 +826,7 @@ public:
Result Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items);
Result Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string& boundary);
const MultipartFormDataItems &items, const std::string &boundary);
Result Put(const char *path);
Result Put(const char *path, const std::string &body,
@@ -1041,8 +1063,7 @@ public:
Result Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items);
Result Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string& boundary);
const MultipartFormDataItems &items, const std::string &boundary);
Result Put(const char *path);
Result Put(const char *path, const std::string &body,
const char *content_type);
@@ -1134,7 +1155,7 @@ private:
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
bool is_ssl_ = false;
#endif
}; // namespace httplib
};
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLServer : public Server {
@@ -2509,7 +2530,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
}
inline bool read_content_with_length(Stream &strm, uint64_t len,
Progress progress, ContentReceiver out) {
Progress progress,
ContentReceiverWithProgress out) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
uint64_t r = 0;
@@ -2518,8 +2540,7 @@ inline bool read_content_with_length(Stream &strm, uint64_t len,
auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return false; }
if (!out(buf, static_cast<size_t>(n))) { return false; }
if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
r += static_cast<uint64_t>(n);
if (progress) {
@@ -2541,8 +2562,10 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) {
}
}
inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
inline bool read_content_without_length(Stream &strm,
ContentReceiverWithProgress out) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
uint64_t r = 0;
for (;;) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
if (n < 0) {
@@ -2550,13 +2573,16 @@ inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
} else if (n == 0) {
return true;
}
if (!out(buf, static_cast<size_t>(n))) { return false; }
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
r += static_cast<uint64_t>(n);
}
return true;
}
inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
inline bool read_content_chunked(Stream &strm,
ContentReceiverWithProgress out) {
const auto bufsiz = 16;
char buf[bufsiz];
@@ -2601,7 +2627,8 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
}
template <typename T, typename U>
bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver,
bool prepare_content_receiver(T &x, int &status,
ContentReceiverWithProgress receiver,
bool decompress, U callback) {
if (decompress) {
std::string encoding = x.get_header_value("Content-Encoding");
@@ -2626,10 +2653,12 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver,
if (decompressor) {
if (decompressor->is_valid()) {
ContentReceiver out = [&](const char *buf, size_t n) {
return decompressor->decompress(
buf, n,
[&](const char *buf, size_t n) { return receiver(buf, n); });
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
uint64_t off, uint64_t len) {
return decompressor->decompress(buf, n,
[&](const char *buf, size_t n) {
return receiver(buf, n, off, len);
});
};
return callback(std::move(out));
} else {
@@ -2639,18 +2668,20 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver,
}
}
ContentReceiver out = [&](const char *buf, size_t n) {
return receiver(buf, n);
ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off,
uint64_t len) {
return receiver(buf, n, off, len);
};
return callback(std::move(out));
}
template <typename T>
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
Progress progress, ContentReceiver receiver,
Progress progress, ContentReceiverWithProgress receiver,
bool decompress) {
return prepare_content_receiver(
x, status, std::move(receiver), decompress, [&](const ContentReceiver &out) {
x, status, std::move(receiver), decompress,
[&](const ContentReceiverWithProgress &out) {
auto ret = true;
auto exceed_payload_max_length = false;
@@ -2906,7 +2937,7 @@ inline bool parse_multipart_boundary(const std::string &content_type,
return !boundary.empty();
}
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
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)) {
@@ -2938,7 +2969,7 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
return all_valid_ranges;
}
return false;
}
} catch (...) { return false; }
class MultipartFormDataParser {
public:
@@ -2949,7 +2980,8 @@ public:
bool is_valid() const { return is_valid_; }
template <typename T, typename U>
bool parse(const char *buf, size_t n, const T &content_callback, const U &header_callback) {
bool parse(const char *buf, size_t n, const T &content_callback,
const U &header_callback) {
static const std::regex re_content_disposition(
"^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
@@ -3150,13 +3182,12 @@ get_range_offset_and_length(const Request &req, size_t content_length,
auto slen = static_cast<ssize_t>(content_length);
if (r.first == -1) {
r.first = slen - r.second;
r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
r.second = slen - 1;
}
if (r.second == -1) { r.second = slen - 1; }
return std::make_pair(r.first, r.second - r.first + 1);
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
}
inline std::string make_content_range_header_field(size_t offset, size_t length,
@@ -3205,21 +3236,21 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
return true;
}
inline std::string make_multipart_ranges_data(const Request &req, Response &res,
const std::string &boundary,
const std::string &content_type) {
std::string data;
process_multipart_ranges_data(
inline bool make_multipart_ranges_data(const Request &req, Response &res,
const std::string &boundary,
const std::string &content_type,
std::string &data) {
return process_multipart_ranges_data(
req, res, boundary, content_type,
[&](const std::string &token) { data += token; },
[&](const char *token) { data += token; },
[&](size_t offset, size_t length) {
data += res.body.substr(offset, length);
return true;
if (offset < res.body.size()) {
data += res.body.substr(offset, length);
return true;
}
return false;
});
return data;
}
inline size_t
@@ -3620,12 +3651,15 @@ inline void Response::set_redirect(const std::string &url, int stat) {
inline void Response::set_content(const char *s, size_t n,
const char *content_type) {
body.assign(s, n);
auto rng = headers.equal_range("Content-Type");
headers.erase(rng.first, rng.second);
set_header("Content-Type", content_type);
}
inline void Response::set_content(std::string s, const char *content_type) {
body = std::move(s);
set_header("Content-Type", content_type);
inline void Response::set_content(const std::string &s,
const char *content_type) {
set_content(s.data(), s.size(), content_type);
}
inline void
@@ -3733,9 +3767,10 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
return -1;
}
return recv(sock_, ptr, static_cast<int>(size), 0);
return recv(sock_, ptr, static_cast<int>(size), CPPHTTPLIB_RECV_FLAGS);
#else
return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
return handle_EINTR(
[&]() { return recv(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); });
#endif
}
@@ -3746,9 +3781,10 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) {
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
return -1;
}
return send(sock_, ptr, static_cast<int>(size), 0);
return send(sock_, ptr, static_cast<int>(size), CPPHTTPLIB_SEND_FLAGS);
#else
return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
return handle_EINTR(
[&]() { return send(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); });
#endif
}
@@ -3797,14 +3833,14 @@ inline Server::Server()
inline Server::~Server() {}
inline Server &Server::Get(const char *pattern, Handler handler) {
get_handlers_.push_back(std::make_pair(std::regex(pattern),
std::move(handler)));
get_handlers_.push_back(
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern, Handler handler) {
post_handlers_.push_back(std::make_pair(std::regex(pattern),
std::move(handler)));
post_handlers_.push_back(
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -3816,8 +3852,8 @@ inline Server &Server::Post(const char *pattern,
}
inline Server &Server::Put(const char *pattern, Handler handler) {
put_handlers_.push_back(std::make_pair(std::regex(pattern),
std::move(handler)));
put_handlers_.push_back(
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -3829,8 +3865,8 @@ inline Server &Server::Put(const char *pattern,
}
inline Server &Server::Patch(const char *pattern, Handler handler) {
patch_handlers_.push_back(std::make_pair(std::regex(pattern),
std::move(handler)));
patch_handlers_.push_back(
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -3842,8 +3878,8 @@ inline Server &Server::Patch(const char *pattern,
}
inline Server &Server::Delete(const char *pattern, Handler handler) {
delete_handlers_.push_back(std::make_pair(std::regex(pattern),
std::move(handler)));
delete_handlers_.push_back(
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -3855,8 +3891,8 @@ inline Server &Server::Delete(const char *pattern,
}
inline Server &Server::Options(const char *pattern, Handler handler) {
options_handlers_.push_back(std::make_pair(std::regex(pattern),
std::move(handler)));
options_handlers_.push_back(
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -3988,17 +4024,26 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
inline bool Server::write_response(Stream &strm, bool close_connection,
const Request &req, Response &res) {
return write_response_core(strm, close_connection, req, res, false);
}
inline bool Server::write_response_with_content(Stream &strm,
bool close_connection,
const Request &req,
Response &res) {
return write_response_core(strm, close_connection, req, res, true);
}
inline bool Server::write_response_core(Stream &strm, bool close_connection,
const Request &req, Response &res,
bool need_apply_ranges) {
assert(res.status != -1);
if (400 <= res.status && error_handler_) { error_handler_(req, res); }
detail::BufferStream bstrm;
// Response line
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
detail::status_message(res.status))) {
return false;
}
std::string content_type;
std::string boundary;
if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
// Headers
if (close_connection || req.get_header_value("Connection") == "close") {
@@ -4015,109 +4060,21 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
res.set_header("Content-Type", "text/plain");
}
if (!res.has_header("Content-Length") && res.body.empty() &&
!res.content_length_ && !res.content_provider_) {
res.set_header("Content-Length", "0");
}
if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
res.set_header("Accept-Ranges", "bytes");
}
std::string content_type;
std::string boundary;
detail::BufferStream bstrm;
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);
}
res.headers.emplace("Content-Type",
"multipart/byteranges; boundary=" + boundary);
}
auto type = detail::encoding_type(req, res);
if (res.body.empty()) {
if (res.content_length_ > 0) {
size_t length = 0;
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);
auto offset = offsets.first;
length = offsets.second;
auto content_range = detail::make_content_range_header_field(
offset, length, res.content_length_);
res.set_header("Content-Range", content_range);
} else {
length = detail::get_multipart_ranges_data_length(req, res, boundary,
content_type);
}
res.set_header("Content-Length", std::to_string(length));
} else {
if (res.content_provider_) {
if (res.is_chunked_content_provider) {
res.set_header("Transfer-Encoding", "chunked");
if (type == detail::EncodingType::Gzip) {
res.set_header("Content-Encoding", "gzip");
} else if (type == detail::EncodingType::Brotli) {
res.set_header("Content-Encoding", "br");
}
}
} else {
res.set_header("Content-Length", "0");
}
}
} else {
if (req.ranges.empty()) {
;
} else if (req.ranges.size() == 1) {
auto offsets =
detail::get_range_offset_and_length(req, res.body.size(), 0);
auto offset = offsets.first;
auto length = offsets.second;
auto content_range = detail::make_content_range_header_field(
offset, length, res.body.size());
res.set_header("Content-Range", content_range);
res.body = res.body.substr(offset, length);
} else {
res.body =
detail::make_multipart_ranges_data(req, res, boundary, content_type);
}
if (type != detail::EncodingType::None) {
std::unique_ptr<detail::compressor> compressor;
if (type == detail::EncodingType::Gzip) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
compressor = detail::make_unique<detail::gzip_compressor>();
res.set_header("Content-Encoding", "gzip");
#endif
} else if (type == detail::EncodingType::Brotli) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
compressor = detail::make_unique<detail::brotli_compressor>();
res.set_header("Content-Encoding", "brotli");
#endif
}
if (compressor) {
std::string compressed;
if (!compressor->compress(res.body.data(), res.body.size(), true,
[&](const char *data, size_t data_len) {
compressed.append(data, data_len);
return true;
})) {
return false;
}
res.body.swap(compressed);
}
}
auto length = std::to_string(res.body.size());
res.set_header("Content-Length", length);
// Response line
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
detail::status_message(res.status))) {
return false;
}
if (!detail::write_headers(bstrm, res, Headers())) { return false; }
@@ -4240,7 +4197,7 @@ inline bool Server::read_content_with_content_receiver(
Stream &strm, Request &req, Response &res, ContentReceiver receiver,
MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver) {
return read_content_core(strm, req, res, std::move(receiver),
return read_content_core(strm, req, res, std::move(receiver),
std::move(multipart_header),
std::move(multipart_receiver));
}
@@ -4250,7 +4207,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
MultipartContentHeader mulitpart_header,
ContentReceiver multipart_receiver) {
detail::MultipartFormDataParser multipart_form_data_parser;
ContentReceiver out;
ContentReceiverWithProgress out;
if (req.is_multipart_form_data()) {
const auto &content_type = req.get_header_value("Content-Type");
@@ -4261,7 +4218,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
}
multipart_form_data_parser.set_boundary(std::move(boundary));
out = [&](const char *buf, size_t n) {
out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) {
/* For debug
size_t pos = 0;
while (pos < n) {
@@ -4277,7 +4234,8 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
mulitpart_header);
};
} else {
out = std::move(receiver);
out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
uint64_t /*len*/) { return receiver(buf, n); };
}
if (req.method == "DELETE" && !req.has_header("Content-Length")) {
@@ -4314,7 +4272,7 @@ inline bool Server::handle_file_request(Request &req, Response &res,
auto type =
detail::find_content_type(path, file_extension_and_mimetype_map_);
if (type) { res.set_header("Content-Type", type); }
for (const auto& kv : entry.headers) {
for (const auto &kv : entry.headers) {
res.set_header(kv.first.c_str(), kv.second);
}
res.status = 200;
@@ -4435,9 +4393,8 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
{
ContentReader reader(
[&](ContentReceiver receiver) {
return read_content_with_content_receiver(strm, req, res,
std::move(receiver),
nullptr, nullptr);
return read_content_with_content_receiver(
strm, req, res, std::move(receiver), nullptr, nullptr);
},
[&](MultipartContentHeader header, ContentReceiver receiver) {
return read_content_with_content_receiver(strm, req, res, nullptr,
@@ -4497,7 +4454,6 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
inline bool Server::dispatch_request(Request &req, Response &res,
const Handlers &handlers) {
try {
for (const auto &x : handlers) {
const auto &pattern = x.first;
@@ -4518,6 +4474,116 @@ inline bool Server::dispatch_request(Request &req, Response &res,
return false;
}
inline void Server::apply_ranges(const Request &req, Response &res,
std::string &content_type,
std::string &boundary) {
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);
}
res.headers.emplace("Content-Type",
"multipart/byteranges; boundary=" + boundary);
}
auto type = detail::encoding_type(req, res);
if (res.body.empty()) {
if (res.content_length_ > 0) {
size_t length = 0;
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);
auto offset = offsets.first;
length = offsets.second;
auto content_range = detail::make_content_range_header_field(
offset, length, res.content_length_);
res.set_header("Content-Range", content_range);
} else {
length = detail::get_multipart_ranges_data_length(req, res, boundary,
content_type);
}
res.set_header("Content-Length", std::to_string(length));
} else {
if (res.content_provider_) {
if (res.is_chunked_content_provider) {
res.set_header("Transfer-Encoding", "chunked");
if (type == detail::EncodingType::Gzip) {
res.set_header("Content-Encoding", "gzip");
} else if (type == detail::EncodingType::Brotli) {
res.set_header("Content-Encoding", "br");
}
}
}
}
} else {
if (req.ranges.empty()) {
;
} else if (req.ranges.size() == 1) {
auto offsets =
detail::get_range_offset_and_length(req, res.body.size(), 0);
auto offset = offsets.first;
auto length = offsets.second;
auto content_range = detail::make_content_range_header_field(
offset, length, res.body.size());
res.set_header("Content-Range", content_range);
if (offset < res.body.size()) {
res.body = res.body.substr(offset, length);
} else {
res.body.clear();
res.status = 416;
}
} 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 = 416;
}
}
if (type != detail::EncodingType::None) {
std::unique_ptr<detail::compressor> compressor;
std::string content_encoding;
if (type == detail::EncodingType::Gzip) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
compressor = detail::make_unique<detail::gzip_compressor>();
content_encoding = "gzip";
#endif
} else if (type == detail::EncodingType::Brotli) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
compressor = detail::make_unique<detail::brotli_compressor>();
content_encoding = "brotli";
#endif
}
if (compressor) {
std::string compressed;
if (compressor->compress(res.body.data(), res.body.size(), true,
[&](const char *data, size_t data_len) {
compressed.append(data, data_len);
return true;
})) {
res.body.swap(compressed);
res.set_header("Content-Encoding", content_encoding);
}
}
}
auto length = std::to_string(res.body.size());
res.set_header("Content-Length", length);
}
}
inline bool Server::dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
const HandlersForContentReader &handlers) {
@@ -4580,7 +4646,8 @@ Server::process_request(Stream &strm, bool close_connection,
if (req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) {
// TODO: error
res.status = 416;
return write_response(strm, close_connection, req, res);
}
}
@@ -4604,11 +4671,11 @@ Server::process_request(Stream &strm, bool close_connection,
// Rounting
if (routing(req, res, strm)) {
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
return write_response_with_content(strm, close_connection, req, res);
} else {
if (res.status == -1) { res.status = 404; }
return write_response(strm, close_connection, req, res);
}
return write_response(strm, close_connection, req, res);
}
inline bool Server::is_valid() const { return true; }
@@ -4721,7 +4788,7 @@ inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
std::cmatch m;
if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
if (!std::regex_match(line_reader.ptr(), m, re)) { return true; }
res.version = std::string(m[1]);
res.status = std::stoi(std::string(m[2]));
res.reason = std::string(m[3]);
@@ -5119,16 +5186,21 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req,
if (req.method != "HEAD" && req.method != "CONNECT") {
auto out =
req.content_receiver
? static_cast<ContentReceiver>([&](const char *buf, size_t n) {
auto ret = req.content_receiver(buf, n);
if (!ret) { error_ = Error::Canceled; }
return ret;
})
: static_cast<ContentReceiver>([&](const char *buf, size_t n) {
if (res.body.size() + n > res.body.max_size()) { return false; }
res.body.append(buf, n);
return true;
});
? static_cast<ContentReceiverWithProgress>(
[&](const char *buf, size_t n, uint64_t off, uint64_t len) {
auto ret = req.content_receiver(buf, n, off, len);
if (!ret) { error_ = Error::Canceled; }
return ret;
})
: static_cast<ContentReceiverWithProgress>(
[&](const char *buf, size_t n, uint64_t /*off*/,
uint64_t /*len*/) {
if (res.body.size() + n > res.body.max_size()) {
return false;
}
res.body.append(buf, n);
return true;
});
auto progress = [&](uint64_t current, uint64_t total) {
if (!req.progress) { return true; }
@@ -5160,10 +5232,9 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req,
inline bool
ClientImpl::process_socket(Socket &socket,
std::function<bool(Stream &strm)> callback) {
return detail::process_client_socket(socket.sock, read_timeout_sec_,
read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_,
std::move(callback));
return detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_, std::move(callback));
}
inline bool ClientImpl::is_ssl() const { return false; }
@@ -5250,7 +5321,11 @@ inline Result ClientImpl::Get(const char *path, const Headers &headers,
req.headers = default_headers_;
req.headers.insert(headers.begin(), headers.end());
req.response_handler = std::move(response_handler);
req.content_receiver = std::move(content_receiver);
req.content_receiver =
[content_receiver](const char *data, size_t data_length,
uint64_t /*offset*/, uint64_t /*total_length*/) {
return content_receiver(data, data_length);
};
req.progress = std::move(progress);
auto res = detail::make_unique<Response>();
@@ -5306,10 +5381,9 @@ inline Result ClientImpl::Post(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
const char *content_type) {
auto ret = send_with_content_provider("POST", path, headers, std::string(),
content_length,
std::move(content_provider),
content_type);
auto ret = send_with_content_provider(
"POST", path, headers, std::string(), content_length,
std::move(content_provider), content_type);
return Result{std::move(ret), get_last_error()};
}
@@ -5330,7 +5404,7 @@ inline Result ClientImpl::Post(const char *path, const Headers &headers,
}
inline Result ClientImpl::Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string& boundary) {
const std::string &boundary) {
for (size_t i = 0; i < boundary.size(); i++) {
char c = boundary[i];
if (!std::isalnum(c) && c != '-' && c != '_') {
@@ -5389,10 +5463,9 @@ inline Result ClientImpl::Put(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
const char *content_type) {
auto ret = send_with_content_provider("PUT", path, headers, std::string(),
content_length,
std::move(content_provider),
content_type);
auto ret = send_with_content_provider(
"PUT", path, headers, std::string(), content_length,
std::move(content_provider), content_type);
return Result{std::move(ret), get_last_error()};
}
@@ -5430,10 +5503,9 @@ inline Result ClientImpl::Patch(const char *path, const Headers &headers,
size_t content_length,
ContentProvider content_provider,
const char *content_type) {
auto ret = send_with_content_provider("PATCH", path, headers, std::string(),
content_length,
std::move(content_provider),
content_type);
auto ret = send_with_content_provider(
"PATCH", path, headers, std::string(), content_length,
std::move(content_provider), content_type);
return Result{std::move(ret), get_last_error()};
}
@@ -5605,7 +5677,9 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
}
if (ssl) {
set_nonblocking(sock, true);
auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
BIO_set_nbio(bio, 1);
SSL_set_bio(ssl, bio, bio);
if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
@@ -5614,8 +5688,11 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
std::lock_guard<std::mutex> guard(ctx_mutex);
SSL_free(ssl);
}
set_nonblocking(sock, false);
return nullptr;
}
BIO_set_nbio(bio, 0);
set_nonblocking(sock, false);
}
return ssl;
@@ -5631,6 +5708,28 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
SSL_free(ssl);
}
template <typename U>
bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
U ssl_connect_or_accept,
time_t timeout_sec,
time_t timeout_usec) {
int res = 0;
while ((res = ssl_connect_or_accept(ssl)) != 1) {
auto err = SSL_get_error(ssl, res);
switch (err) {
case SSL_ERROR_WANT_READ:
if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; }
break;
case SSL_ERROR_WANT_WRITE:
if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; }
break;
default: break;
}
return false;
}
return true;
}
template <typename T>
inline bool
process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
@@ -5718,22 +5817,7 @@ inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
write_timeout_usec_(write_timeout_usec) {
{
timeval tv;
tv.tv_sec = static_cast<long>(read_timeout_sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char *>(&tv),
sizeof(tv));
}
{
timeval tv;
tv.tv_sec = static_cast<long>(write_timeout_sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char *>(&tv),
sizeof(tv));
}
SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
}
inline SSLSocketStream::~SSLSocketStream() {}
@@ -5748,8 +5832,25 @@ inline bool SSLSocketStream::is_writable() const {
}
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
if (SSL_pending(ssl_) > 0 || is_readable()) {
if (SSL_pending(ssl_) > 0) {
return SSL_read(ssl_, ptr, static_cast<int>(size));
} else if (is_readable()) {
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret < 0) {
auto err = SSL_get_error(ssl_, ret);
while (err == SSL_ERROR_WANT_READ) {
if (SSL_pending(ssl_) > 0) {
return SSL_read(ssl_, ptr, static_cast<int>(size));
} else if (is_readable()) {
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret >= 0) { return ret; }
err = SSL_get_error(ssl_, ret);
} else {
return -1;
}
}
}
return ret;
}
return -1;
}
@@ -5841,8 +5942,13 @@ inline SSLServer::~SSLServer() {
inline bool SSLServer::is_valid() const { return ctx_; }
inline bool SSLServer::process_and_close_socket(socket_t sock) {
auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept,
[](SSL * /*ssl*/) { return true; });
auto ssl = detail::ssl_new(
sock, ctx_, ctx_mutex_,
[&](SSL *ssl) {
return detail::ssl_connect_or_accept_nonblocking(
sock, ssl, SSL_accept, read_timeout_sec_, read_timeout_usec_);
},
[](SSL * /*ssl*/) { return true; });
if (ssl) {
auto ret = detail::process_server_socket_ssl(
@@ -6036,7 +6142,9 @@ inline bool SSLClient::initialize_ssl(Socket &socket) {
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
}
if (SSL_connect(ssl) != 1) {
if (!detail::ssl_connect_or_accept_nonblocking(
socket.sock, ssl, SSL_connect, connection_timeout_sec_,
connection_timeout_usec_)) {
error_ = Error::SSLConnection;
return false;
}
@@ -6161,17 +6269,15 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
if (strlen(name) == name_len) {
switch (type) {
case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
switch (type) {
case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
case GEN_IPADD:
if (!memcmp(&addr6, name, addr_len) ||
!memcmp(&addr, name, addr_len)) {
ip_mached = true;
}
break;
case GEN_IPADD:
if (!memcmp(&addr6, name, addr_len) ||
!memcmp(&addr, name, addr_len)) {
ip_mached = true;
}
break;
}
}
}
@@ -6246,6 +6352,8 @@ inline Client::Client(const char *scheme_host_port,
#else
if (!scheme.empty() && scheme != "http") {
#endif
std::string msg = "'" + scheme + "' scheme is not supported.";
throw std::invalid_argument(msg);
return;
}
@@ -6381,7 +6489,7 @@ inline Result Client::Post(const char *path, const Headers &headers,
}
inline Result Client::Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string& boundary) {
const std::string &boundary) {
return cli_->Post(path, headers, items, boundary);
}
inline Result Client::Put(const char *path) { return cli_->Put(path); }
+2 -1
View File
@@ -1,5 +1,5 @@
#CXX = clang++
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
CXXFLAGS = -g -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
OPENSSL_DIR = /usr/local/opt/openssl@1.1
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
@@ -24,6 +24,7 @@ test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
cert.pem:
openssl genrsa 2048 > key.pem
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
openssl genrsa 2048 > rootCA.key.pem
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
openssl genrsa 2048 > client.key.pem
+2 -1
View File
@@ -19,7 +19,8 @@ all : server_fuzzer
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
server_fuzzer : server_fuzzer.cc ../../httplib.h
$(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
# $(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
zip -q -r server_fuzzer_seed_corpus.zip corpus
clean:
+178 -16
View File
@@ -7,6 +7,7 @@
#include <thread>
#define SERVER_CERT_FILE "./cert.pem"
#define SERVER_CERT2_FILE "./cert2.pem"
#define SERVER_PRIVATE_KEY_FILE "./key.pem"
#define CA_CERT_FILE "./ca-bundle.crt"
#define CLIENT_CA_CERT_FILE "./rootCA.cert.pem"
@@ -134,6 +135,17 @@ TEST(GetHeaderValueTest, RegularValue) {
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, SetContent) {
Response res;
res.set_content("html", "text/html");
EXPECT_EQ("text/html", res.get_header_value("Content-Type"));
res.set_content("text", "text/plain");
EXPECT_EQ(1, res.get_header_value_count("Content-Type"));
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
}
TEST(GetHeaderValueTest, RegularValueInt) {
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
auto val =
@@ -829,7 +841,7 @@ TEST(UrlWithSpace, Redirect) {
}
#endif
TEST(Server, BindDualStack) {
TEST(BindServerTest, BindDualStack) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
@@ -862,7 +874,7 @@ TEST(Server, BindDualStack) {
ASSERT_FALSE(svr.is_running());
}
TEST(Server, BindAndListenSeparately) {
TEST(BindServerTest, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
@@ -871,7 +883,7 @@ TEST(Server, BindAndListenSeparately) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLServer, BindAndListenSeparately) {
TEST(BindServerTest, BindAndListenSeparatelySSL) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
int port = svr.bind_to_any_port("0.0.0.0");
@@ -881,6 +893,41 @@ TEST(SSLServer, BindAndListenSeparately) {
}
#endif
TEST(ErrorHandlerTest, ContentLength) {
Server svr;
svr.set_error_handler([](const Request & /*req*/, Response &res) {
res.status = 200;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
res.status = 524;
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -1897,6 +1944,41 @@ TEST_F(ServerTest, GetStreamedWithRange2) {
EXPECT_EQ(std::string("bcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-3"}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("3", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("efg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-9999"}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeError) {
auto res = cli_.Get("/streamed-with-range",
{{"Range", "bytes=92233720368547758079223372036854775806-"
"92233720368547758079223372036854775807"}});
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res =
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
EXPECT_EQ(206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
auto res =
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
@@ -1932,8 +2014,7 @@ TEST_F(ServerTest, ClientStop) {
}));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::seconds(2));
while (cli_.is_socket_open()) {
cli_.stop();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
@@ -1979,6 +2060,12 @@ TEST_F(ServerTest, GetWithRange4) {
EXPECT_EQ(std::string("fg"), res->body);
}
TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) {
auto res = cli_.Get("/with-range", {{make_range_header({{10000, 20000}})}});
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
TEST_F(ServerTest, GetWithRangeMultipart) {
auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
@@ -1988,6 +2075,13 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
EXPECT_EQ(269, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
auto res =
cli_.Get("/with-range", {{make_range_header({{-1, 2}, {10000, 30000}})}});
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res);
@@ -3005,9 +3099,7 @@ TEST(KeepAliveTest, ReadTimeoutSSL) {
res.set_content("b", "text/plain");
});
auto listen_thread = std::thread([&svr]() {
svr.listen("localhost", PORT);
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
@@ -3213,6 +3305,31 @@ TEST(SSLClientTest, ServerCertificateVerification3) {
ASSERT_EQ(301, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification4) {
SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
svr.stop();
ASSERT_TRUE(true);
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
SSLClient cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
cli.enable_server_certificate_verification(true);
cli.set_connection_timeout(30);
auto res = cli.Get("/test");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
t.join();
}
TEST(SSLClientTest, WildcardHostNameMatch) {
SSLClient cli("www.youtube.com");
@@ -3267,6 +3384,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
t.join();
}
#if !defined(_WIN32) || defined(OPENSSL_USE_APPLINK)
TEST(SSLClientServerTest, MemoryClientCertPresent) {
X509 *server_cert;
EVP_PKEY *server_private_key;
@@ -3342,6 +3460,7 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
t.join();
}
#endif
TEST(SSLClientServerTest, ClientCertMissing) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
@@ -3386,6 +3505,51 @@ TEST(SSLClientServerTest, TrustDirOptional) {
t.join();
}
TEST(SSLClientServerTest, SSLConnectTimeout) {
class NoListenSSLServer : public SSLServer {
public:
NoListenSSLServer(const char *cert_path, const char *private_key_path,
const char *client_ca_cert_file_path,
const char *client_ca_cert_dir_path = nullptr)
: SSLServer(cert_path, private_key_path, client_ca_cert_file_path,
client_ca_cert_dir_path),
stop_(false) {}
bool stop_;
private:
bool process_and_close_socket(socket_t /*sock*/) override {
// Don't create SSL context
while (!stop_) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return true;
}
};
NoListenSSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE,
CLIENT_CA_CERT_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
cli.set_connection_timeout(1);
auto res = cli.Get("/test");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLConnection, res.error());
svr.stop_ = true;
svr.stop();
t.join();
}
#endif
#ifdef _WIN32
@@ -3395,17 +3559,15 @@ TEST(CleanupTest, WSACleanup) {
}
#endif
// #ifndef CPPHTTPLIB_OPENSSL_SUPPORT
// TEST(NoSSLSupport, SimpleInterface) {
// Client cli("https://yahoo.com");
// ASSERT_FALSE(cli.is_valid());
// }
// #endif
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(NoSSLSupport, SimpleInterface) {
ASSERT_ANY_THROW(Client cli("https://yahoo.com"));
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(InvalidScheme, SimpleInterface) {
Client cli("scheme://yahoo.com");
ASSERT_FALSE(cli.is_valid());
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
}
TEST(NoScheme, SimpleInterface) {
+3
View File
@@ -16,3 +16,6 @@ emailAddress = test@email.address
[req_attributes]
challengePassword = 1234
[SAN]
subjectAltName=IP:127.0.0.1