Compare commits

..

48 Commits

Author SHA1 Message Date
yhirose 5c1a34e766 Release v0.18.1 2024-10-18 17:16:54 -04:00
yhirose fa90d06dd5 Merge branch 'master' of github.com:yhirose/cpp-httplib 2024-10-18 17:16:14 -04:00
Jiwoo Park d869054318 Allow empty header values (#1965) 2024-10-18 10:16:48 -04:00
Peter Boström 0cc1ca9a8d Remove extra semicolon (#1963)
This fixes a -Wc++98-compat-extra-semi instance.
2024-10-15 00:09:04 -04:00
yhirose 3701195033 Merge branch 'master' of github.com:yhirose/cpp-httplib 2024-10-14 21:32:28 -04:00
Peter Boström f884a56258 Remove space between operator"" and _t (#1962)
This should fix a -Wdeprecated-literal-operator instance since this is
deprecated as a result of CWG2521 (iiuc C++23).
2024-10-14 21:32:13 -04:00
yhirose d79633ff52 clangformat 2024-10-11 14:49:46 -04:00
yhirose e0ebc431dc Fix #1959 2024-10-11 13:43:01 -04:00
Andrew McDaniel 131bc6c674 Add documentation for using Unix domain sockets. (#1954)
* Add documentation for using unix domain sockets.

* Formatting
2024-10-03 10:53:25 -04:00
yhirose 10d68cff50 Added a unit test for #1946 2024-09-26 22:24:44 -04:00
zjyhjqs 996acc5253 Feat: add CPack support (#1950) 2024-09-25 00:19:41 -04:00
yhirose 7c4799d0cf Fix #1798 for CMake (#1944) 2024-09-19 18:33:32 -04:00
yhirose c239087332 Fix Mafile errors 2024-09-17 18:37:44 -04:00
Andrea Pappacoda 7018e9263d test(meson): copy files in www directory (#1941)
These files were added in commits
2d01e71286 and
b8315278cb
2024-09-17 18:06:01 -04:00
yhirose 4990b4b4b7 Fix problems with SSLSlientServerTest.* tests 2024-09-17 17:00:17 -04:00
Andrea Pappacoda 5064373c23 test(meson): fix SSLClientServerTest.* tests with OpenSSL 3.2.0 (#1940)
* build(meson): bump minimum version to 0.62.0

This allows making some minor cleanups

* test(meson): fix SSLClientServerTest.* tests with OpenSSL 3.2.0

Since OpenSSL commit
<https://github.com/openssl/openssl/commit/342e3652c791bdb06e08abcc169b4456c83ccd00>,
the default X.509 certificate format generated with the `openssl req`
command has been changed to X.509 v3 from X.509 v1.

For some reason, this change breaks cpp-httplib's SSLClientServerTest.*
tests.

To fix the test failures, this patch passes the '-x509v1' flag instead
of '-x509' when OpenSSL 3.2.0 or newer is detected. To detect the
version of a command line utility, Meson 0.62.0 or later is required.

Fixes <https://github.com/yhirose/cpp-httplib/issues/1798>, but only for
the Meson build system.
2024-09-17 16:58:09 -04:00
yhirose 6c93aea59a Revert "Enabled HostnameToIPConversionTest.YouTube_Online partially"
This reverts commit 6553cdedab.
2024-09-15 01:18:20 -04:00
yhirose 6553cdedab Enabled HostnameToIPConversionTest.YouTube_Online partially 2024-09-13 20:45:59 -04:00
yhirose a61b2427b0 Update benchmark base vertion to 0.18.0 2024-09-13 20:34:30 -04:00
yhirose af4ece3d5f Update benchmark/Makefile 2024-09-12 20:41:56 -04:00
yhirose e64379c3d7 Release v0.18.0 2024-09-12 12:28:35 -04:00
yhirose 5053912534 Updated actions/upload-artifact from v1 to v4 2024-09-12 12:04:25 -04:00
Jean-Francois Simoneau 932b1cbc32 Fix shadow parameter warning (#1936) 2024-09-12 12:02:25 -04:00
yhirose de36ea7755 Fix #1933 on Linux and macOS 2024-09-09 23:07:27 -04:00
yhirose 9f8db2c230 Fix #1933 2024-09-09 22:22:56 -04:00
yhirose 3f00e1b321 Revert "Changed set_file_content to accept only a regular file path."
This reverts commit 7ab9c119ef.
2024-09-09 20:03:47 -04:00
yhirose 7ab9c119ef Changed set_file_content to accept only a regular file path. 2024-09-09 19:59:18 -04:00
yhirose 3f2922b3fa Fix #1929 2024-09-09 19:25:52 -04:00
yhirose 509f583dca Fix problem caused by #1931. 2024-09-09 19:19:52 -04:00
Paul Harris 2d01e71286 Test reading empty zero-length file (#1931) 2024-09-09 17:54:11 -04:00
yhirose e612154694 Issue1431 (#1926)
* Renamed enable_server_host_verification to enable_server_hostname_verification and added Error::SSLServerHostnameVerification

* Add some Open SSL function calls

* Code cleanup

* Fix #1431
2024-09-08 22:45:47 -04:00
yhirose 82fcbe3901 Code cleanup 2024-09-08 20:10:35 -04:00
yhirose dbd2465b56 Add some Open SSL function calls 2024-09-08 20:10:17 -04:00
yhirose ea79494b29 Renamed enable_server_host_verification to enable_server_hostname_verification and added Error::SSLServerHostnameVerification 2024-09-08 20:08:52 -04:00
yhirose f35aff84c2 Fixed FuzzableServer build error 2024-09-08 19:00:23 -04:00
yhirose 7b18ae6f16 Update benchmark 2024-09-08 10:56:13 -04:00
yhirose a79c56d06b Fix #1796 2024-09-08 09:26:19 -04:00
yhirose 3d6e315a4c Fix #1923 2024-09-08 08:38:36 -04:00
yhirose 4c27f9c6ef Made default server and client read/write timeout settings separately 2024-09-07 21:06:23 -04:00
yhirose d173a37d17 Increased CPPHTTPLIB_READ_TIMEOUT_SECOND to 300 from 5 2024-09-07 16:10:54 -04:00
yhirose 7fd346a2ca Fix #1379 2024-09-07 16:07:45 -04:00
yhirose c673d502b9 Update server_and_client.cc 2024-09-07 11:11:57 -04:00
yhirose c43c51362a Add monitor tool 2024-09-07 10:16:15 -04:00
yhirose 3e86d93d13 clangformat 2024-09-07 10:16:03 -04:00
yhirose f6e4e2d0f3 Code cleanup 2024-09-07 10:15:22 -04:00
yhirose 01a52aa8bd Add example/server_and_client.cc 2024-09-07 10:05:53 -04:00
yhirose 8415bf0823 Resolve #1906 2024-09-07 00:05:41 -04:00
orbea 327ff263f5 httplib.h: support LibreSSL (#1922) 2024-09-06 22:19:53 -04:00
21 changed files with 10798 additions and 176 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
dry-run: false
language: c++
- name: Upload Crash
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
+3 -2
View File
@@ -9,6 +9,8 @@ example/benchmark
example/redirect
example/sse*
example/upload
example/one_time_request
example/server_and_client
example/*.pem
test/httplib.cc
test/httplib.h
@@ -22,8 +24,7 @@ test/*.o
test/*.pem
test/*.srl
work/
benchmark/server
benchmark/server-crow
benchmark/server*
*.swp
+2
View File
@@ -299,6 +299,8 @@ if(HTTPLIB_INSTALL)
# ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE
install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}")
include(CPack)
endif()
if(HTTPLIB_TEST)
+32
View File
@@ -77,6 +77,9 @@ cli.set_ca_cert_path("./ca-bundle.crt");
// Disable cert verification
cli.enable_server_certificate_verification(false);
// Disable host verification
cli.enable_server_host_verification(false);
```
> [!NOTE]
@@ -384,6 +387,18 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
});
```
### Send file content
```cpp
svr.Get("/content", [&](const Request &req, Response &res) {
res.set_file_content("./path/to/conent.html");
});
svr.Get("/content", [&](const Request &req, Response &res) {
res.set_file_content("./path/to/conent", "text/html");
});
```
### 'Expect: 100-continue' handler
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
@@ -828,6 +843,23 @@ Use `poll` instead of `select`
`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`.
Unix Domain Socket Support
--------------------------
Unix Domain Socket support is available on Linux and macOS.
```c++
// Server
httplib::Server svr("./my-socket.sock");
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);
// Client
httplib::Client cli("./my-socket.sock");
cli.set_address_family(AF_UNIX);
```
"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
Split httplib.h into .h and .cc
-------------------------------
+37 -14
View File
@@ -1,23 +1,48 @@
CXXFLAGS = -std=c++11 -O2 -I..
THEAD_POOL_COUNT = 16
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
BENCH_CMD = bombardier -c 8 -d 5s localhost:8080
# BENCH_CMD = wrk -d 5s http://localhost:8080
BENCH = bombardier -c 10 -d 5s localhost:8080
MONITOR = ali http://localhost:8080
# cpp-httplib
bench: server
@./server & export PID=$$!; $(BENCH_CMD); kill $${PID}
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
@./server & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor: server
@./server & export PID=$$!; $(MONITOR); kill $${PID}
run : server
@./server
server : cpp-httplib/main.cpp ../httplib.h
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
# cpp-httplib
bench-base: server-base
@echo "---------------------\n cpp-httplib v0.18.0\n---------------------\n"
@./server-base & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor-base: server-base
@./server-base & export PID=$$!; $(MONITOR); kill $${PID}
run-base : server-base
@./server-base
server-base : cpp-httplib-base/main.cpp cpp-httplib-base/httplib.h
g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-base/main.cpp
# crow
bench-crow: server-crow
@./server-crow & export PID=$$!; $(BENCH_CMD); kill $${PID}
@echo "-------------\n Crow v1.2.0\n-------------\n"
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor-crow: server-crow
@./server-crow & export PID=$$!; $(MONITOR); kill $${PID}
run-crow : server-crow
@./server-crow
@@ -25,15 +50,13 @@ run-crow : server-crow
server-crow : crow/main.cpp
g++ -o $@ $(CXXFLAGS) crow/main.cpp
# flask
bench-flask:
@FLASK_APP=flask/main.py flask run --port=8080 & export PID=$$!; $(BENCH_CMD); kill $${PID}
run-flask:
@FLASK_APP=flask/main.py flask run --port=8080
# misc
bench-all: bench bench-crow bench-flask
build: server server-base server-crow
bench-all: bench-crow bench bench-base
issue:
bombardier -c 10 -d 30s localhost:8080
clean:
rm -rf server*
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
#include "./httplib.h"
using namespace httplib;
int main() {
Server svr;
svr.Get("/", [](const Request &, Response &res) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("0.0.0.0", 8080);
}
+2
View File
@@ -0,0 +1,2 @@
rm -f httplib.h
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
-9
View File
@@ -1,9 +0,0 @@
from flask import Flask
app = Flask(__name__)
import logging
logging.getLogger('werkzeug').disabled = True
@app.route('/')
def hello_world():
return 'Hello, World!'
+4 -1
View File
@@ -53,9 +53,12 @@ benchmark : benchmark.cc ../httplib.h Makefile
one_time_request : one_time_request.cc ../httplib.h Makefile
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
server_and_client : server_and_client.cc ../httplib.h Makefile
$(CXX) -o server_and_client $(CXXFLAGS) server_and_client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
pem:
openssl genrsa 2048 > key.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 ssecli benchmark one_time_request *.pem
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client *.pem
+90
View File
@@ -0,0 +1,90 @@
//
// server_and_client.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License
//
#include <httplib.h>
#include <iostream>
#include <string>
using namespace httplib;
std::string dump_headers(const Headers &headers) {
std::string s;
char buf[BUFSIZ];
for (auto it = headers.begin(); it != headers.end(); ++it) {
const auto &x = *it;
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
s += buf;
}
return s;
}
void logger(const Request &req, const Response &res) {
std::string s;
char buf[BUFSIZ];
s += "================================\n";
snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
req.version.c_str(), req.path.c_str());
s += buf;
std::string query;
for (auto it = req.params.begin(); it != req.params.end(); ++it) {
const auto &x = *it;
snprintf(buf, sizeof(buf), "%c%s=%s",
(it == req.params.begin()) ? '?' : '&', x.first.c_str(),
x.second.c_str());
query += buf;
}
snprintf(buf, sizeof(buf), "%s\n", query.c_str());
s += buf;
s += dump_headers(req.headers);
s += "--------------------------------\n";
snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
s += buf;
s += dump_headers(res.headers);
s += "\n";
if (!res.body.empty()) { s += res.body; }
s += "\n";
std::cout << s;
}
int main(void) {
// Server
Server svr;
svr.set_logger(logger);
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
res.set_content("POST", "text/plain");
});
auto th = std::thread([&]() { svr.listen("localhost", 8080); });
auto se = detail::scope_exit([&] {
svr.stop();
th.join();
});
svr.wait_until_ready();
// Client
Client cli{"localhost", 8080};
std::string body = R"({"hello": "world"})";
auto res = cli.Post("/post", body, "application/json");
std::cout << "--------------------------------" << std::endl;
std::cout << to_string(res.error()) << std::endl;
}
+292 -107
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.17.3"
#define CPPHTTPLIB_VERSION "0.18.1"
/*
* Configuration
@@ -18,6 +18,10 @@
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
#endif
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif
@@ -30,20 +34,36 @@
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
#endif
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
@@ -273,7 +293,7 @@ using socket_t = int;
#include <iostream>
#include <sstream>
#if defined(OPENSSL_IS_BORINGSSL)
#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
#if OPENSSL_VERSION_NUMBER < 0x1010107f
#error Please use OpenSSL or a current version of BoringSSL
#endif
@@ -349,8 +369,9 @@ inline unsigned char to_lower(int c) {
inline bool equal(const std::string &a, const std::string &b) {
return a.size() == b.size() &&
std::equal(a.begin(), a.end(), b.begin(),
[](char a, char b) { return to_lower(a) == to_lower(b); });
std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
return to_lower(ca) == to_lower(cb);
});
}
struct equal_to {
@@ -375,7 +396,7 @@ struct hash {
}
};
}; // namespace case_ignore
} // namespace case_ignore
// This is based on
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
@@ -675,6 +696,10 @@ struct Response {
const std::string &content_type, ContentProviderWithoutLength provider,
ContentProviderResourceReleaser resource_releaser = nullptr);
void set_file_content(const std::string &path,
const std::string &content_type);
void set_file_content(const std::string &path);
Response() = default;
Response(const Response &) = default;
Response &operator=(const Response &) = default;
@@ -692,6 +717,8 @@ struct Response {
ContentProviderResourceReleaser content_provider_resource_releaser_;
bool is_chunked_content_provider_ = false;
bool content_provider_success_ = false;
std::string file_content_path_;
std::string file_content_content_type_;
};
class Stream {
@@ -786,7 +813,8 @@ private:
fn();
}
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL)
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
!defined(LIBRESSL_VERSION_NUMBER)
OPENSSL_thread_stop();
#endif
}
@@ -987,17 +1015,19 @@ public:
std::function<TaskQueue *(void)> new_task_queue;
protected:
bool process_request(Stream &strm, bool close_connection,
bool process_request(Stream &strm, const std::string &remote_addr,
int remote_port, const std::string &local_addr,
int local_port, bool close_connection,
bool &connection_closed,
const std::function<void(Request &)> &setup_request);
std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
@@ -1110,6 +1140,7 @@ enum class Error {
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
SSLServerHostnameVerification,
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
@@ -1425,6 +1456,8 @@ public:
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void enable_server_certificate_verification(bool enabled);
void enable_server_hostname_verification(bool enabled);
void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
#endif
void set_logger(Logger logger);
@@ -1491,10 +1524,10 @@ protected:
time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
std::string basic_auth_username_;
std::string basic_auth_password_;
@@ -1539,6 +1572,8 @@ protected:
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
bool server_certificate_verification_ = true;
bool server_hostname_verification_ = true;
std::function<bool(SSL *ssl)> server_certificate_verifier_;
#endif
Logger logger_;
@@ -1844,6 +1879,8 @@ public:
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void enable_server_certificate_verification(bool enabled);
void enable_server_hostname_verification(bool enabled);
void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
#endif
void set_logger(Logger logger);
@@ -2135,6 +2172,8 @@ inline std::string to_string(const Error error) {
case Error::SSLConnection: return "SSL connection failed";
case Error::SSLLoadingCerts: return "SSL certificate loading failed";
case Error::SSLServerVerification: return "SSL server verification failed";
case Error::SSLServerHostnameVerification:
return "SSL server hostname verification failed";
case Error::UnsupportedMultipartBoundaryChars:
return "Unsupported HTTP multipart boundary characters";
case Error::Compression: return "Compression failed";
@@ -2219,9 +2258,15 @@ make_basic_authentication_header(const std::string &username,
namespace detail {
bool is_file(const std::string &path);
struct FileStat {
FileStat(const std::string &path);
bool is_file() const;
bool is_dir() const;
bool is_dir(const std::string &path);
private:
struct stat st_;
int ret_ = -1;
};
std::string encode_query_param(const std::string &value);
@@ -2428,13 +2473,14 @@ public:
private:
#if defined(_WIN32)
HANDLE hFile_;
HANDLE hMapping_;
HANDLE hFile_ = NULL;
HANDLE hMapping_ = NULL;
#else
int fd_;
int fd_ = -1;
#endif
size_t size_;
void *addr_;
size_t size_ = 0;
void *addr_ = nullptr;
bool is_open_empty_file = false;
};
} // namespace detail
@@ -2592,14 +2638,14 @@ inline bool is_valid_path(const std::string &path) {
return true;
}
inline bool is_file(const std::string &path) {
struct stat st;
return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
inline FileStat::FileStat(const std::string &path) {
ret_ = stat(path.c_str(), &st_);
}
inline bool is_dir(const std::string &path) {
struct stat st;
return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
inline bool FileStat::is_file() const {
return ret_ >= 0 && S_ISREG(st_.st_mode);
}
inline bool FileStat::is_dir() const {
return ret_ >= 0 && S_ISDIR(st_.st_mode);
}
inline std::string encode_query_param(const std::string &value) {
@@ -2855,16 +2901,7 @@ inline void stream_line_reader::append(char c) {
}
}
inline mmap::mmap(const char *path)
#if defined(_WIN32)
: hFile_(NULL), hMapping_(NULL)
#else
: fd_(-1)
#endif
,
size_(0), addr_(nullptr) {
open(path);
}
inline mmap::mmap(const char *path) { open(path); }
inline mmap::~mmap() { close(); }
@@ -2906,6 +2943,13 @@ inline bool mmap::open(const char *path) {
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
#endif
// Special treatment for an empty file...
if (hMapping_ == NULL && size_ == 0) {
close();
is_open_empty_file = true;
return true;
}
if (hMapping_ == NULL) {
close();
return false;
@@ -2916,6 +2960,11 @@ inline bool mmap::open(const char *path) {
#else
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
#endif
if (addr_ == nullptr) {
close();
return false;
}
#else
fd_ = ::open(path, O_RDONLY);
if (fd_ == -1) { return false; }
@@ -2928,22 +2977,26 @@ inline bool mmap::open(const char *path) {
size_ = static_cast<size_t>(sb.st_size);
addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
#endif
if (addr_ == nullptr) {
// Special treatment for an empty file...
if (addr_ == MAP_FAILED && size_ == 0) {
close();
is_open_empty_file = true;
return false;
}
#endif
return true;
}
inline bool mmap::is_open() const { return addr_ != nullptr; }
inline bool mmap::is_open() const {
return is_open_empty_file ? true : addr_ != nullptr;
}
inline size_t mmap::size() const { return size_; }
inline const char *mmap::data() const {
return static_cast<const char *>(addr_);
return is_open_empty_file ? "" : static_cast<const char *>(addr_);
}
inline void mmap::close() {
@@ -2962,6 +3015,8 @@ inline void mmap::close() {
::CloseHandle(hFile_);
hFile_ = INVALID_HANDLE_VALUE;
}
is_open_empty_file = false;
#else
if (addr_ != nullptr) {
munmap(addr_, size_);
@@ -3199,6 +3254,41 @@ private:
};
#endif
inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
time_t keep_alive_timeout_sec) {
using namespace std::chrono;
const auto interval_usec =
CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
// Avoid expensive `steady_clock::now()` call for the first time
if (select_read(sock, 0, interval_usec) > 0) { return true; }
const auto start = steady_clock::now() - microseconds{interval_usec};
const auto timeout = seconds{keep_alive_timeout_sec};
while (true) {
if (svr_sock == INVALID_SOCKET) {
break; // Server socket is closed
}
auto val = select_read(sock, 0, interval_usec);
if (val < 0) {
break; // Ssocket error
} else if (val == 0) {
if (steady_clock::now() - start > timeout) {
break; // Timeout
}
} else {
return true; // Ready for read
}
std::this_thread::sleep_for(microseconds{interval_usec});
}
return false;
}
template <typename T>
inline bool
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
@@ -3207,8 +3297,7 @@ process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
assert(keep_alive_max_count > 0);
auto ret = false;
auto count = keep_alive_max_count;
while (svr_sock != INVALID_SOCKET && count > 0 &&
select_read(sock, keep_alive_timeout_sec, 0) > 0) {
while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
auto close_connection = count == 1;
auto connection_closed = false;
ret = callback(close_connection, connection_closed);
@@ -3343,6 +3432,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
#endif
return INVALID_SOCKET;
}
auto se = detail::scope_exit([&] { freeaddrinfo(result); });
for (auto rp = result; rp; rp = rp->ai_next) {
// Create a socket
@@ -3412,17 +3502,13 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
// bind or connect
auto quit = false;
if (bind_or_connect(sock, *rp, quit)) {
freeaddrinfo(result);
return sock;
}
if (bind_or_connect(sock, *rp, quit)) { return sock; }
close_socket(sock);
if (quit) { break; }
}
freeaddrinfo(result);
return INVALID_SOCKET;
}
@@ -3455,6 +3541,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
hints.ai_protocol = 0;
if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; }
auto se = detail::scope_exit([&] { freeaddrinfo(result); });
auto ret = false;
for (auto rp = result; rp; rp = rp->ai_next) {
@@ -3465,7 +3552,6 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
}
}
freeaddrinfo(result);
return ret;
}
@@ -3477,6 +3563,8 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
inline std::string if2ip(int address_family, const std::string &ifn) {
struct ifaddrs *ifap;
getifaddrs(&ifap);
auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
std::string addr_candidate;
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr && ifn == ifa->ifa_name &&
@@ -3486,7 +3574,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
char buf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
freeifaddrs(ifap);
return std::string(buf, INET_ADDRSTRLEN);
}
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
@@ -3499,7 +3586,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
} else {
freeifaddrs(ifap);
return std::string(buf, INET6_ADDRSTRLEN);
}
}
@@ -3507,7 +3593,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
}
}
}
freeifaddrs(ifap);
return addr_candidate;
}
#endif
@@ -3674,7 +3759,7 @@ inline unsigned int str2tag(const std::string &s) {
namespace udl {
inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
inline constexpr unsigned int operator""_t(const char *s, size_t l) {
return str2tag_core(s, l, 0);
}
@@ -3759,8 +3844,9 @@ inline bool can_compress_content_type(const std::string &content_type) {
case "application/protobuf"_t:
case "application/xhtml+xml"_t: return true;
default:
return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t;
case "text/event-stream"_t: return false;
default: return !content_type.rfind("text/", 0);
}
}
@@ -4028,7 +4114,7 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
p++;
}
if (p < end) {
if (p <= end) {
auto key_len = key_end - beg;
if (!key_len) { return false; }
@@ -4176,8 +4262,19 @@ inline bool read_content_chunked(Stream &strm, T &x,
assert(chunk_len == 0);
// Trailer
if (!line_reader.getline()) { return false; }
// NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked
// transfer coding is complete when a chunk with a chunk-size of zero is
// received, possibly followed by a trailer section, and finally terminated by
// an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
//
// In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
// does't care for the existence of the final CRLF. In other words, it seems
// to be ok whether the final CRLF exists or not in the chunked data.
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
//
// According to the reference code in RFC 9112, cpp-htpplib now allows
// chuncked transfer coding data without the final CRLF.
if (!line_reader.getline()) { return true; }
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
@@ -5496,6 +5593,7 @@ inline void hosted_at(const std::string &hostname,
#endif
return;
}
auto se = detail::scope_exit([&] { freeaddrinfo(result); });
for (auto rp = result; rp; rp = rp->ai_next) {
const auto &addr =
@@ -5507,8 +5605,6 @@ inline void hosted_at(const std::string &hostname,
addrs.push_back(ip);
}
}
freeaddrinfo(result);
}
inline std::string append_query_params(const std::string &path,
@@ -5703,6 +5799,16 @@ inline void Response::set_chunked_content_provider(
is_chunked_content_provider_ = true;
}
inline void Response::set_file_content(const std::string &path,
const std::string &content_type) {
file_content_path_ = path;
file_content_content_type_ = content_type;
}
inline void Response::set_file_content(const std::string &path) {
file_content_path_ = path;
}
// Result implementation
inline bool Result::has_request_header(const std::string &key) const {
return request_headers_.find(key) != request_headers_.end();
@@ -6034,7 +6140,8 @@ inline bool Server::set_base_dir(const std::string &dir,
inline bool Server::set_mount_point(const std::string &mount_point,
const std::string &dir, Headers headers) {
if (detail::is_dir(dir)) {
detail::FileStat stat(dir);
if (stat.is_dir()) {
std::string mnt = !mount_point.empty() ? mount_point : "/";
if (!mnt.empty() && mnt[0] == '/') {
base_dirs_.push_back({mnt, dir, std::move(headers)});
@@ -6518,12 +6625,14 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
auto path = entry.base_dir + sub_path;
if (path.back() == '/') { path += "index.html"; }
if (detail::is_dir(path)) {
detail::FileStat stat(path);
if (stat.is_dir()) {
res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
return true;
}
if (detail::is_file(path)) {
if (stat.is_file()) {
for (const auto &kv : entry.headers) {
res.set_header(kv.first, kv.second);
}
@@ -6904,7 +7013,9 @@ inline bool Server::dispatch_request_for_content_reader(
}
inline bool
Server::process_request(Stream &strm, bool close_connection,
Server::process_request(Stream &strm, const std::string &remote_addr,
int remote_port, const std::string &local_addr,
int local_port, bool close_connection,
bool &connection_closed,
const std::function<void(Request &)> &setup_request) {
std::array<char, 2048> buf{};
@@ -6958,11 +7069,13 @@ Server::process_request(Stream &strm, bool close_connection,
connection_closed = true;
}
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
req.remote_addr = remote_addr;
req.remote_port = remote_port;
req.set_header("REMOTE_ADDR", req.remote_addr);
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
strm.get_local_ip_and_port(req.local_addr, req.local_port);
req.local_addr = local_addr;
req.local_port = local_port;
req.set_header("LOCAL_ADDR", req.local_addr);
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
@@ -7043,6 +7156,32 @@ Server::process_request(Stream &strm, bool close_connection,
return write_response(strm, close_connection, req, res);
}
// Serve file content by using a content provider
if (!res.file_content_path_.empty()) {
const auto &path = res.file_content_path_;
auto mm = std::make_shared<detail::mmap>(path.c_str());
if (!mm->is_open()) {
res.body.clear();
res.content_length_ = 0;
res.content_provider_ = nullptr;
res.status = StatusCode::NotFound_404;
return write_response(strm, close_connection, req, res);
}
auto content_type = res.file_content_content_type_;
if (content_type.empty()) {
content_type = detail::find_content_type(
path, file_extension_and_mimetype_map_, default_file_mimetype_);
}
res.set_content_provider(
mm->size(), content_type,
[mm](size_t offset, size_t length, DataSink &sink) -> bool {
sink.write(mm->data() + offset, length);
return true;
});
}
return write_response_with_content(strm, close_connection, req, res);
} else {
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
@@ -7054,12 +7193,21 @@ Server::process_request(Stream &strm, bool close_connection,
inline bool Server::is_valid() const { return true; }
inline bool Server::process_and_close_socket(socket_t sock) {
std::string remote_addr;
int remote_port = 0;
detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
std::string local_addr;
int local_port = 0;
detail::get_local_ip_and_port(sock, local_addr, local_port);
auto ret = detail::process_server_socket(
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,
[&](Stream &strm, bool close_connection, bool &connection_closed) {
return process_request(strm, remote_addr, remote_port, local_addr,
local_port, close_connection, connection_closed,
nullptr);
});
@@ -7131,6 +7279,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
server_certificate_verification_ = rhs.server_certificate_verification_;
server_hostname_verification_ = rhs.server_hostname_verification_;
server_certificate_verifier_ = rhs.server_certificate_verifier_;
#endif
logger_ = rhs.logger_;
}
@@ -8608,13 +8758,11 @@ inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
std::size_t size) const {
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
auto se = detail::scope_exit([&] { BIO_free_all(mem); });
if (!mem) { return nullptr; }
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
if (!inf) {
BIO_free_all(mem);
return nullptr;
}
if (!inf) { return nullptr; }
auto cts = X509_STORE_new();
if (cts) {
@@ -8628,13 +8776,21 @@ inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free_all(mem);
return cts;
}
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
server_certificate_verification_ = enabled;
}
inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
server_hostname_verification_ = enabled;
}
inline void ClientImpl::set_server_certificate_verifier(
std::function<bool(SSL *ssl)> verifier) {
server_certificate_verifier_ = verifier;
}
#endif
inline void ClientImpl::set_logger(Logger logger) {
@@ -8886,7 +9042,8 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
1) {
1 ||
SSL_CTX_check_private_key(ctx_) != 1) {
SSL_CTX_free(ctx_);
ctx_ = nullptr;
} else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
@@ -8966,13 +9123,22 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
auto ret = false;
if (ssl) {
std::string remote_addr;
int remote_port = 0;
detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
std::string local_addr;
int local_port = 0;
detail::get_local_ip_and_port(sock, local_addr, local_port);
ret = detail::process_server_socket_ssl(
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,
bool &connection_closed) {
return process_request(strm, close_connection, connection_closed,
[&](Stream &strm, bool close_connection, bool &connection_closed) {
return process_request(strm, remote_addr, remote_port, local_addr,
local_port, close_connection,
connection_closed,
[&](Request &req) { req.ssl = ssl; });
});
@@ -9001,6 +9167,8 @@ inline SSLClient::SSLClient(const std::string &host, int port,
: ClientImpl(host, port, client_cert_path, client_key_path) {
ctx_ = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
detail::split(&host_[0], &host_[host_.size()], '.',
[&](const char *b, const char *e) {
host_components_.emplace_back(b, e);
@@ -9208,26 +9376,34 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
}
if (server_certificate_verification_) {
verify_result_ = SSL_get_verify_result(ssl2);
if (server_certificate_verifier_) {
if (!server_certificate_verifier_(ssl2)) {
error = Error::SSLServerVerification;
return false;
}
} else {
verify_result_ = SSL_get_verify_result(ssl2);
if (verify_result_ != X509_V_OK) {
error = Error::SSLServerVerification;
return false;
if (verify_result_ != X509_V_OK) {
error = Error::SSLServerVerification;
return false;
}
auto server_cert = SSL_get1_peer_certificate(ssl2);
auto se = detail::scope_exit([&] { X509_free(server_cert); });
if (server_cert == nullptr) {
error = Error::SSLServerVerification;
return false;
}
if (server_hostname_verification_) {
if (!verify_host(server_cert)) {
error = Error::SSLServerHostnameVerification;
return false;
}
}
}
auto server_cert = SSL_get1_peer_certificate(ssl2);
if (server_cert == nullptr) {
error = Error::SSLServerVerification;
return false;
}
if (!verify_host(server_cert)) {
X509_free(server_cert);
error = Error::SSLServerVerification;
return false;
}
X509_free(server_cert);
}
return true;
@@ -9315,8 +9491,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
auto type = GEN_DNS;
struct in6_addr addr6 {};
struct in_addr addr {};
struct in6_addr addr6{};
struct in_addr addr{};
size_t addr_len = 0;
#ifndef __MINGW32__
@@ -9958,6 +10134,15 @@ inline void Client::set_proxy_digest_auth(const std::string &username,
inline void Client::enable_server_certificate_verification(bool enabled) {
cli_->enable_server_certificate_verification(enabled);
}
inline void Client::enable_server_hostname_verification(bool enabled) {
cli_->enable_server_hostname_verification(enabled);
}
inline void Client::set_server_certificate_verifier(
std::function<bool(SSL *ssl)> verifier) {
cli_->set_server_certificate_verifier(verifier);
}
#endif
inline void Client::set_logger(Logger logger) {
+4 -6
View File
@@ -13,7 +13,7 @@ project(
'b_lto=true',
'warning_level=3'
],
meson_version: '>=0.47.0'
meson_version: '>=0.62.0'
)
# Check just in case downstream decides to edit the source
@@ -98,20 +98,18 @@ if get_option('cpp-httplib_compile')
)
else
install_headers('httplib.h')
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: include_directories('.'))
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.')
import('pkgconfig').generate(
name: 'cpp-httplib',
description: 'A C++ HTTP/HTTPS server and client library',
install_dir: join_paths(get_option('datadir'), 'pkgconfig'),
install_dir: get_option('datadir')/'pkgconfig',
url: 'https://github.com/yhirose/cpp-httplib',
version: version
)
endif
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
endif
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
if get_option('cpp-httplib_test')
subdir('test')
+7 -2
View File
@@ -37,6 +37,11 @@ file(
)
if(HTTPLIB_IS_USING_OPENSSL)
if (OPENSSL_VERSION VERSION_LESS "3.2.0")
set(OPENSSL_X509_FLAG "-x509")
else()
set(OPENSSL_X509_FLAG "-x509v1")
endif()
find_program(OPENSSL_COMMAND
NAMES openssl
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
@@ -56,7 +61,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
@@ -67,7 +72,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
OUTPUT_FILE rootCA.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
+2 -12
View File
@@ -63,18 +63,8 @@ httplib.cc : ../httplib.h
python3 ../split.py -o .
cert.pem:
openssl genrsa 2048 > key.pem
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
openssl genrsa 2048 > rootCA.key.pem
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
openssl genrsa 2048 > client.key.pem
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
openssl genrsa -aes256 -passout pass:test012! 2048 > client_encrypted.key.pem
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
#c_rehash .
./gen-certs.sh
clean:
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
+6 -2
View File
@@ -50,8 +50,12 @@ class FuzzableServer : public httplib::Server {
public:
void ProcessFuzzedRequest(FuzzedStream &stream) {
bool connection_close = false;
process_request(stream, /*last_connection=*/false, connection_close,
nullptr);
process_request(stream,
/*remote_addr=*/"",
/*remote_port =*/0,
/*local_addr=*/"",
/*local_port =*/0,
/*last_connection=*/false, connection_close, nullptr);
}
};
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
if [[ $(openssl version) =~ 3\.[2-9]\.[0-9]+ ]]; then
OPENSSL_X509_FLAG='-x509v1'
else
OPENSSL_X509_FLAG='-x509'
fi
openssl genrsa 2048 > key.pem
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
openssl genrsa 2048 > rootCA.key.pem
openssl req $OPENSSL_X509_FLAG -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
openssl genrsa 2048 > client.key.pem
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
openssl genrsa -aes256 -passout pass:test012! 2048 > client_encrypted.key.pem
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
+7 -6
View File
@@ -6,6 +6,7 @@ gtest_dep = dependency('gtest', main: true)
libcurl_dep = dependency('libcurl')
openssl = find_program('openssl')
test_conf = files('test.conf')
req_x509_flag = openssl.version().version_compare('>=3.2.0') ? '-x509v1' : '-x509'
key_pem = custom_target(
'key_pem',
@@ -31,7 +32,7 @@ cert2_pem = custom_target(
'cert2_pem',
input: key_pem,
output: 'cert2.pem',
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
command: [openssl, 'req', req_x509_flag, '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
)
key_encrypted_pem = custom_target(
@@ -44,7 +45,7 @@ cert_encrypted_pem = custom_target(
'cert_encrypted_pem',
input: key_encrypted_pem,
output: 'cert_encrypted.pem',
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
command: [openssl, 'req', req_x509_flag, '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
)
rootca_key_pem = custom_target(
@@ -57,7 +58,7 @@ rootca_cert_pem = custom_target(
'rootca_cert_pem',
input: rootca_key_pem,
output: 'rootCA.cert.pem',
command: [openssl, 'req', '-x509', '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
command: [openssl, 'req', req_x509_flag, '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
)
client_key_pem = custom_target(
@@ -103,9 +104,9 @@ client_encrypted_cert_pem = custom_target(
# Copy test files to the build directory
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
subdir(join_paths('www', 'dir'))
subdir(join_paths('www2', 'dir'))
subdir(join_paths('www3', 'dir'))
subdir('www')
subdir('www2'/'dir')
subdir('www3'/'dir')
# GoogleTest 1.13.0 requires C++14
test_options = []
+118 -14
View File
@@ -2300,6 +2300,18 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
})
.Get("/file_content",
[&](const Request & /*req*/, Response &res) {
res.set_file_content("./www/dir/test.html");
})
.Get("/file_content_with_content_type",
[&](const Request & /*req*/, Response &res) {
res.set_file_content("./www/file", "text/plain");
})
.Get("/invalid_file_content",
[&](const Request & /*req*/, Response &res) {
res.set_file_content("./www/dir/invalid_file_path");
})
.Get("/http_response_splitting",
[&](const Request & /*req*/, Response &res) {
res.set_header("a", "1\r\nSet-Cookie: a=1");
@@ -2904,6 +2916,39 @@ TEST_F(ServerTest, GetMethod200) {
EXPECT_EQ("Hello World!", res->body);
}
TEST_F(ServerTest, GetEmptyFile) {
auto res = cli_.Get("/empty_file");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("application/octet-stream", res->get_header_value("Content-Type"));
EXPECT_EQ(0, std::stoi(res->get_header_value("Content-Length")));
EXPECT_EQ("", res->body);
}
TEST_F(ServerTest, GetFileContent) {
auto res = cli_.Get("/file_content");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ(9, std::stoi(res->get_header_value("Content-Length")));
EXPECT_EQ("test.html", res->body);
}
TEST_F(ServerTest, GetFileContentWithContentType) {
auto res = cli_.Get("/file_content_with_content_type");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(5, std::stoi(res->get_header_value("Content-Length")));
EXPECT_EQ("file\n", res->body);
}
TEST_F(ServerTest, GetInvalidFileContent) {
auto res = cli_.Get("/invalid_file_content");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
ASSERT_TRUE(res);
@@ -3655,6 +3700,16 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) {
EXPECT_EQ(0U, res->body.size());
}
TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
auto res = cli_.Get("/with-range", {{"Range", "bytes=0-"}});
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("bytes 0-6/7", res->get_header_value("Content-Range"));
EXPECT_EQ(std::string("abcdefg"), res->body);
}
TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
auto res =
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
@@ -4722,9 +4777,10 @@ static void test_raw_request(const std::string &req,
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain");
});
svr.Get("/header_field_value_check", [&](const Request &/*req*/, Response &res) {
res.set_content("ok", "text/plain");
});
svr.Get("/header_field_value_check",
[&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain");
});
// Server read timeout must be longer than the client read timeout for the
// bug to reproduce, probably to force the server to process a request
@@ -4866,6 +4922,15 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, EmptyFieldValue) {
std::string out;
test_raw_request("GET /header_field_value_check HTTP/1.1\r\n"
"Test: \r\n\r\n",
&out);
EXPECT_EQ("HTTP/1.1 200 OK", out.substr(0, 15));
}
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
Server svr;
@@ -5212,6 +5277,41 @@ TEST(KeepAliveTest, Issue1041) {
EXPECT_EQ(StatusCode::OK_200, result->status);
}
TEST(KeepAliveTest, Issue1959) {
Server svr;
svr.set_keep_alive_timeout(5);
svr.Get("/a", [&](const Request & /*req*/, Response &res) {
res.set_content("a", "text/plain");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
auto se = detail::scope_exit([&] {
if (!svr.is_running()) return;
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli("localhost", PORT);
cli.set_keep_alive(true);
using namespace std::chrono;
auto start = steady_clock::now();
cli.Get("/a");
svr.stop();
listen_thread.join();
auto end = steady_clock::now();
auto elapsed = duration_cast<milliseconds>(end - start).count();
EXPECT_LT(elapsed, 5000);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(KeepAliveTest, SSLClientReconnection) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
@@ -7630,17 +7730,19 @@ TEST(FileSystemTest, FileAndDirExistenceCheck) {
auto file_path = "./www/dir/index.html";
auto dir_path = "./www/dir";
EXPECT_TRUE(detail::is_file(file_path));
EXPECT_FALSE(detail::is_dir(file_path));
detail::FileStat stat_file(file_path);
EXPECT_TRUE(stat_file.is_file());
EXPECT_FALSE(stat_file.is_dir());
EXPECT_FALSE(detail::is_file(dir_path));
EXPECT_TRUE(detail::is_dir(dir_path));
detail::FileStat stat_dir(dir_path);
EXPECT_FALSE(stat_dir.is_file());
EXPECT_TRUE(stat_dir.is_dir());
}
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
Server svr;
svr.Get("/test", [&](const Request &/*req*/, Response &res) {
svr.Get("/test", [&](const Request & /*req*/, Response &res) {
EXPECT_EQ(res.status, 400);
});
@@ -7666,11 +7768,12 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
Server svr;
svr.set_expect_100_continue_handler([](const Request &/*req*/, Response &res) {
res.status = StatusCode::Unauthorized_401;
res.set_content(reject, "text/plain");
return res.status;
});
svr.set_expect_100_continue_handler(
[](const Request & /*req*/, Response &res) {
res.status = StatusCode::Unauthorized_401;
res.set_content(reject, "text/plain");
return res.status;
});
svr.Post("/", [&](const Request & /*req*/, Response &res) {
res.set_content(accept, "text/plain");
});
@@ -7745,7 +7848,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
{
auto dl = curl_off_t{};
const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
const auto res =
curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
ASSERT_EQ(res, CURLE_OK);
ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
}
View File
+7
View File
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2024 Andrea Pappacoda
#
# SPDX-License-Identifier: MIT
configure_file(input: 'empty_file', output: 'empty_file', copy: true)
configure_file(input: 'file', output: 'file', copy: true)
subdir('dir')