mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 219d13b718 | |||
| df20c27696 | |||
| a5a62768c0 | |||
| 4001637beb | |||
| 47044c05a8 | |||
| a449d82723 | |||
| fee8e97b4e | |||
| 72d9ed4056 | |||
| 1be1b3a86d | |||
| 9452c0a4b6 | |||
| 307b729549 | |||
| 696239d6e1 | |||
| 6929d90353 | |||
| 348d032029 | |||
| d1d3fcdfd5 | |||
| abf3a67dd0 | |||
| d87abeecf0 | |||
| 4e28e4f741 | |||
| 80a55cedeb | |||
| d05c343602 | |||
| 33f67386fe | |||
| 56d8168dc4 | |||
| 5d87cc0558 | |||
| cb41947eb4 | |||
| 0857eba17b | |||
| 020b0db090 | |||
| bf0760fde4 | |||
| bb8e45383e | |||
| a1df576e4f | |||
| 7fb0254794 | |||
| c82d1e52cc | |||
| 846151b605 | |||
| e44e31dd5b | |||
| f7b9501662 | |||
| e12fe4cbbb | |||
| 49d2e1f135 | |||
| 8191fd8e6c |
+12
-18
@@ -60,10 +60,10 @@
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
|
||||
# Get the user agent and use it as a version
|
||||
# This gets the string with the user agent from the header.
|
||||
# Get the CPPHTTPLIB_VERSION value and use it as a version
|
||||
# This gets the string with the CPPHTTPLIB_VERSION value from the header.
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "User\-Agent.*cpp\-httplib/([0-9]+\.?)+")
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
|
||||
|
||||
# Needed since git tags have "v" prefixing them.
|
||||
# Also used if the fallback to user agent string is being used.
|
||||
@@ -100,6 +100,7 @@ if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
endif()
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||
find_package(Threads REQUIRED)
|
||||
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
|
||||
if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
@@ -160,7 +161,7 @@ if(HTTPLIB_COMPILE)
|
||||
ERROR_VARIABLE _httplib_split_error
|
||||
)
|
||||
if(_httplib_split_error)
|
||||
message(FATAL_ERROR "Failed when trying to split Cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
endif()
|
||||
|
||||
# split.py puts output in "out"
|
||||
@@ -172,6 +173,11 @@ if(HTTPLIB_COMPILE)
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||
)
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
VERSION ${${PROJECT_NAME}_VERSION}
|
||||
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
|
||||
)
|
||||
else()
|
||||
# This is for header-only.
|
||||
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
||||
@@ -182,19 +188,9 @@ endif()
|
||||
# Only useful if building in-tree, versus using it from an installation.
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Might be missing some, but this list is somewhat comprehensive
|
||||
# Require C++11
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
cxx_nullptr
|
||||
cxx_lambdas
|
||||
cxx_override
|
||||
cxx_defaulted_functions
|
||||
cxx_attribute_deprecated
|
||||
cxx_auto_type
|
||||
cxx_decltype
|
||||
cxx_deleted_functions
|
||||
cxx_range_for
|
||||
cxx_sizeof_member
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
@@ -269,11 +265,9 @@ endif()
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
|
||||
@@ -48,17 +48,12 @@ res->status;
|
||||
res->body;
|
||||
```
|
||||
|
||||
### Try out the examples on Repl.it!
|
||||
|
||||
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
|
||||
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
|
||||
|
||||
SSL Support
|
||||
-----------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -183,6 +178,15 @@ The followings are built-in mappings:
|
||||
| webm | video/webm | zip | application/zip |
|
||||
| mp3 | audio/mp3 | wasm | application/wasm |
|
||||
|
||||
### File request handler
|
||||
|
||||
```cpp
|
||||
// The handler is called right before the response is sent to a client
|
||||
svr.set_file_request_handler([](const Request &req, Response &res) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
NOTE: These static file server methods are not thread-safe.
|
||||
|
||||
### Logging
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2021 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2022 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.10.7"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
@@ -72,6 +74,10 @@
|
||||
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
|
||||
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_TCP_NODELAY
|
||||
#define CPPHTTPLIB_TCP_NODELAY false
|
||||
#endif
|
||||
@@ -142,8 +148,6 @@ using ssize_t = int;
|
||||
|
||||
#include <io.h>
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <wincrypt.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
|
||||
@@ -152,8 +156,6 @@ using ssize_t = int;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#pragma comment(lib, "cryptui.lib")
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
@@ -168,8 +170,8 @@ using socket_t = SOCKET;
|
||||
#else // not _WIN32
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#ifdef __linux__
|
||||
@@ -191,6 +193,7 @@ using socket_t = int;
|
||||
#endif
|
||||
#endif //_WIN32
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -217,17 +220,24 @@ using socket_t = int;
|
||||
#include <thread>
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#ifdef _WIN32
|
||||
#include <wincrypt.h>
|
||||
|
||||
// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
|
||||
// used
|
||||
#ifdef _WIN32
|
||||
#undef X509_NAME
|
||||
#undef X509_CERT_PAIR
|
||||
#undef X509_EXTENSIONS
|
||||
#undef PKCS7_SIGNER_INFO
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#pragma comment(lib, "cryptui.lib")
|
||||
#endif
|
||||
#endif //_WIN32
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
@@ -973,7 +983,7 @@ public:
|
||||
|
||||
void stop();
|
||||
|
||||
void set_hostname_addr_map(const std::map<std::string, std::string> addr_map);
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
|
||||
@@ -1308,7 +1318,7 @@ public:
|
||||
|
||||
void stop();
|
||||
|
||||
void set_hostname_addr_map(const std::map<std::string, std::string> addr_map);
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
|
||||
@@ -1384,7 +1394,8 @@ class SSLServer : public Server {
|
||||
public:
|
||||
SSLServer(const char *cert_path, const char *private_key_path,
|
||||
const char *client_ca_cert_file_path = nullptr,
|
||||
const char *client_ca_cert_dir_path = nullptr);
|
||||
const char *client_ca_cert_dir_path = nullptr,
|
||||
const char *private_key_password = nullptr);
|
||||
|
||||
SSLServer(X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store = nullptr);
|
||||
@@ -2648,11 +2659,14 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_IF2IP
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
inline std::string if2ip(int address_family, const std::string &ifn) {
|
||||
struct ifaddrs *ifap;
|
||||
getifaddrs(&ifap);
|
||||
std::string addr_candidate;
|
||||
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr && ifn == ifa->ifa_name) {
|
||||
if (ifa->ifa_addr && ifn == ifa->ifa_name &&
|
||||
(AF_UNSPEC == address_family ||
|
||||
ifa->ifa_addr->sa_family == address_family)) {
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) {
|
||||
auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
@@ -2660,11 +2674,26 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
freeifaddrs(ifap);
|
||||
return std::string(buf, INET_ADDRSTRLEN);
|
||||
}
|
||||
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
|
||||
if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
|
||||
char buf[INET6_ADDRSTRLEN] = {};
|
||||
if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
|
||||
// equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
|
||||
auto s6_addr_head = sa->sin6_addr.s6_addr[0];
|
||||
if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
|
||||
addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
|
||||
} else {
|
||||
freeifaddrs(ifap);
|
||||
return std::string(buf, INET6_ADDRSTRLEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
return std::string();
|
||||
return addr_candidate;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2679,9 +2708,9 @@ inline socket_t create_client_socket(
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock2, ip.c_str())) {
|
||||
auto ip_from_if = if2ip(address_family, intf);
|
||||
if (ip_from_if.empty()) { ip_from_if = intf; }
|
||||
if (!bind_ip_address(sock2, ip_from_if.c_str())) {
|
||||
error = Error::BindIPAddress;
|
||||
return false;
|
||||
}
|
||||
@@ -2812,10 +2841,12 @@ find_content_type(const std::string &path,
|
||||
default: return nullptr;
|
||||
case "css"_t: return "text/css";
|
||||
case "csv"_t: return "text/csv";
|
||||
case "txt"_t: return "text/plain";
|
||||
case "vtt"_t: return "text/vtt";
|
||||
case "htm"_t:
|
||||
case "html"_t: return "text/html";
|
||||
case "js"_t:
|
||||
case "mjs"_t: return "text/javascript";
|
||||
case "txt"_t: return "text/plain";
|
||||
case "vtt"_t: return "text/vtt";
|
||||
|
||||
case "apng"_t: return "image/apng";
|
||||
case "avif"_t: return "image/avif";
|
||||
@@ -2847,8 +2878,6 @@ find_content_type(const std::string &path,
|
||||
case "7z"_t: return "application/x-7z-compressed";
|
||||
case "atom"_t: return "application/atom+xml";
|
||||
case "pdf"_t: return "application/pdf";
|
||||
case "js"_t:
|
||||
case "mjs"_t: return "application/javascript";
|
||||
case "json"_t: return "application/json";
|
||||
case "rss"_t: return "application/rss+xml";
|
||||
case "tar"_t: return "application/x-tar";
|
||||
@@ -2933,14 +2962,21 @@ inline const char *status_message(int status) {
|
||||
}
|
||||
|
||||
inline bool can_compress_content_type(const std::string &content_type) {
|
||||
return (!content_type.rfind("text/", 0) &&
|
||||
content_type != "text/event-stream") ||
|
||||
content_type == "image/svg+xml" ||
|
||||
content_type == "application/javascript" ||
|
||||
content_type == "application/json" ||
|
||||
content_type == "application/xml" ||
|
||||
content_type == "application/protobuf" ||
|
||||
content_type == "application/xhtml+xml";
|
||||
using udl::operator""_t;
|
||||
|
||||
auto tag = str2tag(content_type);
|
||||
|
||||
switch (tag) {
|
||||
case "image/svg+xml"_t:
|
||||
case "application/javascript"_t:
|
||||
case "application/json"_t:
|
||||
case "application/xml"_t:
|
||||
case "application/protobuf"_t:
|
||||
case "application/xhtml+xml"_t: return true;
|
||||
|
||||
default:
|
||||
return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t;
|
||||
}
|
||||
}
|
||||
|
||||
inline EncodingType encoding_type(const Request &req, const Response &res) {
|
||||
@@ -3019,7 +3055,6 @@ inline bool gzip_compressor::compress(const char *data, size_t data_length,
|
||||
assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
|
||||
(flush == Z_NO_FLUSH && ret == Z_OK));
|
||||
assert(strm_.avail_in == 0);
|
||||
|
||||
} while (data_length > 0);
|
||||
|
||||
return true;
|
||||
@@ -3431,7 +3466,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline ssize_t write_headers(Stream &strm, const Headers &headers) {
|
||||
ssize_t write_len = 0;
|
||||
@@ -3745,10 +3780,11 @@ public:
|
||||
bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
|
||||
const MultipartContentHeader &header_callback) {
|
||||
|
||||
// TODO: support 'filename*'
|
||||
static const std::regex re_content_disposition(
|
||||
"^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
|
||||
"\"(.*?)\")?\\s*$",
|
||||
R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
|
||||
std::regex_constants::icase);
|
||||
|
||||
static const std::string dash_ = "--";
|
||||
static const std::string crlf_ = "\r\n";
|
||||
|
||||
@@ -4138,36 +4174,36 @@ inline bool has_crlf(const char *s) {
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
template <typename CTX, typename Init, typename Update, typename Final>
|
||||
inline std::string message_digest(const std::string &s, Init init,
|
||||
Update update, Final final,
|
||||
size_t digest_length) {
|
||||
std::vector<unsigned char> md(digest_length, 0);
|
||||
CTX ctx;
|
||||
init(&ctx);
|
||||
update(&ctx, s.data(), s.size());
|
||||
final(md.data(), &ctx);
|
||||
inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
|
||||
auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
|
||||
EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||
|
||||
unsigned int hash_length = 0;
|
||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||
|
||||
EVP_DigestInit_ex(context.get(), algo, nullptr);
|
||||
EVP_DigestUpdate(context.get(), s.c_str(), s.size());
|
||||
EVP_DigestFinal_ex(context.get(), hash, &hash_length);
|
||||
|
||||
std::stringstream ss;
|
||||
for (auto c : md) {
|
||||
ss << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)c;
|
||||
for (auto i = 0u; i < hash_length; ++i) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< (unsigned int)hash[i];
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
inline std::string MD5(const std::string &s) {
|
||||
return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final,
|
||||
MD5_DIGEST_LENGTH);
|
||||
return message_digest(s, EVP_md5());
|
||||
}
|
||||
|
||||
inline std::string SHA_256(const std::string &s) {
|
||||
return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final,
|
||||
SHA256_DIGEST_LENGTH);
|
||||
return message_digest(s, EVP_sha256());
|
||||
}
|
||||
|
||||
inline std::string SHA_512(const std::string &s) {
|
||||
return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final,
|
||||
SHA512_DIGEST_LENGTH);
|
||||
return message_digest(s, EVP_sha512());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4204,10 +4240,14 @@ class WSInit {
|
||||
public:
|
||||
WSInit() {
|
||||
WSADATA wsaData;
|
||||
WSAStartup(0x0002, &wsaData);
|
||||
if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
|
||||
}
|
||||
|
||||
~WSInit() { WSACleanup(); }
|
||||
~WSInit() {
|
||||
if (is_valid_) WSACleanup();
|
||||
}
|
||||
|
||||
bool is_valid_ = false;
|
||||
};
|
||||
|
||||
static WSInit wsinit_;
|
||||
@@ -4371,6 +4411,8 @@ inline void hosted_at(const char *hostname, std::vector<std::string> &addrs) {
|
||||
addrs.push_back(ip);
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
inline std::string append_query_params(const char *path, const Params ¶ms) {
|
||||
@@ -4651,7 +4693,7 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32) && !defined(_WIN64)
|
||||
size =
|
||||
(std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#endif
|
||||
@@ -4958,6 +5000,14 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
|
||||
if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; }
|
||||
|
||||
{
|
||||
// Skip URL fragment
|
||||
for (size_t i = 0; i < req.target.size(); i++) {
|
||||
if (req.target[i] == '#') {
|
||||
req.target.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
|
||||
@@ -5145,7 +5195,7 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
|
||||
})) {
|
||||
const auto &content_type = req.get_header_value("Content-Type");
|
||||
if (!content_type.find("application/x-www-form-urlencoded")) {
|
||||
if (req.body.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
|
||||
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
|
||||
res.status = 413; // NOTE: should be 414?
|
||||
return false;
|
||||
}
|
||||
@@ -6155,7 +6205,8 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
|
||||
if (!req.has_header("User-Agent")) {
|
||||
req.headers.emplace("User-Agent", "cpp-httplib/0.10.3");
|
||||
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
|
||||
req.headers.emplace("User-Agent", agent);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6271,8 +6322,8 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
auto last = offset + data_len == content_length;
|
||||
|
||||
auto ret = compressor.compress(
|
||||
data, data_len, last, [&](const char *data, size_t data_len) {
|
||||
req.body.append(data, data_len);
|
||||
data, data_len, last, [&](const char *compressed_data, size_t compressed_data_len) {
|
||||
req.body.append(compressed_data, compressed_data_len);
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -6933,8 +6984,8 @@ inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
|
||||
|
||||
inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; }
|
||||
|
||||
inline void ClientImpl::set_hostname_addr_map(
|
||||
const std::map<std::string, std::string> addr_map) {
|
||||
inline void
|
||||
ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
|
||||
addr_map_ = std::move(addr_map);
|
||||
}
|
||||
|
||||
@@ -7177,62 +7228,63 @@ inline bool SSLSocketStream::is_writable() const {
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
size_t readbytes = 0;
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
int n = 1000;
|
||||
#ifdef _WIN32
|
||||
while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
|
||||
(err == SSL_ERROR_SYSCALL &&
|
||||
WSAGetLastError() == WSAETIMEDOUT))) {
|
||||
#else
|
||||
while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
|
||||
#endif
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret >= 0) { return ret; }
|
||||
err = SSL_get_error(ssl_, ret);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
auto ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
|
||||
if (ret == 1) { return static_cast<ssize_t>(readbytes); }
|
||||
if (SSL_get_error(ssl_, ret) == SSL_ERROR_ZERO_RETURN) { return 0; }
|
||||
return -1;
|
||||
}
|
||||
if (!is_readable()) { return -1; }
|
||||
|
||||
auto ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
|
||||
if (ret == 1) { return static_cast<ssize_t>(readbytes); }
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
int n = 1000;
|
||||
#ifdef _WIN32
|
||||
while (--n >= 0 &&
|
||||
(err == SSL_ERROR_WANT_READ ||
|
||||
(err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) {
|
||||
#else
|
||||
while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
|
||||
#endif
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
|
||||
if (ret == 1) { return static_cast<ssize_t>(readbytes); }
|
||||
if (SSL_get_error(ssl_, ret) == SSL_ERROR_ZERO_RETURN) { return 0; }
|
||||
return -1;
|
||||
}
|
||||
if (!is_readable()) { return -1; }
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
|
||||
if (ret == 1) { return static_cast<ssize_t>(readbytes); }
|
||||
err = SSL_get_error(ssl_, ret);
|
||||
}
|
||||
if (err == SSL_ERROR_ZERO_RETURN) { return 0; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
if (is_writable()) {
|
||||
auto ret = SSL_write(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
int n = 1000;
|
||||
if (!is_writable()) { return -1; }
|
||||
size_t written = 0;
|
||||
auto ret = SSL_write_ex(ssl_, ptr, size, &written);
|
||||
if (ret == 1) { return static_cast<ssize_t>(written); }
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
int n = 1000;
|
||||
#ifdef _WIN32
|
||||
while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
|
||||
(err == SSL_ERROR_SYSCALL &&
|
||||
WSAGetLastError() == WSAETIMEDOUT))) {
|
||||
while (--n >= 0 &&
|
||||
(err == SSL_ERROR_WANT_WRITE ||
|
||||
(err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) {
|
||||
#else
|
||||
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
|
||||
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
|
||||
#endif
|
||||
if (is_writable()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
ret = SSL_write(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret >= 0) { return ret; }
|
||||
err = SSL_get_error(ssl_, ret);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
if (!is_writable()) { return -1; }
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
ret = SSL_write_ex(ssl_, ptr, size, &written);
|
||||
if (ret == 1) { return static_cast<ssize_t>(written); }
|
||||
err = SSL_get_error(ssl_, ret);
|
||||
}
|
||||
if (err == SSL_ERROR_ZERO_RETURN) { return 0; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -7250,7 +7302,8 @@ static SSLInit sslinit_;
|
||||
// SSL HTTP server implementation
|
||||
inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
const char *client_ca_cert_file_path,
|
||||
const char *client_ca_cert_dir_path) {
|
||||
const char *client_ca_cert_dir_path,
|
||||
const char *private_key_password) {
|
||||
ctx_ = SSL_CTX_new(TLS_server_method());
|
||||
|
||||
if (ctx_) {
|
||||
@@ -7260,6 +7313,12 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
|
||||
|
||||
// add default password callback before opening encrypted private key
|
||||
if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
|
||||
SSL_CTX_set_default_passwd_cb_userdata(ctx_,
|
||||
(char *)private_key_password);
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
|
||||
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
|
||||
1) {
|
||||
@@ -7321,11 +7380,11 @@ inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
|
||||
inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
auto ssl = detail::ssl_new(
|
||||
sock, ctx_, ctx_mutex_,
|
||||
[&](SSL *ssl) {
|
||||
[&](SSL *ssl2) {
|
||||
return detail::ssl_connect_or_accept_nonblocking(
|
||||
sock, ssl, SSL_accept, read_timeout_sec_, read_timeout_usec_);
|
||||
sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_);
|
||||
},
|
||||
[](SSL * /*ssl*/) { return true; });
|
||||
[](SSL * /*ssl2*/) { return true; });
|
||||
|
||||
bool ret = false;
|
||||
if (ssl) {
|
||||
@@ -7519,31 +7578,31 @@ inline bool SSLClient::load_certs() {
|
||||
inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
auto ssl = detail::ssl_new(
|
||||
socket.sock, ctx_, ctx_mutex_,
|
||||
[&](SSL *ssl) {
|
||||
[&](SSL *ssl2) {
|
||||
if (server_certificate_verification_) {
|
||||
if (!load_certs()) {
|
||||
error = Error::SSLLoadingCerts;
|
||||
return false;
|
||||
}
|
||||
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
|
||||
SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr);
|
||||
}
|
||||
|
||||
if (!detail::ssl_connect_or_accept_nonblocking(
|
||||
socket.sock, ssl, SSL_connect, connection_timeout_sec_,
|
||||
socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
|
||||
connection_timeout_usec_)) {
|
||||
error = Error::SSLConnection;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server_certificate_verification_) {
|
||||
verify_result_ = SSL_get_verify_result(ssl);
|
||||
verify_result_ = SSL_get_verify_result(ssl2);
|
||||
|
||||
if (verify_result_ != X509_V_OK) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto server_cert = SSL_get_peer_certificate(ssl);
|
||||
auto server_cert = SSL_get_peer_certificate(ssl2);
|
||||
|
||||
if (server_cert == nullptr) {
|
||||
error = Error::SSLServerVerification;
|
||||
@@ -7560,8 +7619,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
|
||||
return true;
|
||||
},
|
||||
[&](SSL *ssl) {
|
||||
SSL_set_tlsext_host_name(ssl, host_.c_str());
|
||||
[&](SSL *ssl2) {
|
||||
SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -8052,8 +8111,8 @@ inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
|
||||
|
||||
inline void Client::stop() { cli_->stop(); }
|
||||
|
||||
inline void Client::set_hostname_addr_map(
|
||||
const std::map<std::string, std::string> addr_map) {
|
||||
inline void
|
||||
Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
|
||||
cli_->set_hostname_addr_map(std::move(addr_map));
|
||||
}
|
||||
|
||||
|
||||
+8
-10
@@ -19,22 +19,18 @@ project(
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
python3 = find_program('python3')
|
||||
if version == 'undefined'
|
||||
# Meson doesn't have regular expressions, but since it is implemented
|
||||
# in python we can be sure we can use it to parse the file manually
|
||||
version = run_command(
|
||||
python3, '-c', 'import re; raw_version = re.search("User\-Agent.*cpp\-httplib/([0-9]+\.?)+", open("httplib.h").read()).group(0); print(re.search("([0-9]+\\.?)+", raw_version).group(0))',
|
||||
check: true
|
||||
).stdout().strip()
|
||||
cxx = meson.get_compiler('cpp')
|
||||
version = cxx.get_define('CPPHTTPLIB_VERSION',
|
||||
prefix: '#include <httplib.h>',
|
||||
include_directories: include_directories('.')).strip('"')
|
||||
assert(version != '', 'failed to get version from httplib.h')
|
||||
endif
|
||||
|
||||
message('cpp-httplib version ' + version)
|
||||
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: ['>=1.1.1', '<1.1.2'], required: get_option('cpp-httplib_openssl'))
|
||||
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
@@ -65,6 +61,8 @@ endif
|
||||
cpp_httplib_dep = dependency('', required: false)
|
||||
|
||||
if get_option('cpp-httplib_compile')
|
||||
python3 = find_program('python3')
|
||||
|
||||
httplib_ch = custom_target(
|
||||
'split',
|
||||
input: 'httplib.h',
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@
|
||||
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
CXX = clang++
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
@@ -65,6 +65,8 @@ cert.pem:
|
||||
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
|
||||
openssl genrsa 2048 > client.key.pem
|
||||
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
|
||||
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
|
||||
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
|
||||
#c_rehash .
|
||||
|
||||
clean:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -33,6 +33,19 @@ cert2_pem = custom_target(
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
key_encrypted_pem = custom_target(
|
||||
'key_encrypted_pem',
|
||||
output: 'key_encrypted.pem',
|
||||
command: [openssl, 'genrsa', '-passout', 'pass:test123!', '-out', '@OUTPUT@', '2048']
|
||||
)
|
||||
|
||||
cert_encrypted_pem = custom_target(
|
||||
'cert_encrypted_pem',
|
||||
input: key_encrypted_pem,
|
||||
output: 'cert_encrypted.pem',
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
rootca_key_pem = custom_target(
|
||||
'rootca_key_pem',
|
||||
output: 'rootCA.key.pem',
|
||||
@@ -87,6 +100,8 @@ test(
|
||||
key_pem,
|
||||
cert_pem,
|
||||
cert2_pem,
|
||||
key_encrypted_pem,
|
||||
cert_encrypted_pem,
|
||||
rootca_key_pem,
|
||||
rootca_cert_pem,
|
||||
client_key_pem,
|
||||
|
||||
+211
-12
@@ -18,6 +18,9 @@
|
||||
#define CLIENT_CA_CERT_DIR "."
|
||||
#define CLIENT_CERT_FILE "./client.cert.pem"
|
||||
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
|
||||
#define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem"
|
||||
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
|
||||
#define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!"
|
||||
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
@@ -106,11 +109,11 @@ TEST(SplitTest, ParseQueryString) {
|
||||
detail::split(s.c_str(), s.c_str() + s.size(), '&',
|
||||
[&](const char *b, const char *e) {
|
||||
string key, val;
|
||||
detail::split(b, e, '=', [&](const char *b, const char *e) {
|
||||
detail::split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||
if (key.empty()) {
|
||||
key.assign(b, e);
|
||||
key.assign(b2, e2);
|
||||
} else {
|
||||
val.assign(b, e);
|
||||
val.assign(b2, e2);
|
||||
}
|
||||
});
|
||||
dic.emplace(key, val);
|
||||
@@ -607,7 +610,8 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Connection, res.error());
|
||||
EXPECT_TRUE(Error::Connection == res.error() ||
|
||||
Error::ConnectionTimeout == res.error());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, Timeout_Online) {
|
||||
@@ -1194,6 +1198,17 @@ TEST(BindServerTest, BindAndListenSeparatelySSL) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
|
||||
SSLServer svr(SERVER_ENCRYPTED_CERT_FILE, SERVER_ENCRYPTED_PRIVATE_KEY_FILE,
|
||||
nullptr, nullptr, SERVER_ENCRYPTED_PRIVATE_KEY_PASS);
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ErrorHandlerTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
@@ -1401,6 +1416,34 @@ TEST(InvalidFormatTest, StatusCode) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(URLFragmentTest, WithFragment) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request &req, Response & /*res*/) {
|
||||
EXPECT_TRUE(req.target == "/hi");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi#key1=val1=key2=val2");
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
res = cli.Get("/hi%23key1=val1=key2=val2");
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(404, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -1669,10 +1712,10 @@ protected:
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Post("/post-large",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, LARGE_DATA);
|
||||
res.set_content(req.body, "text/plain");
|
||||
})
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, LARGE_DATA);
|
||||
res.set_content(req.body, "text/plain");
|
||||
})
|
||||
.Put("/empty-no-content-type",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
@@ -2972,8 +3015,8 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
httplib::detail::gzip_compressor compressor;
|
||||
bool result = compressor.compress(
|
||||
data.data(), data.size(),
|
||||
/*last=*/true, [&](const char *data, size_t size) {
|
||||
compressed_data.insert(compressed_data.size(), data, size);
|
||||
/*last=*/true, [&](const char *compressed_data_chunk, size_t compressed_data_size) {
|
||||
compressed_data.insert(compressed_data.size(), compressed_data_chunk, compressed_data_size);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
@@ -2992,8 +3035,8 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
std::min(compressed_data.size() - chunk_begin, chunk_size);
|
||||
bool result = decompressor.decompress(
|
||||
compressed_data.data() + chunk_begin, current_chunk_size,
|
||||
[&](const char *data, size_t size) {
|
||||
decompressed_data.insert(decompressed_data.size(), data, size);
|
||||
[&](const char *decompressed_data_chunk, size_t decompressed_data_chunk_size) {
|
||||
decompressed_data.insert(decompressed_data.size(), decompressed_data_chunk, decompressed_data_chunk_size);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
@@ -4325,6 +4368,20 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(SSLClientTest, SetInterfaceWithINET6) {
|
||||
auto cli = std::make_shared<httplib::Client>("https://httpbin.org");
|
||||
ASSERT_TRUE(cli != nullptr);
|
||||
|
||||
cli->set_address_family(AF_INET6);
|
||||
cli->set_interface("en0");
|
||||
|
||||
auto res = cli->Get("/get");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||
CLIENT_CA_CERT_DIR);
|
||||
@@ -4603,6 +4660,50 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Disabled due to the out-of-memory problem on GitHub Actions Workflows
|
||||
TEST(SSLClientServerTest, DISABLED_LargeDataTransfer) {
|
||||
|
||||
// prepare large data
|
||||
std::random_device seed_gen;
|
||||
std::mt19937 random(seed_gen());
|
||||
constexpr auto large_size_byte = 2147483648UL + 1048576UL; // 2GiB + 1MiB
|
||||
std::vector<std::uint32_t> binary(large_size_byte / sizeof(std::uint32_t));
|
||||
std::generate(binary.begin(), binary.end(), [&random]() { return random(); });
|
||||
|
||||
// server
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
|
||||
svr.Post("/binary", [&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(large_size_byte, req.body.size());
|
||||
EXPECT_EQ(0, std::memcmp(binary.data(), req.body.data(), large_size_byte));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// client POST
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_read_timeout(std::chrono::seconds(100));
|
||||
cli.set_write_timeout(std::chrono::seconds(100));
|
||||
auto res = cli.Post("/binary", reinterpret_cast<char *>(binary.data()),
|
||||
large_size_byte, "application/octet-stream");
|
||||
|
||||
// compare
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(large_size_byte, res->body.size());
|
||||
EXPECT_EQ(0, std::memcmp(binary.data(), res->body.data(), large_size_byte));
|
||||
|
||||
// cleanup
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -4641,6 +4742,40 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
EXPECT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
// Disabled due to out-of-memory problem on GitHub Actions
|
||||
#ifdef _WIN64
|
||||
TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
|
||||
// allocate content size larger than 2GB in memory
|
||||
const size_t content_size = 2LL * 1024LL * 1024LL * 1024LL + 1LL;
|
||||
char *content = (char *)malloc(content_size);
|
||||
ASSERT_TRUE(content);
|
||||
|
||||
Server svr;
|
||||
svr.Get("/foo", [=](const httplib::Request &req, httplib::Response &resp) {
|
||||
resp.set_content(content, content_size, "application/octet-stream");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Get("/foo");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(content_size, res->body.length());
|
||||
|
||||
free(content);
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(YahooRedirectTest2, SimpleInterface_Online) {
|
||||
Client cli("http://yahoo.com");
|
||||
@@ -4776,4 +4911,68 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
|
||||
t.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, LargeData) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Post("/post", [&](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", items);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
<AdditionalUsingDirectories>
|
||||
</AdditionalUsingDirectories>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
@@ -158,6 +159,7 @@
|
||||
<AdditionalUsingDirectories>
|
||||
</AdditionalUsingDirectories>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
|
||||
Reference in New Issue
Block a user