Compare commits

...

18 Commits

Author SHA1 Message Date
yhirose ccbddd8842 Allow LargeRandomData test only on Windows 2021-07-22 22:17:31 -04:00
yosh-matsuda 879dd261c2 Fix gzip compression/decompression over 4 GiB data size (#1002)
* Fix gzip compression/decompression over 4 GiB data size

* Add gzip test for large random data
2021-07-22 22:07:40 -04:00
xxrl 52f5eb5980 [Fix] ca_cert_path/ce_cert_store lose (#1004)
When redirect from http to https, user setting for ca_cert will lose

issue: #1003
2021-07-22 21:41:41 -04:00
Gregor Jasny ea2f69a0d7 Add httplib::Error to std::string function (#999)
Fixes: #978
2021-07-19 21:17:44 -04:00
Gregor Jasny 9f2064a8ed Fix remaining test warnings (#1001)
* Use portable way to encode ESC

'\e' is a GNU extension

* Use length specifier for size_t
2021-07-19 21:17:18 -04:00
yhirose e3750d9ddf Simplefied server APIs 2021-07-17 18:09:30 -04:00
yhirose c1eee3012e Fix #998 2021-07-17 17:18:56 -04:00
Gregor Jasny 6b08babbd2 Use googletest 1.11.0 (#1000)
* Update googletest to version 1.11.0

* Fix test warnings
2021-07-17 13:21:03 -04:00
yhirose 215b81342e Added a test case for #996 2021-07-15 08:24:06 -04:00
yhirose 06bfa7e08b Fix #979 2021-07-14 22:49:49 -04:00
yhirose 3d83cbb872 Improve string compare performance 2021-07-12 23:51:56 -04:00
yhirose 8a803b30f6 Fix #990 2021-07-12 23:46:25 -04:00
yhirose 80be649de7 Fix #961 2021-06-26 18:26:33 -04:00
yhirose 9648f950f5 Updated README 2021-06-18 08:45:50 -04:00
Gregor Jasny 6b9ffc8bec Remove dead code (#965) 2021-06-18 07:20:34 -04:00
Simon Edlund d903053faf Update httplib.h (#964)
operator""_ replaced by operator""_t
2021-06-17 10:57:25 -04:00
yhirose 676f1b5a26 Updated the user agent string 2021-06-14 08:43:12 -04:00
Baruch Nissenbaum b8dec12f15 Limit SSL_ERROR_WANT_READ retries to 1 sec (#957)
retry with 1ms delays to prevent CPU hoggin
2021-06-14 08:41:20 -04:00
7 changed files with 13237 additions and 16802 deletions
+27 -26
View File
@@ -53,6 +53,33 @@ res->body;
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
SSL Support
-----------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib currently supports only version 1.1.1.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// Server
httplib::SSLServer svr("./cert.pem", "./key.pem");
// Client
httplib::Client cli("https://localhost:1234"); // scheme + host
httplib::SSLClient cli("localhost:1234"); // host
// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");
// Disable cert verification
cli.enable_server_certificate_verification(false);
```
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
Server
------
@@ -719,32 +746,6 @@ res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res->body; // Compressed data
```
SSL Support
-----------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib currently supports only version 1.1.1.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// Server
httplib::SSLServer svr("./cert.pem", "./key.pem");
// Client
httplib::Client cli("https://localhost:1234");
// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");
// Disable cert verification
cli.enable_server_certificate_verification(false);
```
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
Split httplib.h into .h and .cc
-------------------------------
+313 -247
View File
@@ -624,35 +624,22 @@ public:
virtual bool is_valid() const;
Server &Get(const char *pattern, Handler handler);
Server &Get(const char *pattern, size_t pattern_len, Handler handler);
Server &Post(const char *pattern, Handler handler);
Server &Post(const char *pattern, size_t pattern_len, Handler handler);
Server &Post(const char *pattern, HandlerWithContentReader handler);
Server &Post(const char *pattern, size_t pattern_len,
HandlerWithContentReader handler);
Server &Put(const char *pattern, Handler handler);
Server &Put(const char *pattern, size_t pattern_len, Handler handler);
Server &Put(const char *pattern, HandlerWithContentReader handler);
Server &Put(const char *pattern, size_t pattern_len,
HandlerWithContentReader handler);
Server &Patch(const char *pattern, Handler handler);
Server &Patch(const char *pattern, size_t pattern_len, Handler handler);
Server &Patch(const char *pattern, HandlerWithContentReader handler);
Server &Patch(const char *pattern, size_t pattern_len,
HandlerWithContentReader handler);
Server &Delete(const char *pattern, Handler handler);
Server &Delete(const char *pattern, size_t pattern_len, Handler handler);
Server &Delete(const char *pattern, HandlerWithContentReader handler);
Server &Delete(const char *pattern, size_t pattern_len,
HandlerWithContentReader handler);
Server &Options(const char *pattern, Handler handler);
Server &Options(const char *pattern, size_t pattern_len, Handler handler);
Server &Get(const std::string &pattern, Handler handler);
Server &Post(const std::string &pattern, Handler handler);
Server &Post(const std::string &pattern, HandlerWithContentReader handler);
Server &Put(const std::string &pattern, Handler handler);
Server &Put(const std::string &pattern, HandlerWithContentReader handler);
Server &Patch(const std::string &pattern, Handler handler);
Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
Server &Delete(const std::string &pattern, Handler handler);
Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
Server &Options(const std::string &pattern, Handler handler);
bool set_base_dir(const char *dir, const char *mount_point = nullptr);
bool set_mount_point(const char *mount_point, const char *dir,
bool set_base_dir(const std::string &dir,
const std::string &mount_point = nullptr);
bool set_mount_point(const std::string &mount_point, const std::string &dir,
Headers headers = Headers());
bool remove_mount_point(const char *mount_point);
bool remove_mount_point(const std::string &mount_point);
Server &set_file_extension_and_mimetype_mapping(const char *ext,
const char *mime);
Server &set_file_request_handler(Handler handler);
@@ -811,8 +798,31 @@ enum class Error {
Compression,
};
inline std::string to_string(const Error error) {
switch (error) {
case Error::Success: return "Success";
case Error::Connection: return "Connection";
case Error::BindIPAddress: return "BindIPAddress";
case Error::Read: return "Read";
case Error::Write: return "Write";
case Error::ExceedRedirectCount: return "ExceedRedirectCount";
case Error::Canceled: return "Canceled";
case Error::SSLConnection: return "SSLConnection";
case Error::SSLLoadingCerts: return "SSLLoadingCerts";
case Error::SSLServerVerification: return "SSLServerVerification";
case Error::UnsupportedMultipartBoundaryChars:
return "UnsupportedMultipartBoundaryChars";
case Error::Compression: return "Compression";
case Error::Unknown: return "Unknown";
default: break;
}
return "Invalid";
}
inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
os << static_cast<std::underlying_type<Error>::type>(obj);
os << to_string(obj);
os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
return os;
}
@@ -1026,6 +1036,12 @@ public:
void set_proxy_digest_auth(const char *username, const char *password);
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path = nullptr);
void set_ca_cert_store(X509_STORE *ca_cert_store);
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void enable_server_certificate_verification(bool enabled);
#endif
@@ -1127,6 +1143,13 @@ protected:
std::string proxy_digest_auth_password_;
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
std::string ca_cert_file_path_;
std::string ca_cert_dir_path_;
X509_STORE *ca_cert_store_ = nullptr;
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
bool server_certificate_verification_ = true;
#endif
@@ -1153,6 +1176,8 @@ private:
ContentProviderWithoutLength content_provider_without_length,
const char *content_type);
std::string adjust_host_string(const std::string &host) const;
virtual bool process_socket(const Socket &socket,
std::function<bool(Stream &strm)> callback);
virtual bool is_ssl() const;
@@ -1161,9 +1186,9 @@ private:
class Client {
public:
// Universal interface
explicit Client(const char *scheme_host_port);
explicit Client(const std::string &scheme_host_port);
explicit Client(const char *scheme_host_port,
explicit Client(const std::string &scheme_host_port,
const std::string &client_cert_path,
const std::string &client_key_path);
@@ -1403,9 +1428,6 @@ public:
bool is_valid() const override;
void set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path = nullptr);
void set_ca_cert_store(X509_STORE *ca_cert_store);
long get_openssl_verify_result() const;
@@ -1438,8 +1460,6 @@ private:
std::vector<std::string> host_components_;
std::string ca_cert_file_path_;
std::string ca_cert_dir_path_;
long verify_result_ = 0;
friend class ClientImpl;
@@ -2334,7 +2354,7 @@ inline unsigned int str2tag(const std::string &s) {
namespace udl {
inline constexpr unsigned int operator"" _(const char *s, size_t l) {
inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
return str2tag_core(s, l, 0);
}
@@ -2348,59 +2368,59 @@ find_content_type(const std::string &path,
auto it = user_data.find(ext);
if (it != user_data.end()) { return it->second.c_str(); }
using udl::operator""_;
using udl::operator""_t;
switch (str2tag(ext)) {
default: return nullptr;
case "css"_: return "text/css";
case "csv"_: return "text/csv";
case "txt"_: return "text/plain";
case "vtt"_: return "text/vtt";
case "htm"_:
case "html"_: return "text/html";
case "css"_t: return "text/css";
case "csv"_t: return "text/csv";
case "txt"_t: return "text/plain";
case "vtt"_t: return "text/vtt";
case "htm"_t:
case "html"_t: return "text/html";
case "apng"_: return "image/apng";
case "avif"_: return "image/avif";
case "bmp"_: return "image/bmp";
case "gif"_: return "image/gif";
case "png"_: return "image/png";
case "svg"_: return "image/svg+xml";
case "webp"_: return "image/webp";
case "ico"_: return "image/x-icon";
case "tif"_: return "image/tiff";
case "tiff"_: return "image/tiff";
case "jpg"_:
case "jpeg"_: return "image/jpeg";
case "apng"_t: return "image/apng";
case "avif"_t: return "image/avif";
case "bmp"_t: return "image/bmp";
case "gif"_t: return "image/gif";
case "png"_t: return "image/png";
case "svg"_t: return "image/svg+xml";
case "webp"_t: return "image/webp";
case "ico"_t: return "image/x-icon";
case "tif"_t: return "image/tiff";
case "tiff"_t: return "image/tiff";
case "jpg"_t:
case "jpeg"_t: return "image/jpeg";
case "mp4"_: return "video/mp4";
case "mpeg"_: return "video/mpeg";
case "webm"_: return "video/webm";
case "mp4"_t: return "video/mp4";
case "mpeg"_t: return "video/mpeg";
case "webm"_t: return "video/webm";
case "mp3"_: return "audio/mp3";
case "mpga"_: return "audio/mpeg";
case "weba"_: return "audio/webm";
case "wav"_: return "audio/wave";
case "mp3"_t: return "audio/mp3";
case "mpga"_t: return "audio/mpeg";
case "weba"_t: return "audio/webm";
case "wav"_t: return "audio/wave";
case "otf"_: return "font/otf";
case "ttf"_: return "font/ttf";
case "woff"_: return "font/woff";
case "woff2"_: return "font/woff2";
case "otf"_t: return "font/otf";
case "ttf"_t: return "font/ttf";
case "woff"_t: return "font/woff";
case "woff2"_t: return "font/woff2";
case "7z"_: return "application/x-7z-compressed";
case "atom"_: return "application/atom+xml";
case "pdf"_: return "application/pdf";
case "js"_:
case "mjs"_: return "application/javascript";
case "json"_: return "application/json";
case "rss"_: return "application/rss+xml";
case "tar"_: return "application/x-tar";
case "xht"_:
case "xhtml"_: return "application/xhtml+xml";
case "xslt"_: return "application/xslt+xml";
case "xml"_: return "application/xml";
case "gz"_: return "application/gzip";
case "zip"_: return "application/zip";
case "wasm"_: return "application/wasm";
case "7z"_t: return "application/x-7z-compressed";
case "atom"_t: return "application/atom+xml";
case "pdf"_t: return "application/pdf";
case "js"_t:
case "mjs"_t: return "application/javascript";
case "json"_t: return "application/json";
case "rss"_t: return "application/rss+xml";
case "tar"_t: return "application/x-tar";
case "xht"_t:
case "xhtml"_t: return "application/xhtml+xml";
case "xslt"_t: return "application/xslt+xml";
case "xml"_t: return "application/xml";
case "gz"_t: return "application/gzip";
case "zip"_t: return "application/zip";
case "wasm"_t: return "application/wasm";
}
}
@@ -2558,28 +2578,40 @@ public:
Callback callback) override {
assert(is_valid_);
auto flush = last ? Z_FINISH : Z_NO_FLUSH;
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
int ret = Z_OK;
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
do {
strm_.avail_out = static_cast<uInt>(buff.size());
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
constexpr size_t max_avail_in =
std::numeric_limits<decltype(strm_.avail_in)>::max();
ret = deflate(&strm_, flush);
if (ret == Z_STREAM_ERROR) { return false; }
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
std::min(data_length, max_avail_in));
strm_.next_in =
const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
} while (strm_.avail_out == 0);
data_length -= strm_.avail_in;
data += strm_.avail_in;
auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
int ret = Z_OK;
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
do {
strm_.avail_out = static_cast<uInt>(buff.size());
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
ret = deflate(&strm_, flush);
if (ret == Z_STREAM_ERROR) { return false; }
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
} while (strm_.avail_out == 0);
assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
(flush == Z_NO_FLUSH && ret == Z_OK));
assert(strm_.avail_in == 0);
} while (data_length > 0);
assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK));
assert(strm_.avail_in == 0);
return true;
}
@@ -2613,28 +2645,41 @@ public:
int ret = Z_OK;
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
do {
constexpr size_t max_avail_in =
std::numeric_limits<decltype(strm_.avail_in)>::max();
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
while (strm_.avail_in > 0) {
strm_.avail_out = static_cast<uInt>(buff.size());
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
std::min(data_length, max_avail_in));
strm_.next_in =
const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
ret = inflate(&strm_, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR: inflateEnd(&strm_); return false;
data_length -= strm_.avail_in;
data += strm_.avail_in;
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
while (strm_.avail_in > 0) {
strm_.avail_out = static_cast<uInt>(buff.size());
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
ret = inflate(&strm_, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR: inflateEnd(&strm_); return false;
}
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
}
if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
}
if (ret != Z_OK && ret != Z_STREAM_END) return false;
return ret == Z_OK || ret == Z_STREAM_END;
} while (data_length > 0);
return true;
}
private:
@@ -4225,128 +4270,79 @@ inline Server::Server()
inline Server::~Server() {}
inline Server &Server::Get(const char *pattern, Handler handler) {
return Get(pattern, strlen(pattern), handler);
}
inline Server &Server::Get(const char *pattern, size_t pattern_len,
Handler handler) {
inline Server &Server::Get(const std::string &pattern, Handler handler) {
get_handlers_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern, Handler handler) {
return Post(pattern, strlen(pattern), handler);
}
inline Server &Server::Post(const char *pattern, size_t pattern_len,
Handler handler) {
inline Server &Server::Post(const std::string &pattern, Handler handler) {
post_handlers_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern,
HandlerWithContentReader handler) {
return Post(pattern, strlen(pattern), handler);
}
inline Server &Server::Post(const char *pattern, size_t pattern_len,
inline Server &Server::Post(const std::string &pattern,
HandlerWithContentReader handler) {
post_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Put(const char *pattern, Handler handler) {
return Put(pattern, strlen(pattern), handler);
}
inline Server &Server::Put(const char *pattern, size_t pattern_len,
Handler handler) {
inline Server &Server::Put(const std::string &pattern, Handler handler) {
put_handlers_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Put(const char *pattern,
HandlerWithContentReader handler) {
return Put(pattern, strlen(pattern), handler);
}
inline Server &Server::Put(const char *pattern, size_t pattern_len,
inline Server &Server::Put(const std::string &pattern,
HandlerWithContentReader handler) {
put_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Patch(const char *pattern, Handler handler) {
return Patch(pattern, strlen(pattern), handler);
}
inline Server &Server::Patch(const char *pattern, size_t pattern_len,
Handler handler) {
inline Server &Server::Patch(const std::string &pattern, Handler handler) {
patch_handlers_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Patch(const char *pattern,
HandlerWithContentReader handler) {
return Patch(pattern, strlen(pattern), handler);
}
inline Server &Server::Patch(const char *pattern, size_t pattern_len,
inline Server &Server::Patch(const std::string &pattern,
HandlerWithContentReader handler) {
patch_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Delete(const char *pattern, Handler handler) {
return Delete(pattern, strlen(pattern), handler);
}
inline Server &Server::Delete(const char *pattern, size_t pattern_len,
Handler handler) {
inline Server &Server::Delete(const std::string &pattern, Handler handler) {
delete_handlers_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Delete(const char *pattern,
HandlerWithContentReader handler) {
return Delete(pattern, strlen(pattern), handler);
}
inline Server &Server::Delete(const char *pattern, size_t pattern_len,
inline Server &Server::Delete(const std::string &pattern,
HandlerWithContentReader handler) {
delete_handlers_for_content_reader_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Options(const char *pattern, Handler handler) {
return Options(pattern, strlen(pattern), handler);
}
inline Server &Server::Options(const char *pattern, size_t pattern_len,
Handler handler) {
inline Server &Server::Options(const std::string &pattern, Handler handler) {
options_handlers_.push_back(
std::make_pair(std::regex(pattern, pattern_len), std::move(handler)));
std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
inline bool Server::set_base_dir(const std::string &dir,
const std::string &mount_point) {
return set_mount_point(mount_point, dir);
}
inline bool Server::set_mount_point(const char *mount_point, const char *dir,
Headers headers) {
inline bool Server::set_mount_point(const std::string &mount_point,
const std::string &dir, Headers headers) {
if (detail::is_dir(dir)) {
std::string mnt = mount_point ? mount_point : "/";
std::string mnt = !mount_point.empty() ? mount_point : "/";
if (!mnt.empty() && mnt[0] == '/') {
base_dirs_.push_back({mnt, dir, std::move(headers)});
return true;
@@ -4355,7 +4351,7 @@ inline bool Server::set_mount_point(const char *mount_point, const char *dir,
return false;
}
inline bool Server::remove_mount_point(const char *mount_point) {
inline bool Server::remove_mount_point(const std::string &mount_point) {
for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
if (it->mount_point == mount_point) {
base_dirs_.erase(it);
@@ -4520,25 +4516,58 @@ inline void Server::stop() {
}
inline bool Server::parse_request_line(const char *s, Request &req) {
const static std::regex re(
"(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
"(([^? ]+)(?:\\?([^ ]*?))?) (HTTP/1\\.[01])\r\n");
auto len = strlen(s);
if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
len -= 2;
std::cmatch m;
if (std::regex_match(s, m, re)) {
req.version = std::string(m[5]);
req.method = std::string(m[1]);
req.target = std::string(m[2]);
req.path = detail::decode_url(m[3], false);
{
size_t count = 0;
// Parse query text
auto len = std::distance(m[4].first, m[4].second);
if (len > 0) { detail::parse_query_text(m[4], req.params); }
detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
switch (count) {
case 0: req.method = std::string(b, e); break;
case 1: req.target = std::string(b, e); break;
case 2: req.version = std::string(b, e); break;
default: break;
}
count++;
});
return true;
if (count != 3) { return false; }
}
return false;
static const std::set<std::string> methods{
"GET", "HEAD", "POST", "PUT", "DELETE",
"CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
if (methods.find(req.method) == methods.end()) { return false; }
if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; }
{
size_t count = 0;
detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
[&](const char *b, const char *e) {
switch (count) {
case 0:
req.path = detail::decode_url(std::string(b, e), false);
break;
case 1: {
if (e - b > 0) {
detail::parse_query_text(std::string(b, e), req.params);
}
break;
}
default: break;
}
count++;
});
if (count > 2) { return false; }
}
return true;
}
inline bool Server::write_response(Stream &strm, bool close_connection,
@@ -4615,8 +4644,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
if (!res.body.empty()) {
if (!strm.write(res.body)) { ret = false; }
} else if (res.content_provider_) {
if (write_content_with_provider(strm, req, res, boundary,
content_type)) {
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
res.content_provider_success_ = true;
} else {
res.content_provider_success_ = false;
@@ -5269,9 +5297,8 @@ inline ClientImpl::ClientImpl(const std::string &host, int port)
inline ClientImpl::ClientImpl(const std::string &host, int port,
const std::string &client_cert_path,
const std::string &client_key_path)
// : (Error::Success), host_(host), port_(port),
: host_(host), port_(port),
host_and_port_(host_ + ":" + std::to_string(port_)),
host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)),
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
inline ClientImpl::~ClientImpl() {
@@ -5315,6 +5342,11 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
ca_cert_file_path_ = rhs.ca_cert_file_path_;
ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
ca_cert_store_ = rhs.ca_cert_store_;
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
server_certificate_verification_ = rhs.server_certificate_verification_;
#endif
@@ -5551,8 +5583,8 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
if (detail::parse_www_authenticate(res, auth, is_proxy)) {
Request new_req = req;
new_req.authorization_count_ += 1;
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
new_req.headers.erase(key);
new_req.headers.erase(is_proxy ? "Proxy-Authorization"
: "Authorization");
new_req.headers.insert(detail::make_digest_authentication_header(
req, auth, new_req.authorization_count_, detail::random_string(10),
username, password, is_proxy));
@@ -5579,7 +5611,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
if (location.empty()) { return false; }
const static std::regex re(
R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
std::smatch m;
if (!std::regex_match(location, m, re)) { return false; }
@@ -5588,8 +5620,9 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
auto next_scheme = m[1].str();
auto next_host = m[2].str();
auto port_str = m[3].str();
auto next_path = m[4].str();
if (next_host.empty()) { next_host = m[3].str(); }
auto port_str = m[4].str();
auto next_path = m[5].str();
auto next_port = port_;
if (!port_str.empty()) {
@@ -5609,6 +5642,9 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(next_host.c_str(), next_port);
cli.copy_settings(*this);
if (ca_cert_store_) {
cli.set_ca_cert_store(ca_cert_store_);
}
return detail::redirect(cli, req, res, next_path, location, error);
#else
return false;
@@ -5649,7 +5685,11 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
inline bool ClientImpl::write_request(Stream &strm, Request &req,
bool close_connection, Error &error) {
// Prepare additional headers
if (close_connection) { req.headers.emplace("Connection", "close"); }
if (close_connection) {
if (!req.has_header("Connection")) {
req.headers.emplace("Connection", "close");
}
}
if (!req.has_header("Host")) {
if (is_ssl()) {
@@ -5670,14 +5710,16 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
if (!req.has_header("User-Agent")) {
req.headers.emplace("User-Agent", "cpp-httplib/0.7");
req.headers.emplace("User-Agent", "cpp-httplib/0.9");
}
if (req.body.empty()) {
if (req.content_provider_) {
if (!req.is_chunked_content_provider_) {
auto length = std::to_string(req.content_length_);
req.headers.emplace("Content-Length", length);
if (!req.has_header("Content-Length")) {
auto length = std::to_string(req.content_length_);
req.headers.emplace("Content-Length", length);
}
}
} else {
if (req.method == "POST" || req.method == "PUT" ||
@@ -5697,24 +5739,32 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
}
if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
req.headers.insert(make_basic_authentication_header(
basic_auth_username_, basic_auth_password_, false));
if (!req.has_header("Authorization")) {
req.headers.insert(make_basic_authentication_header(
basic_auth_username_, basic_auth_password_, false));
}
}
if (!proxy_basic_auth_username_.empty() &&
!proxy_basic_auth_password_.empty()) {
req.headers.insert(make_basic_authentication_header(
proxy_basic_auth_username_, proxy_basic_auth_password_, true));
if (!req.has_header("Proxy-Authorization")) {
req.headers.insert(make_basic_authentication_header(
proxy_basic_auth_username_, proxy_basic_auth_password_, true));
}
}
if (!bearer_token_auth_token_.empty()) {
req.headers.insert(make_bearer_token_authentication_header(
bearer_token_auth_token_, false));
if (!req.has_header("Authorization")) {
req.headers.insert(make_bearer_token_authentication_header(
bearer_token_auth_token_, false));
}
}
if (!proxy_bearer_token_auth_token_.empty()) {
req.headers.insert(make_bearer_token_authentication_header(
proxy_bearer_token_auth_token_, true));
if (!req.has_header("Proxy-Authorization")) {
req.headers.insert(make_bearer_token_authentication_header(
proxy_bearer_token_auth_token_, true));
}
}
// Request line and headers
@@ -5737,11 +5787,9 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
// Body
if (req.body.empty()) {
return write_content_with_provider(strm, req, error);
} else {
return detail::write_data(strm, req.body.data(), req.body.size());
}
return true;
return detail::write_data(strm, req.body.data(), req.body.size());
}
inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
@@ -5853,6 +5901,12 @@ inline Result ClientImpl::send_with_content_provider(
return Result{std::move(res), error, std::move(req.headers)};
}
inline std::string
ClientImpl::adjust_host_string(const std::string &host) const {
if (host.find(':') != std::string::npos) { return "[" + host + "]"; }
return host;
}
inline bool ClientImpl::process_request(Stream &strm, Request &req,
Response &res, bool close_connection,
Error &error) {
@@ -6498,6 +6552,20 @@ inline void ClientImpl::set_proxy_digest_auth(const char *username,
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline void ClientImpl::set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path) {
if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; }
if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
}
inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
if (ca_cert_store && ca_cert_store != ca_cert_store_) {
ca_cert_store_ = ca_cert_store;
}
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
server_certificate_verification_ = enabled;
@@ -6687,15 +6755,18 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret < 0) {
auto err = SSL_get_error(ssl_, ret);
int n = 1000;
#ifdef _WIN32
while (err == SSL_ERROR_WANT_READ ||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT) {
while (--n >= 0 &&
(err == SSL_ERROR_WANT_READ ||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT)) {
#else
while (err == SSL_ERROR_WANT_READ) {
while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
#endif
if (SSL_pending(ssl_) > 0) {
return SSL_read(ssl_, ptr, static_cast<int>(size));
} else if (is_readable()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
if (ret >= 0) { return ret; }
err = SSL_get_error(ssl_, ret);
@@ -6885,12 +6956,6 @@ inline SSLClient::~SSLClient() {
inline bool SSLClient::is_valid() const { return ctx_; }
inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path) {
if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; }
if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
}
inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
if (ca_cert_store) {
if (ctx_) {
@@ -7213,15 +7278,16 @@ inline bool SSLClient::check_host_name(const char *pattern,
#endif
// Universal client implementation
inline Client::Client(const char *scheme_host_port)
inline Client::Client(const std::string &scheme_host_port)
: Client(scheme_host_port, std::string(), std::string()) {}
inline Client::Client(const char *scheme_host_port,
inline Client::Client(const std::string &scheme_host_port,
const std::string &client_cert_path,
const std::string &client_key_path) {
const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)");
const static std::regex re(
R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
std::cmatch m;
std::smatch m;
if (std::regex_match(scheme_host_port, m, re)) {
auto scheme = m[1].str();
@@ -7238,8 +7304,9 @@ inline Client::Client(const char *scheme_host_port,
auto is_ssl = scheme == "https";
auto host = m[2].str();
if (host.empty()) { host = m[3].str(); }
auto port_str = m[3].str();
auto port_str = m[4].str();
auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
if (is_ssl) {
@@ -7631,15 +7698,14 @@ inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline void Client::set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path) {
if (is_ssl_) {
static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path,
ca_cert_dir_path);
}
cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
}
inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
if (is_ssl_) {
static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
} else {
cli_->set_ca_cert_store(ca_cert_store);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
#CXX = clang++
CXXFLAGS = -g -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
CXXFLAGS = -g -std=c++11 -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
+5790 -2421
View File
File diff suppressed because it is too large Load Diff
+6845 -14039
View File
File diff suppressed because it is too large Load Diff
+19 -4
View File
@@ -27,13 +27,28 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <iostream>
#include <cstdio>
#include "gtest/gtest.h"
GTEST_API_ int main(int argc, char **argv) {
std::cout << "Running main() from gtest_main.cc\n";
#if GTEST_OS_ESP8266 || GTEST_OS_ESP32
#if GTEST_OS_ESP8266
extern "C" {
#endif
void setup() {
testing::InitGoogleTest();
}
void loop() { RUN_ALL_TESTS(); }
#if GTEST_OS_ESP8266
}
#endif
#else
GTEST_API_ int main(int argc, char **argv) {
printf("Running main() from %s\n", __FILE__);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#endif
+242 -64
View File
@@ -172,7 +172,7 @@ TEST(GetHeaderValueTest, SetContent) {
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(1U, res.get_header_value_count("Content-Type"));
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
}
@@ -542,7 +542,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
ASSERT_TRUE(!res);
stringstream s;
s << "error code: " << res.error();
EXPECT_EQ("error code: 2", s.str());
EXPECT_EQ("error code: Connection (2)", s.str());
}
TEST(ConnectionErrorTest, InvalidPort) {
@@ -839,6 +839,18 @@ TEST(HttpsToHttpRedirectTest3, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(UrlWithSpace, Redirect) {
SSLClient cli("edge.forgecdn.net");
cli.set_follow_location(true);
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527U, res->get_header_value<uint64_t>("Content-Length"));
}
#endif
TEST(RedirectToDifferentPort, Redirect) {
Server svr8080;
Server svr8081;
@@ -878,16 +890,6 @@ TEST(RedirectToDifferentPort, Redirect) {
ASSERT_FALSE(svr8081.is_running());
}
TEST(UrlWithSpace, Redirect) {
SSLClient cli("edge.forgecdn.net");
cli.set_follow_location(true);
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
}
TEST(RedirectFromPageWithContent, Redirect) {
Server svr;
@@ -943,7 +945,65 @@ TEST(RedirectFromPageWithContent, Redirect) {
ASSERT_FALSE(svr.is_running());
}
#endif
TEST(RedirectFromPageWithContentIP6, Redirect) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
res.set_content("___", "text/plain");
// res.set_redirect("/2");
res.set_redirect("http://[::1]:1234/2");
});
svr.Get("/2", [&](const Request &req, Response &res) {
auto host_header = req.headers.find("Host");
ASSERT_TRUE(host_header != req.headers.end());
EXPECT_EQ("[::1]:1234", host_header->second);
res.set_content("Hello World!", "text/plain");
});
auto th = std::thread([&]() { svr.listen("::1", 1234); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("http://[::1]:1234");
cli.set_follow_location(true);
std::string body;
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!", body);
}
{
Client cli("http://[::1]:1234");
std::string body;
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
EXPECT_EQ(302, res->status);
EXPECT_EQ("___", body);
}
svr.stop();
th.join();
ASSERT_FALSE(svr.is_running());
}
TEST(PathUrlEncodeTest, PathUrlEncode) {
Server svr;
@@ -1166,9 +1226,9 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Routing Handler", res->body);
EXPECT_EQ(1, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(1U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ("on", res->get_header_value("PRE_ROUTING"));
EXPECT_EQ(1, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(1U, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ("on", res->get_header_value("POST_ROUTING"));
}
@@ -1179,8 +1239,8 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!\n", res->body);
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING"));
}
{
@@ -1190,8 +1250,8 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(404, res->status);
EXPECT_EQ("Error", res->body);
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING"));
}
svr.stop();
@@ -1250,31 +1310,31 @@ protected:
.Get("/http_response_splitting",
[&](const Request & /*req*/, Response &res) {
res.set_header("a", "1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\rSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\r\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\rb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_redirect("1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("Location"));
})
.Get("/slow",
@@ -1592,7 +1652,7 @@ protected:
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
EXPECT_EQ(data_length, 7);
EXPECT_EQ(7U, data_length);
body.append(data, data_length);
return true;
});
@@ -1640,7 +1700,7 @@ protected:
})
.Post("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1648,7 +1708,7 @@ protected:
})
.Put("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1656,7 +1716,7 @@ protected:
})
.Patch("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1664,7 +1724,7 @@ protected:
})
.Delete("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1744,7 +1804,7 @@ TEST_F(ServerTest, GetMethod200) {
EXPECT_EQ(200, res->status);
EXPECT_EQ("OK", res->reason);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
@@ -1754,7 +1814,7 @@ TEST_F(ServerTest, GetMethod200withPercentEncoding) {
EXPECT_EQ("HTTP/1.1", res->version);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
@@ -2032,25 +2092,25 @@ TEST_F(ServerTest, Binary) {
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Put("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Patch("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Delete("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
}
TEST_F(ServerTest, BinaryString) {
@@ -2059,22 +2119,22 @@ TEST_F(ServerTest, BinaryString) {
auto res = cli_.Post("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Put("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Patch("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Delete("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
}
TEST_F(ServerTest, EmptyRequest) {
@@ -2390,7 +2450,7 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269, res->body.size());
EXPECT_EQ(269U, res->body.size());
}
TEST_F(ServerTest, GetStreamedEndless) {
@@ -2477,7 +2537,7 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269, res->body.size());
EXPECT_EQ(269U, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
@@ -2717,10 +2777,12 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
TEST_F(ServerTest, PutLargeFileWithGzip2) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
Client cli("https://localhost:1234");
std::string s = std::string("https://") + HOST + ":" + std::to_string(PORT);
Client cli(s.c_str());
cli.enable_server_certificate_verification(false);
#else
Client cli("http://localhost:1234");
std::string s = std::string("http://") + HOST + ":" + std::to_string(PORT);
Client cli(s.c_str());
#endif
cli.set_compress(true);
@@ -2806,6 +2868,54 @@ TEST(GzipDecompressor, ChunkedDecompression) {
}
ASSERT_EQ(data, decompressed_data);
}
#ifdef _WIN32
TEST(GzipDecompressor, LargeRandomData) {
// prepare large random data that is difficult to be compressed and is
// expected to have large size even when compressed
std::random_device seed_gen;
std::mt19937 random(seed_gen());
constexpr auto large_size_byte = 4294967296UL; // 4GiB
constexpr auto data_size = large_size_byte + 134217728UL; // + 128MiB
std::vector<std::uint32_t> data(data_size / sizeof(std::uint32_t));
std::generate(data.begin(), data.end(), [&]() { return random(); });
// compress data over 4GiB
std::string compressed_data;
compressed_data.reserve(large_size_byte + 536870912UL); // + 512MiB reserved
httplib::detail::gzip_compressor compressor;
auto result = compressor.compress(reinterpret_cast<const char *>(data.data()),
data.size() * sizeof(std::uint32_t), true,
[&](const char *data, size_t size) {
compressed_data.insert(
compressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
// FIXME: compressed data size is expected to be greater than 4GiB,
// but there is no guarantee
// ASSERT_TRUE(compressed_data.size() >= large_size_byte);
// decompress data over 4GiB
std::string decompressed_data;
decompressed_data.reserve(data_size);
httplib::detail::gzip_decompressor decompressor;
result = decompressor.decompress(
compressed_data.data(), compressed_data.size(),
[&](const char *data, size_t size) {
decompressed_data.insert(decompressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
// compare
ASSERT_EQ(data_size, decompressed_data.size());
ASSERT_TRUE(std::memcmp(data.data(), decompressed_data.data(), data_size) ==
0);
}
#endif
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
@@ -3066,7 +3176,7 @@ TEST_F(ServerTest, GzipWithContentReceiver) {
std::string body;
auto res = cli_.Get("/compress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3092,14 +3202,14 @@ TEST_F(ServerTest, GzipWithoutDecompressing) {
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("33", res->get_header_value("Content-Length"));
EXPECT_EQ(33, res->body.size());
EXPECT_EQ(33U, res->body.size());
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
std::string body;
auto res = cli_.Get("/compress", [&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3135,7 +3245,7 @@ TEST_F(ServerTest, NoGzipWithContentReceiver) {
std::string body;
auto res = cli_.Get("/nocompress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3231,14 +3341,14 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
// Only space and horizontal tab are whitespace. Make sure other whitespace-
// like characters are not treated the same - use vertical tab and escape.
const std::string req = "GET /validate-ws-in-headers HTTP/1.1\r\n"
"foo: \t \v bar \e\t \r\n"
"foo: \t \v bar \x1B\t \r\n"
"Connection: close\r\n"
"\r\n";
ASSERT_TRUE(send_request(5, req));
svr.stop();
t.join();
EXPECT_EQ(header_value, "\v bar \e");
EXPECT_EQ(header_value, "\v bar \x1B");
}
// Sends a raw request and verifies that there isn't a crash or exception.
@@ -3390,7 +3500,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
res.set_chunked_content_provider("text/event-stream", [](size_t offset,
DataSink &sink) {
char buffer[27];
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
auto size = static_cast<size_t>(sprintf(buffer, "data:%zd\n\n", offset));
auto ret = sink.write(buffer, size);
EXPECT_TRUE(ret);
std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -3647,26 +3757,60 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
TEST(GetWithParametersTest, GetWithParameters) {
Server svr;
svr.Get("/", [&](const Request &req, Response &res) {
auto text = req.get_param_value("hello");
res.set_content(text, "text/plain");
svr.Get("/", [&](const Request &req, Response &) {
EXPECT_EQ("world", req.get_param_value("hello"));
EXPECT_EQ("world2", req.get_param_value("hello2"));
EXPECT_EQ("world3", req.get_param_value("hello3"));
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
svr.Get("/params", [&](const Request &req, Response &) {
EXPECT_EQ("world", req.get_param_value("hello"));
EXPECT_EQ("world2", req.get_param_value("hello2"));
EXPECT_EQ("world3", req.get_param_value("hello3"));
});
svr.Get(R"(/resources/([a-z0-9\\-]+))", [&](const Request& req, Response&) {
EXPECT_EQ("resource-id", req.matches[1]);
EXPECT_EQ("foo", req.get_param_value("param1"));
EXPECT_EQ("bar", req.get_param_value("param2"));
});
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
{
Client cli(HOST, PORT);
Params params;
params.emplace("hello", "world");
auto res = cli.Get("/", params, Headers{});
Params params;
params.emplace("hello", "world");
params.emplace("hello2", "world2");
params.emplace("hello3", "world3");
auto res = cli.Get("/", params, Headers{});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("world", res->body);
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
{
Client cli(HOST, PORT);
auto res = cli.Get("/params?hello=world&hello2=world2&hello3=world3");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
{
Client cli(HOST, PORT);
auto res = cli.Get("/resources/resource-id?param1=foo&param2=bar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
svr.stop();
listen_thread.join();
@@ -4319,7 +4463,7 @@ TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(287630, res->body.size());
EXPECT_EQ(287630U, res->body.size());
EXPECT_EQ("application/javascript; charset=utf-8",
res->get_header_value("Content-Type"));
}
@@ -4360,4 +4504,38 @@ TEST(HttpsToHttpRedirectTest3, SimpleInterface) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST(HttpToHttpsRedirectTest, CertFile) {
Server svr;
ASSERT_TRUE(svr.is_valid());
svr.Get("/index", [&](const Request &, Response &res) {
res.set_redirect("https://127.0.0.1:1235/index");
svr.stop();
});
SSLServer ssl_svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(ssl_svr.is_valid());
ssl_svr.Get("/index", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
ssl_svr.stop();
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
thread t2 = thread([&]() { ASSERT_TRUE(ssl_svr.listen("127.0.0.1", 1235)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Client cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
cli.enable_server_certificate_verification(true);
cli.set_follow_location(true);
cli.set_connection_timeout(30);
auto res = cli.Get("/index");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
t.join();
t2.join();
}
#endif