mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d60dc8839 | |||
| b713a3a651 | |||
| 7e8db1dc68 | |||
| 316add860b | |||
| 79ce6f1745 | |||
| 09fdf4eacd | |||
| 143b2dd15a | |||
| e2c4e9d95c | |||
| d87082f04b |
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: install brotli library on ubuntu
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install -y libbrotli-dev
|
||||
run: sudo apt update && sudo apt-get install -y libbrotli-dev
|
||||
- name: install brotli library on macOS
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: brew install brotli
|
||||
|
||||
@@ -579,7 +579,8 @@ public:
|
||||
Server &Options(const char *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_mount_point(const char *mount_point, const char *dir,
|
||||
Headers headers = Headers());
|
||||
bool remove_mount_point(const char *mount_point);
|
||||
void set_file_extension_and_mimetype_mapping(const char *ext,
|
||||
const char *mime);
|
||||
@@ -663,9 +664,15 @@ private:
|
||||
ContentReceiver multipart_receiver);
|
||||
|
||||
virtual bool process_and_close_socket(socket_t sock);
|
||||
|
||||
struct MountPointEntry {
|
||||
std::string mount_point;
|
||||
std::string base_dir;
|
||||
Headers headers;
|
||||
};
|
||||
std::vector<MountPointEntry> base_dirs_;
|
||||
|
||||
std::atomic<bool> is_running_;
|
||||
std::vector<std::pair<std::string, std::string>> base_dirs_;
|
||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||
Handler file_request_handler_;
|
||||
Handlers get_handlers_;
|
||||
@@ -839,6 +846,10 @@ public:
|
||||
void set_proxy_digest_auth(const char *username, const char *password);
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
|
||||
protected:
|
||||
@@ -859,6 +870,8 @@ protected:
|
||||
|
||||
Error get_last_error() const;
|
||||
|
||||
void copy_settings(const ClientImpl &rhs);
|
||||
|
||||
// Error state
|
||||
mutable Error error_ = Error::Success;
|
||||
|
||||
@@ -916,41 +929,11 @@ protected:
|
||||
std::string proxy_digest_auth_password_;
|
||||
#endif
|
||||
|
||||
Logger logger_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool server_certificate_verification_ = true;
|
||||
#endif
|
||||
|
||||
void copy_settings(const ClientImpl &rhs) {
|
||||
client_cert_path_ = rhs.client_cert_path_;
|
||||
client_key_path_ = rhs.client_key_path_;
|
||||
connection_timeout_sec_ = rhs.connection_timeout_sec_;
|
||||
read_timeout_sec_ = rhs.read_timeout_sec_;
|
||||
read_timeout_usec_ = rhs.read_timeout_usec_;
|
||||
write_timeout_sec_ = rhs.write_timeout_sec_;
|
||||
write_timeout_usec_ = rhs.write_timeout_usec_;
|
||||
basic_auth_username_ = rhs.basic_auth_username_;
|
||||
basic_auth_password_ = rhs.basic_auth_password_;
|
||||
bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
digest_auth_username_ = rhs.digest_auth_username_;
|
||||
digest_auth_password_ = rhs.digest_auth_password_;
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
decompress_ = rhs.decompress_;
|
||||
interface_ = rhs.interface_;
|
||||
proxy_host_ = rhs.proxy_host_;
|
||||
proxy_port_ = rhs.proxy_port_;
|
||||
proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
|
||||
proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
|
||||
proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
|
||||
proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
|
||||
#endif
|
||||
logger_ = rhs.logger_;
|
||||
}
|
||||
Logger logger_;
|
||||
|
||||
private:
|
||||
socket_t create_client_socket() const;
|
||||
@@ -1096,16 +1079,18 @@ public:
|
||||
void set_proxy_digest_auth(const char *username, const char *password);
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
|
||||
// SSL
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client &set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr);
|
||||
void set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr);
|
||||
|
||||
Client &set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
Client &enable_server_certificate_verification(bool enabled);
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
@@ -1163,8 +1148,6 @@ public:
|
||||
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
SSL_CTX *ssl_context() const;
|
||||
@@ -1195,8 +1178,6 @@ private:
|
||||
|
||||
std::string ca_cert_file_path_;
|
||||
std::string ca_cert_dir_path_;
|
||||
X509_STORE *ca_cert_store_ = nullptr;
|
||||
bool server_certificate_verification_ = true;
|
||||
long verify_result_ = 0;
|
||||
|
||||
friend class ClientImpl;
|
||||
@@ -1255,7 +1236,7 @@ inline std::string from_i_to_hex(size_t n) {
|
||||
inline bool start_with(const std::string &a, const std::string &b) {
|
||||
if (a.size() < b.size()) { return false; }
|
||||
for (size_t i = 0; i < b.size(); i++) {
|
||||
if (std::tolower(a[i]) != std::tolower(b[i])) { return false; }
|
||||
if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3841,11 +3822,12 @@ inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
|
||||
return set_mount_point(mount_point, dir);
|
||||
}
|
||||
|
||||
inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
|
||||
inline bool Server::set_mount_point(const char *mount_point, const char *dir,
|
||||
Headers headers) {
|
||||
if (detail::is_dir(dir)) {
|
||||
std::string mnt = mount_point ? mount_point : "/";
|
||||
if (!mnt.empty() && mnt[0] == '/') {
|
||||
base_dirs_.emplace_back(mnt, dir);
|
||||
base_dirs_.push_back({mnt, dir, std::move(headers)});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3854,7 +3836,7 @@ inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
|
||||
|
||||
inline bool Server::remove_mount_point(const char *mount_point) {
|
||||
for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
|
||||
if (it->first == mount_point) {
|
||||
if (it->mount_point == mount_point) {
|
||||
base_dirs_.erase(it);
|
||||
return true;
|
||||
}
|
||||
@@ -4276,15 +4258,12 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
|
||||
inline bool Server::handle_file_request(Request &req, Response &res,
|
||||
bool head) {
|
||||
for (const auto &kv : base_dirs_) {
|
||||
const auto &mount_point = kv.first;
|
||||
const auto &base_dir = kv.second;
|
||||
|
||||
for (const auto &entry : base_dirs_) {
|
||||
// Prefix match
|
||||
if (!req.path.compare(0, mount_point.size(), mount_point)) {
|
||||
std::string sub_path = "/" + req.path.substr(mount_point.size());
|
||||
if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
|
||||
std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
|
||||
if (detail::is_valid_path(sub_path)) {
|
||||
auto path = base_dir + sub_path;
|
||||
auto path = entry.base_dir + sub_path;
|
||||
if (path.back() == '/') { path += "index.html"; }
|
||||
|
||||
if (detail::is_file(path)) {
|
||||
@@ -4292,6 +4271,9 @@ 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) {
|
||||
res.set_header(kv.first.c_str(), kv.second);
|
||||
}
|
||||
res.status = 200;
|
||||
if (!head && file_request_handler_) {
|
||||
file_request_handler_(req, res);
|
||||
@@ -4616,6 +4598,43 @@ inline bool ClientImpl::is_valid() const { return true; }
|
||||
|
||||
inline Error ClientImpl::get_last_error() const { return error_; }
|
||||
|
||||
inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
client_cert_path_ = rhs.client_cert_path_;
|
||||
client_key_path_ = rhs.client_key_path_;
|
||||
connection_timeout_sec_ = rhs.connection_timeout_sec_;
|
||||
read_timeout_sec_ = rhs.read_timeout_sec_;
|
||||
read_timeout_usec_ = rhs.read_timeout_usec_;
|
||||
write_timeout_sec_ = rhs.write_timeout_sec_;
|
||||
write_timeout_usec_ = rhs.write_timeout_usec_;
|
||||
basic_auth_username_ = rhs.basic_auth_username_;
|
||||
basic_auth_password_ = rhs.basic_auth_password_;
|
||||
bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
digest_auth_username_ = rhs.digest_auth_username_;
|
||||
digest_auth_password_ = rhs.digest_auth_password_;
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
decompress_ = rhs.decompress_;
|
||||
interface_ = rhs.interface_;
|
||||
proxy_host_ = rhs.proxy_host_;
|
||||
proxy_port_ = rhs.proxy_port_;
|
||||
proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
|
||||
proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
|
||||
proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
|
||||
proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
|
||||
#endif
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
server_certificate_verification_ = rhs.server_certificate_verification_;
|
||||
#endif
|
||||
logger_ = rhs.logger_;
|
||||
}
|
||||
|
||||
inline socket_t ClientImpl::create_client_socket() const {
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
return detail::create_client_socket(
|
||||
@@ -4870,7 +4889,10 @@ inline bool ClientImpl::write_request(Stream &strm, const Request &req,
|
||||
auto length = std::to_string(req.content_length);
|
||||
headers.emplace("Content-Length", length);
|
||||
} else {
|
||||
headers.emplace("Content-Length", "0");
|
||||
if (req.method == "POST" || req.method == "PUT" ||
|
||||
req.method == "PATCH") {
|
||||
headers.emplace("Content-Length", "0");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!req.has_header("Content-Type")) {
|
||||
@@ -5489,6 +5511,12 @@ inline void ClientImpl::set_proxy_digest_auth(const char *username,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void ClientImpl::set_logger(Logger logger) {
|
||||
logger_ = std::move(logger);
|
||||
}
|
||||
@@ -5826,11 +5854,16 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
|
||||
}
|
||||
|
||||
inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
if (ca_cert_store) { ca_cert_store_ = ca_cert_store; }
|
||||
}
|
||||
|
||||
inline void SSLClient::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
if (ca_cert_store) {
|
||||
if (ctx_) {
|
||||
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
|
||||
// Free memory allocated for old cert and use new store `ca_cert_store`
|
||||
SSL_CTX_set_cert_store(ctx_, ca_cert_store);
|
||||
}
|
||||
} else {
|
||||
X509_STORE_free(ca_cert_store);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline long SSLClient::get_openssl_verify_result() const {
|
||||
@@ -5908,10 +5941,6 @@ inline bool SSLClient::load_certs() {
|
||||
ca_cert_dir_path_.c_str())) {
|
||||
ret = false;
|
||||
}
|
||||
} else if (ca_cert_store_ != nullptr) {
|
||||
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) {
|
||||
SSL_CTX_set_cert_store(ctx_, ca_cert_store_);
|
||||
}
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
|
||||
@@ -6413,31 +6442,27 @@ inline void Client::set_proxy_digest_auth(const char *username,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline void Client::enable_server_certificate_verification(bool enabled) {
|
||||
cli_->enable_server_certificate_verification(enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline Client &Client::set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path) {
|
||||
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);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Client &Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
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);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Client &Client::enable_server_certificate_verification(bool enabled) {
|
||||
if (is_ssl_) {
|
||||
static_cast<SSLClient &>(*cli_).enable_server_certificate_verification(
|
||||
enabled);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline long Client::get_openssl_verify_result() const {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
|
||||
CXXFLAGS = -ggdb -O0 -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
|
||||
|
||||
+63
-1
@@ -2070,9 +2070,14 @@ TEST_F(ServerTest, SlowPost) {
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, SlowPostFail) {
|
||||
char buffer[64 * 1024];
|
||||
memset(buffer, 0x42, sizeof(buffer));
|
||||
|
||||
cli_.set_write_timeout(0, 0);
|
||||
res = cli_.Post(
|
||||
auto res = cli_.Post(
|
||||
"/slowpost", 64 * 1024 * 1024,
|
||||
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(buffer, sizeof(buffer));
|
||||
@@ -2986,6 +2991,50 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
|
||||
svr.Get("/a", [&](const Request & /*req*/, Response &res) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
res.set_content("a", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/b", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("b", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() {
|
||||
svr.listen("localhost", PORT);
|
||||
});
|
||||
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));
|
||||
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1);
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
EXPECT_EQ(Error::Read, resa.error());
|
||||
|
||||
auto resb = cli.Get("/b");
|
||||
ASSERT_TRUE(resb);
|
||||
EXPECT_EQ(200, resb->status);
|
||||
EXPECT_EQ("b", resb->body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
@@ -3120,6 +3169,19 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(SSLClientTest, UpdateCAStore) {
|
||||
httplib::SSLClient httplib_client("www.google.com");
|
||||
auto ca_store_1 = X509_STORE_new();
|
||||
X509_STORE_load_locations(ca_store_1, "/etc/ssl/certs/ca-certificates.crt",
|
||||
nullptr);
|
||||
httplib_client.set_ca_cert_store(ca_store_1);
|
||||
|
||||
auto ca_store_2 = X509_STORE_new();
|
||||
X509_STORE_load_locations(ca_store_2, "/etc/ssl/certs/ca-certificates.crt",
|
||||
nullptr);
|
||||
httplib_client.set_ca_cert_store(ca_store_2);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, ServerNameIndication) {
|
||||
SSLClient cli("httpbin.org", 443);
|
||||
auto res = cli.Get("/get");
|
||||
|
||||
Reference in New Issue
Block a user