Compare commits

...

35 Commits

Author SHA1 Message Date
yhirose ef63f97afe Release v0.17.2 2024-09-05 12:57:26 -04:00
yhirose bda74db01d Fix fuzzing test error 2024-09-05 12:50:05 -04:00
yhirose 9ff3ff9446 Fixed build error 2024-09-05 12:27:50 -04:00
yhirose c75d071615 Add benchmark tool 2024-09-05 12:22:46 -04:00
yhirose b4989130da Peformance improvement by removing tolower function call 2024-09-05 12:09:38 -04:00
yhirose 4fc0303bda clangformat 2024-09-05 12:07:35 -04:00
yhirose 3d9cc51851 Fixed build error on Windows due to max macro in windows.h 2024-09-05 12:02:40 -04:00
Andrea Pappacoda f69587656f build(meson): add libcurl test dependency (#1914)
Prompted by PR #1911
2024-09-04 18:05:03 -04:00
yhirose d5fc340c30 Update README 2024-09-04 12:23:48 -04:00
yhirose d79a547dc9 Merge branch 'solarispika-fix-100-continue' 2024-09-04 10:17:01 -04:00
yhirose bd1da4346a Disable Expect100ContinueTest test on Windows 2024-09-04 09:51:36 -04:00
yhirose 4c2a608a0c Fix GitHub Actions errors 2024-09-04 09:06:27 -04:00
yhirose ee4eb8deaa Merge branch 'fix-100-continue' of github.com:solarispika/cpp-httplib into solarispika-fix-100-continue 2024-09-04 08:45:48 -04:00
Sung, Po Han 7196ac8a07 Fix incorrect handling of Expect: 100-continue
Fix #1808
2024-09-04 17:50:42 +08:00
yhirose c88b09bc6b Release v0.17.1 2024-09-03 21:20:57 -04:00
yhirose 87fab847b8 Fix SIGINT problem in Docker image 2024-09-03 21:20:10 -04:00
yhirose 4e6055f084 Fix problem with Abstract Namespace Unix Domain 2024-09-03 20:56:16 -04:00
yhirose 975cf0dae5 Fix #1908 2024-09-03 18:00:12 -04:00
yhirose 4854a694cd Use IPPROTO_IP 2024-09-03 17:29:28 -04:00
yhirose b1f8e986bf Fix #1908 (#1910)
* Fix #1908

* Code format
2024-09-03 00:47:39 -04:00
yhirose c5ee208775 Fix build error on Mac and Linux 2024-09-02 23:04:38 -04:00
yhirose ddfdacfa49 Fix build error 2024-09-02 22:49:31 -04:00
yhirose 2514ebc20f Fix #1848 2024-09-02 20:38:01 -04:00
yhirose 4f9c6540b2 Fixed warning 2024-09-02 20:33:33 -04:00
mol123 21c9a6a1ff Windows: simplify conditional compilation and fix call to CreateFileMappingW. (#1909) 2024-09-02 18:01:05 -04:00
yhirose 7f6d413ddd Release v0.17.0 2024-09-01 07:53:56 -04:00
yhirose 88277139e7 Added set_ipv6_v6only method (#1905)
* Added `set_ipv6_v6only` method

* Adjust the place where socket_options is called
2024-09-01 07:52:24 -04:00
yhirose 6cdd3493a1 Fix #1788 2024-09-01 01:55:27 -04:00
yhirose 9c91b6f4a6 Fix #1645 2024-09-01 00:11:07 -04:00
yhirose cee838e335 Documentation 2024-08-31 17:42:43 -04:00
yhirose d82c82db2c Add sleep in handle_EINTR 2024-08-31 17:19:52 -04:00
yhirose ba638ff38e Update Docker support 2024-08-31 17:09:20 -04:00
yhirose da0c6579fa Breaking Change! get_header_ methods on Request and Response now take a default value. 2024-08-31 17:07:48 -04:00
yhirose 52a18c78a5 Add docker related files 2024-08-27 00:23:31 -04:00
yhirose 048edec9ed Changed CPPHTTPLIB_KEEPALIVE_MAX_COUNT to 100 2024-08-26 21:10:38 -04:00
17 changed files with 14991 additions and 140 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
- name: install brotli
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev
- name: install libraries
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
- name: build and run tests
run: cd test && make -j4
- name: run fuzz test target
+3
View File
@@ -21,6 +21,8 @@ test/test.xcodeproj/*/xcuser*
test/*.o
test/*.pem
test/*.srl
benchmark/server
benchmark/server-crow
*.swp
@@ -34,6 +36,7 @@ Release
*.db
ipch
*.dSYM
*.pyc
.*
!/.gitattributes
!/.travis.yml
+11
View File
@@ -0,0 +1,11 @@
FROM yhirose4dockerhub/ubuntu-builder AS builder
WORKDIR /build
COPY httplib.h .
COPY docker/main.cc .
RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server
FROM scratch
COPY --from=builder /build/server /server
COPY docker/html/index.html /html/index.html
EXPOSE 80
CMD ["/server"]
+29
View File
@@ -848,6 +848,35 @@ $ ./split.py
Wrote out/httplib.h and out/httplib.cc
```
Dockerfile for Static HTTP Server
---------------------------------
Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container.
```bash
> docker build -t cpp-httplib-server .
...
> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
```
From Docker Hub
```bash
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server
...
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
```
NOTE
----
+31
View File
@@ -0,0 +1,31 @@
CXXFLAGS = -std=c++14 -O2 -I..
THEAD_POOL_COUNT = 16
BENCH_FLAGS = -c 8 -d 5s
# cpp-httplib
bench: server
@./server & export PID=$$!; bombardier $(BENCH_FLAGS) localhost:8080; kill $${PID}
server : cpp-httplib/main.cpp ../httplib.h
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
run : server
@./server
# crow
server-crow : crow/main.cpp
g++ -o $@ $(CXXFLAGS) crow/main.cpp
bench-crow: server-crow
@./server-crow & export PID=$$!; bombardier $(BENCH_FLAGS) localhost:8080; kill $${PID}
# flask
bench-flask:
@FLASK_APP=flask/main.py flask run --port=8080 & export PID=$$!; bombardier $(BENCH_FLAGS) localhost:8080; kill $${PID}
# misc
bench-all: bench bench-crow bench-flask
clean:
rm -rf server*
+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);
}
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
#include "crow_all.h"
class CustomLogger : public crow::ILogHandler {
public:
void log(std::string, crow::LogLevel) {}
};
int main() {
CustomLogger logger;
crow::logger::setHandler(&logger);
crow::SimpleApp app;
CROW_ROUTE(app, "/")([]() { return "Hello world!"; });
app.port(8080).multithreaded().run();
}
+9
View File
@@ -0,0 +1,9 @@
from flask import Flask
app = Flask(__name__)
import logging
logging.getLogger('werkzeug').disabled = True
@app.route('/')
def hello_world():
return 'Hello, World!'
+7
View File
@@ -0,0 +1,7 @@
services:
http:
build: .
ports:
- "8080:80"
volumes:
- ./docker/html:/html
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to cpp-httplib!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to cpp-httplib!</h1>
<p>If you see this page, the cpp-httplib web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
<p><em>Thank you for using cpp-httplib.</em></p>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
//
// main.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License
//
#include <chrono>
#include <ctime>
#include <format>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <httplib.h>
constexpr auto error_html = R"(<html>
<head><title>{} {}</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>cpp-httplib/{}</center>
</body>
</html>
)";
void sigint_handler(int s) { exit(1); }
std::string time_local() {
auto p = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(p);
std::stringstream ss;
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z");
return ss.str();
}
std::string log(auto &req, auto &res) {
auto remote_user = "-"; // TODO:
auto request = std::format("{} {} {}", req.method, req.path, req.version);
auto body_bytes_sent = res.get_header_value("Content-Length");
auto http_referer = "-"; // TODO:
auto http_user_agent = req.get_header_value("User-Agent", "-");
// NOTE: From NGINX defualt access log format
// log_format combined '$remote_addr - $remote_user [$time_local] '
// '"$request" $status $body_bytes_sent '
// '"$http_referer" "$http_user_agent"';
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
remote_user, time_local(), request, res.status,
body_bytes_sent, http_referer, http_user_agent);
}
int main(int argc, const char **argv) {
signal(SIGINT, sigint_handler);
auto base_dir = "./html";
auto host = "0.0.0.0";
auto port = 80;
httplib::Server svr;
svr.set_error_handler([](auto & /*req*/, auto &res) {
auto body =
std::format(error_html, res.status, httplib::status_message(res.status),
CPPHTTPLIB_VERSION);
res.set_content(body, "text/html");
});
svr.set_logger(
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; });
svr.set_mount_point("/", base_dir);
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port)
<< std::endl;
auto ret = svr.listen(host, port);
return ret ? 0 : 1;
}
+247 -130
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.16.3"
#define CPPHTTPLIB_VERSION "0.17.2"
/*
* Configuration
@@ -19,7 +19,7 @@
#endif
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
@@ -90,6 +90,10 @@
#define CPPHTTPLIB_TCP_NODELAY false
#endif
#ifndef CPPHTTPLIB_IPV6_V6ONLY
#define CPPHTTPLIB_IPV6_V6ONLY false
#endif
#ifndef CPPHTTPLIB_RECV_BUFSIZ
#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
#endif
@@ -317,13 +321,49 @@ make_unique(std::size_t n) {
return std::unique_ptr<T>(new RT[n]);
}
struct ci {
inline unsigned char to_lower(int c) {
const static unsigned char table[256] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
255,
};
return table[(unsigned char)(char)c];
}
struct case_ignore_equal {
bool operator()(const std::string &s1, const std::string &s2) const {
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(),
s2.end(),
[](unsigned char c1, unsigned char c2) {
return ::tolower(c1) < ::tolower(c2);
});
return s1.size() == s2.size() &&
std::equal(s1.begin(), s1.end(), s2.begin(), [](char a, char b) {
return to_lower(a) == to_lower(b);
});
}
};
struct case_ignore_hash {
constexpr size_t operator()(const std::string &key) const {
return hash_core(key.data(), key.size(), 0);
}
constexpr size_t hash_core(const char *s, size_t l, size_t h) const {
return (l == 0)
? h
: hash_core(s + 1, l - 1,
(h * 33) ^ static_cast<unsigned char>(to_lower(*s)));
}
};
@@ -432,7 +472,9 @@ enum StatusCode {
NetworkAuthenticationRequired_511 = 511,
};
using Headers = std::multimap<std::string, std::string, detail::ci>;
using Headers =
std::unordered_multimap<std::string, std::string, detail::case_ignore_hash,
detail::case_ignore_equal>;
using Params = std::multimap<std::string, std::string>;
using Match = std::smatch;
@@ -565,8 +607,10 @@ struct Request {
#endif
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
std::string get_header_value(const std::string &key, const char *def = "",
size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
size_t id = 0) const;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
@@ -597,8 +641,10 @@ struct Response {
std::string location; // Redirect location
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
std::string get_header_value(const std::string &key, const char *def = "",
size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
size_t id = 0) const;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
@@ -896,6 +942,7 @@ public:
Server &set_address_family(int family);
Server &set_tcp_nodelay(bool on);
Server &set_ipv6_v6only(bool on);
Server &set_socket_options(SocketOptions socket_options);
Server &set_default_headers(Headers headers);
@@ -928,6 +975,7 @@ public:
bool is_running() const;
void wait_until_ready() const;
void stop();
void decommission();
std::function<TaskQueue *(void)> new_task_queue;
@@ -1002,7 +1050,7 @@ private:
virtual bool process_and_close_socket(socket_t sock);
std::atomic<bool> is_running_{false};
std::atomic<bool> done_{false};
std::atomic<bool> is_decommisioned{false};
struct MountPointEntry {
std::string mount_point;
@@ -1035,6 +1083,7 @@ private:
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
SocketOptions socket_options_ = default_socket_options;
Headers default_headers_;
@@ -1091,9 +1140,10 @@ public:
// Request Headers
bool has_request_header(const std::string &key) const;
std::string get_request_header_value(const std::string &key,
const char *def = "",
size_t id = 0) const;
uint64_t get_request_header_value_u64(const std::string &key,
size_t id = 0) const;
uint64_t def = 0, size_t id = 0) const;
size_t get_request_header_value_count(const std::string &key) const;
private:
@@ -1316,6 +1366,7 @@ public:
void set_address_family(int family);
void set_tcp_nodelay(bool on);
void set_ipv6_v6only(bool on);
void set_socket_options(SocketOptions socket_options);
void set_connection_timeout(time_t sec, time_t usec = 0);
@@ -1453,6 +1504,7 @@ protected:
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
SocketOptions socket_options_ = nullptr;
bool compress_ = false;
@@ -1914,8 +1966,8 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
}
inline uint64_t get_header_value_u64(const Headers &headers,
const std::string &key, size_t id,
uint64_t def) {
const std::string &key, uint64_t def,
size_t id) {
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
@@ -1928,13 +1980,13 @@ inline uint64_t get_header_value_u64(const Headers &headers,
} // namespace detail
inline uint64_t Request::get_header_value_u64(const std::string &key,
size_t id) const {
return detail::get_header_value_u64(headers, key, id, 0);
uint64_t def, size_t id) const {
return detail::get_header_value_u64(headers, key, def, id);
}
inline uint64_t Response::get_header_value_u64(const std::string &key,
size_t id) const {
return detail::get_header_value_u64(headers, key, id, 0);
uint64_t def, size_t id) const {
return detail::get_header_value_u64(headers, key, def, id);
}
template <typename... Args>
@@ -1962,19 +2014,19 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
}
inline void default_socket_options(socket_t sock) {
int yes = 1;
int opt = 1;
#ifdef _WIN32
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char *>(&yes), sizeof(yes));
reinterpret_cast<const char *>(&opt), sizeof(opt));
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<const char *>(&yes), sizeof(yes));
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
#ifdef SO_REUSEPORT
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
reinterpret_cast<const void *>(&yes), sizeof(yes));
reinterpret_cast<const void *>(&opt), sizeof(opt));
#else
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const void *>(&yes), sizeof(yes));
reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
#endif
}
@@ -2119,8 +2171,9 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
}
inline uint64_t Result::get_request_header_value_u64(const std::string &key,
uint64_t def,
size_t id) const {
return detail::get_header_value_u64(request_headers_, key, id, 0);
return detail::get_header_value_u64(request_headers_, key, def, id);
}
template <class Rep, class Period>
@@ -2183,6 +2236,10 @@ make_basic_authentication_header(const std::string &username,
namespace detail {
bool is_file(const std::string &path);
bool is_dir(const std::string &path);
std::string encode_query_param(const std::string &value);
std::string decode_url(const std::string &s, bool convert_plus_to_space);
@@ -2212,15 +2269,18 @@ bool process_client_socket(socket_t sock, time_t read_timeout_sec,
time_t write_timeout_usec,
std::function<bool(Stream &)> callback);
socket_t create_client_socket(
const std::string &host, const std::string &ip, int port,
int address_family, bool tcp_nodelay, SocketOptions socket_options,
time_t connection_timeout_sec, time_t connection_timeout_usec,
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, const std::string &intf, Error &error);
socket_t create_client_socket(const std::string &host, const std::string &ip,
int port, int address_family, bool tcp_nodelay,
bool ipv6_v6only, SocketOptions socket_options,
time_t connection_timeout_sec,
time_t connection_timeout_usec,
time_t read_timeout_sec, time_t read_timeout_usec,
time_t write_timeout_sec,
time_t write_timeout_usec,
const std::string &intf, Error &error);
const char *get_header_value(const Headers &headers, const std::string &key,
size_t id = 0, const char *def = nullptr);
const char *def, size_t id);
std::string params_to_query_str(const Params &params);
@@ -2507,20 +2567,6 @@ inline std::string base64_encode(const std::string &in) {
return out;
}
inline bool is_file(const std::string &path) {
#ifdef _WIN32
return _access_s(path.c_str(), 0) == 0;
#else
struct stat st;
return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
#endif
}
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 is_valid_path(const std::string &path) {
size_t level = 0;
size_t i = 0;
@@ -2563,6 +2609,16 @@ 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 bool is_dir(const std::string &path) {
struct stat st;
return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
}
inline std::string encode_query_param(const std::string &value) {
std::ostringstream escaped;
escaped.fill('0');
@@ -2772,6 +2828,10 @@ inline bool stream_line_reader::getline() {
fixed_buffer_used_size_ = 0;
glowable_buffer_.clear();
#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
char prev_byte = 0;
#endif
for (size_t i = 0;; i++) {
char byte;
auto n = strm_.read(&byte, 1);
@@ -2788,7 +2848,12 @@ inline bool stream_line_reader::getline() {
append(byte);
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
if (byte == '\n') { break; }
#else
if (prev_byte == '\r' && byte == '\n') { break; }
prev_byte = byte;
#endif
}
return true;
@@ -2829,9 +2894,7 @@ inline bool mmap::open(const char *path) {
wpath += path[i];
}
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
WINAPI_PARTITION_GAMES) && \
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
OPEN_EXISTING, NULL);
#else
@@ -2841,26 +2904,23 @@ inline bool mmap::open(const char *path) {
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
WINAPI_PARTITION_GAMES)
LARGE_INTEGER size{};
if (!::GetFileSizeEx(hFile_, &size)) { return false; }
// If the following line doesn't compile due to QuadPart, update Windows SDK.
// See:
// https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
if (static_cast<ULONGLONG>(size.QuadPart) >
(std::numeric_limits<decltype(size_)>::max)()) {
// `size_t` might be 32-bits, on 32-bits Windows.
return false;
}
size_ = static_cast<size_t>(size.QuadPart);
#else
DWORD sizeHigh;
DWORD sizeLow;
sizeLow = ::GetFileSize(hFile_, &sizeHigh);
if (sizeLow == INVALID_FILE_SIZE) { return false; }
size_ = (static_cast<size_t>(sizeHigh) << (sizeof(DWORD) * 8)) | sizeLow;
#endif
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
hMapping_ =
::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
#else
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, size.HighPart,
size.LowPart, NULL);
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
#endif
if (hMapping_ == NULL) {
@@ -2868,8 +2928,7 @@ inline bool mmap::open(const char *path) {
return false;
}
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
#else
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
@@ -2945,7 +3004,10 @@ template <typename T> inline ssize_t handle_EINTR(T fn) {
ssize_t res = 0;
while (true) {
res = fn();
if (res < 0 && errno == EINTR) { continue; }
if (res < 0 && errno == EINTR) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
continue;
}
break;
}
return res;
@@ -3226,10 +3288,29 @@ inline int shutdown_socket(socket_t sock) {
#endif
}
inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
if (s.size() > 1 && s[0] == '\0') {
auto ret = s;
ret[0] = '@';
return ret;
}
return s;
}
inline std::string
unescape_abstract_namespace_unix_domain(const std::string &s) {
if (s.size() > 1 && s[0] == '@') {
auto ret = s;
ret[0] = '\0';
return ret;
}
return s;
}
template <typename BindOrConnect>
socket_t create_socket(const std::string &host, const std::string &ip, int port,
int address_family, int socket_flags, bool tcp_nodelay,
SocketOptions socket_options,
bool ipv6_v6only, SocketOptions socket_options,
BindOrConnect bind_or_connect) {
// Get address info
const char *node = nullptr;
@@ -3238,7 +3319,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_protocol = IPPROTO_IP;
if (!ip.empty()) {
node = ip.c_str();
@@ -3266,7 +3347,9 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
if (sock != INVALID_SOCKET) {
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
std::copy(host.begin(), host.end(), addr.sun_path);
auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
hints.ai_addrlen = static_cast<socklen_t>(
@@ -3340,29 +3423,29 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
#endif
if (tcp_nodelay) {
auto yes = 1;
auto opt = 1;
#ifdef _WIN32
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char *>(&yes), sizeof(yes));
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const void *>(&yes), sizeof(yes));
reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
}
if (rp->ai_family == AF_INET6) {
auto opt = ipv6_v6only ? 1 : 0;
#ifdef _WIN32
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
}
if (socket_options) { socket_options(sock); }
if (rp->ai_family == AF_INET6) {
auto no = 0;
#ifdef _WIN32
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char *>(&no), sizeof(no));
#else
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const void *>(&no), sizeof(no));
#endif
}
// bind or connect
auto quit = false;
if (bind_or_connect(sock, *rp, quit)) {
@@ -3467,12 +3550,14 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
inline socket_t create_client_socket(
const std::string &host, const std::string &ip, int port,
int address_family, bool tcp_nodelay, SocketOptions socket_options,
time_t connection_timeout_sec, time_t connection_timeout_usec,
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
int address_family, bool tcp_nodelay, bool ipv6_v6only,
SocketOptions socket_options, time_t connection_timeout_sec,
time_t connection_timeout_usec, time_t read_timeout_sec,
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, const std::string &intf, Error &error) {
auto sock = create_socket(
host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
std::move(socket_options),
[&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
if (!intf.empty()) {
#ifdef USE_IF2IP
@@ -3948,8 +4033,8 @@ inline bool has_header(const Headers &headers, const std::string &key) {
}
inline const char *get_header_value(const Headers &headers,
const std::string &key, size_t id,
const char *def) {
const std::string &key, const char *def,
size_t id) {
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
@@ -3960,7 +4045,7 @@ inline const char *get_header_value(const Headers &headers,
inline bool compare_case_ignore(const std::string &a, const std::string &b) {
if (a.size() != b.size()) { return false; }
for (size_t i = 0; i < b.size(); i++) {
if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
if (to_lower(a[i]) != to_lower(b[i])) { return false; }
}
return true;
}
@@ -3995,6 +4080,18 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
auto val = compare_case_ignore(key, "Location")
? std::string(p, end)
: decode_url(std::string(p, end), false);
// NOTE: From RFC 9110:
// Field values containing CR, LF, or NUL characters are
// invalid and dangerous, due to the varying ways that
// implementations might parse and interpret those
// characters; a recipient of CR, LF, or NUL within a field
// value MUST either reject the message or replace each of
// those characters with SP before further processing or
// forwarding of that message.
static const std::string CR_LF_NUL("\r\n\0", 3);
if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; }
fn(key, val);
return true;
}
@@ -4032,10 +4129,12 @@ inline bool read_headers(Stream &strm, Headers &headers) {
// Exclude line terminator
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
parse_header(line_reader.ptr(), end,
[&](const std::string &key, const std::string &val) {
headers.emplace(key, val);
});
if (!parse_header(line_reader.ptr(), end,
[&](const std::string &key, std::string &val) {
headers.emplace(key, val);
})) {
return false;
}
}
return true;
@@ -4146,7 +4245,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
inline bool is_chunked_transfer_encoding(const Headers &headers) {
return compare_case_ignore(
get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
}
template <typename T, typename U>
@@ -4761,7 +4860,7 @@ private:
const std::string &b) const {
if (a.size() < b.size()) { return false; }
for (size_t i = 0; i < b.size(); i++) {
if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
if (to_lower(a[i]) != to_lower(b[i])) { return false; }
}
return true;
}
@@ -4844,16 +4943,6 @@ private:
size_t buf_epos_ = 0;
};
inline std::string to_lower(const char *beg, const char *end) {
std::string out;
auto it = beg;
while (it != end) {
out += static_cast<char>(::tolower(*it));
it++;
}
return out;
}
inline std::string random_string(size_t length) {
static const char data[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -5489,8 +5578,8 @@ inline bool Request::has_header(const std::string &key) const {
}
inline std::string Request::get_header_value(const std::string &key,
size_t id) const {
return detail::get_header_value(headers, key, id, "");
const char *def, size_t id) const {
return detail::get_header_value(headers, key, def, id);
}
inline size_t Request::get_header_value_count(const std::string &key) const {
@@ -5554,8 +5643,9 @@ inline bool Response::has_header(const std::string &key) const {
}
inline std::string Response::get_header_value(const std::string &key,
const char *def,
size_t id) const {
return detail::get_header_value(headers, key, id, "");
return detail::get_header_value(headers, key, def, id);
}
inline size_t Response::get_header_value_count(const std::string &key) const {
@@ -5640,8 +5730,9 @@ inline bool Result::has_request_header(const std::string &key) const {
}
inline std::string Result::get_request_header_value(const std::string &key,
const char *def,
size_t id) const {
return detail::get_header_value(request_headers_, key, id, "");
return detail::get_header_value(request_headers_, key, def, id);
}
inline size_t
@@ -6049,6 +6140,11 @@ inline Server &Server::set_tcp_nodelay(bool on) {
return *this;
}
inline Server &Server::set_ipv6_v6only(bool on) {
ipv6_v6only_ = on;
return *this;
}
inline Server &Server::set_socket_options(SocketOptions socket_options) {
socket_options_ = std::move(socket_options);
return *this;
@@ -6100,27 +6196,27 @@ inline Server &Server::set_payload_max_length(size_t length) {
inline bool Server::bind_to_port(const std::string &host, int port,
int socket_flags) {
return bind_internal(host, port, socket_flags) >= 0;
auto ret = bind_internal(host, port, socket_flags);
if (ret == -1) { is_decommisioned = true; }
return ret >= 0;
}
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
return bind_internal(host, 0, socket_flags);
auto ret = bind_internal(host, 0, socket_flags);
if (ret == -1) { is_decommisioned = true; }
return ret;
}
inline bool Server::listen_after_bind() {
auto se = detail::scope_exit([&]() { done_ = true; });
return listen_internal();
}
inline bool Server::listen_after_bind() { return listen_internal(); }
inline bool Server::listen(const std::string &host, int port,
int socket_flags) {
auto se = detail::scope_exit([&]() { done_ = true; });
return bind_to_port(host, port, socket_flags) && listen_internal();
}
inline bool Server::is_running() const { return is_running_; }
inline void Server::wait_until_ready() const {
while (!is_running() && !done_) {
while (!is_running_ && !is_decommisioned) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
}
@@ -6132,8 +6228,11 @@ inline void Server::stop() {
detail::shutdown_socket(sock);
detail::close_socket(sock);
}
is_decommisioned = false;
}
inline void Server::decommission() { is_decommisioned = true; }
inline bool Server::parse_request_line(const char *s, Request &req) const {
auto len = strlen(s);
if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
@@ -6476,7 +6575,7 @@ Server::create_server_socket(const std::string &host, int port,
SocketOptions socket_options) const {
return detail::create_socket(
host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
std::move(socket_options),
ipv6_v6only_, std::move(socket_options),
[](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
return false;
@@ -6488,6 +6587,8 @@ Server::create_server_socket(const std::string &host, int port,
inline int Server::bind_internal(const std::string &host, int port,
int socket_flags) {
if (is_decommisioned) { return -1; }
if (!is_valid()) { return -1; }
svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
@@ -6513,6 +6614,8 @@ inline int Server::bind_internal(const std::string &host, int port,
}
inline bool Server::listen_internal() {
if (is_decommisioned) { return false; }
auto ret = true;
is_running_ = true;
auto se = detail::scope_exit([&]() { is_running_ = false; });
@@ -6602,6 +6705,7 @@ inline bool Server::listen_internal() {
task_queue->shutdown();
}
is_decommisioned = !ret;
return ret;
}
@@ -6900,7 +7004,9 @@ Server::process_request(Stream &strm, bool close_connection,
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
status_message(status));
break;
default: return write_response(strm, close_connection, req, res);
default:
connection_closed = true;
return write_response(strm, true, req, res);
}
}
@@ -6989,8 +7095,8 @@ inline ClientImpl::ClientImpl(const std::string &host, int port)
inline ClientImpl::ClientImpl(const std::string &host, int port,
const std::string &client_cert_path,
const std::string &client_key_path)
: host_(host), port_(port),
host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)),
: host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)),
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
inline ClientImpl::~ClientImpl() {
@@ -7021,6 +7127,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
url_encode_ = rhs.url_encode_;
address_family_ = rhs.address_family_;
tcp_nodelay_ = rhs.tcp_nodelay_;
ipv6_v6only_ = rhs.ipv6_v6only_;
socket_options_ = rhs.socket_options_;
compress_ = rhs.compress_;
decompress_ = rhs.decompress_;
@@ -7049,9 +7156,9 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
if (!proxy_host_.empty() && proxy_port_ != -1) {
return detail::create_client_socket(
proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_, interface_, error);
ipv6_v6only_, socket_options_, connection_timeout_sec_,
connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, interface_, error);
}
// Check is custom IP specified for host_
@@ -7060,10 +7167,10 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
if (it != addr_map_.end()) { ip = it->second; }
return detail::create_client_socket(
host_, ip, port_, address_family_, tcp_nodelay_, socket_options_,
connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
error);
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_, interface_, error);
}
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
@@ -7700,6 +7807,14 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
return ret;
};
if (res.has_header("Content-Length")) {
if (!req.content_receiver) {
auto len = std::min<size_t>(res.get_header_value_u64("Content-Length"),
res.body.max_size());
if (len > 0) { res.body.reserve(len); }
}
}
int dummy_status;
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
dummy_status, std::move(progress), std::move(out),
@@ -8459,6 +8574,8 @@ inline void ClientImpl::set_address_family(int family) {
inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
socket_options_ = std::move(socket_options);
}
+3 -1
View File
@@ -24,9 +24,11 @@ else()
FetchContent_MakeAvailable(gtest)
endif()
find_package(curl REQUIRED)
add_executable(httplib-test test.cc)
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
gtest_discover_tests(httplib-test)
file(
+1 -1
View File
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread -lcurl
# By default, use standalone_fuzz_target_runner.
# This runner does no fuzzing, but simply executes the inputs
+3 -1
View File
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT
gtest_dep = dependency('gtest', main: true)
libcurl_dep = dependency('libcurl')
openssl = find_program('openssl')
test_conf = files('test.conf')
@@ -119,7 +120,8 @@ test(
'test.cc',
dependencies: [
cpp_httplib_dep,
gtest_dep
gtest_dep,
libcurl_dep
],
override_options: test_options
),
+198 -5
View File
@@ -1,6 +1,9 @@
#include <httplib.h>
#include <signal.h>
#ifndef _WIN32
#include <curl/curl.h>
#endif
#include <gtest/gtest.h>
#include <atomic>
@@ -12,6 +15,7 @@
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <vector>
#define SERVER_CERT_FILE "./cert.pem"
#define SERVER_CERT2_FILE "./cert2.pem"
@@ -467,25 +471,25 @@ TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
TEST(GetHeaderValueTest, DefaultValue) {
Headers headers = {{"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
EXPECT_STREQ("text/plain", val);
}
TEST(GetHeaderValueTest, DefaultValueInt) {
Headers headers = {{"Dummy", "Dummy"}};
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
auto val = detail::get_header_value_u64(headers, "Content-Length", 100, 0);
EXPECT_EQ(100ull, val);
}
TEST(GetHeaderValueTest, RegularValue) {
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "content-type", 0, "text/plain");
auto val = detail::get_header_value(headers, "content-type", "text/plain", 0);
EXPECT_STREQ("text/html", val);
}
@@ -4652,7 +4656,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
auto error = Error::Success;
auto client_sock = detail::create_client_socket(
HOST, "", PORT, AF_UNSPEC, false, nullptr,
HOST, "", PORT, AF_UNSPEC, false, false, nullptr,
/*connection_timeout_sec=*/5, 0,
/*read_timeout_sec=*/5, 0,
/*write_timeout_sec=*/5, 0, std::string(), error);
@@ -4718,6 +4722,9 @@ 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");
});
// 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
@@ -4851,6 +4858,14 @@ TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
std::string out;
std::string request(
"GET /header_field_value_check HTTP/1.1\r\nTest: [\r\x00\n]\r\n\r\n", 55);
test_raw_request(request, &out);
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
Server svr;
@@ -4926,6 +4941,52 @@ TEST(ServerStopTest, ListenFailure) {
t.join();
}
TEST(ServerStopTest, Decommision) {
Server svr;
svr.Get("/hi", [&](const Request &, Response &res) { res.body = "hi..."; });
for (int i = 0; i < 4; i++) {
auto is_even = !(i % 2);
std::thread t{[&] {
try {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (is_even) {
throw std::runtime_error("Some thing that happens to go wrong.");
}
svr.listen(HOST, PORT);
} catch (...) { svr.decommission(); }
}};
svr.wait_until_ready();
// Server is up
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
if (is_even) {
EXPECT_FALSE(res);
} else {
EXPECT_TRUE(res);
EXPECT_EQ("hi...", res->body);
}
}
svr.stop();
t.join();
// Server is down...
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
EXPECT_FALSE(res);
}
}
}
TEST(StreamingTest, NoContentLengthStreaming) {
Server svr;
@@ -7515,3 +7576,135 @@ TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
CLIENT_PRIVATE_KEY_FILE);
EXPECT_EQ(cli.port(), port);
}
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));
EXPECT_FALSE(detail::is_file(dir_path));
EXPECT_TRUE(detail::is_dir(dir_path));
}
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
Server svr;
svr.Get("/test", [&](const Request &/*req*/, Response &res) {
EXPECT_EQ(res.status, 400);
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
}
#ifndef _WIN32
TEST(Expect100ContinueTest, ServerClosesConnection) {
static constexpr char reject[] = "Unauthorized";
static constexpr char accept[] = "Upload accepted";
constexpr size_t total_size = 10 * 1024 * 1024 * 1024ULL;
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.Post("/", [&](const Request & /*req*/, Response &res) {
res.set_content(accept, "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
{
const auto curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>{
curl_easy_init(), &curl_easy_cleanup};
ASSERT_NE(curl, nullptr);
curl_easy_setopt(curl.get(), CURLOPT_URL, HOST);
curl_easy_setopt(curl.get(), CURLOPT_PORT, PORT);
curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
auto list = std::unique_ptr<curl_slist, decltype(&curl_slist_free_all)>{
curl_slist_append(nullptr, "Content-Type: application/octet-stream"),
&curl_slist_free_all};
ASSERT_NE(list, nullptr);
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, list.get());
struct read_data {
size_t read_size;
size_t total_size;
} data = {0, total_size};
using read_callback_t =
size_t (*)(char *ptr, size_t size, size_t nmemb, void *userdata);
read_callback_t read_callback = [](char *ptr, size_t size, size_t nmemb,
void *userdata) -> size_t {
read_data *data = (read_data *)userdata;
if (!userdata || data->read_size >= data->total_size) { return 0; }
std::fill_n(ptr, size * nmemb, 'A');
data->read_size += size * nmemb;
return size * nmemb;
};
curl_easy_setopt(curl.get(), CURLOPT_READDATA, data);
curl_easy_setopt(curl.get(), CURLOPT_READFUNCTION, read_callback);
std::vector<char> buffer;
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &buffer);
using write_callback_t =
size_t (*)(char *ptr, size_t size, size_t nmemb, void *userdata);
write_callback_t write_callback = [](char *ptr, size_t size, size_t nmemb,
void *userdata) -> size_t {
std::vector<char> *buffer = (std::vector<char> *)userdata;
buffer->reserve(buffer->size() + size * nmemb + 1);
buffer->insert(buffer->end(), (char *)ptr, (char *)ptr + size * nmemb);
return size * nmemb;
};
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
{
const auto res = curl_easy_perform(curl.get());
ASSERT_EQ(res, CURLE_OK);
}
{
auto response_code = long{};
const auto res =
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &response_code);
ASSERT_EQ(res, CURLE_OK);
ASSERT_EQ(response_code, StatusCode::Unauthorized_401);
}
{
auto dl = curl_off_t{};
const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
ASSERT_EQ(res, CURLE_OK);
ASSERT_EQ(dl, sizeof reject - 1);
}
{
buffer.push_back('\0');
ASSERT_STRCASEEQ(buffer.data(), reject);
}
}
}
#endif