mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b0ed9ee88 | |||
| 20056f6cda | |||
| 3b35279b16 | |||
| 27deb44df5 | |||
| 24a3ef949b | |||
| bc3e098964 | |||
| c247dcdd7b | |||
| 793ae9855e | |||
| 9fa426d51b | |||
| cec6288a99 | |||
| 9639578c2a | |||
| 743ecbd365 | |||
| 084c643973 | |||
| 824e7682e4 | |||
| f9074684dd | |||
| ddff782133 | |||
| 3051152103 | |||
| 06026bb47d | |||
| 226388ae27 | |||
| ea7548b4cc | |||
| c7486ead96 | |||
| 90a291214c | |||
| c111c42a86 | |||
| 6fb5b63018 | |||
| ec56dfa35e | |||
| 943cd51b67 | |||
| 301faa074c | |||
| dc0481e832 | |||
| 4f8fcdbaf7 |
@@ -0,0 +1,26 @@
|
||||
name: CIFuzz
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'cpp-httplib'
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Run Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'cpp-httplib'
|
||||
fuzz-seconds: 600
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
@@ -39,55 +39,3 @@ jobs:
|
||||
cd test
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
|
||||
x64\Release\test.exe
|
||||
|
||||
|
||||
meson-build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Prepare Git for checkout on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies on Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get -qq update && sudo apt-get -qq install meson libssl-dev zlib1g-dev libbrotli-dev libgtest-dev
|
||||
|
||||
- name: Install dependencies on MacOS
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install meson openssl brotli googletest
|
||||
|
||||
- name: Setup MSVC on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
# It is necessary to remove MinGW and StrawberryPerl as they both provide
|
||||
# GCC. This causes issues because CMake prefers to use MSVC, while Meson
|
||||
# uses GCC, if found, causing linking errors.
|
||||
- name: Install dependencies on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco uninstall mingw strawberryperl --yes --all-versions --remove-dependencies --skip-autouninstaller --no-color
|
||||
Remove-Item -Path C:\Strawberry -Recurse
|
||||
choco install pkgconfiglite --yes --skip-virus-check --no-color
|
||||
pip install meson ninja
|
||||
Invoke-WebRequest -Uri https://github.com/google/googletest/archive/refs/heads/master.zip -OutFile googletest-master.zip
|
||||
Expand-Archive -Path googletest-master.zip
|
||||
cd googletest-master\googletest-master
|
||||
cmake -S . -B build -DINSTALL_GTEST=ON -DBUILD_GMOCK=OFF -Dgtest_hide_internal_symbols=ON -DCMAKE_INSTALL_PREFIX=C:/googletest
|
||||
cmake --build build --config=Release
|
||||
cmake --install build --config=Release
|
||||
cd ..\..
|
||||
|
||||
- name: Build and test
|
||||
run: |
|
||||
meson setup build -Dcpp-httplib_test=true -Dpkg_config_path=C:\googletest\lib\pkgconfig -Db_vscrt=static_from_buildtype
|
||||
meson test --no-stdsplit --print-errorlogs -C build
|
||||
|
||||
@@ -13,10 +13,12 @@ example/*.pem
|
||||
test/httplib.cc
|
||||
test/httplib.h
|
||||
test/test
|
||||
test/server_fuzzer
|
||||
test/test_proxy
|
||||
test/test_split
|
||||
test/test.xcodeproj/xcuser*
|
||||
test/test.xcodeproj/*/xcuser*
|
||||
test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
|
||||
|
||||
+4
-20
@@ -60,27 +60,11 @@
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
|
||||
# On systems without Git installed, there were errors since execute_process seemed to not throw an error without it?
|
||||
find_package(Git QUIET)
|
||||
if(Git_FOUND)
|
||||
# Gets the latest tag as a string like "v0.6.6"
|
||||
# Can silently fail if git isn't on the system
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE _raw_version_string
|
||||
ERROR_VARIABLE _git_tag_error
|
||||
)
|
||||
endif()
|
||||
# Get the user agent and use it as a version
|
||||
# This gets the string with the user agent from the header.
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "User\-Agent.*cpp\-httplib/([0-9]+\.?)+")
|
||||
|
||||
# execute_process can fail silenty, so check for an error
|
||||
# if there was an error, just use the user agent as a version
|
||||
if(_git_tag_error OR NOT Git_FOUND)
|
||||
message(WARNING "cpp-httplib failed to find the latest Git tag, falling back to using user agent as the version.")
|
||||
# Get the user agent and use it as a version
|
||||
# This gets the string with the user agent from the header.
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "User\-Agent.*cpp\-httplib/([0-9]+\.?)+")
|
||||
endif()
|
||||
# Needed since git tags have "v" prefixing them.
|
||||
# Also used if the fallback to user agent string is being used.
|
||||
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
@@ -220,7 +220,7 @@ svr.set_exception_handler([](const auto& req, auto& res, std::exception &e) {
|
||||
### Pre routing handler
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto& req, auto& res) -> bool {
|
||||
svr.set_pre_routing_handler([](const auto& req, auto& res) {
|
||||
if (req.path == "/hello") {
|
||||
res.set_content("world", "text/html");
|
||||
return Server::HandlerResponse::Handled;
|
||||
|
||||
+8
-5
@@ -1,14 +1,17 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
|
||||
OPENSSL_DIR = /usr/local/opt/openssl
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = /usr/local/opt/brotli
|
||||
# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon-static -lbrotlienc-static -lbrotlidec-static
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
|
||||
|
||||
@@ -47,4 +50,4 @@ pem:
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr sselci benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark *.pem
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#/usr/bin/env bash
|
||||
for i in {1..10000}
|
||||
do
|
||||
echo "#### $i ####"
|
||||
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
|
||||
done
|
||||
@@ -95,6 +95,10 @@
|
||||
#define CPPHTTPLIB_SEND_FLAGS 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_LISTEN_BACKLOG
|
||||
#define CPPHTTPLIB_LISTEN_BACKLOG 5
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Headers
|
||||
*/
|
||||
@@ -505,7 +509,7 @@ public:
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle(){};
|
||||
virtual void on_idle() {}
|
||||
};
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
@@ -955,6 +959,8 @@ public:
|
||||
|
||||
void stop();
|
||||
|
||||
void set_hostname_addr_map(const std::map<std::string, std::string> addr_map);
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
|
||||
void set_address_family(int family);
|
||||
@@ -1058,6 +1064,9 @@ protected:
|
||||
std::thread::id socket_requests_are_from_thread_ = std::thread::id();
|
||||
bool socket_should_be_closed_when_request_is_done_ = false;
|
||||
|
||||
// Hostname-IP map
|
||||
std::map<std::string, std::string> addr_map_;
|
||||
|
||||
// Default headers
|
||||
Headers default_headers_;
|
||||
|
||||
@@ -1285,6 +1294,8 @@ public:
|
||||
|
||||
void stop();
|
||||
|
||||
void set_hostname_addr_map(const std::map<std::string, std::string> addr_map);
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
|
||||
void set_address_family(int family);
|
||||
@@ -1371,6 +1382,8 @@ public:
|
||||
|
||||
bool is_valid() const override;
|
||||
|
||||
SSL_CTX *ssl_context() const;
|
||||
|
||||
private:
|
||||
bool process_and_close_socket(socket_t sock) override;
|
||||
|
||||
@@ -1480,10 +1493,10 @@ inline T Response::get_header_value(const char *key, size_t id) const {
|
||||
template <typename... Args>
|
||||
inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
|
||||
const auto bufsiz = 2048;
|
||||
std::array<char, bufsiz> buf;
|
||||
std::array<char, bufsiz> buf{};
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...);
|
||||
auto sn = _snprintf_s(buf.data(), bufsiz, _TRUNCATE, fmt, args...);
|
||||
#else
|
||||
auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
|
||||
#endif
|
||||
@@ -1656,14 +1669,12 @@ bool process_client_socket(socket_t sock, time_t read_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
std::function<bool(Stream &)> callback);
|
||||
|
||||
socket_t create_client_socket(const char *host, int port, int address_family,
|
||||
bool tcp_nodelay, SocketOptions socket_options,
|
||||
time_t connection_timeout_sec,
|
||||
time_t connection_timeout_usec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
const std::string &intf, Error &error);
|
||||
socket_t create_client_socket(
|
||||
const char *host, const char *ip, int port, int address_family,
|
||||
bool tcp_nodelay, SocketOptions socket_options,
|
||||
time_t connection_timeout_sec, time_t connection_timeout_usec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error);
|
||||
|
||||
const char *get_header_value(const Headers &headers, const char *key,
|
||||
size_t id = 0, const char *def = nullptr);
|
||||
@@ -2202,25 +2213,22 @@ inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
|
||||
return handle_EINTR([&]() {
|
||||
return recv(sock,
|
||||
#ifdef _WIN32
|
||||
static_cast<char *>(ptr),
|
||||
static_cast<int>(size),
|
||||
static_cast<char *>(ptr), static_cast<int>(size),
|
||||
#else
|
||||
ptr,
|
||||
size,
|
||||
ptr, size,
|
||||
#endif
|
||||
flags);
|
||||
});
|
||||
}
|
||||
|
||||
inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags) {
|
||||
inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
|
||||
int flags) {
|
||||
return handle_EINTR([&]() {
|
||||
return send(sock,
|
||||
#ifdef _WIN32
|
||||
static_cast<const char *>(ptr),
|
||||
static_cast<int>(size),
|
||||
static_cast<const char *>(ptr), static_cast<int>(size),
|
||||
#else
|
||||
ptr,
|
||||
size,
|
||||
ptr, size,
|
||||
#endif
|
||||
flags);
|
||||
});
|
||||
@@ -2331,6 +2339,17 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool is_socket_alive(socket_t sock) {
|
||||
const auto val = detail::select_read(sock, 0, 0);
|
||||
if (val == 0) {
|
||||
return true;
|
||||
} else if (val < 0 && errno == EBADF) {
|
||||
return false;
|
||||
}
|
||||
char buf[1];
|
||||
return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
|
||||
}
|
||||
|
||||
class SocketStream : public Stream {
|
||||
public:
|
||||
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
@@ -2404,12 +2423,14 @@ inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
process_server_socket_core(socket_t sock, size_t keep_alive_max_count,
|
||||
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
size_t keep_alive_max_count,
|
||||
time_t keep_alive_timeout_sec, T callback) {
|
||||
assert(keep_alive_max_count > 0);
|
||||
auto ret = false;
|
||||
auto count = keep_alive_max_count;
|
||||
while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) {
|
||||
while (svr_sock != INVALID_SOCKET && count > 0 &&
|
||||
keep_alive(sock, keep_alive_timeout_sec)) {
|
||||
auto close_connection = count == 1;
|
||||
auto connection_closed = false;
|
||||
ret = callback(close_connection, connection_closed);
|
||||
@@ -2421,12 +2442,13 @@ process_server_socket_core(socket_t sock, size_t keep_alive_max_count,
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
process_server_socket(socket_t sock, size_t keep_alive_max_count,
|
||||
process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
size_t keep_alive_max_count,
|
||||
time_t keep_alive_timeout_sec, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, T callback) {
|
||||
return process_server_socket_core(
|
||||
sock, keep_alive_max_count, keep_alive_timeout_sec,
|
||||
svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
|
||||
[&](bool close_connection, bool &connection_closed) {
|
||||
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec);
|
||||
@@ -2453,8 +2475,8 @@ inline int shutdown_socket(socket_t sock) {
|
||||
}
|
||||
|
||||
template <typename BindOrConnect>
|
||||
socket_t create_socket(const char *host, int port, int address_family,
|
||||
int socket_flags, bool tcp_nodelay,
|
||||
socket_t create_socket(const char *host, const char *ip, int port,
|
||||
int address_family, int socket_flags, bool tcp_nodelay,
|
||||
SocketOptions socket_options,
|
||||
BindOrConnect bind_or_connect) {
|
||||
// Get address info
|
||||
@@ -2467,9 +2489,16 @@ socket_t create_socket(const char *host, int port, int address_family,
|
||||
hints.ai_flags = socket_flags;
|
||||
hints.ai_protocol = 0;
|
||||
|
||||
// Ask getaddrinfo to convert IP in c-string to address
|
||||
if (ip[0] != '\0') {
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
}
|
||||
|
||||
auto service = std::to_string(port);
|
||||
|
||||
if (getaddrinfo(host, service.c_str(), &hints, &result)) {
|
||||
if (ip[0] != '\0' ? getaddrinfo(ip, service.c_str(), &hints, &result)
|
||||
: getaddrinfo(host, service.c_str(), &hints, &result)) {
|
||||
#if defined __linux__ && !defined __ANDROID__
|
||||
res_init();
|
||||
#endif
|
||||
@@ -2604,13 +2633,13 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(
|
||||
const char *host, int port, int address_family, bool tcp_nodelay,
|
||||
SocketOptions socket_options, time_t connection_timeout_sec,
|
||||
time_t connection_timeout_usec, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
const char *host, const char *ip, int port, int address_family,
|
||||
bool tcp_nodelay, SocketOptions socket_options,
|
||||
time_t connection_timeout_sec, time_t connection_timeout_usec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error) {
|
||||
auto sock = create_socket(
|
||||
host, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
@@ -2847,11 +2876,13 @@ inline const char *status_message(int status) {
|
||||
}
|
||||
|
||||
inline bool can_compress_content_type(const std::string &content_type) {
|
||||
return (!content_type.find("text/") && content_type != "text/event-stream") ||
|
||||
return (!content_type.rfind("text/", 0) &&
|
||||
content_type != "text/event-stream") ||
|
||||
content_type == "image/svg+xml" ||
|
||||
content_type == "application/javascript" ||
|
||||
content_type == "application/json" ||
|
||||
content_type == "application/xml" ||
|
||||
content_type == "application/protobuf" ||
|
||||
content_type == "application/xhtml+xml";
|
||||
}
|
||||
|
||||
@@ -2980,9 +3011,7 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
||||
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
|
||||
if (prev_avail_in - strm_.avail_in == 0) {
|
||||
return false;
|
||||
}
|
||||
if (prev_avail_in - strm_.avail_in == 0) { return false; }
|
||||
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
switch (ret) {
|
||||
@@ -3595,7 +3624,11 @@ inline bool parse_multipart_boundary(const std::string &content_type,
|
||||
return !boundary.empty();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
|
||||
#else
|
||||
inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
|
||||
#endif
|
||||
static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
|
||||
std::smatch m;
|
||||
if (std::regex_match(s, m, re_first_range)) {
|
||||
@@ -3627,7 +3660,11 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
|
||||
return all_valid_ranges;
|
||||
}
|
||||
return false;
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
}
|
||||
#else
|
||||
} catch (...) { return false; }
|
||||
#endif
|
||||
|
||||
class MultipartFormDataParser {
|
||||
public:
|
||||
@@ -3647,17 +3684,15 @@ public:
|
||||
static const std::string dash_ = "--";
|
||||
static const std::string crlf_ = "\r\n";
|
||||
|
||||
buf_.append(buf, n); // TODO: performance improvement
|
||||
buf_append(buf, n);
|
||||
|
||||
while (!buf_.empty()) {
|
||||
while (buf_size() > 0) {
|
||||
switch (state_) {
|
||||
case 0: { // Initial boundary
|
||||
auto pattern = dash_ + boundary_ + crlf_;
|
||||
if (pattern.size() > buf_.size()) { return true; }
|
||||
auto pos = buf_.find(pattern);
|
||||
if (pos != 0) { return false; }
|
||||
buf_.erase(0, pattern.size());
|
||||
off_ += pattern.size();
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
if (!buf_start_with(pattern)) { return false; }
|
||||
buf_erase(pattern.size());
|
||||
state_ = 1;
|
||||
break;
|
||||
}
|
||||
@@ -3667,22 +3702,21 @@ public:
|
||||
break;
|
||||
}
|
||||
case 2: { // Headers
|
||||
auto pos = buf_.find(crlf_);
|
||||
while (pos != std::string::npos) {
|
||||
auto pos = buf_find(crlf_);
|
||||
while (pos < buf_size()) {
|
||||
// Empty line
|
||||
if (pos == 0) {
|
||||
if (!header_callback(file_)) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
buf_.erase(0, crlf_.size());
|
||||
off_ += crlf_.size();
|
||||
buf_erase(crlf_.size());
|
||||
state_ = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
static const std::string header_name = "content-type:";
|
||||
const auto header = buf_.substr(0, pos);
|
||||
const auto header = buf_head(pos);
|
||||
if (start_with_case_ignore(header, header_name)) {
|
||||
file_.content_type = trim_copy(header.substr(header_name.size()));
|
||||
} else {
|
||||
@@ -3693,9 +3727,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
buf_.erase(0, pos + crlf_.size());
|
||||
off_ += pos + crlf_.size();
|
||||
pos = buf_.find(crlf_);
|
||||
buf_erase(pos + crlf_.size());
|
||||
pos = buf_find(crlf_);
|
||||
}
|
||||
if (state_ != 3) { return true; }
|
||||
break;
|
||||
@@ -3703,56 +3736,51 @@ public:
|
||||
case 3: { // Body
|
||||
{
|
||||
auto pattern = crlf_ + dash_;
|
||||
if (pattern.size() > buf_.size()) { return true; }
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
|
||||
auto pos = find_string(buf_, pattern);
|
||||
auto pos = buf_find(pattern);
|
||||
|
||||
if (!content_callback(buf_.data(), pos)) {
|
||||
if (!content_callback(buf_data(), pos)) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
off_ += pos;
|
||||
buf_.erase(0, pos);
|
||||
buf_erase(pos);
|
||||
}
|
||||
{
|
||||
auto pattern = crlf_ + dash_ + boundary_;
|
||||
if (pattern.size() > buf_.size()) { return true; }
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
|
||||
auto pos = buf_.find(pattern);
|
||||
if (pos != std::string::npos) {
|
||||
if (!content_callback(buf_.data(), pos)) {
|
||||
auto pos = buf_find(pattern);
|
||||
if (pos < buf_size()) {
|
||||
if (!content_callback(buf_data(), pos)) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
off_ += pos + pattern.size();
|
||||
buf_.erase(0, pos + pattern.size());
|
||||
buf_erase(pos + pattern.size());
|
||||
state_ = 4;
|
||||
} else {
|
||||
if (!content_callback(buf_.data(), pattern.size())) {
|
||||
if (!content_callback(buf_data(), pattern.size())) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
off_ += pattern.size();
|
||||
buf_.erase(0, pattern.size());
|
||||
buf_erase(pattern.size());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: { // Boundary
|
||||
if (crlf_.size() > buf_.size()) { return true; }
|
||||
if (buf_.compare(0, crlf_.size(), crlf_) == 0) {
|
||||
buf_.erase(0, crlf_.size());
|
||||
off_ += crlf_.size();
|
||||
if (crlf_.size() > buf_size()) { return true; }
|
||||
if (buf_start_with(crlf_)) {
|
||||
buf_erase(crlf_.size());
|
||||
state_ = 1;
|
||||
} else {
|
||||
auto pattern = dash_ + crlf_;
|
||||
if (pattern.size() > buf_.size()) { return true; }
|
||||
if (buf_.compare(0, pattern.size(), pattern) == 0) {
|
||||
buf_.erase(0, pattern.size());
|
||||
off_ += pattern.size();
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
if (buf_start_with(pattern)) {
|
||||
buf_erase(pattern.size());
|
||||
is_valid_ = true;
|
||||
state_ = 5;
|
||||
} else {
|
||||
@@ -3787,41 +3815,80 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_with(const std::string &a, size_t off,
|
||||
std::string boundary_;
|
||||
|
||||
size_t state_ = 0;
|
||||
bool is_valid_ = false;
|
||||
MultipartFormData file_;
|
||||
|
||||
// Buffer
|
||||
bool start_with(const std::string &a, size_t spos, size_t epos,
|
||||
const std::string &b) const {
|
||||
if (a.size() - off < b.size()) { return false; }
|
||||
if (epos - spos < b.size()) { return false; }
|
||||
for (size_t i = 0; i < b.size(); i++) {
|
||||
if (a[i + off] != b[i]) { return false; }
|
||||
if (a[i + spos] != b[i]) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t find_string(const std::string &s, const std::string &pattern) const {
|
||||
auto c = pattern.front();
|
||||
size_t buf_size() const { return buf_epos_ - buf_spos_; }
|
||||
|
||||
size_t off = 0;
|
||||
while (off < s.size()) {
|
||||
auto pos = s.find(c, off);
|
||||
if (pos == std::string::npos) { return s.size(); }
|
||||
const char *buf_data() const { return &buf_[buf_spos_]; }
|
||||
|
||||
auto rem = s.size() - pos;
|
||||
if (pattern.size() > rem) { return pos; }
|
||||
std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
|
||||
|
||||
if (start_with(s, pos, pattern)) { return pos; }
|
||||
bool buf_start_with(const std::string &s) const {
|
||||
return start_with(buf_, buf_spos_, buf_epos_, s);
|
||||
}
|
||||
|
||||
size_t buf_find(const std::string &s) const {
|
||||
auto c = s.front();
|
||||
|
||||
size_t off = buf_spos_;
|
||||
while (off < buf_epos_) {
|
||||
auto pos = off;
|
||||
while (true) {
|
||||
if (pos == buf_epos_) { return buf_size(); }
|
||||
if (buf_[pos] == c) { break; }
|
||||
pos++;
|
||||
}
|
||||
|
||||
auto remaining_size = buf_epos_ - pos;
|
||||
if (s.size() > remaining_size) { return buf_size(); }
|
||||
|
||||
if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
|
||||
|
||||
off = pos + 1;
|
||||
}
|
||||
|
||||
return s.size();
|
||||
return buf_size();
|
||||
}
|
||||
|
||||
std::string boundary_;
|
||||
void buf_append(const char *data, size_t n) {
|
||||
auto remaining_size = buf_size();
|
||||
if (remaining_size > 0) {
|
||||
for (size_t i = 0; i < remaining_size; i++) {
|
||||
buf_[i] = buf_[buf_spos_ + i];
|
||||
}
|
||||
}
|
||||
buf_spos_ = 0;
|
||||
buf_epos_ = remaining_size;
|
||||
|
||||
if (remaining_size + n > buf_.size()) {
|
||||
buf_.resize(remaining_size + n);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
buf_[buf_epos_ + i] = data[i];
|
||||
}
|
||||
buf_epos_ += n;
|
||||
}
|
||||
|
||||
void buf_erase(size_t size) { buf_spos_ += size; }
|
||||
|
||||
std::string buf_;
|
||||
size_t state_ = 0;
|
||||
bool is_valid_ = false;
|
||||
size_t off_ = 0;
|
||||
MultipartFormData file_;
|
||||
size_t buf_spos_ = 0;
|
||||
size_t buf_epos_ = 0;
|
||||
};
|
||||
|
||||
inline std::string to_lower(const char *beg, const char *end) {
|
||||
@@ -4284,7 +4351,7 @@ inline size_t Request::get_param_value_count(const char *key) const {
|
||||
|
||||
inline bool Request::is_multipart_form_data() const {
|
||||
const auto &content_type = get_header_value("Content-Type");
|
||||
return !content_type.find("multipart/form-data");
|
||||
return !content_type.rfind("multipart/form-data", 0);
|
||||
}
|
||||
|
||||
inline bool Request::has_file(const char *key) const {
|
||||
@@ -4418,8 +4485,7 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
|
||||
: sock_(sock), read_timeout_sec_(read_timeout_sec),
|
||||
read_timeout_usec_(read_timeout_usec),
|
||||
write_timeout_sec_(write_timeout_sec),
|
||||
write_timeout_usec_(write_timeout_usec),
|
||||
read_buff_(read_buff_size_, 0) {}
|
||||
write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {}
|
||||
|
||||
inline SocketStream::~SocketStream() {}
|
||||
|
||||
@@ -4433,9 +4499,11 @@ inline bool SocketStream::is_writable() const {
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
#ifdef _WIN32
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
size =
|
||||
(std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#else
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
|
||||
size = (std::min)(size,
|
||||
static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
|
||||
#endif
|
||||
|
||||
if (read_buff_off_ < read_buff_content_size_) {
|
||||
@@ -4457,7 +4525,8 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
read_buff_content_size_ = 0;
|
||||
|
||||
if (size < read_buff_size_) {
|
||||
auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, CPPHTTPLIB_RECV_FLAGS);
|
||||
auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
|
||||
CPPHTTPLIB_RECV_FLAGS);
|
||||
if (n <= 0) {
|
||||
return n;
|
||||
} else if (n <= static_cast<ssize_t>(size)) {
|
||||
@@ -4478,7 +4547,8 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
size =
|
||||
(std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#endif
|
||||
|
||||
return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
|
||||
@@ -5079,15 +5149,13 @@ inline socket_t
|
||||
Server::create_server_socket(const char *host, int port, int socket_flags,
|
||||
SocketOptions socket_options) const {
|
||||
return detail::create_socket(
|
||||
host, port, address_family_, socket_flags, tcp_nodelay_,
|
||||
host, "", port, address_family_, socket_flags, tcp_nodelay_,
|
||||
std::move(socket_options),
|
||||
[](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
|
||||
return false;
|
||||
}
|
||||
if (::listen(sock, 5)) { // Listen through 5 channels
|
||||
return false;
|
||||
}
|
||||
if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; }
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -5489,6 +5557,9 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
|
||||
// Rounting
|
||||
bool routed = false;
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
routed = routing(req, res, strm);
|
||||
#else
|
||||
try {
|
||||
routed = routing(req, res, strm);
|
||||
} catch (std::exception &e) {
|
||||
@@ -5503,6 +5574,7 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
res.status = 500;
|
||||
res.set_header("EXCEPTION_WHAT", "UNKNOWN");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (routed) {
|
||||
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
|
||||
@@ -5517,8 +5589,9 @@ inline bool Server::is_valid() const { return true; }
|
||||
|
||||
inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
auto ret = detail::process_server_socket(
|
||||
sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_,
|
||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
|
||||
svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_,
|
||||
[this](Stream &strm, bool close_connection, bool &connection_closed) {
|
||||
return process_request(strm, close_connection, connection_closed,
|
||||
nullptr);
|
||||
@@ -5598,16 +5671,21 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
return detail::create_client_socket(
|
||||
proxy_host_.c_str(), proxy_port_, address_family_, tcp_nodelay_,
|
||||
proxy_host_.c_str(), "", proxy_port_, address_family_, tcp_nodelay_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
}
|
||||
// Check is custom IP specified for host_
|
||||
std::string ip;
|
||||
auto it = addr_map_.find(host_);
|
||||
if (it != addr_map_.end()) ip = it->second;
|
||||
|
||||
return detail::create_client_socket(
|
||||
host_.c_str(), port_, address_family_, tcp_nodelay_, socket_options_,
|
||||
connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
|
||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
|
||||
error);
|
||||
host_.c_str(), ip.c_str(), port_, address_family_, tcp_nodelay_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
|
||||
@@ -5652,7 +5730,7 @@ inline void ClientImpl::close_socket(Socket &socket) {
|
||||
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) {
|
||||
std::array<char, 2048> buf;
|
||||
std::array<char, 2048> buf{};
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
|
||||
|
||||
@@ -5687,13 +5765,14 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
|
||||
// Set this to false immediately - if it ever gets set to true by the end of
|
||||
// the request, we know another thread instructed us to close the socket.
|
||||
socket_should_be_closed_when_request_is_done_ = false;
|
||||
|
||||
auto is_alive = false;
|
||||
if (socket_.is_open()) {
|
||||
is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
|
||||
is_alive = detail::is_socket_alive(socket_.sock);
|
||||
if (!is_alive) {
|
||||
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
|
||||
// like the other side has already closed the connection Also, there
|
||||
@@ -5950,7 +6029,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
|
||||
|
||||
if (!req.has_header("User-Agent")) {
|
||||
req.headers.emplace("User-Agent", "cpp-httplib/0.9");
|
||||
req.headers.emplace("User-Agent", "cpp-httplib/0.10.0");
|
||||
}
|
||||
|
||||
if (req.body.empty()) {
|
||||
@@ -6732,6 +6811,11 @@ inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
|
||||
|
||||
inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; }
|
||||
|
||||
inline void ClientImpl::set_hostname_addr_map(
|
||||
const std::map<std::string, std::string> addr_map) {
|
||||
addr_map_ = std::move(addr_map);
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_default_headers(Headers headers) {
|
||||
default_headers_ = std::move(headers);
|
||||
}
|
||||
@@ -6871,14 +6955,13 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
|
||||
time_t keep_alive_timeout_sec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
T callback) {
|
||||
inline bool process_server_socket_ssl(
|
||||
const std::atomic<socket_t> &svr_sock, SSL *ssl, socket_t sock,
|
||||
size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, T callback) {
|
||||
return process_server_socket_core(
|
||||
sock, keep_alive_max_count, keep_alive_timeout_sec,
|
||||
svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
|
||||
[&](bool close_connection, bool &connection_closed) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec);
|
||||
@@ -7046,17 +7129,14 @@ static SSLInit sslinit_;
|
||||
inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
const char *client_ca_cert_file_path,
|
||||
const char *client_ca_cert_dir_path) {
|
||||
ctx_ = SSL_CTX_new(TLS_method());
|
||||
ctx_ = SSL_CTX_new(TLS_server_method());
|
||||
|
||||
if (ctx_) {
|
||||
SSL_CTX_set_options(ctx_,
|
||||
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
// auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
||||
// SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
|
||||
// EC_KEY_free(ecdh);
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
|
||||
|
||||
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
|
||||
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
|
||||
@@ -7064,46 +7144,35 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
SSL_CTX_free(ctx_);
|
||||
ctx_ = nullptr;
|
||||
} else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
|
||||
// if (client_ca_cert_file_path) {
|
||||
// auto list = SSL_load_client_CA_file(client_ca_cert_file_path);
|
||||
// SSL_CTX_set_client_CA_list(ctx_, list);
|
||||
// }
|
||||
|
||||
SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
|
||||
client_ca_cert_dir_path);
|
||||
|
||||
SSL_CTX_set_verify(
|
||||
ctx_,
|
||||
SSL_VERIFY_PEER |
|
||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
|
||||
nullptr);
|
||||
ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store) {
|
||||
ctx_ = SSL_CTX_new(SSLv23_server_method());
|
||||
ctx_ = SSL_CTX_new(TLS_server_method());
|
||||
|
||||
if (ctx_) {
|
||||
SSL_CTX_set_options(ctx_,
|
||||
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
|
||||
|
||||
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
|
||||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
|
||||
SSL_CTX_free(ctx_);
|
||||
ctx_ = nullptr;
|
||||
} else if (client_ca_cert_store) {
|
||||
|
||||
SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
|
||||
|
||||
SSL_CTX_set_verify(
|
||||
ctx_,
|
||||
SSL_VERIFY_PEER |
|
||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
|
||||
nullptr);
|
||||
ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7125,6 +7194,8 @@ inline SSLServer::~SSLServer() {
|
||||
|
||||
inline bool SSLServer::is_valid() const { return ctx_; }
|
||||
|
||||
inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
|
||||
|
||||
inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
auto ssl = detail::ssl_new(
|
||||
sock, ctx_, ctx_mutex_,
|
||||
@@ -7137,7 +7208,7 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
bool ret = false;
|
||||
if (ssl) {
|
||||
ret = detail::process_server_socket_ssl(
|
||||
ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_,
|
||||
[this, ssl](Stream &strm, bool close_connection,
|
||||
@@ -7168,12 +7239,13 @@ inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
: ClientImpl(host, port, client_cert_path, client_key_path) {
|
||||
ctx_ = SSL_CTX_new(SSLv23_client_method());
|
||||
ctx_ = SSL_CTX_new(TLS_client_method());
|
||||
|
||||
detail::split(&host_[0], &host_[host_.size()], '.',
|
||||
[&](const char *b, const char *e) {
|
||||
host_components_.emplace_back(std::string(b, e));
|
||||
});
|
||||
|
||||
if (!client_cert_path.empty() && !client_key_path.empty()) {
|
||||
if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
|
||||
SSL_FILETYPE_PEM) != 1 ||
|
||||
@@ -7188,12 +7260,13 @@ inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
X509 *client_cert, EVP_PKEY *client_key)
|
||||
: ClientImpl(host, port) {
|
||||
ctx_ = SSL_CTX_new(SSLv23_client_method());
|
||||
ctx_ = SSL_CTX_new(TLS_client_method());
|
||||
|
||||
detail::split(&host_[0], &host_[host_.size()], '.',
|
||||
[&](const char *b, const char *e) {
|
||||
host_components_.emplace_back(std::string(b, e));
|
||||
});
|
||||
|
||||
if (client_cert != nullptr && client_key != nullptr) {
|
||||
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
|
||||
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
|
||||
@@ -7553,8 +7626,10 @@ inline Client::Client(const std::string &scheme_host_port,
|
||||
#else
|
||||
if (!scheme.empty() && scheme != "http") {
|
||||
#endif
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
std::string msg = "'" + scheme + "' scheme is not supported.";
|
||||
throw std::invalid_argument(msg);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7855,6 +7930,11 @@ inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
|
||||
|
||||
inline void Client::stop() { cli_->stop(); }
|
||||
|
||||
inline void Client::set_hostname_addr_map(
|
||||
const std::map<std::string, std::string> addr_map) {
|
||||
cli_->set_hostname_addr_map(std::move(addr_map));
|
||||
}
|
||||
|
||||
inline void Client::set_default_headers(Headers headers) {
|
||||
cli_->set_default_headers(std::move(headers));
|
||||
}
|
||||
|
||||
+3
-13
@@ -19,18 +19,7 @@ project(
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
if version == 'undefined'
|
||||
git = find_program('git', required: false)
|
||||
if git.found()
|
||||
result = run_command(git, 'describe', '--tags', '--abbrev=0')
|
||||
if result.returncode() == 0
|
||||
version = result.stdout().strip('v\n')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
python = import('python').find_installation('python3')
|
||||
# If version is still undefined it means that the git method failed
|
||||
if version == 'undefined'
|
||||
# Meson doesn't have regular expressions, but since it is implemented
|
||||
# in python we can be sure we can use it to parse the file manually
|
||||
@@ -91,17 +80,18 @@ if get_option('cpp-httplib_compile')
|
||||
version: version,
|
||||
install: true
|
||||
)
|
||||
cpp_httplib_dep = declare_dependency(link_with: lib, sources: httplib_ch[1])
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
|
||||
|
||||
import('pkgconfig').generate(
|
||||
lib,
|
||||
description: 'A C++ HTTP/HTTPS server and client library',
|
||||
extra_cflags: args,
|
||||
url: 'https://github.com/yhirose/cpp-httplib',
|
||||
version: version
|
||||
)
|
||||
else
|
||||
install_headers('httplib.h')
|
||||
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'), dependencies: deps)
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: include_directories('.'))
|
||||
endif
|
||||
|
||||
if meson.version().version_compare('>=0.54.0')
|
||||
|
||||
+9
-5
@@ -1,12 +1,16 @@
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
|
||||
CXX = clang++
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
|
||||
|
||||
OPENSSL_DIR = /usr/local/opt/openssl@1.1
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = /usr/local/opt/brotli
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
|
||||
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
|
||||
@@ -64,4 +68,4 @@ cert.pem:
|
||||
#c_rehash .
|
||||
|
||||
clean:
|
||||
rm -f test test_proxy server_fuzzer pem *.0 *.o *.1 *.srl httplib.h httplib.cc
|
||||
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+112
-18
@@ -37,8 +37,12 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
auto it = std::find_if(
|
||||
files.begin(), files.end(),
|
||||
[&](const MultipartFormData &file) { return file.name == key; });
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
return *it;
|
||||
#else
|
||||
if (it != files.end()) { return *it; }
|
||||
throw std::runtime_error("invalid mulitpart form data name error");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ConstructorTest, MoveConstructible) {
|
||||
@@ -171,6 +175,12 @@ TEST(GetHeaderValueTest, RegularValue) {
|
||||
EXPECT_STREQ("text/html", val);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
|
||||
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "content-type", 0, "text/plain");
|
||||
EXPECT_STREQ("text/html", val);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, SetContent) {
|
||||
Response res;
|
||||
|
||||
@@ -736,6 +746,39 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
|
||||
auto host = "google.com";
|
||||
auto another_host = "example.com";
|
||||
auto wrong_ip = "0.0.0.0";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.set_hostname_addr_map({{another_host, wrong_ip}});
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
TEST(SpecifyServerIPAddressTest, RealHostname_Online) {
|
||||
auto host = "google.com";
|
||||
auto wrong_ip = "0.0.0.0";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.set_hostname_addr_map({{host, wrong_ip}});
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Connection, res.error());
|
||||
}
|
||||
|
||||
TEST(AbsoluteRedirectTest, Redirect_Online) {
|
||||
auto host = "nghttp2.org";
|
||||
|
||||
@@ -1148,15 +1191,17 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(ExceptionHandlerTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
svr.set_exception_handler(
|
||||
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
|
||||
res.status = 500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13
|
||||
});
|
||||
svr.set_exception_handler([](const Request & /*req*/, Response &res,
|
||||
std::exception &e) {
|
||||
EXPECT_EQ("abc", std::string(e.what()));
|
||||
res.status = 500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13 at this point
|
||||
});
|
||||
|
||||
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!\n", "text/plain");
|
||||
@@ -1168,21 +1213,35 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
||||
for (size_t j = 0; j < 100; j++) {
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
||||
}
|
||||
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
for (size_t j = 0; j < 100; j++) {
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("26", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
|
||||
}
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(NoContentTest, ContentLength) {
|
||||
Server svr;
|
||||
@@ -3321,7 +3380,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock = detail::create_client_socket(
|
||||
HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
HOST, "", PORT, AF_UNSPEC, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||
@@ -3580,10 +3639,11 @@ TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
|
||||
auto get_thread = std::thread([&client]() {
|
||||
std::string s;
|
||||
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||
s += std::string(data, len);
|
||||
return true;
|
||||
});
|
||||
auto res =
|
||||
client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||
s += std::string(data, len);
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ("aaabbb", s);
|
||||
});
|
||||
|
||||
@@ -3642,6 +3702,7 @@ TEST(MountTest, Unmount) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
@@ -3670,6 +3731,7 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(KeepAliveTest, ReadTimeout) {
|
||||
Server svr;
|
||||
@@ -3709,6 +3771,36 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, Issue1041) {
|
||||
const auto resourcePath = "/hi";
|
||||
|
||||
Server svr;
|
||||
svr.set_keep_alive_timeout(3);
|
||||
|
||||
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Get(resourcePath);
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
|
||||
result = cli.Get(resourcePath);
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
svr.stop();
|
||||
a2.wait();
|
||||
}
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
Server svr;
|
||||
|
||||
@@ -4476,9 +4568,11 @@ TEST(NoSSLSupport, SimpleInterface) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(InvalidScheme, SimpleInterface) {
|
||||
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(NoScheme, SimpleInterface) {
|
||||
Client cli("yahoo.com:80");
|
||||
|
||||
Reference in New Issue
Block a user