Compare commits

...

28 Commits

Author SHA1 Message Date
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
14 changed files with 10430 additions and 138 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
+15
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.
+33 -12
View File
@@ -2,12 +2,17 @@ CXXFLAGS = -std=c++11 -O2 -I..
THEAD_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
@@ -15,9 +20,29 @@ run : server
server : cpp-httplib/main.cpp ../httplib.h
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
# cpp-httplib
bench-base: server-base
@echo "---------------------\n cpp-httplib v0.17.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) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) 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,11 @@ 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
bench-all: bench-crow bench bench-base
issue:
$(BENCH)
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;
}
+245 -97
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.0"
/*
* Configuration
@@ -30,20 +30,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 +289,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
@@ -350,7 +366,7 @@ 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); });
[](char ca, char cb) { return to_lower(ca) == to_lower(cb); });
}
struct equal_to {
@@ -675,6 +691,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 +712,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 +808,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 +1010,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 +1135,7 @@ enum class Error {
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
SSLServerHostnameVerification,
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
@@ -1425,6 +1451,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 +1519,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 +1567,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 +1874,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 +2167,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 +2253,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 +2468,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 +2633,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,14 +2896,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) {
inline mmap::mmap(const char *path) {
open(path);
}
@@ -2906,6 +2940,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 +2957,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 +2974,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 +3012,8 @@ inline void mmap::close() {
::CloseHandle(hFile_);
hFile_ = INVALID_HANDLE_VALUE;
}
is_open_empty_file = false;
#else
if (addr_ != nullptr) {
munmap(addr_, size_);
@@ -3343,6 +3395,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 +3465,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 +3504,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 +3515,6 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
}
}
freeaddrinfo(result);
return ret;
}
@@ -3477,6 +3526,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 +3537,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 +3549,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 +3556,6 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
}
}
}
freeifaddrs(ifap);
return addr_candidate;
}
#endif
@@ -3759,8 +3807,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);
}
}
@@ -4176,8 +4225,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 +5556,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 +5568,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 +5762,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 +6103,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 +6588,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 +6976,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 +7032,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 +7119,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 +7156,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 +7242,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 +8721,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 +8739,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 +9005,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 +9086,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 +9130,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 +9339,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;
@@ -9958,6 +10097,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) {
+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);
}
};
+64 -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);
@@ -4722,9 +4767,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
@@ -7630,17 +7676,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 +7714,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 +7794,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