mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27c0e1186c | |||
| c54c71a3e5 | |||
| 3409c00e6f | |||
| f8ef5fab64 | |||
| 5b397d455d | |||
| f977558a28 | |||
| c2e156e0e0 | |||
| 7aba2938d3 | |||
| d587548250 | |||
| 21f9c51556 | |||
| 985ceba525 | |||
| e62a4b02e5 | |||
| ff34749572 | |||
| e5804d4a50 | |||
| 3956a2b790 | |||
| b33aa52dc2 | |||
| 76230db97f | |||
| a66a013ed7 | |||
| f4b02dfdc1 | |||
| 4cf218643e | |||
| 8f96b69a25 | |||
| d262033ded | |||
| 5745eabe69 | |||
| 5f18642271 | |||
| 88a9278872 | |||
| 9bb3ca8169 | |||
| f2f4728489 | |||
| d1b616286f | |||
| df74526f91 | |||
| 9f7ae0737a | |||
| 1ebb8412c5 | |||
| 7b69999c37 | |||
| c7e959a948 | |||
| ba5884e779 | |||
| cdaa5c48db | |||
| bab5c0e907 | |||
| 016838fd10 | |||
| 75053bf855 | |||
| ae3a6dd2a9 | |||
| 6d963fbe8d | |||
| 88f6245c84 | |||
| 0e7d2f9f93 |
@@ -17,7 +17,7 @@ jobs:
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: install brotli library on ubuntu
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt update && sudo apt-get install -y libbrotli-dev
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
run: cd test && make fuzz_test
|
||||
- name: setup msbuild on windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
- name: make-windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
|
||||
+35
-22
@@ -6,16 +6,18 @@
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
@@ -43,6 +45,7 @@
|
||||
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
|
||||
* 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_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain 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).
|
||||
* HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
|
||||
@@ -79,12 +82,14 @@ 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)
|
||||
# Lets you compile the program as a regular library instead of header-only
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Lets you disable the installation (useful when fetched from another CMake project)
|
||||
option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
@@ -92,6 +97,7 @@ endif()
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
|
||||
# Defaults to static library
|
||||
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
|
||||
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
@@ -137,6 +143,10 @@ if(Brotli_FOUND)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN TRUE)
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
|
||||
include(GNUInstallDirs)
|
||||
@@ -206,6 +216,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}>,$<BOOL:${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}>>:-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>
|
||||
@@ -220,6 +232,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_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>
|
||||
)
|
||||
|
||||
# CMake configuration files installation directory
|
||||
@@ -233,8 +246,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)
|
||||
@@ -254,31 +265,33 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
if(HTTPLIB_INSTALL)
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
install(FILES
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Install it so it can be used later by the httplibConfig.cmake file.
|
||||
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
include(CTest)
|
||||
|
||||
@@ -55,6 +55,8 @@ SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcry
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
|
||||
NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
@@ -74,7 +76,7 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
```
|
||||
|
||||
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
Server
|
||||
------
|
||||
@@ -346,6 +348,27 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
});
|
||||
```
|
||||
|
||||
With trailer:
|
||||
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_header("Trailer", "Dummy1, Dummy2");
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[](size_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done_with_trailer({
|
||||
{"Dummy1", "DummyVal1"},
|
||||
{"Dummy2", "DummyVal2"}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||
@@ -812,14 +835,14 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
Note: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
|
||||
NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
|
||||
|
||||
Note: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported.
|
||||
NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2022 Yuji Hirose)
|
||||
MIT license (© 2023 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
+8
-1
@@ -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)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2022 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2023 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.12.0"
|
||||
#define CPPHTTPLIB_VERSION "0.12.5"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -239,7 +239,13 @@ using socket_t = int;
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#pragma comment(lib, "cryptui.lib")
|
||||
#endif
|
||||
#endif //_WIN32
|
||||
#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_OSX
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
#endif // TARGET_OS_OSX
|
||||
#endif // _WIN32
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -308,6 +314,34 @@ struct ci {
|
||||
}
|
||||
};
|
||||
|
||||
// This is based on
|
||||
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
|
||||
|
||||
struct scope_exit {
|
||||
explicit scope_exit(std::function<void(void)> &&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;
|
||||
|
||||
std::function<void(void)> exit_function;
|
||||
bool execute_on_destruction;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
using Headers = std::multimap<std::string, std::string, detail::ci>;
|
||||
@@ -340,6 +374,7 @@ public:
|
||||
|
||||
std::function<bool(const char *data, size_t data_len)> write;
|
||||
std::function<void()> done;
|
||||
std::function<void(const Headers &trailer)> done_with_trailer;
|
||||
std::ostream os;
|
||||
|
||||
private:
|
||||
@@ -454,6 +489,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;
|
||||
@@ -707,6 +743,7 @@ public:
|
||||
bool listen(const std::string &host, int port, int socket_flags = 0);
|
||||
|
||||
bool is_running() const;
|
||||
void wait_until_ready() const;
|
||||
void stop();
|
||||
|
||||
std::function<TaskQueue *(void)> new_task_queue;
|
||||
@@ -716,7 +753,7 @@ protected:
|
||||
bool &connection_closed,
|
||||
const std::function<void(Request &)> &setup_request);
|
||||
|
||||
std::atomic<socket_t> svr_sock_;
|
||||
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
|
||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
||||
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
|
||||
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
|
||||
@@ -768,7 +805,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);
|
||||
@@ -780,7 +817,8 @@ private:
|
||||
};
|
||||
std::vector<MountPointEntry> base_dirs_;
|
||||
|
||||
std::atomic<bool> is_running_;
|
||||
std::atomic<bool> is_running_{false};
|
||||
std::atomic<bool> done_{false};
|
||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||
Handler file_request_handler_;
|
||||
Handlers get_handlers_;
|
||||
@@ -822,6 +860,9 @@ enum class Error {
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
|
||||
// For internal use only
|
||||
SSLPeerCouldBeClosed_,
|
||||
};
|
||||
|
||||
std::string to_string(const Error error);
|
||||
@@ -1094,8 +1135,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 +1156,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 +1235,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 +1829,9 @@ std::string params_to_query_str(const Params ¶ms);
|
||||
|
||||
void parse_query_text(const std::string &s, Params ¶ms);
|
||||
|
||||
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);
|
||||
@@ -2625,7 +2670,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
|
||||
auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
|
||||
if (sock != INVALID_SOCKET) {
|
||||
sockaddr_un addr;
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
std::copy(host.begin(), host.end(), addr.sun_path);
|
||||
|
||||
@@ -2683,7 +2728,10 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
if (sock == INVALID_SOCKET) { continue; }
|
||||
|
||||
#ifndef _WIN32
|
||||
if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
|
||||
if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
|
||||
close_socket(sock);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (tcp_nodelay) {
|
||||
@@ -2938,9 +2986,14 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
|
||||
inline constexpr unsigned int str2tag_core(const char *s, size_t l,
|
||||
unsigned int h) {
|
||||
return (l == 0) ? h
|
||||
: str2tag_core(s + 1, l - 1,
|
||||
(h * 33) ^ static_cast<unsigned char>(*s));
|
||||
return (l == 0)
|
||||
? h
|
||||
: str2tag_core(
|
||||
s + 1, l - 1,
|
||||
// Unsets the 6 high bits of h, therefore no overflow happens
|
||||
(((std::numeric_limits<unsigned int>::max)() >> 6) &
|
||||
h * 33) ^
|
||||
static_cast<unsigned char>(*s));
|
||||
}
|
||||
|
||||
inline unsigned int str2tag(const std::string &s) {
|
||||
@@ -3353,6 +3406,14 @@ inline const char *get_header_value(const Headers &headers,
|
||||
return def;
|
||||
}
|
||||
|
||||
inline bool compare_case_ignore(const std::string &a, const std::string &b) {
|
||||
if (a.size() != b.size()) { return false; }
|
||||
for (size_t i = 0; i < b.size(); i++) {
|
||||
if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
// Skip trailing spaces and tabs.
|
||||
@@ -3376,7 +3437,11 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
}
|
||||
|
||||
if (p < end) {
|
||||
fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
|
||||
auto key = std::string(beg, key_end);
|
||||
auto val = compare_case_ignore(key, "Location")
|
||||
? std::string(p, end)
|
||||
: decode_url(std::string(p, end), false);
|
||||
fn(std::move(key), std::move(val));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3474,7 +3539,8 @@ inline bool read_content_without_length(Stream &strm,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool read_content_chunked(Stream &strm,
|
||||
template <typename T>
|
||||
inline bool read_content_chunked(Stream &strm, T &x,
|
||||
ContentReceiverWithProgress out) {
|
||||
const auto bufsiz = 16;
|
||||
char buf[bufsiz];
|
||||
@@ -3500,15 +3566,29 @@ inline bool read_content_chunked(Stream &strm,
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { return false; }
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
}
|
||||
|
||||
if (chunk_len == 0) {
|
||||
// Reader terminator after chunks
|
||||
if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
|
||||
return false;
|
||||
assert(chunk_len == 0);
|
||||
|
||||
// Trailer
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
while (strcmp(line_reader.ptr(), "\r\n")) {
|
||||
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
|
||||
|
||||
// Exclude line terminator
|
||||
constexpr auto line_terminator_len = 2;
|
||||
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
|
||||
|
||||
parse_header(line_reader.ptr(), end,
|
||||
[&](std::string &&key, std::string &&val) {
|
||||
x.headers.emplace(std::move(key), std::move(val));
|
||||
});
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -3578,7 +3658,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
auto exceed_payload_max_length = false;
|
||||
|
||||
if (is_chunked_transfer_encoding(x.headers)) {
|
||||
ret = read_content_chunked(strm, out);
|
||||
ret = read_content_chunked(strm, x, out);
|
||||
} else if (!has_header(x.headers, "Content-Length")) {
|
||||
ret = read_content_without_length(strm, out);
|
||||
} else {
|
||||
@@ -3734,7 +3814,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) {
|
||||
auto done_with_trailer = [&](const Headers *trailer) {
|
||||
if (!ok) { return; }
|
||||
|
||||
data_available = false;
|
||||
@@ -3752,16 +3832,36 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
if (!payload.empty()) {
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) {
|
||||
if (!strm.is_writable() ||
|
||||
!write_data(strm, chunk.data(), chunk.size())) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string done_marker("0\r\n\r\n");
|
||||
static const std::string done_marker("0\r\n");
|
||||
if (!write_data(strm, done_marker.data(), done_marker.size())) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
// Trailer
|
||||
if (trailer) {
|
||||
for (const auto &kv : *trailer) {
|
||||
std::string field_line = kv.first + ": " + kv.second + "\r\n";
|
||||
if (!write_data(strm, field_line.data(), field_line.size())) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string crlf("\r\n");
|
||||
if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; }
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) { done_with_trailer(nullptr); };
|
||||
|
||||
data_sink.done_with_trailer = [&](const Headers &trailer) {
|
||||
done_with_trailer(&trailer);
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
@@ -3852,9 +3952,12 @@ inline void parse_query_text(const std::string &s, Params ¶ms) {
|
||||
|
||||
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 +4490,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 +4509,109 @@ 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(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
|
||||
#if TARGET_OS_OSX
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
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_macos(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 // TARGET_OS_OSX
|
||||
#endif // _WIN32
|
||||
#endif // CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
|
||||
#ifdef _WIN32
|
||||
class WSInit {
|
||||
public:
|
||||
WSInit() {
|
||||
@@ -4690,6 +4886,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();
|
||||
@@ -4740,10 +4946,9 @@ inline void Response::set_content(const std::string &s,
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length, const std::string &content_type, ContentProvider provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
assert(in_length > 0);
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = in_length;
|
||||
content_provider_ = std::move(provider);
|
||||
if (in_length > 0) { content_provider_ = std::move(provider); }
|
||||
content_provider_resource_releaser_ = resource_releaser;
|
||||
is_chunked_content_provider_ = false;
|
||||
}
|
||||
@@ -4920,8 +5125,7 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
// HTTP server implementation
|
||||
inline Server::Server()
|
||||
: new_task_queue(
|
||||
[] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
|
||||
svr_sock_(INVALID_SOCKET), is_running_(false) {
|
||||
[] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
@@ -5134,15 +5338,25 @@ inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
|
||||
return bind_internal(host, 0, socket_flags);
|
||||
}
|
||||
|
||||
inline bool Server::listen_after_bind() { return listen_internal(); }
|
||||
inline bool Server::listen_after_bind() {
|
||||
auto se = detail::scope_exit([&]() { done_ = true; });
|
||||
return listen_internal();
|
||||
}
|
||||
|
||||
inline bool Server::listen(const std::string &host, int port,
|
||||
int socket_flags) {
|
||||
auto se = detail::scope_exit([&]() { done_ = true; });
|
||||
return bind_to_port(host, port, socket_flags) && listen_internal();
|
||||
}
|
||||
|
||||
inline bool Server::is_running() const { return is_running_; }
|
||||
|
||||
inline void Server::wait_until_ready() const {
|
||||
while (!is_running() && !done_) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
}
|
||||
}
|
||||
|
||||
inline void Server::stop() {
|
||||
if (is_running_) {
|
||||
assert(svr_sock_ != INVALID_SOCKET);
|
||||
@@ -5405,7 +5619,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 +5639,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*/,
|
||||
@@ -5533,6 +5747,7 @@ inline int Server::bind_internal(const std::string &host, int port,
|
||||
inline bool Server::listen_internal() {
|
||||
auto ret = true;
|
||||
is_running_ = true;
|
||||
auto se = detail::scope_exit([&]() { is_running_ = false; });
|
||||
|
||||
{
|
||||
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
|
||||
@@ -5598,17 +5813,12 @@ 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();
|
||||
}
|
||||
|
||||
is_running_ = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -5715,8 +5925,8 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
res.headers.erase(it);
|
||||
}
|
||||
|
||||
res.headers.emplace("Content-Type",
|
||||
"multipart/byteranges; boundary=" + boundary);
|
||||
res.set_header("Content-Type",
|
||||
"multipart/byteranges; boundary=" + boundary);
|
||||
}
|
||||
|
||||
auto type = detail::encoding_type(req, res);
|
||||
@@ -6149,7 +6359,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 +6398,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 +6425,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([&]() {
|
||||
// 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 +6443,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; }
|
||||
@@ -6315,11 +6535,11 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto location = detail::decode_url(res.get_header_value("location"), true);
|
||||
auto location = res.get_header_value("location");
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
|
||||
|
||||
std::smatch m;
|
||||
if (!std::regex_match(location, m, re)) { return false; }
|
||||
@@ -6331,6 +6551,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (next_host.empty()) { next_host = m[3].str(); }
|
||||
auto port_str = m[4].str();
|
||||
auto next_path = m[5].str();
|
||||
auto next_query = m[6].str();
|
||||
|
||||
auto next_port = port_;
|
||||
if (!port_str.empty()) {
|
||||
@@ -6343,22 +6564,24 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (next_host.empty()) { next_host = host_; }
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
auto path = detail::decode_url(next_path, true) + next_query;
|
||||
|
||||
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
|
||||
return detail::redirect(*this, req, res, next_path, location, error);
|
||||
return detail::redirect(*this, req, res, path, location, error);
|
||||
} else {
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); }
|
||||
return detail::redirect(cli, req, res, next_path, location, error);
|
||||
return detail::redirect(cli, req, res, path, location, error);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
ClientImpl cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path, location, error);
|
||||
return detail::redirect(cli, req, res, path, location, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6369,7 +6592,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_) {
|
||||
@@ -6393,32 +6616,32 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
// Prepare additional headers
|
||||
if (close_connection) {
|
||||
if (!req.has_header("Connection")) {
|
||||
req.headers.emplace("Connection", "close");
|
||||
req.set_header("Connection", "close");
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.has_header("Host")) {
|
||||
if (is_ssl()) {
|
||||
if (port_ == 443) {
|
||||
req.headers.emplace("Host", host_);
|
||||
req.set_header("Host", host_);
|
||||
} else {
|
||||
req.headers.emplace("Host", host_and_port_);
|
||||
req.set_header("Host", host_and_port_);
|
||||
}
|
||||
} else {
|
||||
if (port_ == 80) {
|
||||
req.headers.emplace("Host", host_);
|
||||
req.set_header("Host", host_);
|
||||
} else {
|
||||
req.headers.emplace("Host", host_and_port_);
|
||||
req.set_header("Host", host_and_port_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
|
||||
if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
|
||||
if (!req.has_header("User-Agent")) {
|
||||
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
|
||||
req.headers.emplace("User-Agent", agent);
|
||||
req.set_header("User-Agent", agent);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6427,23 +6650,23 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
if (!req.is_chunked_content_provider_) {
|
||||
if (!req.has_header("Content-Length")) {
|
||||
auto length = std::to_string(req.content_length_);
|
||||
req.headers.emplace("Content-Length", length);
|
||||
req.set_header("Content-Length", length);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (req.method == "POST" || req.method == "PUT" ||
|
||||
req.method == "PATCH") {
|
||||
req.headers.emplace("Content-Length", "0");
|
||||
req.set_header("Content-Length", "0");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!req.has_header("Content-Type")) {
|
||||
req.headers.emplace("Content-Type", "text/plain");
|
||||
req.set_header("Content-Type", "text/plain");
|
||||
}
|
||||
|
||||
if (!req.has_header("Content-Length")) {
|
||||
auto length = std::to_string(req.body.size());
|
||||
req.headers.emplace("Content-Length", length);
|
||||
req.set_header("Content-Length", length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6511,12 +6734,10 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
ContentProvider content_provider,
|
||||
ContentProviderWithoutLength content_provider_without_length,
|
||||
const std::string &content_type, Error &error) {
|
||||
if (!content_type.empty()) {
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
}
|
||||
if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); }
|
||||
if (compress_) { req.set_header("Content-Encoding", "gzip"); }
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
@@ -6577,7 +6798,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
req.content_provider_ = detail::ContentProviderAdapter(
|
||||
std::move(content_provider_without_length));
|
||||
req.is_chunked_content_provider_ = true;
|
||||
req.headers.emplace("Transfer-Encoding", "chunked");
|
||||
req.set_header("Transfer-Encoding", "chunked");
|
||||
} else {
|
||||
req.body.assign(body, content_length);
|
||||
;
|
||||
@@ -6619,6 +6840,20 @@ 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()) {
|
||||
auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
|
||||
if (!is_proxy_enabled) {
|
||||
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)) {
|
||||
@@ -7186,9 +7421,7 @@ inline Result ClientImpl::Delete(const std::string &path,
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
|
||||
if (!content_type.empty()) {
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
}
|
||||
if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
|
||||
req.body.assign(body, content_length);
|
||||
|
||||
return send_(std::move(req));
|
||||
@@ -7245,7 +7478,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 +7891,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 +8069,16 @@ 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_);
|
||||
#endif
|
||||
loaded =
|
||||
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
|
||||
#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
|
||||
#if TARGET_OS_OSX
|
||||
loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
|
||||
#endif // TARGET_OS_OSX
|
||||
#endif // _WIN32
|
||||
if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7985,7 +8223,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 +8239,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
@@ -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)
|
||||
|
||||
@@ -34,6 +34,13 @@ 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'
|
||||
macosx_keychain_dep = dependency('appleframeworks', modules: ['CoreFoundation', 'Security'], required: get_option('cpp-httplib_macosx_keychain'))
|
||||
if macosx_keychain_dep.found()
|
||||
deps += macosx_keychain_dep
|
||||
args += '-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
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_macosx_keychain', type: 'feature', value: 'auto', description: 'Enable loading certs from the Keychain on Apple devices')
|
||||
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')
|
||||
|
||||
+23
-12
@@ -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)
|
||||
|
||||
|
||||
@@ -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 += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -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
|
||||
|
||||
+619
-285
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user