Compare commits

...

12 Commits

Author SHA1 Message Date
yhirose e00fd06355 Release v0.16.1 2024-08-06 17:04:22 -04:00
yhirose 521529d24d Fix #1481 (with content provider) (#1527)
* Fix #1481 (with content provider)

* Improve shutdown performance

* Make shutdown action more stable

* Move some tests up

* Simplified

* Simplified
2024-08-06 13:43:00 -04:00
yhirose ed0719f2bc Code format 2024-08-06 07:20:05 -04:00
hanslivingstone 6a848b1a16 Require a minimum of TLS 1.2 (#1889)
TLS 1. is deprecated: https://www.ietf.org/rfc/rfc8996.html
2024-07-30 17:18:33 -04:00
mol123 c8bcaf8a91 Fix build when targeting Windows 7 as platform. (#1869)
* Fix build when targeting Windows 7 as platform.

This change makes more of the code introduced in
https://github.com/yhirose/cpp-httplib/pull/1775
conditional on feature macros.

`CreateFile2`, `CreateFileMappingFromApp` and `MapViewOfFileFromApp` are
available only starting from Windows 8.

 * https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
 * https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingfromapp
 * https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffilefromapp

* Update feature macros used and use `GetFileSizeEx` conditionally.
2024-07-02 10:58:40 -04:00
Hlado 8cd0ed0509 Added move assignment operator to Client class. (#1873) 2024-06-30 11:17:00 -04:00
Hlado 177d8420a1 Added .gitattributes file to prevent git from changing line endings (#1872)
of text files using as data for tests.
2024-06-30 11:16:48 -04:00
Daniel Ludwig 388a8c007c Fix build on Windows with no WINAPI_PARTITION_APP support (#1865) 2024-06-24 15:13:37 -04:00
Andrea Pappacoda bdefdce1ae test: fix GetRangeWithMaxLongLength on 32 bit machines (#1867)
The test used the hardcoded long value for 64 bit machines even on 32
bit ones, leading to test failures. With this patch the max long length
is obtained using std::numeric_limits<long>::max(). Thanks to q2a3z for
the hint!

Fixes: https://github.com/yhirose/cpp-httplib/issues/1795
2024-06-23 17:49:00 -04:00
Zhenlin Huang 9e4f93d87e Allow hex for ipv6 literal addr in redirect (#1859)
Co-authored-by: jaredhuang <jaredhuang@tencent.com>
2024-06-17 11:44:51 -04:00
yhirose 0b657d28cf Added example/one_time_request.cc. 2024-06-14 18:29:34 -04:00
Rainer Schielke c1a09daf15 avoid memory leaks if linked with static openssl libs (#1857)
* New function SSLServer::update_certs. Allows to update certificates while server is running

* New function SSLServer::update_certs. Added unit test

* avoid memory leaks if linked with static openssl libs

---------

Co-authored-by: CEU\schielke <Rainer.Schielke@heidelberg.com>
2024-06-14 15:40:03 -04:00
6 changed files with 417 additions and 229 deletions
+2
View File
@@ -0,0 +1,2 @@
/test/www*/dir/*.html text eol=lf
/test/www*/dir/*.txt text eol=lf
+2
View File
@@ -24,6 +24,7 @@ test/*.srl
*.swp
build/
Debug
Release
*.vcxproj.user
@@ -34,4 +35,5 @@ Release
ipch
*.dSYM
.*
!/.gitattributes
!/.travis.yml
+4 -2
View File
@@ -1,8 +1,7 @@
#CXX = clang++
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
PREFIX = /usr/local
#PREFIX = $(shell brew --prefix)
PREFIX ?= $(shell brew --prefix)
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
@@ -51,6 +50,9 @@ ssecli : ssecli.cc ../httplib.h Makefile
benchmark : benchmark.cc ../httplib.h Makefile
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
one_time_request : one_time_request.cc ../httplib.h Makefile
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
pem:
openssl genrsa 2048 > key.pem
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
+56
View File
@@ -0,0 +1,56 @@
#include <httplib.h>
#include <iostream>
using namespace httplib;
const char *HOST = "localhost";
const int PORT = 1234;
void one_time_request_server(const char *label) {
std::thread th;
Server svr;
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
res.set_content(std::string("Hello from ") + label, "text/plain");
// Stop server
th = std::thread([&]() { svr.stop(); });
});
svr.listen(HOST, PORT);
th.join();
std::cout << label << " ended..." << std::endl;
}
void send_request(const char *label) {
Client cli(HOST, PORT);
std::cout << "Send " << label << " request" << std::endl;
auto res = cli.Get("/hi");
if (res) {
std::cout << res->body << std::endl;
} else {
std::cout << "Request error: " + to_string(res.error()) << std::endl;
}
}
int main(void) {
auto th1 = std::thread([&]() { one_time_request_server("Server #1"); });
auto th2 = std::thread([&]() { one_time_request_server("Server #2"); });
std::this_thread::sleep_for(std::chrono::milliseconds(100));
send_request("1st");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
send_request("2nd");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
send_request("3rd");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
th1.join();
th2.join();
}
+90 -30
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.16.0"
#define CPPHTTPLIB_VERSION "0.16.1"
/*
* Configuration
@@ -726,6 +726,10 @@ private:
assert(true == static_cast<bool>(fn));
fn();
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
OPENSSL_thread_stop();
#endif
}
ThreadPool &pool_;
@@ -1526,6 +1530,7 @@ public:
const std::string &client_key_path);
Client(Client &&) = default;
Client &operator=(Client &&) = default;
~Client();
@@ -1819,9 +1824,9 @@ public:
bool is_valid() const override;
SSL_CTX *ssl_context() const;
void update_certs (X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store = nullptr);
void update_certs(X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store = nullptr);
private:
bool process_and_close_socket(socket_t sock) override;
@@ -2819,24 +2824,51 @@ inline bool mmap::open(const char *path) {
wpath += path[i];
}
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
WINAPI_PARTITION_GAMES) && \
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
OPEN_EXISTING, NULL);
#else
hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
#endif
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
WINAPI_PARTITION_GAMES)
LARGE_INTEGER size{};
if (!::GetFileSizeEx(hFile_, &size)) { return false; }
size_ = static_cast<size_t>(size.QuadPart);
#else
DWORD sizeHigh;
DWORD sizeLow;
sizeLow = ::GetFileSize(hFile_, &sizeHigh);
if (sizeLow == INVALID_FILE_SIZE) { return false; }
size_ = (static_cast<size_t>(sizeHigh) << (sizeof(DWORD) * 8)) | sizeLow;
#endif
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
hMapping_ =
::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
#else
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, size.HighPart,
size.LowPart, NULL);
#endif
if (hMapping_ == NULL) {
close();
return false;
}
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
#else
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
#endif
#else
fd_ = ::open(path, O_RDONLY);
if (fd_ == -1) { return false; }
@@ -7271,7 +7303,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
if (location.empty()) { return false; }
const static std::regex re(
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
std::smatch m;
if (!std::regex_match(location, m, re)) { return false; }
@@ -8157,7 +8189,8 @@ inline Result ClientImpl::Patch(const std::string &path,
inline Result ClientImpl::Patch(const std::string &path,
const std::string &body,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return Patch(path, Headers(), body, content_type, progress);
}
@@ -8508,13 +8541,29 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
return ssl;
}
inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
bool shutdown_gracefully) {
// sometimes we may want to skip this to try to avoid SIGPIPE if we know
// the remote has closed the network connection
// Note that it is not always possible to avoid SIGPIPE, this is merely a
// best-efforts.
if (shutdown_gracefully) { SSL_shutdown(ssl); }
if (shutdown_gracefully) {
#ifdef _WIN32
SSL_shutdown(ssl);
#else
timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const void *>(&tv), sizeof(tv));
auto ret = SSL_shutdown(ssl);
while (ret == 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
ret = SSL_shutdown(ssl);
}
#endif
}
std::lock_guard<std::mutex> guard(ctx_mutex);
SSL_free(ssl);
@@ -8690,7 +8739,7 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
SSL_CTX_set_default_passwd_cb_userdata(
@@ -8722,7 +8771,7 @@ inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
@@ -8756,17 +8805,17 @@ inline bool SSLServer::is_valid() const { return ctx_; }
inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
inline void SSLServer::update_certs (X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store) {
inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store) {
std::lock_guard<std::mutex> guard(ctx_mutex_);
std::lock_guard<std::mutex> guard(ctx_mutex_);
SSL_CTX_use_certificate (ctx_, cert);
SSL_CTX_use_PrivateKey (ctx_, private_key);
SSL_CTX_use_certificate(ctx_, cert);
SSL_CTX_use_PrivateKey(ctx_, private_key);
if (client_ca_cert_store != nullptr) {
SSL_CTX_set_cert_store (ctx_, client_ca_cert_store);
}
if (client_ca_cert_store != nullptr) {
SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
}
}
inline bool SSLServer::process_and_close_socket(socket_t sock) {
@@ -8793,7 +8842,7 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
// Shutdown gracefully if the result seemed successful, non-gracefully if
// the connection appeared to be closed.
const bool shutdown_gracefully = ret;
detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully);
detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully);
}
detail::shutdown_socket(sock);
@@ -9076,7 +9125,8 @@ inline void SSLClient::shutdown_ssl_impl(Socket &socket,
return;
}
if (socket.ssl) {
detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock,
shutdown_gracefully);
socket.ssl = nullptr;
}
assert(socket.ssl == nullptr);
@@ -9551,7 +9601,8 @@ inline Result Client::Patch(const std::string &path, const char *body,
}
inline Result Client::Patch(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return cli_->Patch(path, body, content_length, content_type, progress);
}
inline Result Client::Patch(const std::string &path, const Headers &headers,
@@ -9561,15 +9612,18 @@ inline Result Client::Patch(const std::string &path, const Headers &headers,
}
inline Result Client::Patch(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type, Progress progress) {
return cli_->Patch(path, headers, body, content_length, content_type, progress);
const std::string &content_type,
Progress progress) {
return cli_->Patch(path, headers, body, content_length, content_type,
progress);
}
inline Result Client::Patch(const std::string &path, const std::string &body,
const std::string &content_type) {
return cli_->Patch(path, body, content_type);
}
inline Result Client::Patch(const std::string &path, const std::string &body,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return cli_->Patch(path, body, content_type, progress);
}
inline Result Client::Patch(const std::string &path, const Headers &headers,
@@ -9579,7 +9633,8 @@ inline Result Client::Patch(const std::string &path, const Headers &headers,
}
inline Result Client::Patch(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return cli_->Patch(path, headers, body, content_type, progress);
}
inline Result Client::Patch(const std::string &path, size_t content_length,
@@ -9618,7 +9673,8 @@ inline Result Client::Delete(const std::string &path, const char *body,
}
inline Result Client::Delete(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return cli_->Delete(path, body, content_length, content_type, progress);
}
inline Result Client::Delete(const std::string &path, const Headers &headers,
@@ -9628,15 +9684,18 @@ inline Result Client::Delete(const std::string &path, const Headers &headers,
}
inline Result Client::Delete(const std::string &path, const Headers &headers,
const char *body, size_t content_length,
const std::string &content_type, Progress progress) {
return cli_->Delete(path, headers, body, content_length, content_type, progress);
const std::string &content_type,
Progress progress) {
return cli_->Delete(path, headers, body, content_length, content_type,
progress);
}
inline Result Client::Delete(const std::string &path, const std::string &body,
const std::string &content_type) {
return cli_->Delete(path, body, content_type);
}
inline Result Client::Delete(const std::string &path, const std::string &body,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return cli_->Delete(path, body, content_type, progress);
}
inline Result Client::Delete(const std::string &path, const Headers &headers,
@@ -9646,7 +9705,8 @@ inline Result Client::Delete(const std::string &path, const Headers &headers,
}
inline Result Client::Delete(const std::string &path, const Headers &headers,
const std::string &body,
const std::string &content_type, Progress progress) {
const std::string &content_type,
Progress progress) {
return cli_->Delete(path, headers, body, content_type, progress);
}
inline Result Client::Options(const std::string &path) {
+263 -197
View File
@@ -6,6 +6,7 @@
#include <atomic>
#include <chrono>
#include <future>
#include <limits>
#include <memory>
#include <sstream>
#include <stdexcept>
@@ -53,11 +54,176 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
#endif
}
TEST(ConstructorTest, MoveConstructible) {
#ifndef _WIN32
class UnixSocketTest : public ::testing::Test {
protected:
void TearDown() override { std::remove(pathname_.c_str()); }
void client_GET(const std::string &addr) {
httplib::Client cli{addr};
cli.set_address_family(AF_UNIX);
ASSERT_TRUE(cli.is_valid());
const auto &result = cli.Get(pattern_);
ASSERT_TRUE(result) << "error: " << result.error();
const auto &resp = result.value();
EXPECT_EQ(resp.status, StatusCode::OK_200);
EXPECT_EQ(resp.body, content_);
}
const std::string pathname_{"./httplib-server.sock"};
const std::string pattern_{"/hi"};
const std::string content_{"Hello World!"};
};
TEST_F(UnixSocketTest, pathname) {
httplib::Server svr;
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
res.set_content(content_, "text/plain");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
client_GET(pathname_);
}
#if defined(__linux__) || \
/* __APPLE__ */ (defined(SOL_LOCAL) && defined(SO_PEERPID))
TEST_F(UnixSocketTest, PeerPid) {
httplib::Server svr;
std::string remote_port_val;
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
res.set_content(content_, "text/plain");
remote_port_val = req.get_header_value("REMOTE_PORT");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
client_GET(pathname_);
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
}
#endif
#ifdef __linux__
TEST_F(UnixSocketTest, abstract) {
constexpr char svr_path[]{"\x00httplib-server.sock"};
const std::string abstract_addr{svr_path, sizeof(svr_path) - 1};
httplib::Server svr;
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
res.set_content(content_, "text/plain");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
client_GET(abstract_addr);
}
#endif
TEST(SocketStream, is_writable_UNIX) {
int fds[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) {
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
};
asSocketStream(fds[0], [&](Stream &s0) {
EXPECT_EQ(s0.socket(), fds[0]);
EXPECT_TRUE(s0.is_writable());
EXPECT_EQ(0, close(fds[1]));
EXPECT_FALSE(s0.is_writable());
return true;
});
EXPECT_EQ(0, close(fds[0]));
}
TEST(SocketStream, is_writable_INET) {
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT + 1);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int disconnected_svr_sock = -1;
std::thread svr{[&] {
const int s = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_LE(0, s);
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
ASSERT_EQ(0, listen(s, 1));
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
ASSERT_EQ(0, close(s));
}};
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::thread cli{[&] {
const int s = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_LE(0, s);
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
ASSERT_EQ(0, close(s));
}};
cli.join();
svr.join();
ASSERT_NE(disconnected_svr_sock, -1);
const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) {
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
};
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
EXPECT_FALSE(ss.is_writable());
return true;
});
ASSERT_EQ(0, close(disconnected_svr_sock));
}
#endif // #ifndef _WIN32
TEST(ClientTest, MoveConstructible) {
EXPECT_FALSE(std::is_copy_constructible<Client>::value);
EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value);
}
TEST(ClientTest, MoveAssignable) {
EXPECT_FALSE(std::is_copy_assignable<Client>::value);
EXPECT_TRUE(std::is_nothrow_move_assignable<Client>::value);
}
#ifdef _WIN32
TEST(StartupTest, WSAStartup) {
WSADATA wsaData;
@@ -1748,32 +1914,34 @@ TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
X509* readCertificate (const std::string& strFileName) {
std::ifstream inStream (strFileName);
std::string strCertPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
X509 *readCertificate(const std::string &strFileName) {
std::ifstream inStream(strFileName);
std::string strCertPEM((std::istreambuf_iterator<char>(inStream)),
std::istreambuf_iterator<char>());
if (strCertPEM.empty ()) return (nullptr);
if (strCertPEM.empty()) return (nullptr);
BIO* pbCert = BIO_new (BIO_s_mem ());
BIO_write (pbCert, strCertPEM.c_str (), (int)strCertPEM.size ());
X509* pCert = PEM_read_bio_X509 (pbCert, NULL, 0, NULL);
BIO_free (pbCert);
BIO *pbCert = BIO_new(BIO_s_mem());
BIO_write(pbCert, strCertPEM.c_str(), (int)strCertPEM.size());
X509 *pCert = PEM_read_bio_X509(pbCert, NULL, 0, NULL);
BIO_free(pbCert);
return (pCert);
return (pCert);
}
EVP_PKEY* readPrivateKey (const std::string& strFileName) {
std::ifstream inStream (strFileName);
std::string strPrivateKeyPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
EVP_PKEY *readPrivateKey(const std::string &strFileName) {
std::ifstream inStream(strFileName);
std::string strPrivateKeyPEM((std::istreambuf_iterator<char>(inStream)),
std::istreambuf_iterator<char>());
if (strPrivateKeyPEM.empty ()) return (nullptr);
if (strPrivateKeyPEM.empty()) return (nullptr);
BIO* pbPrivKey = BIO_new (BIO_s_mem ());
BIO_write (pbPrivKey, strPrivateKeyPEM.c_str (), (int) strPrivateKeyPEM.size ());
EVP_PKEY* pPrivateKey = PEM_read_bio_PrivateKey (pbPrivKey, NULL, NULL, NULL);
BIO_free (pbPrivKey);
BIO *pbPrivKey = BIO_new(BIO_s_mem());
BIO_write(pbPrivKey, strPrivateKeyPEM.c_str(), (int)strPrivateKeyPEM.size());
EVP_PKEY *pPrivateKey = PEM_read_bio_PrivateKey(pbPrivKey, NULL, NULL, NULL);
BIO_free(pbPrivKey);
return (pPrivateKey);
return (pPrivateKey);
}
TEST(BindServerTest, UpdateCerts) {
@@ -1782,26 +1950,26 @@ TEST(BindServerTest, UpdateCerts) {
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
X509* cert = readCertificate (SERVER_CERT_FILE);
X509* ca_cert = readCertificate (CLIENT_CA_CERT_FILE);
EVP_PKEY* key = readPrivateKey (SERVER_PRIVATE_KEY_FILE);
X509 *cert = readCertificate(SERVER_CERT_FILE);
X509 *ca_cert = readCertificate(CLIENT_CA_CERT_FILE);
EVP_PKEY *key = readPrivateKey(SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(cert != nullptr);
ASSERT_TRUE(cert != nullptr);
ASSERT_TRUE(ca_cert != nullptr);
ASSERT_TRUE(key != nullptr);
ASSERT_TRUE(key != nullptr);
X509_STORE* cert_store = X509_STORE_new ();
X509_STORE *cert_store = X509_STORE_new();
X509_STORE_add_cert (cert_store, ca_cert);
X509_STORE_add_cert(cert_store, ca_cert);
svr.update_certs (cert, key, cert_store);
svr.update_certs(cert, key, cert_store);
ASSERT_TRUE(svr.is_valid());
svr.stop();
X509_free (cert);
X509_free (ca_cert);
EVP_PKEY_free (key);
X509_free(cert);
X509_free(ca_cert);
EVP_PKEY_free(key);
}
#endif
@@ -2350,8 +2518,8 @@ protected:
})
.Get("/with-range-customized-response",
[&](const Request & /*req*/, Response &res) {
res.status = StatusCode::BadRequest_400;
res.set_content(JSON_DATA, "application/json");
res.status = StatusCode::BadRequest_400;
res.set_content(JSON_DATA, "application/json");
})
.Post("/chunked",
[&](const Request &req, Response & /*res*/) {
@@ -3473,8 +3641,10 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
}
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res =
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
auto res = cli_.Get(
"/with-range",
{{"Range",
"bytes=0-" + std::to_string(std::numeric_limits<long>::max())}});
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
@@ -3630,7 +3800,8 @@ TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
}
TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}})}});
auto res = cli_.Get("/with-range-customized-response",
{{make_range_header({{1, 2}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
EXPECT_EQ(true, res->has_header("Content-Length"));
@@ -3639,7 +3810,8 @@ TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
}
TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}, {4, 5}})}});
auto res = cli_.Get("/with-range-customized-response",
{{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
EXPECT_EQ(true, res->has_header("Content-Length"));
@@ -4984,6 +5156,60 @@ TEST(KeepAliveTest, SSLClientReconnection) {
ASSERT_TRUE(result);
EXPECT_EQ(StatusCode::OK_200, result->status);
}
TEST(KeepAliveTest, SSLClientReconnectionPost) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.set_keep_alive_timeout(1);
std::string content = "reconnect";
svr.Post("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
std::this_thread::sleep_for(std::chrono::milliseconds(200));
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
cli.set_keep_alive(true);
auto result = cli.Post(
"/hi", content.size(),
[&content](size_t offset, size_t length, DataSink &sink) {
sink.write(content.c_str(), content.size());
return true;
},
"text/plain");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
std::this_thread::sleep_for(std::chrono::seconds(2));
// Recoonect
result = cli.Post(
"/hi", content.size(),
[&content](size_t offset, size_t length, DataSink &sink) {
sink.write(content.c_str(), content.size());
return true;
},
"text/plain");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
result = cli.Post(
"/hi", content.size(),
[&content](size_t offset, size_t length, DataSink &sink) {
sink.write(content.c_str(), content.size());
return true;
},
"text/plain");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
svr.stop();
f.wait();
}
#endif
TEST(ClientProblemDetectionTest, ContentProvider) {
@@ -6958,166 +7184,6 @@ TEST(MultipartFormDataTest, ContentLength) {
#endif
#ifndef _WIN32
class UnixSocketTest : public ::testing::Test {
protected:
void TearDown() override { std::remove(pathname_.c_str()); }
void client_GET(const std::string &addr) {
httplib::Client cli{addr};
cli.set_address_family(AF_UNIX);
ASSERT_TRUE(cli.is_valid());
const auto &result = cli.Get(pattern_);
ASSERT_TRUE(result) << "error: " << result.error();
const auto &resp = result.value();
EXPECT_EQ(resp.status, StatusCode::OK_200);
EXPECT_EQ(resp.body, content_);
}
const std::string pathname_{"./httplib-server.sock"};
const std::string pattern_{"/hi"};
const std::string content_{"Hello World!"};
};
TEST_F(UnixSocketTest, pathname) {
httplib::Server svr;
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
res.set_content(content_, "text/plain");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
client_GET(pathname_);
}
#if defined(__linux__) || \
/* __APPLE__ */ (defined(SOL_LOCAL) && defined(SO_PEERPID))
TEST_F(UnixSocketTest, PeerPid) {
httplib::Server svr;
std::string remote_port_val;
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
res.set_content(content_, "text/plain");
remote_port_val = req.get_header_value("REMOTE_PORT");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
client_GET(pathname_);
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
}
#endif
#ifdef __linux__
TEST_F(UnixSocketTest, abstract) {
constexpr char svr_path[]{"\x00httplib-server.sock"};
const std::string abstract_addr{svr_path, sizeof(svr_path) - 1};
httplib::Server svr;
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
res.set_content(content_, "text/plain");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
client_GET(abstract_addr);
}
#endif
TEST(SocketStream, is_writable_UNIX) {
int fds[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) {
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
};
asSocketStream(fds[0], [&](Stream &s0) {
EXPECT_EQ(s0.socket(), fds[0]);
EXPECT_TRUE(s0.is_writable());
EXPECT_EQ(0, close(fds[1]));
EXPECT_FALSE(s0.is_writable());
return true;
});
EXPECT_EQ(0, close(fds[0]));
}
TEST(SocketStream, is_writable_INET) {
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT + 1);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int disconnected_svr_sock = -1;
std::thread svr{[&] {
const int s = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_LE(0, s);
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
ASSERT_EQ(0, listen(s, 1));
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
ASSERT_EQ(0, close(s));
}};
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::thread cli{[&] {
const int s = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_LE(0, s);
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
ASSERT_EQ(0, close(s));
}};
cli.join();
svr.join();
ASSERT_NE(disconnected_svr_sock, -1);
const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) {
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
};
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
EXPECT_FALSE(ss.is_writable());
return true;
});
ASSERT_EQ(0, close(disconnected_svr_sock));
}
#endif // #ifndef _WIN32
TEST(TaskQueueTest, IncreaseAtomicInteger) {
static constexpr unsigned int number_of_tasks{1000000};
std::atomic_uint count{0};
@@ -7443,6 +7509,6 @@ TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
std::string ipV6TestURL = "http://[ff06::c3]";
Client cli(ipV6TestURL + ":" + std::to_string(port), CLIENT_CERT_FILE,
CLIENT_PRIVATE_KEY_FILE);
CLIENT_PRIVATE_KEY_FILE);
EXPECT_EQ(cli.port(), port);
}