mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85b18a9c64 | |||
| 0853ce7753 | |||
| c99d7472b5 | |||
| defd907c74 | |||
| c6c75e4c69 | |||
| c2002f6e06 | |||
| 7c33fd47bf | |||
| 21243b3c9e | |||
| e068da4f6b | |||
| f6aec98145 | |||
| f29bb15f9d | |||
| c53d93d145 | |||
| b4d16a582d | |||
| e61a8bcec7 | |||
| 33dbe00cce |
+9
-6
@@ -10,7 +10,7 @@
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_REQUIRE_ZSTD (default off)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES (default off)
|
||||
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
@@ -51,7 +51,7 @@
|
||||
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
|
||||
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
|
||||
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
|
||||
* HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled.
|
||||
* HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES - a bool for if support of loading system certs from the Apple Keychain is enabled.
|
||||
* HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO - a bool for if nonblocking getaddrinfo is enabled.
|
||||
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
|
||||
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
|
||||
@@ -107,7 +107,7 @@ option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
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)
|
||||
option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
|
||||
option(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES "Disable loading system certs from the Apple Keychain on macOS." OFF)
|
||||
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
||||
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
||||
@@ -152,7 +152,10 @@ endif()
|
||||
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
|
||||
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES TRUE)
|
||||
if(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES)
|
||||
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES FALSE)
|
||||
endif()
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
@@ -316,7 +319,7 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:crypt32>
|
||||
# Needed for API from MacOS Security framework
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}>>:-framework CFNetwork -framework CoreFoundation -framework Security>"
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:-framework CFNetwork -framework CoreFoundation -framework Security>"
|
||||
# Needed for non-blocking getaddrinfo on MacOS
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>>:-framework CFNetwork -framework CoreFoundation>"
|
||||
# Can't put multiple targets in a single generator expression or it bugs out.
|
||||
@@ -336,7 +339,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES>
|
||||
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
|
||||
)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ cpp-httplib supports multiple TLS backends through an abstraction layer:
|
||||
> **Mbed TLS / wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()` or `load_ca_cert_store(pem, size)`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable with these backends.
|
||||
|
||||
> [!TIP]
|
||||
> For macOS: cpp-httplib can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
|
||||
> For macOS: cpp-httplib automatically loads system certs from the Keychain when a TLS backend is enabled. `CoreFoundation` and `Security` must be linked with `-framework`. To disable this, define `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`.
|
||||
|
||||
```c++
|
||||
// Use either OpenSSL, Mbed TLS, or wolfSSL
|
||||
@@ -989,7 +989,7 @@ httplib::UploadFormDataItems items = {
|
||||
auto res = cli.Post("/multipart", items);
|
||||
```
|
||||
|
||||
To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is read and sent in chunks with a correct `Content-Length` header.
|
||||
To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is sent with chunked transfer encoding.
|
||||
|
||||
```cpp
|
||||
httplib::FormDataProviderItems providers = {
|
||||
@@ -1000,6 +1000,15 @@ httplib::FormDataProviderItems providers = {
|
||||
auto res = cli.Post("/upload", {}, {}, providers);
|
||||
```
|
||||
|
||||
### POST with a file body
|
||||
|
||||
To POST a file as a raw binary body with `Content-Length`, use `make_file_body`.
|
||||
|
||||
```cpp
|
||||
auto [size, provider] = httplib::make_file_body("/path/to/data.bin");
|
||||
auto res = cli.Post("/upload", size, provider, "application/octet-stream");
|
||||
```
|
||||
|
||||
### PUT
|
||||
|
||||
```c++
|
||||
|
||||
+8
-5
@@ -1,4 +1,7 @@
|
||||
CXXFLAGS = -std=c++11 -O2 -I..
|
||||
CXXFLAGS = -O2 -I..
|
||||
|
||||
CPPHTTPLIB_CXXFLAGS = -std=c++11
|
||||
CROW_CXXFLAGS = -std=c++17
|
||||
|
||||
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
||||
|
||||
@@ -18,11 +21,11 @@ run : server
|
||||
@./server
|
||||
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@echo "-------------\n Crow v1.2.0\n-------------\n"
|
||||
@echo "-------------\n Crow v1.3.1\n-------------\n"
|
||||
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
@@ -32,8 +35,8 @@ monitor-crow: server-crow
|
||||
run-crow : server-crow
|
||||
@./server-crow
|
||||
|
||||
server-crow : crow/main.cpp
|
||||
@g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||
server-crow : crow/main.cpp crow/crow_all.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CROW_CXXFLAGS) crow/main.cpp
|
||||
|
||||
# misc
|
||||
build: server server-crow
|
||||
|
||||
+10819
-11339
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
class CustomLogger : public crow::ILogHandler {
|
||||
public:
|
||||
void log(std::string, crow::LogLevel) {}
|
||||
void log(const std::string &, crow::LogLevel) {}
|
||||
};
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.33.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x002100"
|
||||
#define CPPHTTPLIB_VERSION "0.35.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x002300"
|
||||
|
||||
/*
|
||||
* Platform compatibility check
|
||||
@@ -357,14 +357,32 @@ using socket_t = int;
|
||||
#include <any>
|
||||
#endif
|
||||
|
||||
// On macOS with a TLS backend, enable Keychain root certificates by default
|
||||
// unless the user explicitly opts out.
|
||||
#if defined(__APPLE__) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) && \
|
||||
(defined(CPPHTTPLIB_OPENSSL_SUPPORT) || \
|
||||
defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || \
|
||||
defined(CPPHTTPLIB_WOLFSSL_SUPPORT))
|
||||
#ifndef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#define CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// On Windows, enable Schannel certificate verification by default
|
||||
// unless the user explicitly opts out.
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#define CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
#endif
|
||||
|
||||
#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \
|
||||
defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
#if TARGET_OS_MAC
|
||||
#include <CFNetwork/CFHost.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or
|
||||
// CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#ifdef _WIN32
|
||||
@@ -382,11 +400,11 @@ using socket_t = int;
|
||||
#endif
|
||||
#endif // _WIN32
|
||||
|
||||
#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#if TARGET_OS_MAC
|
||||
#include <Security/Security.h>
|
||||
#endif
|
||||
#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
|
||||
#endif
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -430,11 +448,11 @@ using socket_t = int;
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#endif
|
||||
#endif // _WIN32
|
||||
#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#if TARGET_OS_MAC
|
||||
#include <Security/Security.h>
|
||||
#endif
|
||||
#endif // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#endif
|
||||
|
||||
// Mbed TLS 3.x API compatibility
|
||||
#if MBEDTLS_VERSION_MAJOR >= 3
|
||||
@@ -473,11 +491,11 @@ using socket_t = int;
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#endif
|
||||
#endif // _WIN32
|
||||
#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#if TARGET_OS_MAC
|
||||
#include <Security/Security.h>
|
||||
#endif
|
||||
#endif // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
#endif
|
||||
#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
|
||||
|
||||
// Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available
|
||||
@@ -1042,6 +1060,32 @@ make_file_provider(const std::string &name, const std::string &filepath,
|
||||
return fdp;
|
||||
}
|
||||
|
||||
inline std::pair<size_t, ContentProvider>
|
||||
make_file_body(const std::string &filepath) {
|
||||
std::ifstream f(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!f) { return {0, ContentProvider{}}; }
|
||||
auto size = static_cast<size_t>(f.tellg());
|
||||
|
||||
ContentProvider provider = [filepath](size_t offset, size_t length,
|
||||
DataSink &sink) -> bool {
|
||||
std::ifstream f(filepath, std::ios::binary);
|
||||
if (!f) { return false; }
|
||||
f.seekg(static_cast<std::streamoff>(offset));
|
||||
if (!f.good()) { return false; }
|
||||
char buf[8192];
|
||||
while (length > 0) {
|
||||
auto to_read = (std::min)(sizeof(buf), length);
|
||||
f.read(buf, static_cast<std::streamsize>(to_read));
|
||||
auto n = static_cast<size_t>(f.gcount());
|
||||
if (n == 0) { break; }
|
||||
if (!sink.write(buf, n)) { return false; }
|
||||
length -= n;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return {size, std::move(provider)};
|
||||
}
|
||||
|
||||
using ContentReceiverWithProgress = std::function<bool(
|
||||
const char *data, size_t data_length, size_t offset, size_t total_length)>;
|
||||
|
||||
@@ -1356,6 +1400,7 @@ public:
|
||||
virtual bool is_readable() const = 0;
|
||||
virtual bool wait_readable() const = 0;
|
||||
virtual bool wait_writable() const = 0;
|
||||
virtual bool is_peer_alive() const { return wait_writable(); }
|
||||
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
@@ -2534,8 +2579,7 @@ public:
|
||||
|
||||
tls::ctx_t tls_context() const;
|
||||
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
void enable_windows_certificate_verification(bool enabled);
|
||||
#endif
|
||||
|
||||
@@ -2656,8 +2700,7 @@ public:
|
||||
|
||||
tls::ctx_t tls_context() const { return ctx_; }
|
||||
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
void enable_windows_certificate_verification(bool enabled);
|
||||
#endif
|
||||
|
||||
@@ -2689,8 +2732,7 @@ private:
|
||||
|
||||
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
|
||||
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
bool enable_windows_cert_verification_ = true;
|
||||
#endif
|
||||
|
||||
@@ -5427,6 +5469,7 @@ public:
|
||||
bool is_readable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
bool is_peer_alive() const override;
|
||||
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;
|
||||
@@ -6337,10 +6380,46 @@ find_content_type(const std::string &path,
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string
|
||||
extract_media_type(const std::string &content_type,
|
||||
std::map<std::string, std::string> *params = nullptr) {
|
||||
// Extract type/subtype from Content-Type value (RFC 2045)
|
||||
// e.g. "application/json; charset=utf-8" -> "application/json"
|
||||
auto media_type = content_type;
|
||||
auto semicolon_pos = media_type.find(';');
|
||||
if (semicolon_pos != std::string::npos) {
|
||||
auto param_str = media_type.substr(semicolon_pos + 1);
|
||||
media_type = media_type.substr(0, semicolon_pos);
|
||||
|
||||
if (params) {
|
||||
// Parse parameters: key=value pairs separated by ';'
|
||||
split(param_str.data(), param_str.data() + param_str.size(), ';',
|
||||
[&](const char *b, const char *e) {
|
||||
std::string key;
|
||||
std::string val;
|
||||
split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||
if (key.empty()) {
|
||||
key.assign(b2, e2);
|
||||
} else {
|
||||
val.assign(b2, e2);
|
||||
}
|
||||
});
|
||||
if (!key.empty()) {
|
||||
params->emplace(trim_copy(key), trim_double_quotes_copy(val));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Trim whitespace from media type
|
||||
return trim_copy(media_type);
|
||||
}
|
||||
|
||||
inline bool can_compress_content_type(const std::string &content_type) {
|
||||
using udl::operator""_t;
|
||||
|
||||
auto tag = str2tag(content_type);
|
||||
auto mime_type = extract_media_type(content_type);
|
||||
auto tag = str2tag(mime_type);
|
||||
|
||||
switch (tag) {
|
||||
case "image/svg+xml"_t:
|
||||
@@ -6352,7 +6431,7 @@ inline bool can_compress_content_type(const std::string &content_type) {
|
||||
|
||||
case "text/event-stream"_t: return false;
|
||||
|
||||
default: return !content_type.rfind("text/", 0);
|
||||
default: return !mime_type.rfind("text/", 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6907,7 +6986,8 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
template <typename T, typename U>
|
||||
bool prepare_content_receiver(T &x, int &status,
|
||||
ContentReceiverWithProgress receiver,
|
||||
bool decompress, U callback) {
|
||||
bool decompress, size_t payload_max_length,
|
||||
bool &exceed_payload_max_length, U callback) {
|
||||
if (decompress) {
|
||||
std::string encoding = x.get_header_value("Content-Encoding");
|
||||
std::unique_ptr<decompressor> decompressor;
|
||||
@@ -6923,12 +7003,22 @@ bool prepare_content_receiver(T &x, int &status,
|
||||
|
||||
if (decompressor) {
|
||||
if (decompressor->is_valid()) {
|
||||
size_t decompressed_size = 0;
|
||||
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
|
||||
size_t off, size_t len) {
|
||||
return decompressor->decompress(buf, n,
|
||||
[&](const char *buf2, size_t n2) {
|
||||
return receiver(buf2, n2, off, len);
|
||||
});
|
||||
return decompressor->decompress(
|
||||
buf, n, [&](const char *buf2, size_t n2) {
|
||||
// Guard against zip-bomb: check
|
||||
// decompressed size against limit.
|
||||
if (payload_max_length > 0 &&
|
||||
(decompressed_size >= payload_max_length ||
|
||||
n2 > payload_max_length - decompressed_size)) {
|
||||
exceed_payload_max_length = true;
|
||||
return false;
|
||||
}
|
||||
decompressed_size += n2;
|
||||
return receiver(buf2, n2, off, len);
|
||||
});
|
||||
};
|
||||
return callback(std::move(out));
|
||||
} else {
|
||||
@@ -6949,11 +7039,14 @@ template <typename T>
|
||||
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
DownloadProgress progress,
|
||||
ContentReceiverWithProgress receiver, bool decompress) {
|
||||
bool exceed_payload_max_length = false;
|
||||
return prepare_content_receiver(
|
||||
x, status, std::move(receiver), decompress,
|
||||
[&](const ContentReceiverWithProgress &out) {
|
||||
x, status, std::move(receiver), decompress, payload_max_length,
|
||||
exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
|
||||
auto ret = true;
|
||||
auto exceed_payload_max_length = false;
|
||||
// Note: exceed_payload_max_length may also be set by the decompressor
|
||||
// wrapper in prepare_content_receiver when the decompressed payload
|
||||
// size exceeds the limit.
|
||||
|
||||
if (is_chunked_transfer_encoding(x.headers)) {
|
||||
auto result = read_content_chunked(strm, x, payload_max_length, out);
|
||||
@@ -7080,10 +7173,10 @@ inline bool write_content_with_progress(Stream &strm,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
|
||||
|
||||
while (offset < end_offset && !is_shutting_down()) {
|
||||
if (!strm.wait_writable()) {
|
||||
if (!strm.wait_writable() || !strm.is_peer_alive()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
@@ -7095,6 +7188,11 @@ inline bool write_content_with_progress(Stream &strm,
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < end_offset) { // exited due to is_shutting_down(), not completion
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
}
|
||||
@@ -7134,12 +7232,12 @@ write_content_without_length(Stream &strm,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
|
||||
|
||||
data_sink.done = [&](void) { data_available = false; };
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!strm.wait_writable()) {
|
||||
if (!strm.wait_writable() || !strm.is_peer_alive()) {
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
return false;
|
||||
@@ -7147,7 +7245,8 @@ write_content_without_length(Stream &strm,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return !data_available; // true only if done() was called, false if shutting
|
||||
// down
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -7183,7 +7282,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
|
||||
|
||||
auto done_with_trailer = [&](const Headers *trailer) {
|
||||
if (!ok) { return; }
|
||||
@@ -7233,7 +7332,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!strm.wait_writable()) {
|
||||
if (!strm.wait_writable() || !strm.is_peer_alive()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
@@ -7245,6 +7344,11 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
}
|
||||
}
|
||||
|
||||
if (data_available) { // exited due to is_shutting_down(), not done()
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
}
|
||||
@@ -7358,12 +7462,11 @@ inline std::string normalize_query_string(const std::string &query) {
|
||||
|
||||
inline bool parse_multipart_boundary(const std::string &content_type,
|
||||
std::string &boundary) {
|
||||
auto boundary_keyword = "boundary=";
|
||||
auto pos = content_type.find(boundary_keyword);
|
||||
if (pos == std::string::npos) { return false; }
|
||||
auto end = content_type.find(';', pos);
|
||||
auto beg = pos + strlen(boundary_keyword);
|
||||
boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
|
||||
std::map<std::string, std::string> params;
|
||||
extract_media_type(content_type, ¶ms);
|
||||
auto it = params.find("boundary");
|
||||
if (it == params.end()) { return false; }
|
||||
boundary = it->second;
|
||||
return !boundary.empty();
|
||||
}
|
||||
|
||||
@@ -7531,11 +7634,7 @@ inline bool parse_accept_header(const std::string &s,
|
||||
}
|
||||
|
||||
// Remove additional parameters from media type
|
||||
auto param_pos = accept_entry.media_type.find(';');
|
||||
if (param_pos != std::string::npos) {
|
||||
accept_entry.media_type =
|
||||
trim_copy(accept_entry.media_type.substr(0, param_pos));
|
||||
}
|
||||
accept_entry.media_type = extract_media_type(accept_entry.media_type);
|
||||
|
||||
// Basic validation of media type format
|
||||
if (accept_entry.media_type.empty()) {
|
||||
@@ -7951,10 +8050,19 @@ make_multipart_content_provider(const UploadFormDataItems &items,
|
||||
}
|
||||
segs.push_back({owned.back().data(), owned.back().size()});
|
||||
|
||||
return [owned = std::move(owned), segs = std::move(segs)](
|
||||
size_t offset, size_t length, DataSink &sink) -> bool {
|
||||
struct MultipartState {
|
||||
std::vector<std::string> owned;
|
||||
std::vector<MultipartSegment> segs;
|
||||
};
|
||||
auto state = std::make_shared<MultipartState>();
|
||||
state->owned = std::move(owned);
|
||||
// `segs` holds raw pointers into owned strings; std::string move preserves
|
||||
// the data pointer, so these pointers remain valid after the move above.
|
||||
state->segs = std::move(segs);
|
||||
|
||||
return [state](size_t offset, size_t length, DataSink &sink) -> bool {
|
||||
size_t pos = 0;
|
||||
for (const auto &seg : segs) {
|
||||
for (const auto &seg : state->segs) {
|
||||
// Loop invariant: pos <= offset (proven by advancing pos only when
|
||||
// offset - pos >= seg.size, i.e., the segment doesn't contain offset)
|
||||
if (seg.size > 0 && offset - pos < seg.size) {
|
||||
@@ -8404,6 +8512,7 @@ public:
|
||||
bool is_readable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
bool is_peer_alive() const override;
|
||||
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;
|
||||
@@ -9355,7 +9464,7 @@ inline size_t Request::get_param_value_count(const std::string &key) const {
|
||||
|
||||
inline bool Request::is_multipart_form_data() const {
|
||||
const auto &content_type = get_header_value("Content-Type");
|
||||
return !content_type.rfind("multipart/form-data", 0);
|
||||
return detail::extract_media_type(content_type) == "multipart/form-data";
|
||||
}
|
||||
|
||||
// Multipart FormData implementation
|
||||
@@ -9827,8 +9936,11 @@ inline bool SocketStream::wait_readable() const {
|
||||
}
|
||||
|
||||
inline bool SocketStream::wait_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_);
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_peer_alive() const {
|
||||
return detail::is_socket_alive(sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
@@ -10159,7 +10271,11 @@ inline bool SSLSocketStream::wait_readable() const {
|
||||
|
||||
inline bool SSLSocketStream::wait_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_) && !tls::is_peer_closed(session_, sock_);
|
||||
!tls::is_peer_closed(session_, sock_);
|
||||
}
|
||||
|
||||
inline bool SSLSocketStream::is_peer_alive() const {
|
||||
return !tls::is_peer_closed(session_, sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
@@ -10683,35 +10799,33 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
if (post_routing_handler_) { post_routing_handler_(req, res); }
|
||||
|
||||
// Response line and headers
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
if (!detail::write_response_line(bstrm, res.status)) { return false; }
|
||||
if (header_writer_(bstrm, res.headers) <= 0) { return false; }
|
||||
detail::BufferStream bstrm;
|
||||
if (!detail::write_response_line(bstrm, res.status)) { return false; }
|
||||
if (header_writer_(bstrm, res.headers) <= 0) { return false; }
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
detail::write_data(strm, data.data(), data.size());
|
||||
// Combine small body with headers to reduce write syscalls
|
||||
if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
|
||||
bstrm.write(res.body.data(), res.body.size());
|
||||
}
|
||||
|
||||
// Body
|
||||
// Log before writing to avoid race condition with client-side code that
|
||||
// accesses logger-captured data immediately after receiving the response.
|
||||
output_log(req, res);
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
if (!detail::write_data(strm, data.data(), data.size())) { return false; }
|
||||
|
||||
// Streaming body
|
||||
auto ret = true;
|
||||
if (req.method != "HEAD") {
|
||||
if (!res.body.empty()) {
|
||||
if (!detail::write_data(strm, res.body.data(), res.body.size())) {
|
||||
ret = false;
|
||||
}
|
||||
} else if (res.content_provider_) {
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
if (req.method != "HEAD" && res.content_provider_) {
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Log
|
||||
output_log(req, res);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -10832,7 +10946,8 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
|
||||
return true;
|
||||
})) {
|
||||
const auto &content_type = req.get_header_value("Content-Type");
|
||||
if (!content_type.find("application/x-www-form-urlencoded")) {
|
||||
if (detail::extract_media_type(content_type) ==
|
||||
"application/x-www-form-urlencoded") {
|
||||
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
|
||||
res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
|
||||
output_error_log(Error::ExceedMaxPayloadSize, &req);
|
||||
@@ -11219,45 +11334,63 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
||||
if (detail::expect_content(req)) {
|
||||
// Content reader handler
|
||||
{
|
||||
// Track whether the ContentReader was aborted due to the decompressed
|
||||
// payload exceeding `payload_max_length_`.
|
||||
// The user handler runs after the lambda returns, so we must restore the
|
||||
// 413 status if the handler overwrites it.
|
||||
bool content_reader_payload_too_large = false;
|
||||
|
||||
ContentReader reader(
|
||||
[&](ContentReceiver receiver) {
|
||||
auto result = read_content_with_content_receiver(
|
||||
strm, req, res, std::move(receiver), nullptr, nullptr);
|
||||
if (!result) { output_error_log(Error::Read, &req); }
|
||||
if (!result) {
|
||||
output_error_log(Error::Read, &req);
|
||||
if (res.status == StatusCode::PayloadTooLarge_413) {
|
||||
content_reader_payload_too_large = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[&](FormDataHeader header, ContentReceiver receiver) {
|
||||
auto result = read_content_with_content_receiver(
|
||||
strm, req, res, nullptr, std::move(header),
|
||||
std::move(receiver));
|
||||
if (!result) { output_error_log(Error::Read, &req); }
|
||||
if (!result) {
|
||||
output_error_log(Error::Read, &req);
|
||||
if (res.status == StatusCode::PayloadTooLarge_413) {
|
||||
content_reader_payload_too_large = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
bool dispatched = false;
|
||||
if (req.method == "POST") {
|
||||
if (dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader),
|
||||
post_handlers_for_content_reader_)) {
|
||||
return true;
|
||||
}
|
||||
dispatched = dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader), post_handlers_for_content_reader_);
|
||||
} else if (req.method == "PUT") {
|
||||
if (dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader),
|
||||
put_handlers_for_content_reader_)) {
|
||||
return true;
|
||||
}
|
||||
dispatched = dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader), put_handlers_for_content_reader_);
|
||||
} else if (req.method == "PATCH") {
|
||||
if (dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader),
|
||||
patch_handlers_for_content_reader_)) {
|
||||
return true;
|
||||
}
|
||||
dispatched = dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader), patch_handlers_for_content_reader_);
|
||||
} else if (req.method == "DELETE") {
|
||||
if (dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader),
|
||||
delete_handlers_for_content_reader_)) {
|
||||
return true;
|
||||
dispatched = dispatch_request_for_content_reader(
|
||||
req, res, std::move(reader), delete_handlers_for_content_reader_);
|
||||
}
|
||||
|
||||
if (dispatched) {
|
||||
if (content_reader_payload_too_large) {
|
||||
// Enforce the limit: override any status the handler may have set
|
||||
// and return false so the error path sends a plain 413 response.
|
||||
res.status = StatusCode::PayloadTooLarge_413;
|
||||
res.body.clear();
|
||||
res.content_length_ = 0;
|
||||
res.content_provider_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11670,16 +11803,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
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_) {
|
||||
@@ -11688,7 +11811,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
res.set_header("EXCEPTION_WHAT", "UNKNOWN");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -15369,8 +15491,7 @@ inline void SSLClient::set_session_verifier(
|
||||
session_verifier_ = std::move(verifier);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
|
||||
enable_windows_cert_verification_ = enabled;
|
||||
}
|
||||
@@ -15528,8 +15649,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
// Additional Windows Schannel verification.
|
||||
// This provides real-time certificate validation with Windows Update
|
||||
// integration, working with both OpenSSL and MbedTLS backends.
|
||||
@@ -15575,8 +15695,7 @@ inline void Client::enable_server_hostname_verification(bool enabled) {
|
||||
cli_->enable_server_hostname_verification(enabled);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && \
|
||||
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
inline void Client::enable_windows_certificate_verification(bool enabled) {
|
||||
if (is_ssl_) {
|
||||
static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
|
||||
@@ -15699,7 +15818,7 @@ inline bool enumerate_windows_system_certs(Callback cb) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
// Enumerate macOS Keychain certificates and call callback with DER data
|
||||
template <typename Callback>
|
||||
inline bool enumerate_macos_keychain_certs(Callback cb) {
|
||||
|
||||
+6
-4
@@ -62,11 +62,13 @@ if get_option('tls').allowed()
|
||||
endif
|
||||
|
||||
if tls_found and host_machine.system() == 'darwin'
|
||||
macosx_keychain_dep = dependency('appleframeworks', modules: ['CFNetwork', 'CoreFoundation', 'Security'], required: get_option('macosx_keychain'))
|
||||
if macosx_keychain_dep.found()
|
||||
disable_macosx_certs = get_option('disable_macosx_automatic_root_certificates')
|
||||
if disable_macosx_certs
|
||||
args += '-DCPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES'
|
||||
else
|
||||
macosx_keychain_dep = dependency('appleframeworks', modules: ['CFNetwork', 'CoreFoundation', 'Security'], required: true)
|
||||
deps += macosx_keychain_dep
|
||||
args += '-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
+2
-1
@@ -7,7 +7,7 @@ option('tls_backend', type: 'combo', choices: ['openssl', 'mbedtls'], value: 'op
|
||||
option('zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('zstd', type: 'feature', value: 'auto', description: 'Enable zstd support')
|
||||
option('macosx_keychain', type: 'feature', value: 'auto', description: 'Enable loading certs from the Keychain on Apple devices')
|
||||
option('disable_macosx_automatic_root_certificates', type: 'boolean', value: false, description: 'Disable loading system certs from the Apple Keychain on macOS')
|
||||
option('non_blocking_getaddrinfo', type: 'feature', value: 'auto', description: 'Enable asynchronous name lookup')
|
||||
option('compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('test', type: 'boolean', value: false, description: 'Build tests')
|
||||
@@ -17,6 +17,7 @@ option('openssl', type: 'feature', deprecated: 'tls')
|
||||
option('cpp-httplib_openssl', type: 'feature', deprecated: 'openssl')
|
||||
option('cpp-httplib_zlib', type: 'feature', deprecated: 'zlib')
|
||||
option('cpp-httplib_brotli', type: 'feature', deprecated: 'brotli')
|
||||
option('macosx_keychain', type: 'feature', deprecated: true, description: 'Deprecated: use disable_macosx_automatic_root_certificates instead')
|
||||
option('cpp-httplib_macosx_keychain', type: 'feature', deprecated: 'macosx_keychain')
|
||||
option('cpp-httplib_non_blocking_getaddrinfo', type: 'feature', deprecated: 'non_blocking_getaddrinfo')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, deprecated: 'compile')
|
||||
|
||||
+3
-3
@@ -7,13 +7,13 @@ ifneq ($(OS), Windows_NT)
|
||||
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
|
||||
OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
|
||||
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
MBEDTLS_DIR ?= $(shell brew --prefix mbedtls@3)
|
||||
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto
|
||||
MBEDTLS_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
|
||||
MBEDTLS_SUPPORT += -framework CoreFoundation -framework Security
|
||||
WOLFSSL_DIR ?= $(shell brew --prefix wolfssl)
|
||||
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -I$(WOLFSSL_DIR)/include -I$(WOLFSSL_DIR)/include/wolfssl -L$(WOLFSSL_DIR)/lib -lwolfssl
|
||||
WOLFSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
|
||||
WOLFSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
else
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto
|
||||
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto
|
||||
|
||||
+133
-11
@@ -323,9 +323,10 @@ TEST(SocketStream, wait_writable_UNIX) {
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.wait_writable());
|
||||
EXPECT_TRUE(s0.is_peer_alive());
|
||||
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.wait_writable());
|
||||
EXPECT_FALSE(s0.is_peer_alive());
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -367,7 +368,9 @@ TEST(SocketStream, wait_writable_INET) {
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.wait_writable());
|
||||
// wait_writable() returns true because select_write() only checks if the
|
||||
// send buffer has space. Peer disconnection is detected later by send().
|
||||
EXPECT_TRUE(ss.wait_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -2698,16 +2701,14 @@ TEST(ExceptionTest, WithoutExceptionHandler) {
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
EXPECT_FALSE(res->has_header("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
EXPECT_FALSE(res->has_header("EXCEPTION_WHAT"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3886,6 +3887,13 @@ protected:
|
||||
"01234567890123456789012345678901234567890",
|
||||
"text/plain");
|
||||
})
|
||||
.Get("/compress-with-charset",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(
|
||||
"12345678901234567890123456789012345678901234567890123456789"
|
||||
"01234567890123456789012345678901234567890",
|
||||
"application/json; charset=utf-8");
|
||||
})
|
||||
.Get("/nocompress",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(
|
||||
@@ -4802,12 +4810,9 @@ TEST_F(ServerTest, HeaderCountSecurityTest) {
|
||||
if (res) {
|
||||
// If we get a response, it should be 400 Bad Request
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
} else {
|
||||
// Request failed, which is the expected behavior for DoS protection
|
||||
EXPECT_FALSE(res);
|
||||
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||
}
|
||||
|
||||
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||
EXPECT_FALSE(cli_.is_socket_open());
|
||||
}
|
||||
|
||||
@@ -6249,6 +6254,21 @@ TEST_F(ServerTest, Gzip) {
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithContentTypeParameters) {
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "gzip, deflate");
|
||||
auto res = cli_.Get("/compress-with-charset", headers);
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_EQ("application/json; charset=utf-8",
|
||||
res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
"7890123456789012345678901234567890",
|
||||
res->body);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "");
|
||||
@@ -8651,6 +8671,68 @@ TEST_F(LargePayloadMaxLengthTest, NoContentLengthExceeds10MB) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
// `payload_max_length` is not enforced on decompressed body in ContentReader
|
||||
// path.
|
||||
TEST(PayloadLimitBypassTest, StreamingGzipDecompression) {
|
||||
Server svr;
|
||||
const size_t LIMIT = 64 * 1024; // 64KB
|
||||
svr.set_payload_max_length(LIMIT);
|
||||
|
||||
size_t total = 0;
|
||||
svr.Post("/stream", [&](const Request & /*req*/, Response &res,
|
||||
const ContentReader &content_reader) {
|
||||
content_reader([&](const char * /*data*/, size_t len) {
|
||||
total += len;
|
||||
return true;
|
||||
});
|
||||
res.status = 200;
|
||||
res.set_content("stream_ok", "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Prepare 256KB raw data and gzip-compress it
|
||||
std::string raw(256 * 1024, 'A');
|
||||
std::string gz;
|
||||
{
|
||||
z_stream zs{};
|
||||
deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
zs.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(raw.data()));
|
||||
zs.avail_in = static_cast<uInt>(raw.size());
|
||||
char outbuf[4096];
|
||||
int ret;
|
||||
do {
|
||||
zs.next_out = reinterpret_cast<Bytef *>(outbuf);
|
||||
zs.avail_out = sizeof(outbuf);
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
gz.append(outbuf, sizeof(outbuf) - zs.avail_out);
|
||||
} while (ret != Z_STREAM_END);
|
||||
deflateEnd(&zs);
|
||||
}
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
Headers headers = {{"Content-Encoding", "gzip"}};
|
||||
auto res = cli.Post("/stream", headers, gz.data(), gz.size(),
|
||||
"application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
|
||||
// Server must reject oversized decompressed payloads with 413.
|
||||
EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status);
|
||||
|
||||
// Decompressed bytes delivered to the handler must not exceed LIMIT.
|
||||
EXPECT_LE(total, LIMIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Regression test for DoS vulnerability: a malicious server sending a response
|
||||
// without Content-Length header must not cause unbounded memory consumption on
|
||||
// the client side. The client should stop reading after a reasonable limit,
|
||||
@@ -11821,6 +11903,46 @@ TEST(MultipartFormDataTest, MakeFileProvider) {
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(MakeFileBodyTest, Basic) {
|
||||
const std::string file_content(4096, 'Z');
|
||||
const std::string tmp_path = "/tmp/httplib_test_make_file_body.bin";
|
||||
{
|
||||
std::ofstream ofs(tmp_path, std::ios::binary);
|
||||
ofs.write(file_content.data(),
|
||||
static_cast<std::streamsize>(file_content.size()));
|
||||
}
|
||||
|
||||
auto handled = false;
|
||||
|
||||
Server svr;
|
||||
svr.Post("/upload", [&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(file_content, req.body);
|
||||
handled = true;
|
||||
res.status = StatusCode::OK_200;
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
auto t = thread([&] { svr.listen_after_bind(); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
ASSERT_TRUE(handled);
|
||||
std::remove(tmp_path.c_str());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto fb = make_file_body(tmp_path);
|
||||
ASSERT_GT(fb.first, 0u);
|
||||
|
||||
Client cli(HOST, port);
|
||||
auto res =
|
||||
cli.Post("/upload", fb.first, fb.second, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
std::atomic_uint count{0};
|
||||
@@ -15620,7 +15742,7 @@ TEST(ZipBombProtectionTest, DecompressedSizeExceedsLimit) {
|
||||
|
||||
// Server should reject because decompressed size (8KB) exceeds limit (1KB)
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user