Compare commits

..

20 Commits

Author SHA1 Message Date
yhirose b7cac4f4b8 Release v0.15.1 2024-01-29 07:40:56 -05:00
yhirose e323374d2a Fix #1766 2024-01-28 17:43:51 -05:00
Jiwoo Park ffc294d37e Reduce object copy (#1767) 2024-01-28 08:18:29 -05:00
yhirose fceada9ef4 Changed to return 416 for a request with an invalid range 2024-01-28 08:13:19 -05:00
yhirose 5f0f73fad9 Reduce duplicate computation for ranges 2024-01-27 19:07:52 -05:00
yhirose 530d6ee098 Release v0.15.0 2024-01-27 17:39:58 -05:00
yhirose 420c9759c6 Fix #1694 2024-01-27 16:13:54 -05:00
yhirose 2ce7c22218 Fix #1747 2024-01-27 12:56:39 -05:00
Wander Nauta 4ef9ed80cd Treat paths with embedded NUL bytes as invalid (#1765)
Fixes #1763.
2024-01-27 08:22:00 -05:00
Jiwoo Park 44b3fe6277 Support move semantics for Response::set_content() (#1764) 2024-01-27 07:53:19 -05:00
Ilya Andreev 449801990f Add a getter for a bearer token from a request (#1755)
* Add a getter for a bearer token from a request

* Replace a method for bearer token getter with a free function
2024-01-15 08:57:22 -05:00
Jean-Francois Simoneau af2928d316 Fix select() return code for fd >= 1024 (#1757) 2024-01-15 08:27:31 -05:00
KTGH d948e38820 Minor cmake fix & cleanup (#1754)
* Reorder cmake docs a bit

Just wanted to group the more related build options together.

Also removed a pointless reference to the old reasoning for the required
min ver since it's 3.14 now anyways.

* Fix outdated cmake comment

We don't use Git to find the version string anymore.
Just updated to match what it's actually used for now.

* Group options and build-tree vars in Cmake

Doesn't really change anything, I just wanted to clean these up a bit.

* Fix how we set HTTPLIB_IS_USING_XXX vars in Cmake

Prevents us acidentally using libs when the user didn't want them
actually used. This could happen if they set the option to OFF but their
own project itself is using the lib, thus we'd find and use it anyways.

Ref #1602 to see an example of this already happening before.
This is merely apply that kind of fix to all 3 of our deps, instead of
just OpenSSL.

* Minor formatting/comment change to Cmake

Pointless, but these things were bothering me..
2024-01-04 18:20:37 -05:00
Matthias Bilger 65218ce222 added missing include of exception (#1752) 2023-12-31 18:59:43 -05:00
Adam Gajda 55e99c4030 Fix -Wold-style-cast warning (#1751) 2023-12-31 18:43:31 -05:00
TheOnlyJoey b63d50671d Fixes Windows std::max macro problems (#1750) 2023-12-30 11:37:58 -05:00
yhirose eba980846b Fix #1628 (OpenSSL 1.1.1 End of Life on September 11, 2023) (#1745) 2023-12-24 08:20:58 -05:00
vmaffione 374d058de7 ThreadPool: optional limit for jobs queue (#1741)
For very busy servers, the internal jobs queue where accepted
sockets are enqueued can grow without limit.
This is a problem for two reasons:
 - queueing too much work causes the server to respond with huge latency,
   resulting in repetead timeouts on the clients; it is definitely
   better to reject the connection early, so that the client
   receives the backpressure signal as soon as the queue is
   becoming too large
 - the jobs list can eventually cause an out of memory condition
2023-12-24 08:20:22 -05:00
yhirose 31cdcc3c3a Update README about MSYS2 and MinGW 2023-12-23 21:37:34 -05:00
yhirose ad9f6423e2 Fix #1744 2023-12-23 11:45:08 -05:00
7 changed files with 442 additions and 224 deletions
+31 -38
View File
@@ -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)
+17 -4
View File
@@ -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
View File
@@ -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)
+222 -160
View File
@@ -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 &params);
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 &params,
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 &params,
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 &params,
@@ -7602,12 +7662,13 @@ inline Result ClientImpl::Get(const std::string &path, const Params &params,
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 &params,
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 &params,
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 &params,
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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {