Compare commits

..

35 Commits

Author SHA1 Message Date
yhirose 4e6ded1f36 Release v0.12.0 2023-02-07 10:27:40 -05:00
yhirose d663588491 Removed is_writable() from DataSink (Resolve #1478, too) (#1483) 2023-02-04 13:53:42 -05:00
yhirose 439caf5b79 Fix #1479 2023-02-01 19:11:11 -05:00
yhirose c4ba43ca6f Removed incorrect comment 2023-01-24 09:07:42 -05:00
Jiwoo Park 20cba2ecd9 Support CTest (#1468)
* Add test/CMakeLists.txt to enable testing with CTest

Add HTTPLIB_TEST option
Downlaod GoogleTest source using FetchContent module
Generate cert files to test httplib with OpenSSL

* Generate cert2.pem with a new certificate request

* Use the latest GoogleTest library
2023-01-21 03:43:22 -05:00
yhirose 0ff2e16d69 Issue 52666: cpp-httplib:server_fuzzer: Timeout in server_fuzzer 2023-01-21 01:09:19 -05:00
Ray Beck 51607ec752 add to_human_string (#1467)
* add to_human_string

* replace to_string with to_human_string

* fix test
2023-01-19 07:01:34 -05:00
yhirose 7992b14896 Release v0.11.4 2023-01-09 17:08:11 -05:00
Ray Beck 7e420aeed3 add support for requests with both MultipartFormDataItems and Content Providers (#1454)
* add support for requests with both MultipartFormDataItems and ContentProviders

* rework implementation

* use const auto & and fix offset calculation

* fix zero items

* snake case variables

* clang-format

* commonize get_multipart_content_provider, add Put() with MultipartFormDataProviderItems

* fix linker multiple definition error

* add test MultipartFormDataTest.DataProviderItems
2023-01-08 18:38:14 -05:00
yukun 227d2c2050 Add EINTR and EAGAIN judge for accept (#1438)
* Add EINTR and EAGAIN judge for accept

* Add EINTR signal tests

* Cancel win32 and win64 compile on signal unittest

Co-authored-by: yukun.yu <yukun.yu@alibaba-inc.com>
2022-12-20 19:34:51 -05:00
yhirose 93e53c91f7 Updated unit test 2022-12-10 11:45:56 -05:00
Jiwoo Park 58cffd3223 std::condition_variable::notify_one/all() should be called after unlocking mutex (#1448)
* Move next job in task queue rather than copy

* Notify waiting thread after unlocking mutex

* Add unit test for TaskQueue

* Don't use C++14 feature in test code
2022-12-09 17:37:48 -05:00
Ingo Bauersachs 8f32271e8c Support LOCAL_ADDR and LOCAL_PORT header in client Request (#1450)
Having the local address/port is useful if the server is bound to
all interfaces, e.g. to serve different content for developers
on localhost only.
2022-12-06 08:23:09 -05:00
yhirose c8c1c3d376 Fix #1442 2022-12-01 17:21:40 -05:00
yhirose 9f512acb42 Removed code for upsupported OpenSSL 2022-12-01 13:41:18 -05:00
yhirose c0b461a3b7 Release v0.11.3 2022-11-29 19:26:10 -05:00
yhirose 74fe5a5029 Fix #1426 2022-11-27 10:53:11 -05:00
yhirose 9d0a9d4e23 Fix #1437 2022-11-27 10:21:24 -05:00
TheMarpe 5758769ad3 Windows CMake directory install fix (#1434)
* Fixed install convention pre 3.25

* Simplified CMAKEDIR installation directory
2022-11-24 22:51:55 -05:00
Ray Beck e7eadc3605 add SYSTEM to include to prevent warnings (#1428) 2022-11-24 22:23:11 -05:00
yhirose 07c6e58951 Fix #1421 2022-11-15 11:57:14 -05:00
Pavel Artemkin 87994811a1 undef poll at the end if CPPHTTPLIB_USE_POLL (#1427)
* undef poll at the end if CPPHTTPLIB_USE_POLL

* win32 only
2022-11-13 21:49:49 -05:00
yhirose 42feb7e8be Updated README 2022-11-10 08:27:30 -05:00
yhirose 26196b70af Add test case for 'Issue 52666 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer' 2022-11-03 13:07:04 -04:00
Changbin Park 93a51979c4 Get client process id over ip/port when server runs on UNIX socket. (#1418)
* handle socket options for UNIX socket same as others

 * set FD_CLOEXEC by default
 * invoke `socket_options` callback if set

* Offer Client info even on UNIX socket based Server

HTTP Request header "REMOTE_PORT" contains client process id if possible
when Server works on UNIX socket.

* retrigger checks

* retrigger checks

* add support macOS

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-11-03 10:25:18 -04:00
Changbin Park ad7edc7b27 avoid lockup in ThreadPool::shutdown() on legacy host (#1417)
It is found on CentOS 7.0.1406 which is very early version of CentOS 7.

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-11-03 10:23:45 -04:00
Andreas Kempf 27cd4e6ffe Support compilation on AIX (#1402)
Disable the ifaddrs.h include statement and the USE_IF2IP flag on AIX.
AIX is a Unix OS but does not offer the ifaddrs.h header.
2022-10-14 10:49:48 -04:00
yhirose cae5a8be1c Added more fuzzing corpus 2022-09-24 08:31:21 -04:00
yhirose 8e10d4e8e7 Release v0.11.2 2022-09-12 11:36:14 -04:00
Changbin Park b57f79f438 Detecting client disconnection (#1373)
* SocketStream need to check connectivity for writability.

When the stream is used for SSE server which works via chunked content
provider it's not possible to check connectivity over writability
because it's wrapped by DataSink. It could make the server stalled, when
the provider wants to only keep connection without send data and certain
amount of clients consumes entire threadpool.

* add unittest for SocketStream::is_writable()

SocketStream::is_writable() should return false if peer is disconnected.

* revise broken unittest ServerTest.ClientStop

DataSink could be unwritable if it's backed by connection based. Because
it could be disconnected by client right after enter centent provider.

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-08-30 21:11:19 -04:00
Andrea Pappacoda a9cf097951 build: set soversion to major.minor (#1357)
Release 0.11 broke backwards compatibility, meaning that different
cpp-httplib versions are compatible with each other only if the major
and minor version numbers are the same.

This patch reflects this in the build systems.

See #1209 for some more context.
2022-08-12 13:48:40 -04:00
yhirose 5c3624e1af Updated example/uploader.sh 2022-08-06 14:43:56 -04:00
yhirose cba9ef8c0b Issue 49740 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer 2022-08-06 08:08:08 -04:00
yhirose 4f8407a3a7 Refactoring the previous commit 2022-08-04 20:56:02 -04:00
Gopinath K 656e936f49 add multipart formdata for PUT requests. (#1351)
* httplib.h

  add multipart formdata for PUT in addition to POST as some REST
  APIs use that.

  Factor the boundary checking code into a helper and use it from
  both Post() and Put().

* test/test.cc

  add test cases for the above.
2022-08-04 20:42:13 -04:00
11 changed files with 1225 additions and 182 deletions
+13 -14
View File
@@ -8,6 +8,7 @@
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
* HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_COMPILE (default off)
* HTTPLIB_TEST (default off)
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
@@ -88,7 +89,7 @@ option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a c
if(HTTPLIB_COMPILE)
set(HTTPLIB_IS_COMPILED TRUE)
endif()
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
# Defaults to static library
@@ -176,7 +177,7 @@ if(HTTPLIB_COMPILE)
set_target_properties(${PROJECT_NAME}
PROPERTIES
VERSION ${${PROJECT_NAME}_VERSION}
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
)
else()
# This is for header-only.
@@ -193,7 +194,7 @@ target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
cxx_std_11
)
target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
$<BUILD_INTERFACE:${_httplib_build_includedir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
@@ -221,15 +222,8 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
)
# Cmake's find_package search path is different based on the system
# See https://cmake.org/cmake/help/latest/command/find_package.html for the list
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_PREFIX}/cmake/${PROJECT_NAME}")
else()
# On Non-Windows, it should be /usr/lib/cmake/<name>/<name>Config.cmake
# NOTE: This may or may not work for macOS...
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
endif()
# CMake configuration files installation directory
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
include(CMakePackageConfigHelpers)
@@ -247,13 +241,13 @@ if(HTTPLIB_COMPILE)
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
# Example: if you find_package(httplib 0.5.4)
# then anything >= 0.5 and <= 1.0 is accepted
COMPATIBILITY SameMajorVersion
COMPATIBILITY SameMinorVersion
)
else()
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
# Example: if you find_package(httplib 0.5.4)
# then anything >= 0.5 and <= 1.0 is accepted
COMPATIBILITY SameMajorVersion
COMPATIBILITY SameMinorVersion
# Tells Cmake that it's a header-only lib
# Mildly useful for end-users :)
ARCH_INDEPENDENT
@@ -285,3 +279,8 @@ install(EXPORT httplibTargets
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
if(HTTPLIB_TEST)
include(CTest)
add_subdirectory(test)
endif()
+5
View File
@@ -65,6 +65,7 @@ httplib::SSLServer svr("./cert.pem", "./key.pem");
// Client
httplib::Client cli("https://localhost:1234"); // scheme + host
httplib::SSLClient cli("localhost:1234"); // host
httplib::SSLClient cli("localhost", 1234); // host, port
// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");
@@ -492,6 +493,10 @@ auto res = cli.Get("/hi", headers);
```
or
```c++
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
```
or
```c++
cli.set_default_headers({
{ "Accept-Encoding", "gzip, deflate" }
});
+1 -1
View File
@@ -19,7 +19,7 @@ public:
unique_lock<mutex> lk(m_);
int id = id_;
cv_.wait(lk, [&] { return cid_ == id; });
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
sink->write(message_.data(), message_.size());
}
void send_event(const string &message) {
+1 -1
View File
@@ -1,5 +1,5 @@
#/usr/bin/env bash
for i in {1..10000}
for i in {1..1000000}
do
echo "#### $i ####"
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
+378 -133
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.11.1"
#define CPPHTTPLIB_VERSION "0.12.0"
/*
* Configuration
@@ -70,6 +70,10 @@
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
#endif
#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
#endif
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
#endif
@@ -168,7 +172,9 @@ using socket_t = SOCKET;
#else // not _WIN32
#include <arpa/inet.h>
#ifndef _AIX
#include <ifaddrs.h>
#endif
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
@@ -249,14 +255,10 @@ using socket_t = int;
#if OPENSSL_VERSION_NUMBER < 0x1010100fL
#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
#define SSL_get1_peer_certificate SSL_get_peer_certificate
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/crypto.h>
inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
return M_ASN1_STRING_data(asn1);
}
#endif
#endif
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
@@ -338,7 +340,6 @@ public:
std::function<bool(const char *data, size_t data_len)> write;
std::function<void()> done;
std::function<bool()> is_writable;
std::ostream os;
private:
@@ -367,6 +368,14 @@ using ContentProviderWithoutLength =
using ContentProviderResourceReleaser = std::function<void(bool success)>;
struct MultipartFormDataProvider {
std::string name;
ContentProviderWithoutLength provider;
std::string filename;
std::string content_type;
};
using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>;
using ContentReceiverWithProgress =
std::function<bool(const char *data, size_t data_length, uint64_t offset,
uint64_t total_length)>;
@@ -411,6 +420,8 @@ struct Request {
std::string remote_addr;
int remote_port = -1;
std::string local_addr;
int local_port = -1;
// for server
std::string version;
@@ -512,6 +523,7 @@ public:
virtual ssize_t read(char *ptr, size_t size) = 0;
virtual ssize_t write(const char *ptr, size_t size) = 0;
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
virtual socket_t socket() const = 0;
template <typename... Args>
@@ -544,8 +556,11 @@ public:
~ThreadPool() override = default;
void enqueue(std::function<void()> fn) override {
std::unique_lock<std::mutex> lock(mutex_);
jobs_.push_back(std::move(fn));
{
std::unique_lock<std::mutex> lock(mutex_);
jobs_.push_back(std::move(fn));
}
cond_.notify_one();
}
@@ -579,7 +594,7 @@ private:
if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
fn = pool_.jobs_.front();
fn = std::move(pool_.jobs_.front());
pool_.jobs_.pop_front();
}
@@ -897,6 +912,7 @@ public:
Result Head(const std::string &path, const Headers &headers);
Result Post(const std::string &path);
Result Post(const std::string &path, const Headers &headers);
Result Post(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
Result Post(const std::string &path, const Headers &headers, const char *body,
@@ -925,6 +941,9 @@ public:
const MultipartFormDataItems &items);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Put(const std::string &path);
Result Put(const std::string &path, const char *body, size_t content_length,
@@ -949,6 +968,14 @@ public:
Result Put(const std::string &path, const Params &params);
Result Put(const std::string &path, const Headers &headers,
const Params &params);
Result Put(const std::string &path, const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Patch(const std::string &path);
Result Patch(const std::string &path, const char *body, size_t content_length,
@@ -1187,6 +1214,9 @@ private:
ContentProvider content_provider,
ContentProviderWithoutLength content_provider_without_length,
const std::string &content_type);
ContentProviderWithoutLength get_multipart_content_provider(
const std::string &boundary, const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
std::string adjust_host_string(const std::string &host) const;
@@ -1253,6 +1283,7 @@ public:
Result Head(const std::string &path, const Headers &headers);
Result Post(const std::string &path);
Result Post(const std::string &path, const Headers &headers);
Result Post(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
Result Post(const std::string &path, const Headers &headers, const char *body,
@@ -1281,6 +1312,10 @@ public:
const MultipartFormDataItems &items);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Put(const std::string &path);
Result Put(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
@@ -1304,6 +1339,15 @@ public:
Result Put(const std::string &path, const Params &params);
Result Put(const std::string &path, const Headers &headers,
const Params &params);
Result Put(const std::string &path, const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Patch(const std::string &path);
Result Patch(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
@@ -1519,7 +1563,7 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
duration - std::chrono::seconds(sec))
.count();
callback(sec, usec);
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
}
template <typename T>
@@ -1620,20 +1664,20 @@ Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
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::Success: return "Success (no error)";
case Error::Connection: return "Could not establish connection";
case Error::BindIPAddress: return "Failed to bind IP address";
case Error::Read: return "Failed to read connection";
case Error::Write: return "Failed to write connection";
case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
case Error::Canceled: return "Connection handling canceled";
case Error::SSLConnection: return "SSL connection failed";
case Error::SSLLoadingCerts: return "SSL certificate loading failed";
case Error::SSLServerVerification: return "SSL server verification failed";
case Error::UnsupportedMultipartBoundaryChars:
return "UnsupportedMultipartBoundaryChars";
case Error::Compression: return "Compression";
case Error::ConnectionTimeout: return "ConnectionTimeout";
return "Unsupported HTTP multipart boundary characters";
case Error::Compression: return "Compression failed";
case Error::ConnectionTimeout: return "Connection timed out";
case Error::Unknown: return "Unknown";
default: break;
}
@@ -1765,6 +1809,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
const std::string &get_buffer() const;
@@ -2433,6 +2478,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
private:
@@ -2462,6 +2508,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
private:
@@ -2582,10 +2629,13 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
addr.sun_family = AF_UNIX;
std::copy(host.begin(), host.end(), addr.sun_path);
hints.ai_addr = reinterpret_cast<sockaddr*>(&addr);
hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
hints.ai_addrlen = static_cast<socklen_t>(
sizeof(addr) - sizeof(addr.sun_path) + addrlen);
fcntl(sock, F_SETFD, FD_CLOEXEC);
if (socket_options) { socket_options(sock); }
if (!bind_or_connect(sock, hints)) {
close_socket(sock);
sock = INVALID_SOCKET;
@@ -2706,7 +2756,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
return ret;
}
#if !defined _WIN32 && !defined ANDROID
#if !defined _WIN32 && !defined ANDROID && !defined _AIX
#define USE_IF2IP
#endif
@@ -2827,9 +2877,8 @@ inline socket_t create_client_socket(
return sock;
}
inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
socklen_t addr_len, std::string &ip,
int &port) {
inline bool get_ip_and_port(const struct sockaddr_storage &addr,
socklen_t addr_len, std::string &ip, int &port) {
if (addr.ss_family == AF_INET) {
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
} else if (addr.ss_family == AF_INET6) {
@@ -2850,13 +2899,40 @@ inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
return true;
}
inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
&addr_len)) {
get_ip_and_port(addr, addr_len, ip, port);
}
}
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
&addr_len)) {
get_remote_ip_and_port(addr, addr_len, ip, port);
#ifndef _WIN32
if (addr.ss_family == AF_UNIX) {
#if defined(__linux__)
struct ucred ucred;
socklen_t len = sizeof(ucred);
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
port = ucred.pid;
}
#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__
pid_t pid;
socklen_t len = sizeof(pid);
if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
port = pid;
}
#endif
return;
}
#endif
get_ip_and_port(addr, addr_len, ip, port);
}
}
@@ -3555,7 +3631,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
data_sink.write = [&](const char *d, size_t l) -> bool {
if (ok) {
if (write_data(strm, d, l)) {
if (strm.is_writable() && write_data(strm, d, l)) {
offset += l;
} else {
ok = false;
@@ -3564,14 +3640,14 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
return ok;
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (offset < end_offset && !is_shutting_down()) {
if (!content_provider(offset, end_offset - offset, data_sink)) {
if (!strm.is_writable()) {
error = Error::Write;
return false;
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
error = Error::Canceled;
return false;
}
if (!ok) {
} else if (!ok) {
error = Error::Write;
return false;
}
@@ -3603,18 +3679,21 @@ write_content_without_length(Stream &strm,
data_sink.write = [&](const char *d, size_t l) -> bool {
if (ok) {
offset += l;
if (!write_data(strm, d, l)) { ok = false; }
if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; }
}
return ok;
};
data_sink.done = [&](void) { data_available = false; };
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (data_available && !is_shutting_down()) {
if (!content_provider(offset, 0, data_sink)) { return false; }
if (!ok) { return false; }
if (!strm.is_writable()) {
return false;
} else if (!content_provider(offset, 0, data_sink)) {
return false;
} else if (!ok) {
return false;
}
}
return true;
}
@@ -3643,7 +3722,10 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
// Emit chunked response header and footer for each chunk
auto chunk =
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
if (!strm.is_writable() ||
!write_data(strm, chunk.data(), chunk.size())) {
ok = false;
}
}
} else {
ok = false;
@@ -3682,14 +3764,14 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
}
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (data_available && !is_shutting_down()) {
if (!content_provider(offset, 0, data_sink)) {
if (!strm.is_writable()) {
error = Error::Write;
return false;
} else if (!content_provider(offset, 0, data_sink)) {
error = Error::Canceled;
return false;
}
if (!ok) {
} else if (!ok) {
error = Error::Write;
return false;
}
@@ -3883,6 +3965,9 @@ public:
if (std::regex_match(header, m, re_content_disposition)) {
file_.name = m[1];
file_.filename = m[2];
} else {
is_valid_ = false;
return false;
}
}
buf_erase(pos + crlf_.size());
@@ -4064,6 +4149,63 @@ inline std::string make_multipart_data_boundary() {
return result;
}
inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
auto valid = true;
for (size_t i = 0; i < boundary.size(); i++) {
auto c = boundary[i];
if (!std::isalnum(c) && c != '-' && c != '_') {
valid = false;
break;
}
}
return valid;
}
template <typename T>
inline std::string
serialize_multipart_formdata_item_begin(const T &item,
const std::string &boundary) {
std::string body = "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
if (!item.filename.empty()) {
body += "; filename=\"" + item.filename + "\"";
}
body += "\r\n";
if (!item.content_type.empty()) {
body += "Content-Type: " + item.content_type + "\r\n";
}
body += "\r\n";
return body;
}
inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
inline std::string
serialize_multipart_formdata_finish(const std::string &boundary) {
return "--" + boundary + "--\r\n";
}
inline std::string
serialize_multipart_formdata_get_content_type(const std::string &boundary) {
return "multipart/form-data; boundary=" + boundary;
}
inline std::string
serialize_multipart_formdata(const MultipartFormDataItems &items,
const std::string &boundary, bool finish = true) {
std::string body;
for (const auto &item : items) {
body += serialize_multipart_formdata_item_begin(item, boundary);
body += item.content + serialize_multipart_formdata_item_end();
}
if (finish) body += serialize_multipart_formdata_finish(boundary);
return body;
}
inline std::pair<size_t, size_t>
get_range_offset_and_length(const Request &req, size_t content_length,
size_t index) {
@@ -4445,8 +4587,8 @@ inline void hosted_at(const std::string &hostname,
*reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
std::string ip;
int dummy = -1;
if (detail::get_remote_ip_and_port(addr, sizeof(struct sockaddr_storage),
ip, dummy)) {
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
dummy)) {
addrs.push_back(ip);
}
}
@@ -4670,7 +4812,8 @@ inline bool SocketStream::is_readable() const {
}
inline bool SocketStream::is_writable() const {
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
is_socket_alive(sock_);
}
inline ssize_t SocketStream::read(char *ptr, size_t size) {
@@ -4735,6 +4878,11 @@ inline void SocketStream::get_remote_ip_and_port(std::string &ip,
return detail::get_remote_ip_and_port(sock_, ip, port);
}
inline void SocketStream::get_local_ip_and_port(std::string &ip,
int &port) const {
return detail::get_local_ip_and_port(sock_, ip, port);
}
inline socket_t SocketStream::socket() const { return sock_; }
// Buffer stream implementation
@@ -4760,6 +4908,9 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
int & /*port*/) const {}
inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
int & /*port*/) const {}
inline socket_t BufferStream::socket() const { return 0; }
inline const std::string &BufferStream::get_buffer() const { return buffer; }
@@ -5207,6 +5358,7 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
MultipartFormDataMap::iterator cur;
auto file_count = 0;
if (read_content_core(
strm, req, res,
// Regular
@@ -5217,6 +5369,9 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
},
// Multipart
[&](const MultipartFormData &file) {
if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
return false;
}
cur = req.files.emplace(file.name, file);
return true;
},
@@ -5403,6 +5558,8 @@ inline bool Server::listen_internal() {
// Try to accept new connections after a short sleep.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
} else if (errno == EINTR || errno == EAGAIN) {
continue;
}
if (svr_sock_ != INVALID_SOCKET) {
detail::close_socket(svr_sock_);
@@ -5735,6 +5892,10 @@ Server::process_request(Stream &strm, bool close_connection,
req.set_header("REMOTE_ADDR", req.remote_addr);
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
strm.get_local_ip_and_port(req.local_addr, req.local_port);
req.set_header("LOCAL_ADDR", req.local_addr);
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
if (req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) {
@@ -5774,7 +5935,16 @@ Server::process_request(Stream &strm, bool close_connection,
routed = true;
} else {
res.status = 500;
res.set_header("EXCEPTION_WHAT", e.what());
std::string val;
auto s = e.what();
for (size_t i = 0; s[i]; i++) {
switch (s[i]) {
case '\r': val += "\\r"; break;
case '\n': val += "\\n"; break;
default: val += s[i]; break;
}
}
res.set_header("EXCEPTION_WHAT", val);
}
} catch (...) {
if (exception_handler_) {
@@ -6216,7 +6386,7 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
return detail::write_content(strm, req.content_provider_, 0,
req.content_length_, is_shutting_down, error);
}
} // namespace httplib
}
inline bool ClientImpl::write_request(Stream &strm, Request &req,
bool close_connection, Error &error) {
@@ -6379,8 +6549,6 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
return ok;
};
data_sink.is_writable = [&](void) { return ok && true; };
while (ok && offset < content_length) {
if (!content_provider(offset, content_length - offset, data_sink)) {
error = Error::Canceled;
@@ -6528,6 +6696,48 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
return true;
}
inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
const std::string &boundary, const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
size_t cur_item = 0, cur_start = 0;
// cur_item and cur_start are copied to within the std::function and maintain
// state between successive calls
return [&, cur_item, cur_start](size_t offset,
DataSink &sink) mutable -> bool {
if (!offset && items.size()) {
sink.os << detail::serialize_multipart_formdata(items, boundary, false);
return true;
} else if (cur_item < provider_items.size()) {
if (!cur_start) {
const auto &begin = detail::serialize_multipart_formdata_item_begin(
provider_items[cur_item], boundary);
offset += begin.size();
cur_start = offset;
sink.os << begin;
}
DataSink cur_sink;
bool has_data = true;
cur_sink.write = sink.write;
cur_sink.done = [&]() { has_data = false; };
if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
return false;
if (!has_data) {
sink.os << detail::serialize_multipart_formdata_item_end();
cur_item++;
cur_start = 0;
}
return true;
} else {
sink.os << detail::serialize_multipart_formdata_finish(boundary);
sink.done();
return true;
}
};
}
inline bool
ClientImpl::process_socket(const Socket &socket,
std::function<bool(Stream &strm)> callback) {
@@ -6673,6 +6883,11 @@ inline Result ClientImpl::Post(const std::string &path) {
return Post(path, std::string(), std::string());
}
inline Result ClientImpl::Post(const std::string &path,
const Headers &headers) {
return Post(path, headers, nullptr, 0, std::string());
}
inline Result ClientImpl::Post(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type) {
@@ -6745,40 +6960,39 @@ inline Result ClientImpl::Post(const std::string &path,
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items) {
return Post(path, headers, items, detail::make_multipart_data_boundary());
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Post(path, headers, body, content_type.c_str());
}
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string &boundary) {
for (size_t i = 0; i < boundary.size(); i++) {
char c = boundary[i];
if (!std::isalnum(c) && c != '-' && c != '_') {
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
}
if (!detail::is_multipart_boundary_chars_valid(boundary)) {
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
}
std::string body;
for (const auto &item : items) {
body += "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
if (!item.filename.empty()) {
body += "; filename=\"" + item.filename + "\"";
}
body += "\r\n";
if (!item.content_type.empty()) {
body += "Content-Type: " + item.content_type + "\r\n";
}
body += "\r\n";
body += item.content + "\r\n";
}
body += "--" + boundary + "--\r\n";
std::string content_type = "multipart/form-data; boundary=" + boundary;
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Post(path, headers, body, content_type.c_str());
}
inline Result
ClientImpl::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
return send_with_content_provider(
"POST", path, headers, nullptr, 0, nullptr,
get_multipart_content_provider(boundary, items, provider_items),
content_type);
}
inline Result ClientImpl::Put(const std::string &path) {
return Put(path, std::string(), std::string());
}
@@ -6848,6 +7062,45 @@ inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
return Put(path, headers, query, "application/x-www-form-urlencoded");
}
inline Result ClientImpl::Put(const std::string &path,
const MultipartFormDataItems &items) {
return Put(path, Headers(), items);
}
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items) {
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Put(path, headers, body, content_type);
}
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string &boundary) {
if (!detail::is_multipart_boundary_chars_valid(boundary)) {
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
}
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Put(path, headers, body, content_type);
}
inline Result
ClientImpl::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
return send_with_content_provider(
"PUT", path, headers, nullptr, 0, nullptr,
get_multipart_content_provider(boundary, items, provider_items),
content_type);
}
inline Result ClientImpl::Patch(const std::string &path) {
return Patch(path, std::string(), std::string());
}
@@ -6973,9 +7226,7 @@ inline size_t ClientImpl::is_socket_open() const {
return socket_.is_open();
}
inline socket_t ClientImpl::socket() const {
return socket_.sock;
}
inline socket_t ClientImpl::socket() const { return socket_.sock; }
inline void ClientImpl::stop() {
std::lock_guard<std::mutex> guard(socket_mutex_);
@@ -7209,55 +7460,12 @@ process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
return callback(strm);
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
class SSLThreadLocks {
public:
SSLThreadLocks() {
openSSL_locks_ =
std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
CRYPTO_set_locking_callback(locking_callback);
}
~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
private:
static void locking_callback(int mode, int type, const char * /*file*/,
int /*line*/) {
auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
if (mode & CRYPTO_LOCK) {
lk.lock();
} else {
lk.unlock();
}
}
};
#endif
class SSLInit {
public:
SSLInit() {
#if OPENSSL_VERSION_NUMBER < 0x1010001fL
SSL_load_error_strings();
SSL_library_init();
#else
OPENSSL_init_ssl(
OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
#endif
}
~SSLInit() {
#if OPENSSL_VERSION_NUMBER < 0x1010001fL
ERR_free_strings();
#endif
}
private:
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSLThreadLocks thread_init_;
#endif
};
// SSL socket stream implementation
@@ -7280,8 +7488,8 @@ inline bool SSLSocketStream::is_readable() const {
}
inline bool SSLSocketStream::is_writable() const {
return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
0;
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
is_socket_alive(sock_);
}
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
@@ -7352,6 +7560,11 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
detail::get_remote_ip_and_port(sock_, ip, port);
}
inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
int &port) const {
detail::get_local_ip_and_port(sock_, ip, port);
}
inline socket_t SSLSocketStream::socket() const { return sock_; }
static SSLInit sslinit_;
@@ -7661,7 +7874,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
return false;
}
auto server_cert = SSL_get_peer_certificate(ssl2);
auto server_cert = SSL_get1_peer_certificate(ssl2);
if (server_cert == nullptr) {
error = Error::SSLServerVerification;
@@ -7883,13 +8096,13 @@ inline Client::Client(const std::string &scheme_host_port,
if (is_ssl) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
cli_ = detail::make_unique<SSLClient>(host, port,
client_cert_path, client_key_path);
cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
client_key_path);
is_ssl_ = is_ssl;
#endif
} else {
cli_ = detail::make_unique<ClientImpl>(host, port,
client_cert_path, client_key_path);
cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
client_key_path);
}
} else {
cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
@@ -7987,6 +8200,9 @@ inline Result Client::Head(const std::string &path, const Headers &headers) {
}
inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
inline Result Client::Post(const std::string &path, const Headers &headers) {
return cli_->Post(path, headers);
}
inline Result Client::Post(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type) {
@@ -8049,6 +8265,12 @@ inline Result Client::Post(const std::string &path, const Headers &headers,
const std::string &boundary) {
return cli_->Post(path, headers, items, boundary);
}
inline Result
Client::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
return cli_->Post(path, headers, items, provider_items);
}
inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
inline Result Client::Put(const std::string &path, const char *body,
size_t content_length,
@@ -8099,6 +8321,25 @@ inline Result Client::Put(const std::string &path, const Headers &headers,
const Params &params) {
return cli_->Put(path, headers, params);
}
inline Result Client::Put(const std::string &path,
const MultipartFormDataItems &items) {
return cli_->Put(path, items);
}
inline Result Client::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items) {
return cli_->Put(path, headers, items);
}
inline Result Client::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const std::string &boundary) {
return cli_->Put(path, headers, items, boundary);
}
inline Result
Client::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
return cli_->Put(path, headers, items, provider_items);
}
inline Result Client::Patch(const std::string &path) {
return cli_->Patch(path);
}
@@ -8304,4 +8545,8 @@ inline SSL_CTX *Client::ssl_context() const {
} // namespace httplib
#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL)
#undef poll
#endif
#endif // CPPHTTPLIB_HTTPLIB_H
+1
View File
@@ -77,6 +77,7 @@ if get_option('cpp-httplib_compile')
dependencies: deps,
cpp_args: args,
version: version,
soversion: version.split('.')[0] + '.' + version.split('.')[1],
install: true
)
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
+93
View File
@@ -0,0 +1,93 @@
cmake_policy(SET CMP0135 NEW)
include(FetchContent)
include(GoogleTest)
set(BUILD_GMOCK OFF)
set(INSTALL_GTEST OFF)
set(gtest_force_shared_crt ON)
FetchContent_Declare(
gtest
URL https://github.com/google/googletest/archive/main.tar.gz
)
FetchContent_MakeAvailable(gtest)
add_executable(httplib-test test.cc)
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
gtest_discover_tests(httplib-test)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
if(HTTPLIB_IS_USING_OPENSSL)
find_program(OPENSSL_COMMAND
NAMES openssl
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
REQUIRED
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa 2048
OUTPUT_FILE key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key.pem
OUTPUT_FILE cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa 2048
OUTPUT_FILE rootCA.key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
OUTPUT_FILE rootCA.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa 2048
OUTPUT_FILE client.key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client.key.pem
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
OUTPUT_FILE client.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa -passout pass:test123! 2048
OUTPUT_FILE key_encrypted.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key_encrypted.pem
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key_encrypted.pem
OUTPUT_FILE cert_encrypted.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endif()
+5 -2
View File
@@ -22,8 +22,6 @@ public:
ssize_t write(const std::string &s) { return write(s.data(), s.size()); }
std::string get_remote_addr() const { return ""; }
bool is_readable() const override { return true; }
bool is_writable() const override { return true; }
@@ -33,6 +31,11 @@ public:
port = 8080;
}
void get_local_ip_and_port(std::string &ip, int &port) const override {
ip = "127.0.0.1";
port = 8080;
}
socket_t socket() const override { return 0; }
private:
+728 -31
View File
@@ -1,10 +1,12 @@
#include <httplib.h>
#include <signal.h>
#include <gtest/gtest.h>
#include <atomic>
#include <chrono>
#include <future>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <thread>
@@ -594,7 +596,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
ASSERT_TRUE(!res);
stringstream s;
s << "error code: " << res.error();
EXPECT_EQ("error code: Connection (2)", s.str());
EXPECT_EQ("error code: Could not establish connection (2)", s.str());
}
TEST(ConnectionErrorTest, InvalidPort) {
@@ -942,6 +944,44 @@ TEST(UrlWithSpace, Redirect_Online) {
#endif
#if !defined(_WIN32) && !defined(_WIN64)
TEST(ReceiveSignals, Signal) {
auto setupSignalHandlers = []() {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = [](int sig, siginfo_t *, void *) {
switch (sig) {
case SIGINT:
default: break;
}
};
::sigaction(SIGINT, &act, nullptr);
};
Server svr;
int port = 0;
auto thread = std::thread([&]() {
setupSignalHandlers();
port = svr.bind_to_any_port("localhost");
svr.listen_after_bind();
});
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
ASSERT_TRUE(svr.is_running());
pthread_kill(thread.native_handle(), SIGINT);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
ASSERT_TRUE(svr.is_running());
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
#endif
TEST(RedirectToDifferentPort, Redirect) {
Server svr1;
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
@@ -1324,7 +1364,12 @@ TEST(NoContentTest, ContentLength) {
}
TEST(RoutingHandlerTest, PreRoutingHandler) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
#else
Server svr;
#endif
svr.set_pre_routing_handler([](const Request &req, Response &res) {
if (req.path == "/routing_handler") {
@@ -1355,7 +1400,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
#else
Client cli(HOST, PORT);
#endif
auto res = cli.Get("/routing_handler");
ASSERT_TRUE(res);
@@ -1368,7 +1418,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
}
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
#else
Client cli(HOST, PORT);
#endif
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
@@ -1379,7 +1434,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
}
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
#else
Client cli(HOST, PORT);
#endif
auto res = cli.Get("/aaa");
ASSERT_TRUE(res);
@@ -1521,6 +1581,17 @@ protected:
std::stoi(req.get_header_value("REMOTE_PORT")));
res.set_content(remote_addr.c_str(), "text/plain");
})
.Get("/local_addr",
[&](const Request &req, Response &res) {
EXPECT_TRUE(req.has_header("LOCAL_PORT"));
EXPECT_TRUE(req.has_header("LOCAL_ADDR"));
auto local_addr = req.get_header_value("LOCAL_ADDR");
auto local_port = req.get_header_value("LOCAL_PORT");
EXPECT_EQ(req.local_addr, local_addr);
EXPECT_EQ(req.local_port, std::stoi(local_port));
res.set_content(local_addr.append(":").append(local_port),
"text/plain");
})
.Get("/endwith%",
[&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
@@ -1579,7 +1650,6 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_chunked_content_provider(
"text/plain", [](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "123";
sink.os << "456";
sink.os << "789";
@@ -1593,7 +1663,6 @@ protected:
res.set_chunked_content_provider(
"text/plain",
[i](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
switch (*i) {
case 0: sink.os << "123"; break;
case 1: sink.os << "456"; break;
@@ -1623,7 +1692,6 @@ protected:
res.set_content_provider(
data->size(), "text/plain",
[data](size_t offset, size_t length, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
size_t DATA_CHUNK_SIZE = 4;
const auto &d = *data;
auto out_len =
@@ -1643,7 +1711,6 @@ protected:
res.set_content_provider(
size_t(-1), "text/plain",
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "data_chunk";
return true;
});
@@ -1714,6 +1781,22 @@ protected:
EXPECT_EQ("0", req.get_header_value("Content-Length"));
res.set_content("empty-no-content-type", "text/plain");
})
.Post("/path-only",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "");
EXPECT_EQ("", req.get_header_value("Content-Type"));
EXPECT_EQ("0", req.get_header_value("Content-Length"));
res.set_content("path-only", "text/plain");
})
.Post("/path-headers-only",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "");
EXPECT_EQ("", req.get_header_value("Content-Type"));
EXPECT_EQ("0", req.get_header_value("Content-Length"));
EXPECT_EQ("world", req.get_header_value("hello"));
EXPECT_EQ("world2", req.get_header_value("hello2"));
res.set_content("path-headers-only", "text/plain");
})
.Post("/post-large",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, LARGE_DATA);
@@ -2124,6 +2207,21 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
ASSERT_EQ("empty-no-content-type", res->body);
}
TEST_F(ServerTest, PostPathOnly) {
auto res = cli_.Post("/path-only");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ("path-only", res->body);
}
TEST_F(ServerTest, PostPathAndHeadersOnly) {
auto res = cli_.Post("/path-headers-only",
Headers({{"hello", "world"}, {"hello2", "world2"}}));
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ("path-headers-only", res->body);
}
TEST_F(ServerTest, PostLarge) {
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
ASSERT_TRUE(res);
@@ -2778,6 +2876,15 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
}
TEST_F(ServerTest, GetMethodLocalAddr) {
auto res = cli_.Get("/local_addr");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_TRUE(res->body == std::string("::1:").append(to_string(PORT)) ||
res->body == std::string("127.0.0.1:").append(to_string(PORT)));
}
TEST_F(ServerTest, HTTPResponseSplitting) {
auto res = cli_.Get("/http_response_splitting");
ASSERT_TRUE(res);
@@ -2840,7 +2947,6 @@ TEST_F(ServerTest, PutWithContentProvider) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
return true;
},
@@ -2867,7 +2973,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
sink.done();
return true;
@@ -2894,7 +2999,6 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
return true;
},
@@ -2923,7 +3027,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
sink.done();
return true;
@@ -3807,9 +3910,12 @@ TEST(MountTest, Unmount) {
TEST(ExceptionTest, ThrowExceptionInHandler) {
Server svr;
svr.Get("/hi", [&](const Request & /*req*/, Response & /*res*/) {
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("exception...");
// res.set_content("Hello World!", "text/plain");
});
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
throw std::runtime_error("exception\r\n...");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
@@ -3822,11 +3928,21 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
Client cli("localhost", PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
{
auto res = cli.Get("/exception");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
}
{
auto res = cli.Get("/unknown");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
}
svr.stop();
listen_thread.join();
@@ -4180,7 +4296,8 @@ protected:
res.set_content("Hello World!", "text/plain");
});
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(std::string(), PORT, AI_PASSIVE)); });
t_ = thread(
[&]() { ASSERT_TRUE(svr_.listen(std::string(), PORT, AI_PASSIVE)); });
while (!svr_.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
@@ -4752,7 +4869,7 @@ TEST(SendAPI, SimpleInterface_Online) {
TEST(ClientImplMethods, GetSocketTest) {
httplib::Server svr;
svr.Get( "/", [&](const httplib::Request& /*req*/, httplib::Response& res) {
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
res.status = 200;
});
@@ -5020,6 +5137,247 @@ TEST(MultipartFormDataTest, LargeData) {
t.join();
}
TEST(MultipartFormDataTest, DataProviderItems) {
std::random_device seed_gen;
std::mt19937 random(seed_gen());
std::string rand1;
rand1.resize(1000);
std::generate(rand1.begin(), rand1.end(), [&]() { return random(); });
std::string rand2;
rand2.resize(3000);
std::generate(rand2.begin(), rand2.end(), [&]() { return random(); });
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
svr.Post("/post-none", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_FALSE(req.is_multipart_form_data());
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
EXPECT_EQ(body, "");
});
svr.Post("/post-items", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_TRUE(req.is_multipart_form_data());
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
ASSERT_TRUE(files.size() == 2);
EXPECT_EQ(std::string(files[0].name), "name1");
EXPECT_EQ(files[0].content, "Testing123");
EXPECT_EQ(files[0].filename, "filename1");
EXPECT_EQ(files[0].content_type, "application/octet-stream");
EXPECT_EQ(files[1].name, "name2");
EXPECT_EQ(files[1].content, "Testing456");
EXPECT_EQ(files[1].filename, "");
EXPECT_EQ(files[1].content_type, "");
});
svr.Post("/post-providers", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_TRUE(req.is_multipart_form_data());
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
ASSERT_TRUE(files.size() == 2);
EXPECT_EQ(files[0].name, "name3");
EXPECT_EQ(files[0].content, rand1);
EXPECT_EQ(files[0].filename, "filename3");
EXPECT_EQ(files[0].content_type, "");
EXPECT_EQ(files[1].name, "name4");
EXPECT_EQ(files[1].content, rand2);
EXPECT_EQ(files[1].filename, "filename4");
EXPECT_EQ(files[1].content_type, "");
});
svr.Post("/post-both", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_TRUE(req.is_multipart_form_data());
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
ASSERT_TRUE(files.size() == 4);
EXPECT_EQ(std::string(files[0].name), "name1");
EXPECT_EQ(files[0].content, "Testing123");
EXPECT_EQ(files[0].filename, "filename1");
EXPECT_EQ(files[0].content_type, "application/octet-stream");
EXPECT_EQ(files[1].name, "name2");
EXPECT_EQ(files[1].content, "Testing456");
EXPECT_EQ(files[1].filename, "");
EXPECT_EQ(files[1].content_type, "");
EXPECT_EQ(files[2].name, "name3");
EXPECT_EQ(files[2].content, rand1);
EXPECT_EQ(files[2].filename, "filename3");
EXPECT_EQ(files[2].content_type, "");
EXPECT_EQ(files[3].name, "name4");
EXPECT_EQ(files[3].content, rand2);
EXPECT_EQ(files[3].filename, "filename4");
EXPECT_EQ(files[3].content_type, "");
});
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"name1", "Testing123", "filename1", "application/octet-stream"},
{"name2", "Testing456", "", ""}, // not a file
};
{
auto res = cli.Post("/post-none", {}, {}, {});
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
MultipartFormDataProviderItems providers;
{
auto res =
cli.Post("/post-items", {}, items, providers); // empty providers
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
providers.push_back({"name3",
[&](size_t offset, httplib::DataSink &sink) -> bool {
// test the offset is given correctly at each step
if (!offset)
sink.os.write(rand1.data(), 30);
else if (offset == 30)
sink.os.write(rand1.data() + 30, 300);
else if (offset == 330)
sink.os.write(rand1.data() + 330, 670);
else if (offset == rand1.size())
sink.done();
return true;
},
"filename3",
{}});
providers.push_back({"name4",
[&](size_t offset, httplib::DataSink &sink) -> bool {
// test the offset is given correctly at each step
if (!offset)
sink.os.write(rand2.data(), 2000);
else if (offset == 2000)
sink.os.write(rand2.data() + 2000, 1);
else if (offset == 2001)
sink.os.write(rand2.data() + 2001, 999);
else if (offset == rand2.size())
sink.done();
return true;
},
"filename4",
{}});
{
auto res = cli.Post("/post-providers", {}, {}, providers);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
{
auto res = cli.Post("/post-both", {}, items, providers);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
}
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, BadHeader) {
Server svr;
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain");
});
thread t = thread([&] { svr.listen(HOST, PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
const std::string body =
"This is the preamble. It is to be ignored, though it\r\n"
"is a handy place for composition agents to include an\r\n"
"explanatory note to non-MIME conformant readers.\r\n"
"\r\n"
"\r\n"
"--simple boundary\r\n"
"Content-Disposition: form-data; name=\"field1\"\r\n"
": BAD...\r\n"
"\r\n"
"value1\r\n"
"--simple boundary\r\n"
"Content-Disposition: form-data; name=\"field2\"; "
"filename=\"example.txt\"\r\n"
"\r\n"
"value2\r\n"
"--simple boundary--\r\n"
"This is the epilogue. It is also to be ignored.\r\n";
std::string content_type =
R"(multipart/form-data; boundary="simple boundary")";
Client cli(HOST, PORT);
auto res = cli.Post("/post", body, content_type.c_str());
ASSERT_TRUE(res);
EXPECT_EQ(400, res->status);
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, WithPreamble) {
Server svr;
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
@@ -5062,14 +5420,248 @@ TEST(MultipartFormDataTest, WithPreamble) {
t.join();
}
TEST(MultipartFormDataTest, PostCustomBoundary) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
svr.Post("/post_customboundary", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
EXPECT_TRUE(std::string(files[0].name) == "document");
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
EXPECT_TRUE(files[0].filename == "2MB_data");
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
EXPECT_TRUE(files[1].name == "hello");
EXPECT_TRUE(files[1].content == "world");
EXPECT_TRUE(files[1].filename == "");
EXPECT_TRUE(files[1].content_type == "");
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
}
});
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '.');
std::stringstream buffer;
buffer << data;
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
{"hello", "world", "", ""},
};
auto res = cli.Post("/post_customboundary", {}, items, "abc-abc");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string data(1024 * 1024 * 2, '&');
std::stringstream buffer;
buffer << data;
Client cli("https://localhost:8080");
MultipartFormDataItems items{
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
{"hello", "world", "", ""},
};
for (const char &c : " \t\r\n") {
auto res =
cli.Post("/invalid_boundary", {}, items, string("abc123").append(1, c));
ASSERT_EQ(Error::UnsupportedMultipartBoundaryChars, res.error());
ASSERT_FALSE(res);
}
}
TEST(MultipartFormDataTest, PutFormData) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
svr.Put("/put", [&](const Request &req, const Response & /*res*/,
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
EXPECT_TRUE(std::string(files[0].name) == "document");
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
EXPECT_TRUE(files[0].filename == "2MB_data");
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
EXPECT_TRUE(files[1].name == "hello");
EXPECT_TRUE(files[1].content == "world");
EXPECT_TRUE(files[1].filename == "");
EXPECT_TRUE(files[1].content_type == "");
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
}
});
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '&');
std::stringstream buffer;
buffer << data;
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
{"hello", "world", "", ""},
};
auto res = cli.Put("/put", items);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, PutFormDataCustomBoundary) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
svr.Put("/put_customboundary",
[&](const Request &req, const Response & /*res*/,
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
EXPECT_TRUE(std::string(files[0].name) == "document");
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
EXPECT_TRUE(files[0].filename == "2MB_data");
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
EXPECT_TRUE(files[1].name == "hello");
EXPECT_TRUE(files[1].content == "world");
EXPECT_TRUE(files[1].filename == "");
EXPECT_TRUE(files[1].content_type == "");
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
}
});
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '&');
std::stringstream buffer;
buffer << data;
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
{"hello", "world", "", ""},
};
auto res = cli.Put("/put_customboundary", {}, items, "abc-abc_");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string data(1024 * 1024 * 2, '&');
std::stringstream buffer;
buffer << data;
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
{"hello", "world", "", ""},
};
for (const char &c : " \t\r\n") {
auto res = cli.Put("/put", {}, items, string("abc123").append(1, c));
ASSERT_EQ(Error::UnsupportedMultipartBoundaryChars, res.error());
ASSERT_FALSE(res);
}
}
#endif
#ifndef _WIN32
class UnixSocketTest : public ::testing::Test {
protected:
void TearDown() override {
std::remove(pathname_.c_str());
}
void TearDown() override { std::remove(pathname_.c_str()); }
void client_GET(const std::string &addr) {
httplib::Client cli{addr};
@@ -5084,9 +5676,9 @@ protected:
EXPECT_EQ(resp.body, content_);
}
const std::string pathname_ {"./httplib-server.sock"};
const std::string pattern_ {"/hi"};
const std::string content_ {"Hello World!"};
const std::string pathname_{"./httplib-server.sock"};
const std::string pattern_{"/hi"};
const std::string content_{"Hello World!"};
};
TEST_F(UnixSocketTest, pathname) {
@@ -5095,8 +5687,9 @@ TEST_F(UnixSocketTest, pathname) {
res.set_content(content_, "text/plain");
});
std::thread t {[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80)); }};
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
}};
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
@@ -5108,18 +5701,45 @@ TEST_F(UnixSocketTest, pathname) {
t.join();
}
#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));
}};
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
ASSERT_TRUE(svr.is_running());
client_GET(pathname_);
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
svr.stop();
t.join();
}
#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};
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)); }};
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
}};
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
@@ -5131,4 +5751,81 @@ TEST_F(UnixSocketTest, abstract) {
t.join();
}
#endif
#endif // #ifndef _WIN32
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_task{1000000};
std::atomic_uint count{0};
std::unique_ptr<TaskQueue> task_queue{
new ThreadPool{CPPHTTPLIB_THREAD_POOL_COUNT}};
for (unsigned int i = 0; i < number_of_task; ++i) {
task_queue->enqueue(
[&count] { count.fetch_add(1, std::memory_order_relaxed); });
}
EXPECT_NO_THROW(task_queue->shutdown());
EXPECT_EQ(number_of_task, count.load());
}