mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e64379c3d7 | |||
| 5053912534 | |||
| 932b1cbc32 | |||
| de36ea7755 | |||
| 9f8db2c230 | |||
| 3f00e1b321 | |||
| 7ab9c119ef | |||
| 3f2922b3fa | |||
| 509f583dca | |||
| 2d01e71286 | |||
| e612154694 | |||
| 82fcbe3901 | |||
| dbd2465b56 | |||
| ea79494b29 | |||
| f35aff84c2 | |||
| 7b18ae6f16 | |||
| a79c56d06b | |||
| 3d6e315a4c | |||
| 4c27f9c6ef | |||
| d173a37d17 | |||
| 7fd346a2ca | |||
| c673d502b9 | |||
| c43c51362a | |||
| 3e86d93d13 | |||
| f6e4e2d0f3 | |||
| 01a52aa8bd | |||
| 8415bf0823 | |||
| 327ff263f5 |
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
rm -f httplib.h
|
||||
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user