mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7cac4f4b8 | |||
| e323374d2a | |||
| ffc294d37e | |||
| fceada9ef4 | |||
| 5f0f73fad9 | |||
| 530d6ee098 | |||
| 420c9759c6 | |||
| 2ce7c22218 | |||
| 4ef9ed80cd | |||
| 44b3fe6277 | |||
| 449801990f | |||
| af2928d316 | |||
| d948e38820 | |||
| 65218ce222 | |||
| 55e99c4030 | |||
| b63d50671d | |||
| eba980846b | |||
| 374d058de7 | |||
| 31cdcc3c3a | |||
| ad9f6423e2 |
+31
-38
@@ -3,11 +3,11 @@
|
||||
* BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* 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_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_TEST (default off)
|
||||
@@ -59,7 +59,6 @@
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
FindPython3 requires Cmake v3.12
|
||||
ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
@@ -69,20 +68,19 @@ cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
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.
|
||||
# Extracts just the version string itself from the whole string contained in _raw_version_string
|
||||
# since _raw_version_string would contain the entire line of code where it found the version string
|
||||
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
project(httplib VERSION ${_httplib_version} LANGUAGES CXX)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0")
|
||||
|
||||
# Lets you disable C++ exception during CMake configure time.
|
||||
# The value is used in the install CMake config file.
|
||||
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
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)
|
||||
@@ -94,10 +92,6 @@ option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib co
|
||||
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)
|
||||
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)
|
||||
@@ -110,45 +104,48 @@ if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||
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)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(OPENSSL_FOUND AND NOT DEFINED HTTPLIB_IS_USING_OPENSSL)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
# Avoid a rare circumstance of not finding all components but the end-user did their
|
||||
# own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted
|
||||
if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
|
||||
set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND})
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Adds our cmake folder to the search path for find_package
|
||||
# This is so we can use our custom FindBrotli.cmake
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
if(HTTPLIB_REQUIRE_BROTLI)
|
||||
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
|
||||
find_package(Brotli COMPONENTS encoder decoder common QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
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)
|
||||
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
@@ -204,9 +201,7 @@ endif()
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Require C++11
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
)
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
@@ -273,9 +268,7 @@ 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(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
|
||||
@@ -53,7 +53,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: cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information.
|
||||
|
||||
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`.
|
||||
|
||||
@@ -433,6 +433,17 @@ If you want to set the thread count at runtime, there is no convenient way... Bu
|
||||
svr.new_task_queue = [] { return new ThreadPool(12); };
|
||||
```
|
||||
|
||||
You can also provide an optional parameter to limit the maximum number
|
||||
of pending requests, i.e. requests `accept()`ed by the listener but
|
||||
still waiting to be serviced by worker threads.
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); };
|
||||
```
|
||||
|
||||
Default limit is 0 (unlimited). Once the limit is reached, the listener
|
||||
will shutdown the client connection.
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
You can supply your own thread pool implementation according to your need.
|
||||
@@ -444,8 +455,10 @@ public:
|
||||
pool_.start_with_thread_count(n);
|
||||
}
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) override {
|
||||
pool_.enqueue(fn);
|
||||
virtual bool enqueue(std::function<void()> fn) override {
|
||||
/* Return true if the task was actually enqueued, or false
|
||||
* if the caller must drop the corresponding connection. */
|
||||
return pool_.enqueue(fn);
|
||||
}
|
||||
|
||||
virtual void shutdown() override {
|
||||
@@ -848,7 +861,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
|
||||
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 and MSYS2 including MinGW are neither supported nor tested.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
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)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.14.3"
|
||||
#define CPPHTTPLIB_VERSION "0.15.1"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -82,6 +82,10 @@
|
||||
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
|
||||
#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_TCP_NODELAY
|
||||
#define CPPHTTPLIB_TCP_NODELAY false
|
||||
#endif
|
||||
@@ -160,10 +164,6 @@ using ssize_t = long;
|
||||
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp _stricmp
|
||||
#endif // strcasecmp
|
||||
|
||||
using socket_t = SOCKET;
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
|
||||
@@ -214,6 +214,7 @@ using socket_t = int;
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <exception>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
@@ -268,10 +269,8 @@ using socket_t = int;
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010100fL
|
||||
#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
|
||||
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#define SSL_get1_peer_certificate SSL_get_peer_certificate
|
||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -601,6 +600,7 @@ struct Response {
|
||||
void set_redirect(const std::string &url, int status = StatusCode::Found_302);
|
||||
void set_content(const char *s, size_t n, const std::string &content_type);
|
||||
void set_content(const std::string &s, const std::string &content_type);
|
||||
void set_content(std::string &&s, const std::string &content_type);
|
||||
|
||||
void set_content_provider(
|
||||
size_t length, const std::string &content_type, ContentProvider provider,
|
||||
@@ -657,7 +657,7 @@ public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual bool enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle() {}
|
||||
@@ -665,7 +665,8 @@ public:
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
public:
|
||||
explicit ThreadPool(size_t n) : shutdown_(false) {
|
||||
explicit ThreadPool(size_t n, size_t mqr = 0)
|
||||
: shutdown_(false), max_queued_requests_(mqr) {
|
||||
while (n) {
|
||||
threads_.emplace_back(worker(*this));
|
||||
n--;
|
||||
@@ -675,13 +676,17 @@ public:
|
||||
ThreadPool(const ThreadPool &) = delete;
|
||||
~ThreadPool() override = default;
|
||||
|
||||
void enqueue(std::function<void()> fn) override {
|
||||
bool enqueue(std::function<void()> fn) override {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
|
||||
return false;
|
||||
}
|
||||
jobs_.push_back(std::move(fn));
|
||||
}
|
||||
|
||||
cond_.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
@@ -731,6 +736,7 @@ private:
|
||||
std::list<std::function<void()>> jobs_;
|
||||
|
||||
bool shutdown_;
|
||||
size_t max_queued_requests_ = 0;
|
||||
|
||||
std::condition_variable cond_;
|
||||
std::mutex mutex_;
|
||||
@@ -744,6 +750,8 @@ void default_socket_options(socket_t sock);
|
||||
|
||||
const char *status_message(int status);
|
||||
|
||||
std::string get_bearer_token_auth(const Request &req);
|
||||
|
||||
namespace detail {
|
||||
|
||||
class MatcherBase {
|
||||
@@ -1942,6 +1950,15 @@ inline const char *status_message(int status) {
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string get_bearer_token_auth(const Request &req) {
|
||||
if (req.has_header("Authorization")) {
|
||||
static std::string BearerHeaderPrefix = "Bearer ";
|
||||
return req.get_header_value("Authorization")
|
||||
.substr(BearerHeaderPrefix.length());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &
|
||||
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
@@ -2052,7 +2069,7 @@ void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
|
||||
|
||||
std::string append_query_params(const std::string &path, const Params ¶ms);
|
||||
|
||||
std::pair<std::string, std::string> make_range_header(Ranges ranges);
|
||||
std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
|
||||
|
||||
std::pair<std::string, std::string>
|
||||
make_basic_authentication_header(const std::string &username,
|
||||
@@ -2400,6 +2417,7 @@ inline bool is_valid_path(const std::string &path) {
|
||||
// Read component
|
||||
auto beg = i;
|
||||
while (i < path.size() && path[i] != '/') {
|
||||
if (path[i] == '\0') { return false; }
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -2557,7 +2575,7 @@ inline std::string trim_double_quotes_copy(const std::string &s) {
|
||||
|
||||
inline void split(const char *b, const char *e, char d,
|
||||
std::function<void(const char *, const char *)> fn) {
|
||||
return split(b, e, d, std::numeric_limits<size_t>::max(), fn);
|
||||
return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
|
||||
}
|
||||
|
||||
inline void split(const char *b, const char *e, char d, size_t m,
|
||||
@@ -2705,7 +2723,9 @@ inline bool mmap::is_open() const { return addr_ != nullptr; }
|
||||
|
||||
inline size_t mmap::size() const { return size_; }
|
||||
|
||||
inline const char *mmap::data() const { return (const char *)addr_; }
|
||||
inline const char *mmap::data() const {
|
||||
return static_cast<const char *>(addr_);
|
||||
}
|
||||
|
||||
inline void mmap::close() {
|
||||
#if defined(_WIN32)
|
||||
@@ -2790,7 +2810,7 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return 1; }
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
@@ -2818,7 +2838,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return 1; }
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
@@ -3925,8 +3945,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
}
|
||||
|
||||
inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
|
||||
"chunked");
|
||||
return compare_case_ignore(
|
||||
get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -4621,28 +4641,32 @@ inline std::string to_lower(const char *beg, const char *end) {
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::string make_multipart_data_boundary() {
|
||||
inline std::string random_string(size_t length) {
|
||||
static const char data[] =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
// std::random_device might actually be deterministic on some
|
||||
// platforms, but due to lack of support in the c++ standard library,
|
||||
// doing better requires either some ugly hacks or breaking portability.
|
||||
std::random_device seed_gen;
|
||||
static std::random_device seed_gen;
|
||||
|
||||
// Request 128 bits of entropy for initialization
|
||||
std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
|
||||
std::mt19937 engine(seed_sequence);
|
||||
static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(),
|
||||
seed_gen()};
|
||||
|
||||
std::string result = "--cpp-httplib-multipart-data-";
|
||||
static std::mt19937 engine(seed_sequence);
|
||||
|
||||
for (auto i = 0; i < 16; i++) {
|
||||
std::string result;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result += data[engine() % (sizeof(data) - 1)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string make_multipart_data_boundary() {
|
||||
return "--cpp-httplib-multipart-data-" + detail::random_string(16);
|
||||
}
|
||||
|
||||
inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
|
||||
auto valid = true;
|
||||
for (size_t i = 0; i < boundary.size(); i++) {
|
||||
@@ -4700,44 +4724,91 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
|
||||
return body;
|
||||
}
|
||||
|
||||
inline bool normalize_ranges(Request &req, Response &res) {
|
||||
ssize_t contant_len = static_cast<ssize_t>(
|
||||
res.content_length_ ? res.content_length_ : res.body.size());
|
||||
|
||||
ssize_t prev_first_pos = -1;
|
||||
ssize_t prev_last_pos = -1;
|
||||
size_t overwrapping_count = 0;
|
||||
|
||||
if (!req.ranges.empty()) {
|
||||
// NOTE: The following Range check is based on '14.2. Range' in RFC 9110
|
||||
// 'HTTP Semantics' to avoid potential denial-of-service attacks.
|
||||
// https://www.rfc-editor.org/rfc/rfc9110#section-14.2
|
||||
|
||||
// Too many ranges
|
||||
if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return false; }
|
||||
|
||||
for (auto &r : req.ranges) {
|
||||
auto &first_pos = r.first;
|
||||
auto &last_pos = r.second;
|
||||
|
||||
if (first_pos == -1 && last_pos == -1) {
|
||||
first_pos = 0;
|
||||
last_pos = contant_len;
|
||||
}
|
||||
|
||||
if (first_pos == -1) {
|
||||
first_pos = contant_len - last_pos;
|
||||
last_pos = contant_len - 1;
|
||||
}
|
||||
|
||||
if (last_pos == -1) { last_pos = contant_len - 1; }
|
||||
|
||||
// Range must be within content length
|
||||
if (!(0 <= first_pos && first_pos <= last_pos &&
|
||||
last_pos <= contant_len - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ranges must be in ascending order
|
||||
if (first_pos <= prev_first_pos) { return false; }
|
||||
|
||||
// Request must not have more than two overlapping ranges
|
||||
if (first_pos <= prev_last_pos) {
|
||||
overwrapping_count++;
|
||||
if (overwrapping_count > 2) { return false; }
|
||||
}
|
||||
|
||||
prev_first_pos = (std::max)(prev_first_pos, first_pos);
|
||||
prev_last_pos = (std::max)(prev_last_pos, last_pos);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, size_t content_length,
|
||||
size_t index) {
|
||||
auto r = req.ranges[index];
|
||||
get_range_offset_and_length(Range r, size_t content_length) {
|
||||
assert(r.first != -1 && r.second != -1);
|
||||
assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
|
||||
assert(r.first <= r.second &&
|
||||
r.second < static_cast<ssize_t>(content_length));
|
||||
|
||||
if (r.first == -1 && r.second == -1) {
|
||||
return std::make_pair(0, content_length);
|
||||
}
|
||||
|
||||
auto slen = static_cast<ssize_t>(content_length);
|
||||
|
||||
if (r.first == -1) {
|
||||
r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
|
||||
r.second = slen - 1;
|
||||
}
|
||||
|
||||
if (r.second == -1) { r.second = slen - 1; }
|
||||
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
||||
}
|
||||
|
||||
inline std::string
|
||||
make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
|
||||
size_t content_length) {
|
||||
inline std::string make_content_range_header_field(
|
||||
const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
|
||||
auto st = offset_and_length.first;
|
||||
auto ed = st + offset_and_length.second - 1;
|
||||
|
||||
std::string field = "bytes ";
|
||||
if (range.first != -1) { field += std::to_string(range.first); }
|
||||
field += std::to_string(st);
|
||||
field += "-";
|
||||
if (range.second != -1) { field += std::to_string(range.second); }
|
||||
field += std::to_string(ed);
|
||||
field += "/";
|
||||
field += std::to_string(content_length);
|
||||
return field;
|
||||
}
|
||||
|
||||
template <typename SToken, typename CToken, typename Content>
|
||||
bool process_multipart_ranges_data(const Request &req, Response &res,
|
||||
bool process_multipart_ranges_data(const Request &req,
|
||||
const std::string &boundary,
|
||||
const std::string &content_type,
|
||||
SToken stoken, CToken ctoken,
|
||||
Content content) {
|
||||
size_t content_length, SToken stoken,
|
||||
CToken ctoken, Content content) {
|
||||
for (size_t i = 0; i < req.ranges.size(); i++) {
|
||||
ctoken("--");
|
||||
stoken(boundary);
|
||||
@@ -4748,16 +4819,17 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
|
||||
ctoken("\r\n");
|
||||
}
|
||||
|
||||
auto offset_and_length =
|
||||
get_range_offset_and_length(req.ranges[i], content_length);
|
||||
|
||||
ctoken("Content-Range: ");
|
||||
const auto &range = req.ranges[i];
|
||||
stoken(make_content_range_header_field(range, res.content_length_));
|
||||
stoken(make_content_range_header_field(offset_and_length, content_length));
|
||||
ctoken("\r\n");
|
||||
ctoken("\r\n");
|
||||
|
||||
auto offsets = get_range_offset_and_length(req, res.content_length_, i);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
if (!content(offset, length)) { return false; }
|
||||
if (!content(offset_and_length.first, offset_and_length.second)) {
|
||||
return false;
|
||||
}
|
||||
ctoken("\r\n");
|
||||
}
|
||||
|
||||
@@ -4768,31 +4840,30 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool make_multipart_ranges_data(const Request &req, Response &res,
|
||||
inline void make_multipart_ranges_data(const Request &req, Response &res,
|
||||
const std::string &boundary,
|
||||
const std::string &content_type,
|
||||
size_t content_length,
|
||||
std::string &data) {
|
||||
return process_multipart_ranges_data(
|
||||
req, res, boundary, content_type,
|
||||
process_multipart_ranges_data(
|
||||
req, boundary, content_type, content_length,
|
||||
[&](const std::string &token) { data += token; },
|
||||
[&](const std::string &token) { data += token; },
|
||||
[&](size_t offset, size_t length) {
|
||||
if (offset < res.body.size()) {
|
||||
data += res.body.substr(offset, length);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
assert(offset + length <= content_length);
|
||||
data += res.body.substr(offset, length);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
inline size_t
|
||||
get_multipart_ranges_data_length(const Request &req, Response &res,
|
||||
const std::string &boundary,
|
||||
const std::string &content_type) {
|
||||
inline size_t get_multipart_ranges_data_length(const Request &req,
|
||||
const std::string &boundary,
|
||||
const std::string &content_type,
|
||||
size_t content_length) {
|
||||
size_t data_length = 0;
|
||||
|
||||
process_multipart_ranges_data(
|
||||
req, res, boundary, content_type,
|
||||
req, boundary, content_type, content_length,
|
||||
[&](const std::string &token) { data_length += token.size(); },
|
||||
[&](const std::string &token) { data_length += token.size(); },
|
||||
[&](size_t /*offset*/, size_t length) {
|
||||
@@ -4804,13 +4875,13 @@ get_multipart_ranges_data_length(const Request &req, Response &res,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
||||
Response &res,
|
||||
const std::string &boundary,
|
||||
const std::string &content_type,
|
||||
const T &is_shutting_down) {
|
||||
inline bool
|
||||
write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
|
||||
const std::string &boundary,
|
||||
const std::string &content_type,
|
||||
size_t content_length, const T &is_shutting_down) {
|
||||
return process_multipart_ranges_data(
|
||||
req, res, boundary, content_type,
|
||||
req, boundary, content_type, content_length,
|
||||
[&](const std::string &token) { strm.write(token); },
|
||||
[&](const std::string &token) { strm.write(token); },
|
||||
[&](size_t offset, size_t length) {
|
||||
@@ -4819,18 +4890,6 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
||||
});
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, const Response &res,
|
||||
size_t index) {
|
||||
auto r = req.ranges[index];
|
||||
|
||||
if (r.second == -1) {
|
||||
r.second = static_cast<ssize_t>(res.content_length_) - 1;
|
||||
}
|
||||
|
||||
return std::make_pair(r.first, r.second - r.first + 1);
|
||||
}
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "PRI" || req.method == "DELETE") {
|
||||
@@ -5117,20 +5176,6 @@ inline bool parse_www_authenticate(const Response &res,
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
|
||||
inline std::string random_string(size_t length) {
|
||||
auto randchar = []() -> char {
|
||||
const char charset[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
const size_t max_index = (sizeof(charset) - 1);
|
||||
return charset[static_cast<size_t>(std::rand()) % max_index];
|
||||
};
|
||||
std::string str(length, 0);
|
||||
std::generate_n(str.begin(), length, randchar);
|
||||
return str;
|
||||
}
|
||||
|
||||
class ContentProviderAdapter {
|
||||
public:
|
||||
explicit ContentProviderAdapter(
|
||||
@@ -5195,10 +5240,11 @@ inline std::string append_query_params(const std::string &path,
|
||||
}
|
||||
|
||||
// Header utilities
|
||||
inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
|
||||
inline std::pair<std::string, std::string>
|
||||
make_range_header(const Ranges &ranges) {
|
||||
std::string field = "bytes=";
|
||||
auto i = 0;
|
||||
for (auto r : ranges) {
|
||||
for (const auto &r : ranges) {
|
||||
if (i != 0) { field += ", "; }
|
||||
if (r.first != -1) { field += std::to_string(r.first); }
|
||||
field += '-';
|
||||
@@ -5336,13 +5382,22 @@ inline void Response::set_content(const std::string &s,
|
||||
set_content(s.data(), s.size(), content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content(std::string &&s,
|
||||
const std::string &content_type) {
|
||||
body = std::move(s);
|
||||
|
||||
auto rng = headers.equal_range("Content-Type");
|
||||
headers.erase(rng.first, rng.second);
|
||||
set_header("Content-Type", content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length, const std::string &content_type, ContentProvider provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = in_length;
|
||||
if (in_length > 0) { content_provider_ = std::move(provider); }
|
||||
content_provider_resource_releaser_ = resource_releaser;
|
||||
content_provider_resource_releaser_ = std::move(resource_releaser);
|
||||
is_chunked_content_provider_ = false;
|
||||
}
|
||||
|
||||
@@ -5352,7 +5407,7 @@ inline void Response::set_content_provider(
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
content_provider_resource_releaser_ = resource_releaser;
|
||||
content_provider_resource_releaser_ = std::move(resource_releaser);
|
||||
is_chunked_content_provider_ = false;
|
||||
}
|
||||
|
||||
@@ -5362,7 +5417,7 @@ inline void Response::set_chunked_content_provider(
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
content_provider_resource_releaser_ = resource_releaser;
|
||||
content_provider_resource_releaser_ = std::move(resource_releaser);
|
||||
is_chunked_content_provider_ = true;
|
||||
}
|
||||
|
||||
@@ -6006,7 +6061,6 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
res.content_provider_success_ = false;
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
@@ -6031,15 +6085,16 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
return detail::write_content(strm, res.content_provider_, 0,
|
||||
res.content_length_, is_shutting_down);
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
return detail::write_content(strm, res.content_provider_, offset, length,
|
||||
is_shutting_down);
|
||||
auto offset_and_length = detail::get_range_offset_and_length(
|
||||
req.ranges[0], res.content_length_);
|
||||
|
||||
return detail::write_content(strm, res.content_provider_,
|
||||
offset_and_length.first,
|
||||
offset_and_length.second, is_shutting_down);
|
||||
} else {
|
||||
return detail::write_multipart_ranges_data(
|
||||
strm, req, res, boundary, content_type, is_shutting_down);
|
||||
strm, req, res, boundary, content_type, res.content_length_,
|
||||
is_shutting_down);
|
||||
}
|
||||
} else {
|
||||
if (res.is_chunked_content_provider_) {
|
||||
@@ -6323,7 +6378,11 @@ inline bool Server::listen_internal() {
|
||||
#endif
|
||||
}
|
||||
|
||||
task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
|
||||
if (!task_queue->enqueue(
|
||||
[this, sock]() { process_and_close_socket(sock); })) {
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
task_queue->shutdown();
|
||||
@@ -6427,14 +6486,14 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
std::string &content_type,
|
||||
std::string &boundary) const {
|
||||
if (req.ranges.size() > 1) {
|
||||
boundary = detail::make_multipart_data_boundary();
|
||||
|
||||
auto it = res.headers.find("Content-Type");
|
||||
if (it != res.headers.end()) {
|
||||
content_type = it->second;
|
||||
res.headers.erase(it);
|
||||
}
|
||||
|
||||
boundary = detail::make_multipart_data_boundary();
|
||||
|
||||
res.set_header("Content-Type",
|
||||
"multipart/byteranges; boundary=" + boundary);
|
||||
}
|
||||
@@ -6447,16 +6506,17 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
if (req.ranges.empty()) {
|
||||
length = res.content_length_;
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
length = offsets.second;
|
||||
auto offset_and_length = detail::get_range_offset_and_length(
|
||||
req.ranges[0], res.content_length_);
|
||||
|
||||
length = offset_and_length.second;
|
||||
|
||||
auto content_range = detail::make_content_range_header_field(
|
||||
req.ranges[0], res.content_length_);
|
||||
offset_and_length, res.content_length_);
|
||||
res.set_header("Content-Range", content_range);
|
||||
} else {
|
||||
length = detail::get_multipart_ranges_data_length(req, res, boundary,
|
||||
content_type);
|
||||
length = detail::get_multipart_ranges_data_length(
|
||||
req, boundary, content_type, res.content_length_);
|
||||
}
|
||||
res.set_header("Content-Length", std::to_string(length));
|
||||
} else {
|
||||
@@ -6475,30 +6535,22 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
if (req.ranges.empty()) {
|
||||
;
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offset_and_length =
|
||||
detail::get_range_offset_and_length(req.ranges[0], res.body.size());
|
||||
auto offset = offset_and_length.first;
|
||||
auto length = offset_and_length.second;
|
||||
|
||||
auto content_range = detail::make_content_range_header_field(
|
||||
req.ranges[0], res.body.size());
|
||||
offset_and_length, res.body.size());
|
||||
res.set_header("Content-Range", content_range);
|
||||
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
|
||||
if (offset < res.body.size()) {
|
||||
res.body = res.body.substr(offset, length);
|
||||
} else {
|
||||
res.body.clear();
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
}
|
||||
assert(offset + length <= res.body.size());
|
||||
res.body = res.body.substr(offset, length);
|
||||
} else {
|
||||
std::string data;
|
||||
if (detail::make_multipart_ranges_data(req, res, boundary, content_type,
|
||||
data)) {
|
||||
res.body.swap(data);
|
||||
} else {
|
||||
res.body.clear();
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
}
|
||||
detail::make_multipart_ranges_data(req, res, boundary, content_type,
|
||||
res.body.size(), data);
|
||||
res.body.swap(data);
|
||||
}
|
||||
|
||||
if (type != detail::EncodingType::None) {
|
||||
@@ -6674,13 +6726,20 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (routed) {
|
||||
if (res.status == -1) {
|
||||
res.status = req.ranges.empty() ? StatusCode::OK_200
|
||||
: StatusCode::PartialContent_206;
|
||||
if (detail::normalize_ranges(req, res)) {
|
||||
if (res.status == -1) {
|
||||
res.status = req.ranges.empty() ? StatusCode::OK_200
|
||||
: StatusCode::PartialContent_206;
|
||||
}
|
||||
return write_response_with_content(strm, close_connection, req, res);
|
||||
} else {
|
||||
res.body.clear();
|
||||
res.content_length_ = 0;
|
||||
res.content_provider_ = nullptr;
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
return write_response_with_content(strm, close_connection, req, res);
|
||||
} else {
|
||||
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
|
||||
return write_response(strm, close_connection, req, res);
|
||||
@@ -7586,14 +7645,15 @@ inline Result ClientImpl::Get(const std::string &path, const Params ¶ms,
|
||||
if (params.empty()) { return Get(path, headers); }
|
||||
|
||||
std::string path_with_query = append_query_params(path, params);
|
||||
return Get(path_with_query, headers, progress);
|
||||
return Get(path_with_query, headers, std::move(progress));
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Get(const std::string &path, const Params ¶ms,
|
||||
const Headers &headers,
|
||||
ContentReceiver content_receiver,
|
||||
Progress progress) {
|
||||
return Get(path, params, headers, nullptr, content_receiver, progress);
|
||||
return Get(path, params, headers, nullptr, std::move(content_receiver),
|
||||
std::move(progress));
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Get(const std::string &path, const Params ¶ms,
|
||||
@@ -7602,12 +7662,13 @@ inline Result ClientImpl::Get(const std::string &path, const Params ¶ms,
|
||||
ContentReceiver content_receiver,
|
||||
Progress progress) {
|
||||
if (params.empty()) {
|
||||
return Get(path, headers, response_handler, content_receiver, progress);
|
||||
return Get(path, headers, std::move(response_handler),
|
||||
std::move(content_receiver), std::move(progress));
|
||||
}
|
||||
|
||||
std::string path_with_query = append_query_params(path, params);
|
||||
return Get(path_with_query, headers, response_handler, content_receiver,
|
||||
progress);
|
||||
return Get(path_with_query, headers, std::move(response_handler),
|
||||
std::move(content_receiver), std::move(progress));
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Head(const std::string &path) {
|
||||
@@ -8689,11 +8750,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
return true;
|
||||
},
|
||||
[&](SSL *ssl2) {
|
||||
// NOTE: With -Wold-style-cast, this can produce a warning, since
|
||||
// SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains
|
||||
// an old style cast. Short of doing compiler specific pragma's
|
||||
// here, we can't get rid of this warning. :'(
|
||||
SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
// NOTE: Direct call instead of using the OpenSSL macro to suppress
|
||||
// -Wold-style-cast warning
|
||||
// SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
|
||||
static_cast<void *>(const_cast<char *>(host_.c_str())));
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -8982,19 +9043,20 @@ inline Result Client::Get(const std::string &path, const Headers &headers,
|
||||
}
|
||||
inline Result Client::Get(const std::string &path, const Params ¶ms,
|
||||
const Headers &headers, Progress progress) {
|
||||
return cli_->Get(path, params, headers, progress);
|
||||
return cli_->Get(path, params, headers, std::move(progress));
|
||||
}
|
||||
inline Result Client::Get(const std::string &path, const Params ¶ms,
|
||||
const Headers &headers,
|
||||
ContentReceiver content_receiver, Progress progress) {
|
||||
return cli_->Get(path, params, headers, content_receiver, progress);
|
||||
return cli_->Get(path, params, headers, std::move(content_receiver),
|
||||
std::move(progress));
|
||||
}
|
||||
inline Result Client::Get(const std::string &path, const Params ¶ms,
|
||||
const Headers &headers,
|
||||
ResponseHandler response_handler,
|
||||
ContentReceiver content_receiver, Progress progress) {
|
||||
return cli_->Get(path, params, headers, response_handler, content_receiver,
|
||||
progress);
|
||||
return cli_->Get(path, params, headers, std::move(response_handler),
|
||||
std::move(content_receiver), std::move(progress));
|
||||
}
|
||||
|
||||
inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ endif
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
|
||||
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow #
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
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)
|
||||
|
||||
+169
-17
@@ -71,6 +71,15 @@ TEST(DecodeURLTest, PercentCharacter) {
|
||||
R"(descrip=Gastos áéíóúñÑ 6)");
|
||||
}
|
||||
|
||||
TEST(DecodeURLTest, PercentCharacterNUL) {
|
||||
string expected;
|
||||
expected.push_back('x');
|
||||
expected.push_back('\0');
|
||||
expected.push_back('x');
|
||||
|
||||
EXPECT_EQ(detail::decode_url("x%00x", false), expected);
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
||||
string unescapedCharacters = "-_.!~*'()";
|
||||
|
||||
@@ -1822,7 +1831,7 @@ protected:
|
||||
});
|
||||
})
|
||||
.Get("/streamed-with-range",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
[&](const Request &req, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
res.set_content_provider(
|
||||
data->size(), "text/plain",
|
||||
@@ -1836,8 +1845,8 @@ protected:
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
[data](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
[data, &req](bool success) {
|
||||
EXPECT_EQ(success, !req.has_param("error"));
|
||||
delete data;
|
||||
});
|
||||
})
|
||||
@@ -2482,6 +2491,12 @@ TEST_F(ServerTest, GetMethodInvalidMountPath) {
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodEmbeddedNUL) {
|
||||
auto res = cli_.Get("/mount/dir/test.html%00.js");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodOutOfBaseDirMount) {
|
||||
auto res = cli_.Get("/mount/../www2/dir/test.html");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -2525,6 +2540,7 @@ TEST_F(ServerTest, StaticFileRange) {
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 2-3/5", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("cd"), res->body);
|
||||
}
|
||||
|
||||
@@ -2538,7 +2554,7 @@ TEST_F(ServerTest, StaticFileRanges) {
|
||||
.find(
|
||||
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
|
||||
0);
|
||||
EXPECT_EQ("265", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("266", res->get_header_value("Content-Length"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileRangeHead) {
|
||||
@@ -2548,6 +2564,7 @@ TEST_F(ServerTest, StaticFileRangeHead) {
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 2-3/5", res->get_header_value("Content-Range"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileRangeBigFile) {
|
||||
@@ -2557,6 +2574,8 @@ TEST_F(ServerTest, StaticFileRangeBigFile) {
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("5", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1048571-1048575/1048576",
|
||||
res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ("LAST\n", res->body);
|
||||
}
|
||||
|
||||
@@ -2567,6 +2586,7 @@ TEST_F(ServerTest, StaticFileRangeBigFile2) {
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4097", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1-4097/1048576", res->get_header_value("Content-Range"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileBigFile) {
|
||||
@@ -2893,6 +2913,8 @@ TEST_F(ServerTest, GetStreamed2) {
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 2-3/6", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("ab"), res->body);
|
||||
}
|
||||
|
||||
@@ -2910,6 +2932,7 @@ TEST_F(ServerTest, GetStreamedWithRange1) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 3-5/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("def"), res->body);
|
||||
}
|
||||
|
||||
@@ -2919,6 +2942,7 @@ TEST_F(ServerTest, GetStreamedWithRange2) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("6", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("bcdefg"), res->body);
|
||||
}
|
||||
|
||||
@@ -2928,16 +2952,17 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 4-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("efg"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
|
||||
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-9999"}});
|
||||
auto res = cli_.Get("/streamed-with-range?error", {{"Range", "bytes=-9999"}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithRangeError) {
|
||||
@@ -2946,15 +2971,18 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
|
||||
"92233720368547758079223372036854775807"}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
|
||||
auto res =
|
||||
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
||||
@@ -2967,6 +2995,41 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
||||
EXPECT_EQ(267U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithTooManyRanges) {
|
||||
Ranges ranges;
|
||||
for (size_t i = 0; i < CPPHTTPLIB_RANGE_MAX_COUNT + 1; i++) {
|
||||
ranges.emplace_back(0, -1);
|
||||
}
|
||||
|
||||
auto res =
|
||||
cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) {
|
||||
auto res = cli_.Get("/streamed-with-range?error",
|
||||
{{make_range_header({{0, -1}, {0, -1}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) {
|
||||
auto res = cli_.Get("/streamed-with-range?error",
|
||||
{{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedEndless) {
|
||||
uint64_t offset = 0;
|
||||
auto res = cli_.Get("/streamed-cancel",
|
||||
@@ -3014,6 +3077,7 @@ TEST_F(ServerTest, GetWithRange1) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 3-5/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("def"), res->body);
|
||||
}
|
||||
|
||||
@@ -3023,6 +3087,7 @@ TEST_F(ServerTest, GetWithRange2) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("6", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("bcdefg"), res->body);
|
||||
}
|
||||
|
||||
@@ -3032,6 +3097,7 @@ TEST_F(ServerTest, GetWithRange3) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("1", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 0-0/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("a"), res->body);
|
||||
}
|
||||
|
||||
@@ -3041,6 +3107,7 @@ TEST_F(ServerTest, GetWithRange4) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 5-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("fg"), res->body);
|
||||
}
|
||||
|
||||
@@ -6511,18 +6578,103 @@ TEST(SocketStream, is_writable_INET) {
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_task{1000000};
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
std::atomic_uint count{0};
|
||||
std::unique_ptr<TaskQueue> task_queue{
|
||||
new ThreadPool{CPPHTTPLIB_THREAD_POOL_COUNT}};
|
||||
|
||||
for (unsigned int i = 0; i < number_of_task; ++i) {
|
||||
task_queue->enqueue(
|
||||
for (unsigned int i = 0; i < number_of_tasks; ++i) {
|
||||
auto queued = task_queue->enqueue(
|
||||
[&count] { count.fetch_add(1, std::memory_order_relaxed); });
|
||||
EXPECT_TRUE(queued);
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_tasks, count.load());
|
||||
}
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicIntegerWithQueueLimit) {
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
static constexpr unsigned int qlimit{2};
|
||||
unsigned int queued_count{0};
|
||||
std::atomic_uint count{0};
|
||||
std::unique_ptr<TaskQueue> task_queue{
|
||||
new ThreadPool{/*num_threads=*/1, qlimit}};
|
||||
|
||||
for (unsigned int i = 0; i < number_of_tasks; ++i) {
|
||||
if (task_queue->enqueue(
|
||||
[&count] { count.fetch_add(1, std::memory_order_relaxed); })) {
|
||||
queued_count++;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(queued_count, count.load());
|
||||
EXPECT_TRUE(queued_count <= number_of_tasks);
|
||||
EXPECT_TRUE(queued_count >= qlimit);
|
||||
}
|
||||
|
||||
TEST(TaskQueueTest, MaxQueuedRequests) {
|
||||
static constexpr unsigned int qlimit{3};
|
||||
std::unique_ptr<TaskQueue> task_queue{new ThreadPool{1, qlimit}};
|
||||
std::condition_variable sem_cv;
|
||||
std::mutex sem_mtx;
|
||||
int credits = 0;
|
||||
bool queued;
|
||||
|
||||
/* Fill up the queue with tasks that will block until we give them credits to
|
||||
* complete. */
|
||||
for (unsigned int n = 0; n <= qlimit;) {
|
||||
queued = task_queue->enqueue([&sem_mtx, &sem_cv, &credits] {
|
||||
std::unique_lock<std::mutex> lock(sem_mtx);
|
||||
while (credits <= 0) {
|
||||
sem_cv.wait(lock);
|
||||
}
|
||||
/* Consume the credit and signal the test code if they are all gone. */
|
||||
if (--credits == 0) { sem_cv.notify_one(); }
|
||||
});
|
||||
|
||||
if (n < qlimit) {
|
||||
/* The first qlimit enqueues must succeed. */
|
||||
EXPECT_TRUE(queued);
|
||||
} else {
|
||||
/* The last one will succeed only when the worker thread
|
||||
* starts and dequeues the first blocking task. Although
|
||||
* not necessary for the correctness of this test, we sleep for
|
||||
* a short while to avoid busy waiting. */
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
if (queued) { n++; }
|
||||
}
|
||||
|
||||
/* Further enqueues must fail since the queue is full. */
|
||||
for (auto i = 0; i < 4; i++) {
|
||||
queued = task_queue->enqueue([] {});
|
||||
EXPECT_FALSE(queued);
|
||||
}
|
||||
|
||||
/* Give the credits to allow the previous tasks to complete. */
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(sem_mtx);
|
||||
credits += qlimit + 1;
|
||||
}
|
||||
sem_cv.notify_all();
|
||||
|
||||
/* Wait for all the credits to be consumed. */
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(sem_mtx);
|
||||
while (credits > 0) {
|
||||
sem_cv.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that we are able again to enqueue at least qlimit tasks. */
|
||||
for (unsigned int i = 0; i < qlimit; i++) {
|
||||
queued = task_queue->enqueue([] {});
|
||||
EXPECT_TRUE(queued);
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_task, count.load());
|
||||
}
|
||||
|
||||
TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
|
||||
|
||||
Reference in New Issue
Block a user