Compare commits

...

16 Commits

Author SHA1 Message Date
yhirose f2f4728489 Release v0.12.1 2023-03-10 17:53:19 -05:00
yhirose d1b616286f Add note for macOS regarding system certs 2023-03-10 17:48:19 -05:00
Mathieu Gaillard df74526f91 Fix multipart Content-Type headers with both boundary and charset parameters (#1516)
* Fix multipart Content-Type headers with both boundary and charset parameters

* Improve code readability

* Add missing forward declaration

---------

Co-authored-by: Mathieu Gaillard <gaillard@adobe.com>
2023-03-08 23:57:17 -05:00
Joel Rosdahl 9f7ae0737a Fix typos (#1517) 2023-03-08 17:03:20 -05:00
yhirose 1ebb8412c5 Use SSL_ERROR_ZERO_RETURN to check if the SSL peer is closed. 2023-03-07 08:15:16 -05:00
Petr Menšík 7b69999c37 Reuse gtest on system (#1493)
* Reuse gtest on the system

Try to use gtest on the system if found. Avoid using recent CMake
features.

* Set CMP0135 only when using FetchContent

* Add /bigobj option for MSVC

* Support also cmake between 3.14 and 3.20

Older versions provided only GTest::Main target, not currently used
gtest_main. Provide backward compatibility also to old cmake versions.

* Remove redundant variable checking

---------

Co-authored-by: Jiwoo Park <jiwoo_90@naver.com>
2023-03-05 21:35:35 -05:00
yhirose c7e959a948 Fix #1481 2023-03-04 18:14:00 -05:00
yhirose ba5884e779 Fix #1481 (#1513) 2023-03-03 23:45:19 -05:00
yhirose cdaa5c48db Code cleanup 2023-03-03 22:41:57 -05:00
Alexandre Bouvier bab5c0e907 cmake: fix find_dependency (#1509) 2023-03-01 08:38:58 -05:00
Alexandre Bouvier 016838fd10 cmake: support components (#1504) 2023-02-25 09:56:56 -05:00
yhirose 75053bf855 Fix #1498 (#1501)
* Fix #1498

* Fixed build error
2023-02-22 13:19:36 -05:00
yhirose ae3a6dd2a9 Fixed an issue with example/Makefile on MacOS 2023-02-21 22:00:10 -05:00
Sergey Kazmin 6d963fbe8d Support loading system certs from Keychein on MacOS (#1474)
* Support loading system certs from Keychein on MacOS

* review improvements: add deps to meson.build and improve conditional expressions in cmake

* fix tabs

* fix tabs

* review improvements

* fix after review

* additionally load root certs from the system root keychain

* cmake fix

* fix

* small refactoring

* small refactoring

---------

Co-authored-by: Sergey Kazmin <sergey.kazmin@kaspersky.com>
2023-02-17 12:06:55 -05:00
jingTian-z 88f6245c84 feat: Add Request::get_file_multi_value func. (#1495)
Support to get multiple values of a key.

perf: Rename function names, variable names etc.
2023-02-16 21:51:06 -05:00
yhirose 0e7d2f9f93 Resolve #1482 2023-02-14 11:40:47 -05:00
11 changed files with 426 additions and 77 deletions
+5 -5
View File
@@ -14,8 +14,8 @@
-------------------------------------------------------------------------------
After installation with Cmake, a find_package(httplib) is available.
This creates a httplib::httplib target (if found).
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli) is available.
This creates a httplib::httplib target (if found and if listed components are supported).
It can be linked like so:
target_link_libraries(your_exe httplib::httplib)
@@ -79,7 +79,7 @@ set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
# Allow for a build to require OpenSSL to pass, instead of just being optional
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
# Allow for a build to casually enable OpenSSL/ZLIB support, but silenty continue if not found.
# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found.
# Make these options so their automatic use can be specifically disabled (as needed)
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
@@ -206,6 +206,8 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<PLATFORM_ID:Windows>:ws2_32>
$<$<PLATFORM_ID:Windows>:crypt32>
$<$<PLATFORM_ID:Windows>:cryptui>
# Needed for API from MacOS Security framework
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:-framework CoreFoundation -framework Security>"
# Can't put multiple targets in a single generator expression or it bugs out.
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
@@ -233,8 +235,6 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
# Passes the includedir install path
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
# There aren't any components, so don't use the macro
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
if(HTTPLIB_COMPILE)
+1
View File
@@ -54,6 +54,7 @@ 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 and 3.0.
NOTE for macOS: cpp-httplib now uses system certs. `CoreFoundation` and `Security` should be linked with `-framework`.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
+8 -1
View File
@@ -8,12 +8,19 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ifneq ($(OS), Windows_NT)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Darwin)
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
endif
endif
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark issue
server : server.cc ../httplib.h Makefile
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
+196 -34
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.12.0"
#define CPPHTTPLIB_VERSION "0.12.1"
/*
* Configuration
@@ -239,7 +239,10 @@ using socket_t = int;
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "cryptui.lib")
#endif
#endif //_WIN32
#elif defined(__APPLE__) // _WIN32
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#endif // __APPLE__
#include <openssl/err.h>
#include <openssl/evp.h>
@@ -308,6 +311,34 @@ struct ci {
}
};
// This is based on
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
template <typename EF> struct scope_exit {
explicit scope_exit(EF &&f)
: exit_function(std::move(f)), execute_on_destruction{true} {}
scope_exit(scope_exit &&rhs)
: exit_function(std::move(rhs.exit_function)),
execute_on_destruction{rhs.execute_on_destruction} {
rhs.release();
}
~scope_exit() {
if (execute_on_destruction) { this->exit_function(); }
}
void release() { this->execute_on_destruction = false; }
private:
scope_exit(const scope_exit &) = delete;
void operator=(const scope_exit &) = delete;
scope_exit &operator=(scope_exit &&) = delete;
EF exit_function;
bool execute_on_destruction;
};
} // namespace detail
using Headers = std::multimap<std::string, std::string, detail::ci>;
@@ -454,6 +485,7 @@ struct Request {
bool has_file(const std::string &key) const;
MultipartFormData get_file_value(const std::string &key) const;
std::vector<MultipartFormData> get_file_values(const std::string &key) const;
// private members...
size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
@@ -768,7 +800,7 @@ private:
ContentReceiver multipart_receiver);
bool read_content_core(Stream &strm, Request &req, Response &res,
ContentReceiver receiver,
MultipartContentHeader mulitpart_header,
MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver);
virtual bool process_and_close_socket(socket_t sock);
@@ -822,6 +854,9 @@ enum class Error {
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
// For internal use only
SSLPeerCouldBeClosed_,
};
std::string to_string(const Error error);
@@ -1094,8 +1129,6 @@ protected:
bool is_open() const { return sock != INVALID_SOCKET; }
};
Result send_(Request &&req);
virtual bool create_and_connect_socket(Socket &socket, Error &error);
// All of:
@@ -1117,7 +1150,7 @@ protected:
void copy_settings(const ClientImpl &rhs);
// Socket endoint information
// Socket endpoint information
const std::string host_;
const int port_;
const std::string host_and_port_;
@@ -1196,6 +1229,9 @@ protected:
Logger logger_;
private:
bool send_(Request &req, Response &res, Error &error);
Result send_(Request &&req);
socket_t create_client_socket(Error &error) const;
bool read_response_line(Stream &strm, const Request &req, Response &res);
bool write_request(Stream &strm, Request &req, bool close_connection,
@@ -1787,6 +1823,8 @@ std::string params_to_query_str(const Params &params);
void parse_query_text(const std::string &s, Params &params);
bool parse_multipart_boundary(const std::string &content_type, std::string &boundary);
bool parse_range_header(const std::string &s, Ranges &ranges);
int close_socket(socket_t sock);
@@ -3852,9 +3890,12 @@ inline void parse_query_text(const std::string &s, Params &params) {
inline bool parse_multipart_boundary(const std::string &content_type,
std::string &boundary) {
auto pos = content_type.find("boundary=");
auto boundary_keyword = "boundary=";
auto pos = content_type.find(boundary_keyword);
if (pos == std::string::npos) { return false; }
boundary = content_type.substr(pos + 9);
auto end = content_type.find(';', pos);
auto beg = pos + strlen(boundary_keyword);
boundary = content_type.substr(beg, end - beg);
if (boundary.length() >= 2 && boundary.front() == '"' &&
boundary.back() == '"') {
boundary = boundary.substr(1, boundary.size() - 2);
@@ -4387,15 +4428,15 @@ inline std::string SHA_512(const std::string &s) {
}
#endif
#ifdef _WIN32
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef _WIN32
// NOTE: This code came up with the following stackoverflow post:
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
inline bool load_system_certs_on_windows(X509_STORE *store) {
auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
if (!hStore) { return false; }
auto result = false;
PCCERT_CONTEXT pContext = NULL;
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
nullptr) {
@@ -4406,16 +4447,107 @@ inline bool load_system_certs_on_windows(X509_STORE *store) {
if (x509) {
X509_STORE_add_cert(store, x509);
X509_free(x509);
result = true;
}
}
CertFreeCertificateContext(pContext);
CertCloseStore(hStore, 0);
return result;
}
#elif defined(__APPLE__)
template <typename T>
using CFObjectPtr =
std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>;
inline void cf_object_ptr_deleter(CFTypeRef obj) {
if (obj) { CFRelease(obj); }
}
inline bool retrieve_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll,
kCFBooleanTrue};
CFObjectPtr<CFDictionaryRef> query(
CFDictionaryCreate(nullptr, reinterpret_cast<const void **>(keys), values,
sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks),
cf_object_ptr_deleter);
if (!query) { return false; }
CFTypeRef security_items = nullptr;
if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess ||
CFArrayGetTypeID() != CFGetTypeID(security_items)) {
return false;
}
certs.reset(reinterpret_cast<CFArrayRef>(security_items));
return true;
}
inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
CFArrayRef root_security_items = nullptr;
if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) {
return false;
}
certs.reset(root_security_items);
return true;
}
inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
auto result = false;
for (int i = 0; i < CFArrayGetCount(certs); ++i) {
const auto cert = reinterpret_cast<const __SecCertificate *>(
CFArrayGetValueAtIndex(certs, i));
if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; }
CFDataRef cert_data = nullptr;
if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) !=
errSecSuccess) {
continue;
}
CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
auto encoded_cert = static_cast<const unsigned char *>(
CFDataGetBytePtr(cert_data_ptr.get()));
auto x509 =
d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get()));
if (x509) {
X509_STORE_add_cert(store, x509);
X509_free(x509);
result = true;
}
}
return result;
}
inline bool load_system_certs_on_apple(X509_STORE *store) {
auto result = false;
CFObjectPtr<CFArrayRef> certs(nullptr, cf_object_ptr_deleter);
if (retrieve_certs_from_keychain(certs) && certs) {
result = add_certs_to_x509_store(certs.get(), store);
}
if (retrieve_root_certs_from_keychain(certs) && certs) {
result = add_certs_to_x509_store(certs.get(), store) || result;
}
return result;
}
#endif
#endif
#ifdef _WIN32
class WSInit {
public:
WSInit() {
@@ -4690,6 +4822,16 @@ inline MultipartFormData Request::get_file_value(const std::string &key) const {
return MultipartFormData();
}
inline std::vector<MultipartFormData>
Request::get_file_values(const std::string &key) const {
std::vector<MultipartFormData> values;
auto rng = files.equal_range(key);
for (auto it = rng.first; it != rng.second; it++) {
values.push_back(it->second);
}
return values;
}
// Response implementation
inline bool Response::has_header(const std::string &key) const {
return headers.find(key) != headers.end();
@@ -5405,7 +5547,7 @@ inline bool Server::read_content_with_content_receiver(
inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
ContentReceiver receiver,
MultipartContentHeader mulitpart_header,
MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver) {
detail::MultipartFormDataParser multipart_form_data_parser;
ContentReceiverWithProgress out;
@@ -5425,14 +5567,14 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
while (pos < n) {
auto read_size = (std::min)<size_t>(1, n - pos);
auto ret = multipart_form_data_parser.parse(
buf + pos, read_size, multipart_receiver, mulitpart_header);
buf + pos, read_size, multipart_receiver, multipart_header);
if (!ret) { return false; }
pos += read_size;
}
return true;
*/
return multipart_form_data_parser.parse(buf, n, multipart_receiver,
mulitpart_header);
multipart_header);
};
} else {
out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
@@ -5598,11 +5740,7 @@ inline bool Server::listen_internal() {
#endif
}
#if __cplusplus > 201703L
task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
#else
task_queue->enqueue([=]() { process_and_close_socket(sock); });
#endif
task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
}
task_queue->shutdown();
@@ -6149,7 +6287,15 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
auto ret = send_(req, res, error);
if (error == Error::SSLPeerCouldBeClosed_) {
assert(!ret);
ret = send_(req, res, error);
}
return ret;
}
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
{
std::lock_guard<std::mutex> guard(socket_mutex_);
@@ -6180,7 +6326,7 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
if (is_ssl()) {
auto &scli = static_cast<SSLClient &>(*this);
if (!proxy_host_.empty() && proxy_port_ != -1) {
bool success = false;
auto success = false;
if (!scli.connect_with_proxy(socket_, res, success, error)) {
return success;
}
@@ -6207,13 +6353,11 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
}
}
auto ret = false;
auto close_connection = !keep_alive_;
auto ret = process_socket(socket_, [&](Stream &strm) {
return handle_request(strm, req, res, close_connection, error);
});
// Briefly lock mutex in order to mark that a request is no longer ongoing
{
auto se = detail::scope_exit<std::function<void(void)>>([&]() {
// Briefly lock mutex in order to mark that a request is no longer ongoing
std::lock_guard<std::mutex> guard(socket_mutex_);
socket_requests_in_flight_ -= 1;
if (socket_requests_in_flight_ <= 0) {
@@ -6227,7 +6371,11 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
shutdown_socket(socket_);
close_socket(socket_);
}
}
});
ret = process_socket(socket_, [&](Stream &strm) {
return handle_request(strm, req, res, close_connection, error);
});
if (!ret) {
if (error == Error::Success) { error = Error::Unknown; }
@@ -6369,7 +6517,7 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
auto is_shutting_down = []() { return false; };
if (req.is_chunked_content_provider_) {
// TODO: Brotli suport
// TODO: Brotli support
std::unique_ptr<detail::compressor> compressor;
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (compress_) {
@@ -6619,6 +6767,17 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
// Send request
if (!write_request(strm, req, close_connection, error)) { return false; }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
if (is_ssl()) {
char buf[1];
if (SSL_peek(socket_.ssl, buf, 1) == 0 &&
SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) {
error = Error::SSLPeerCouldBeClosed_;
return false;
}
}
#endif
// Receive response and headers
if (!read_response_line(strm, req, res) ||
!detail::read_headers(strm, res.headers)) {
@@ -7245,7 +7404,7 @@ inline void ClientImpl::stop() {
return;
}
// Otherwise, sitll holding the mutex, we can shut everything down ourselves
// Otherwise, still holding the mutex, we can shut everything down ourselves
shutdown_ssl(socket_, true);
shutdown_socket(socket_);
close_socket(socket_);
@@ -7658,7 +7817,7 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
},
[](SSL * /*ssl2*/) { return true; });
bool ret = false;
auto ret = false;
if (ssl) {
ret = detail::process_server_socket_ssl(
svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
@@ -7836,11 +7995,14 @@ inline bool SSLClient::load_certs() {
ret = false;
}
} else {
auto loaded = false;
#ifdef _WIN32
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
#else
SSL_CTX_set_default_verify_paths(ctx_);
loaded =
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
#elif defined(__APPLE__)
loaded = detail::load_system_certs_on_apple(SSL_CTX_get_cert_store(ctx_));
#endif
if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
}
});
@@ -7985,7 +8147,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
if (alt_names) {
auto dsn_matched = false;
auto ip_mached = false;
auto ip_matched = false;
auto count = sk_GENERAL_NAME_num(alt_names);
@@ -8001,14 +8163,14 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
case GEN_IPADD:
if (!memcmp(&addr6, name, addr_len) ||
!memcmp(&addr, name, addr_len)) {
ip_mached = true;
ip_matched = true;
}
break;
}
}
}
if (dsn_matched || ip_mached) { ret = true; }
if (dsn_matched || ip_matched) { ret = true; }
}
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
+17 -8
View File
@@ -12,19 +12,19 @@ set(HTTPLIB_VERSION @PROJECT_VERSION@)
include(CMakeFindDependencyMacro)
# We add find_dependency calls here to not make the end-user have to call them.
find_dependency(Threads REQUIRED)
find_dependency(Threads)
if(@HTTPLIB_IS_USING_OPENSSL@)
# OpenSSL COMPONENTS were added in Cmake v3.11
if(CMAKE_VERSION VERSION_LESS "3.11")
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ REQUIRED)
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@)
else()
# Once the COMPONENTS were added, they were made optional when not specified.
# Since we use both, we need to search for both.
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL REQUIRED)
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL)
endif()
endif()
if(@HTTPLIB_IS_USING_ZLIB@)
find_dependency(ZLIB REQUIRED)
find_dependency(ZLIB)
endif()
if(@HTTPLIB_IS_USING_BROTLI@)
@@ -32,7 +32,7 @@ if(@HTTPLIB_IS_USING_BROTLI@)
# Note that the FindBrotli.cmake file is installed in the same dir as this file.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
find_dependency(Brotli COMPONENTS common encoder decoder REQUIRED)
find_dependency(Brotli COMPONENTS common encoder decoder)
endif()
# Mildly useful for end-users
@@ -42,10 +42,19 @@ set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
# This is helpful if you're using Cmake's pre-compiled header feature
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
# Brings in the target library
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
# Consider each library support as a "component"
set(httplib_OpenSSL_FOUND @HTTPLIB_IS_USING_OPENSSL@)
set(httplib_ZLIB_FOUND @HTTPLIB_IS_USING_ZLIB@)
set(httplib_Brotli_FOUND @HTTPLIB_IS_USING_BROTLI@)
# Ouputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
check_required_components(httplib)
# Brings in the target library, but only if all required components are found
if(NOT DEFINED httplib_FOUND OR httplib_FOUND)
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
endif()
# Outputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
include(FindPackageMessage)
if(TARGET httplib::httplib)
set(HTTPLIB_FOUND TRUE)
+3
View File
@@ -34,6 +34,9 @@ openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cp
if openssl_dep.found()
deps += openssl_dep
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
if host_machine.system() == 'darwin'
deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security'])
endif
endif
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
+23 -12
View File
@@ -1,20 +1,31 @@
cmake_policy(SET CMP0135 NEW)
find_package(GTest)
include(FetchContent)
include(GoogleTest)
if(GTest_FOUND)
if(NOT TARGET GTest::gtest_main AND TARGET GTest::Main)
# CMake <3.20
add_library(GTest::gtest_main INTERFACE IMPORTED)
target_link_libraries(GTest::gtest_main INTERFACE GTest::Main)
endif()
else()
if(POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
endif()
set(BUILD_GMOCK OFF)
set(INSTALL_GTEST OFF)
set(gtest_force_shared_crt ON)
include(FetchContent)
FetchContent_Declare(
gtest
URL https://github.com/google/googletest/archive/main.tar.gz
)
FetchContent_MakeAvailable(gtest)
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)
endif()
add_executable(httplib-test test.cc)
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
gtest_discover_tests(httplib-test)
+7
View File
@@ -8,6 +8,13 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ifneq ($(OS), Windows_NT)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Darwin)
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
endif
endif
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = $(PREFIX)/opt/brotli
@@ -11,7 +11,7 @@
#include <vector>
// Forward declare the "fuzz target" interface.
// We deliberately keep this inteface simple and header-free.
// We deliberately keep this interface simple and header-free.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
// It reads all files passed as parameters and feeds their contents
+164 -15
View File
@@ -46,7 +46,7 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
return *it;
#else
if (it != files.end()) { return *it; }
throw std::runtime_error("invalid mulitpart form data name error");
throw std::runtime_error("invalid multipart form data name error");
#endif
}
@@ -169,6 +169,39 @@ TEST(ParamsToQueryTest, ConvertParamsToQuery) {
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1&key2=val2&key3=val3");
}
TEST(ParseMultipartBoundaryTest, DefaultValue) {
string content_type = "multipart/form-data; boundary=something";
string boundary;
auto ret = detail::parse_multipart_boundary(content_type, boundary);
EXPECT_TRUE(ret);
EXPECT_EQ(boundary, "something");
}
TEST(ParseMultipartBoundaryTest, ValueWithQuote) {
string content_type = "multipart/form-data; boundary=\"gc0pJq0M:08jU534c0p\"";
string boundary;
auto ret = detail::parse_multipart_boundary(content_type, boundary);
EXPECT_TRUE(ret);
EXPECT_EQ(boundary, "gc0pJq0M:08jU534c0p");
}
TEST(ParseMultipartBoundaryTest, ValueWithCharset) {
string content_type = "multipart/mixed; boundary=THIS_STRING_SEPARATES;charset=UTF-8";
string boundary;
auto ret = detail::parse_multipart_boundary(content_type, boundary);
EXPECT_TRUE(ret);
EXPECT_EQ(boundary, "THIS_STRING_SEPARATES");
}
TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
string content_type =
"multipart/mixed; boundary=\"cpp-httplib-multipart-data\"; charset=UTF-8";
string boundary;
auto ret = detail::parse_multipart_boundary(content_type, boundary);
EXPECT_TRUE(ret);
EXPECT_EQ(boundary, "cpp-httplib-multipart-data");
}
TEST(GetHeaderValueTest, DefaultValue) {
Headers headers = {{"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
@@ -779,7 +812,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// auto res = cli.Get(path.c_str());
@@ -1767,6 +1800,40 @@ protected:
EXPECT_EQ("application/json tmp-string", file.content_type);
}
})
.Post("/multipart/multi_file_values",
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(5u, req.files.size());
ASSERT_TRUE(!req.has_file("???"));
ASSERT_TRUE(req.body.empty());
{
const auto &text_value = req.get_file_values("text");
EXPECT_EQ(text_value.size(), 1);
auto &text = text_value[0];
EXPECT_TRUE(text.filename.empty());
EXPECT_EQ("default text", text.content);
}
{
const auto &text1_values = req.get_file_values("multi_text1");
EXPECT_EQ(text1_values.size(), 2);
EXPECT_EQ("aaaaa", text1_values[0].content);
EXPECT_EQ("bbbbb", text1_values[1].content);
}
{
const auto &file1_values = req.get_file_values("multi_file1");
EXPECT_EQ(file1_values.size(), 2);
auto file1 = file1_values[0];
EXPECT_EQ(file1.filename, "hello.txt");
EXPECT_EQ(file1.content_type, "text/plain");
EXPECT_EQ("h\ne\n\nl\nl\no\n", file1.content);
auto file2 = file1_values[1];
EXPECT_EQ(file2.filename, "world.json");
EXPECT_EQ(file2.content_type, "application/json");
EXPECT_EQ("{\n \"world\", true\n}\n", file2.content);
}
})
.Post("/empty",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "");
@@ -2611,6 +2678,23 @@ TEST_F(ServerTest, MultipartFormData) {
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, MultipartFormDataMultiFileValues) {
MultipartFormDataItems items = {
{"text", "default text", "", ""},
{"multi_text1", "aaaaa", "", ""},
{"multi_text1", "bbbbb", "", ""},
{"multi_file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
{"multi_file1", "{\n \"world\", true\n}\n", "world.json", "application/json"},
};
auto res = cli_.Post("/multipart/multi_file_values", items);
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, CaseInsensitiveHeaderName) {
auto res = cli_.Get("/hi");
ASSERT_TRUE(res);
@@ -3134,7 +3218,7 @@ TEST(GzipDecompressor, ChunkedDecompression) {
{
httplib::detail::gzip_decompressor decompressor;
// Chunk size is chosen specificaly to have a decompressed chunk size equal
// Chunk size is chosen specifically to have a decompressed chunk size equal
// to 16384 bytes 16384 bytes is the size of decompressor output buffer
size_t chunk_size = 130;
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size();
@@ -3283,7 +3367,7 @@ TEST_F(ServerTest, PostContentReceiver) {
ASSERT_EQ("content", res->body);
}
TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
TEST_F(ServerTest, PostMultipartFileContentReceiver) {
MultipartFormDataItems items = {
{"text1", "text default", "", ""},
{"text2", "aωb", "", ""},
@@ -3298,7 +3382,7 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, PostMulitpartPlusBoundary) {
TEST_F(ServerTest, PostMultipartPlusBoundary) {
MultipartFormDataItems items = {
{"text1", "text default", "", ""},
{"text2", "aωb", "", ""},
@@ -3714,10 +3798,10 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
"&&&%%%");
}
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparsableHeaderLine) {
// Make sure this doesn't crash the server.
// In a previous version of the header line regex, the "\r" rendered the line
// unparseable and the regex engine repeatedly backtracked, trying to look for
// unparsable and the regex engine repeatedly backtracked, trying to look for
// a new position where the leading white space ended and the field value
// began.
// The crash occurs with libc++ but not libstdc++.
@@ -3820,6 +3904,32 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
ASSERT_FALSE(svr.is_running());
}
TEST(ServerStopTest, ClientAccessAfterServerDown) {
httplib::Server svr;
svr.Post("/hi", [&](const httplib::Request & /*req*/, httplib::Response &res) {
res.status = 200;
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
Client cli(HOST, PORT);
auto res = cli.Post("/hi", "data", "text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
res = cli.Post("/hi", "data", "text/plain");
ASSERT_FALSE(res);
}
TEST(StreamingTest, NoContentLengthStreaming) {
Server svr;
@@ -3975,7 +4085,7 @@ TEST(KeepAliveTest, ReadTimeout) {
cli.set_read_timeout(std::chrono::seconds(1));
auto resa = cli.Get("/a");
ASSERT_TRUE(!resa);
ASSERT_FALSE(resa);
EXPECT_EQ(Error::Read, resa.error());
auto resb = cli.Get("/b");
@@ -3989,35 +4099,74 @@ TEST(KeepAliveTest, ReadTimeout) {
}
TEST(KeepAliveTest, Issue1041) {
const auto resourcePath = "/hi";
Server svr;
svr.set_keep_alive_timeout(3);
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
std::this_thread::sleep_for(std::chrono::milliseconds(200));
Client cli(HOST, PORT);
cli.set_keep_alive(true);
auto result = cli.Get(resourcePath);
auto result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
std::this_thread::sleep_for(std::chrono::seconds(5));
result = cli.Get(resourcePath);
result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
svr.stop();
a2.wait();
f.wait();
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(KeepAliveTest, SSLClientReconnection) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.set_keep_alive_timeout(1);
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
std::this_thread::sleep_for(std::chrono::milliseconds(200));
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
cli.set_keep_alive(true);
auto result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
std::this_thread::sleep_for(std::chrono::seconds(2));
// Recoonect
result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
svr.stop();
f.wait();
}
#endif
TEST(ClientProblemDetectionTest, ContentProvider) {
Server svr;
+1 -1
View File
@@ -193,7 +193,7 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// auto res = cli.Get(path.c_str());