Compare commits

...

22 Commits

Author SHA1 Message Date
yhirose 3a1f379e75 Release v0.21.0 2025-06-09 22:19:30 -04:00
Marek Kwasecki fd8da4d8e4 Feature/multipart headers (#2152)
* Adds headers to multipart form data

Adds a `headers` field to the `MultipartFormData` struct.

Populates this field by parsing headers from the multipart form data.
This allows access to specific headers associated with each form data part.

* Adds multipart header access test

Verifies the correct retrieval of headers from multipart form data file parts.

Ensures that custom and content-related headers are accessible and parsed as expected.

* Enables automatic test discovery with GoogleTest

Uses `gtest_discover_tests` to automatically find and run tests, simplifying test maintenance and improving discoverability.

* Removes explicit GoogleTest include

* Refactors header parsing logic

Improves header parsing by using a dedicated parsing function,
resulting in cleaner and more robust code.

This change also adds error handling during header parsing,
returning an error and marking the request as invalid
if parsing fails.

* clang-format corrected

* Renames variable for better readability.

Renames the `customHeader` variable to `custom_header` for improved code readability and consistency.

* typo
2025-06-09 15:59:25 -04:00
yhirose 365cbe37fa Fix #2101 2025-05-25 21:56:28 -04:00
Piotr 4a7aae5469 Detect if afunix.h exists (#2145)
Signed-off-by: Piotr Stankiewicz <piotr.stankiewicz@docker.com>
2025-05-23 23:14:48 -04:00
yhirose fd324e1412 Add KeepAliveTest.MaxCount 2025-05-20 21:41:50 +09:00
QWERTIOX 366eda72dc Specify version in meson.build (#2139)
I prefer to specify version when I'm calling dependency() and lack of makes me unable to do it
2025-05-16 10:02:32 -04:00
yhirose c216dc94d2 Code cleanup 2025-05-09 18:45:31 +09:00
yhirose 61893a00a4 Fix #2135 2025-05-03 22:50:47 +09:00
yhirose 3af7f2c161 Release v0.20.1 2025-05-03 21:24:22 +09:00
yhirose a0de42ebc4 clang-format 2025-05-03 17:40:34 +09:00
Ville Vesilehto 7b752106ac Merge commit from fork
* fix(parser): Limit line length in getline

Prevents potential infinite loop and memory exhaustion in
stream_line_reader::getline by enforcing max line length.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

* fix: increase default max line length to 32k

LONG_QUERY_VALUE test is set at 25k.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

* test(client): expect read error with too long query

Adds a test case (`TooLongQueryValue`) to verify client behavior
when the request URI is excessively long, exceeding
`CPPHTTPLIB_MAX_LINE_LENGTH`. In this scenario, the server is
expected to reset the connection.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

---------

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-05-03 04:39:01 -04:00
yhirose 9589519d58 Fix #2130 2025-04-17 11:52:22 -04:00
Alexey Sokolov caf7c55785 Fix regression of #2121 (#2126) 2025-04-08 16:08:41 -04:00
yhirose 9e4aed482e Fix style error 2025-04-06 09:02:25 -04:00
yhirose 65d6316d65 Fix #2113 2025-04-05 22:40:08 -04:00
yhirose 3e3a8cc02f Made the max timeout threshold for SSL longer. 2025-04-05 22:38:50 -04:00
KTGH b7e33b08f1 Add missing component comment (#2124)
Fix #2123
2025-03-31 20:34:28 -04:00
Alexey Sokolov 0dbe8ba144 Support zstd also via pkg-config (#2121)
* Support zstd also via pkg-config

It doesn't always provide cmake config

* Find zstd with pkg-config also in non-required case

Code by @sum01, slightly modified
2025-03-29 11:46:22 -04:00
Piotr dbc4af819a Fix compilation error on windows (#2118)
afunix.h uses types declared in winsock2.h, and so has to be included
after it. Including afunix.h first will result in a somewhat unhelpful
compilation error:

error C3646: 'sun_family': unknown override specifier

Signed-off-by: Piotr Stankiewicz <piotr.stankiewicz@docker.com>
2025-03-25 08:36:20 -04:00
yhirose 7dbf5471ce Fix the style error and comment 2025-03-24 19:16:48 -04:00
Piotr 72b35befb2 Add AF_UNIX support on windows (#2115)
Signed-off-by: Piotr Stankiewicz <piotr.stankiewicz@docker.com>
2025-03-24 19:14:24 -04:00
Jean-Francois Simoneau 65ce51aed7 Fix start_time shadow variable (#2114) 2025-03-18 19:17:47 -04:00
6 changed files with 316 additions and 31 deletions
+18 -2
View File
@@ -18,7 +18,7 @@
-------------------------------------------------------------------------------
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli) is available.
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli zstd) is available.
This creates a httplib::httplib target (if found and if listed components are supported).
It can be linked like so:
@@ -159,10 +159,26 @@ elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
endif()
if(HTTPLIB_REQUIRE_ZSTD)
find_package(zstd REQUIRED)
find_package(zstd)
if(NOT zstd_FOUND)
find_package(PkgConfig REQUIRED)
pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd)
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
endif()
set(HTTPLIB_IS_USING_ZSTD TRUE)
elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
find_package(zstd QUIET)
if(NOT zstd_FOUND)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd)
if(TARGET PkgConfig::zstd)
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
endif()
endif()
endif()
# Both find_package and PkgConf set a XXX_FOUND var
set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND})
endif()
+16
View File
@@ -285,6 +285,22 @@ svr.set_post_routing_handler([](const auto& req, auto& res) {
});
```
### Pre request handler
```cpp
svr.set_pre_request_handler([](const auto& req, auto& res) {
if (req.matched_route == "/user/:user") {
auto user = req.path_params.at("user");
if (user != "john") {
res.status = StatusCode::Forbidden_403;
res.set_content("error", "text/html");
return Server::HandlerResponse::Handled;
}
}
return Server::HandlerResponse::Unhandled;
});
```
### 'multipart/form-data' POST data
```cpp
+19 -1
View File
@@ -39,7 +39,25 @@ if(@HTTPLIB_IS_USING_BROTLI@)
endif()
if(@HTTPLIB_IS_USING_ZSTD@)
find_dependency(zstd)
set(httplib_fd_zstd_quiet_arg)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
set(httplib_fd_zstd_quiet_arg QUIET)
endif()
set(httplib_fd_zstd_required_arg)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
set(httplib_fd_zstd_required_arg REQUIRED)
endif()
find_package(zstd QUIET)
if(NOT zstd_FOUND)
find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg})
if(PKG_CONFIG_FOUND)
pkg_check_modules(zstd ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg} IMPORTED_TARGET libzstd)
if(TARGET PkgConfig::zstd)
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
endif()
endif()
endif()
set(httplib_zstd_FOUND ${zstd_FOUND})
endif()
+88 -23
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.20.0"
#define CPPHTTPLIB_VERSION "0.21.0"
/*
* Configuration
@@ -145,6 +145,10 @@
#define CPPHTTPLIB_LISTEN_BACKLOG 5
#endif
#ifndef CPPHTTPLIB_MAX_LINE_LENGTH
#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
#endif
/*
* Headers
*/
@@ -188,6 +192,14 @@ using ssize_t = long;
#include <winsock2.h>
#include <ws2tcpip.h>
#if defined(__has_include)
#if __has_include(<afunix.h>)
// afunix.h uses types declared in winsock2.h, so has to be included after it.
#include <afunix.h>
#define CPPHTTPLIB_HAVE_AFUNIX_H 1
#endif
#endif
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
#endif
@@ -532,6 +544,7 @@ struct MultipartFormData {
std::string content;
std::string filename;
std::string content_type;
Headers headers;
};
using MultipartFormDataItems = std::vector<MultipartFormData>;
using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
@@ -624,6 +637,7 @@ using Ranges = std::vector<Range>;
struct Request {
std::string method;
std::string path;
std::string matched_route;
Params params;
Headers headers;
std::string body;
@@ -875,10 +889,16 @@ namespace detail {
class MatcherBase {
public:
MatcherBase(std::string pattern) : pattern_(pattern) {}
virtual ~MatcherBase() = default;
const std::string &pattern() const { return pattern_; }
// Match request path and populate its matches and
virtual bool match(Request &request) const = 0;
private:
std::string pattern_;
};
/**
@@ -930,7 +950,8 @@ private:
*/
class RegexMatcher final : public MatcherBase {
public:
RegexMatcher(const std::string &pattern) : regex_(pattern) {}
RegexMatcher(const std::string &pattern)
: MatcherBase(pattern), regex_(pattern) {}
bool match(Request &request) const override;
@@ -997,9 +1018,12 @@ public:
}
Server &set_exception_handler(ExceptionHandler handler);
Server &set_pre_routing_handler(HandlerWithResponse handler);
Server &set_post_routing_handler(Handler handler);
Server &set_pre_request_handler(HandlerWithResponse handler);
Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
Server &set_logger(Logger logger);
@@ -1080,8 +1104,7 @@ private:
bool listen_internal();
bool routing(Request &req, Response &res, Stream &strm);
bool handle_file_request(const Request &req, Response &res,
bool head = false);
bool handle_file_request(const Request &req, Response &res);
bool dispatch_request(Request &req, Response &res,
const Handlers &handlers) const;
bool dispatch_request_for_content_reader(
@@ -1142,6 +1165,7 @@ private:
ExceptionHandler exception_handler_;
HandlerWithResponse pre_routing_handler_;
Handler post_routing_handler_;
HandlerWithResponse pre_request_handler_;
Expect100ContinueHandler expect_100_continue_handler_;
Logger logger_;
@@ -2059,7 +2083,9 @@ template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
}
inline bool is_numeric(const std::string &str) {
return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);
return !str.empty() &&
std::all_of(str.cbegin(), str.cend(),
[](unsigned char c) { return std::isdigit(c); });
}
inline uint64_t get_header_value_u64(const Headers &headers,
@@ -3064,6 +3090,11 @@ inline bool stream_line_reader::getline() {
#endif
for (size_t i = 0;; i++) {
if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) {
// Treat exceptionally long lines as an error to
// prevent infinite loops/memory exhaustion
return false;
}
char byte;
auto n = strm_.read(&byte, 1);
@@ -3365,7 +3396,7 @@ private:
time_t write_timeout_sec_;
time_t write_timeout_usec_;
time_t max_timeout_msec_;
const std::chrono::time_point<std::chrono::steady_clock> start_time;
const std::chrono::time_point<std::chrono::steady_clock> start_time_;
std::vector<char> read_buff_;
size_t read_buff_off_ = 0;
@@ -3403,7 +3434,7 @@ private:
time_t write_timeout_sec_;
time_t write_timeout_usec_;
time_t max_timeout_msec_;
const std::chrono::time_point<std::chrono::steady_clock> start_time;
const std::chrono::time_point<std::chrono::steady_clock> start_time_;
};
#endif
@@ -3538,7 +3569,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
hints.ai_flags = socket_flags;
}
#ifndef _WIN32
#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
if (hints.ai_family == AF_UNIX) {
const auto addrlen = host.length();
if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
@@ -3562,11 +3593,19 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
sizeof(addr) - sizeof(addr.sun_path) + addrlen);
#ifndef SOCK_CLOEXEC
#ifndef _WIN32
fcntl(sock, F_SETFD, FD_CLOEXEC);
#endif
#endif
if (socket_options) { socket_options(sock); }
#ifdef _WIN32
// Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
// remove the option.
detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
#endif
bool dummy;
if (!bind_or_connect(sock, hints, dummy)) {
close_socket(sock);
@@ -5007,6 +5046,16 @@ public:
return false;
}
// parse and emplace space trimmed headers into a map
if (!parse_header(
header.data(), header.data() + header.size(),
[&](const std::string &key, const std::string &val) {
file_.headers.emplace(key, val);
})) {
is_valid_ = false;
return false;
}
constexpr const char header_content_type[] = "Content-Type:";
if (start_with_case_ignore(header, header_content_type)) {
@@ -5106,6 +5155,7 @@ private:
file_.name.clear();
file_.filename.clear();
file_.content_type.clear();
file_.headers.clear();
}
bool start_with_case_ignore(const std::string &a, const char *b) const {
@@ -6046,6 +6096,8 @@ inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
auto actual_timeout_msec =
(std::min)(max_timeout_msec - duration_msec, timeout_msec);
if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
actual_timeout_sec = actual_timeout_msec / 1000;
actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
}
@@ -6060,7 +6112,7 @@ inline SocketStream::SocketStream(
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
write_timeout_usec_(write_timeout_usec),
max_timeout_msec_(max_timeout_msec), start_time(start_time),
max_timeout_msec_(max_timeout_msec), start_time_(start_time),
read_buff_(read_buff_size_, 0) {}
inline SocketStream::~SocketStream() = default;
@@ -6158,7 +6210,7 @@ inline socket_t SocketStream::socket() const { return sock_; }
inline time_t SocketStream::duration() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start_time)
std::chrono::steady_clock::now() - start_time_)
.count();
}
@@ -6196,7 +6248,8 @@ inline time_t BufferStream::duration() const { return 0; }
inline const std::string &BufferStream::get_buffer() const { return buffer; }
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
: MatcherBase(pattern) {
constexpr const char marker[] = "/:";
// One past the last ending position of a path param substring
@@ -6447,6 +6500,11 @@ inline Server &Server::set_post_routing_handler(Handler handler) {
return *this;
}
inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) {
pre_request_handler_ = std::move(handler);
return *this;
}
inline Server &Server::set_logger(Logger logger) {
logger_ = std::move(logger);
return *this;
@@ -6858,8 +6916,7 @@ Server::read_content_core(Stream &strm, Request &req, Response &res,
return true;
}
inline bool Server::handle_file_request(const Request &req, Response &res,
bool head) {
inline bool Server::handle_file_request(const Request &req, Response &res) {
for (const auto &entry : base_dirs_) {
// Prefix match
if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
@@ -6892,7 +6949,7 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
return true;
});
if (!head && file_request_handler_) {
if (req.method != "HEAD" && file_request_handler_) {
file_request_handler_(req, res);
}
@@ -7026,9 +7083,8 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
}
// File handler
auto is_head_request = req.method == "HEAD";
if ((req.method == "GET" || is_head_request) &&
handle_file_request(req, res, is_head_request)) {
if ((req.method == "GET" || req.method == "HEAD") &&
handle_file_request(req, res)) {
return true;
}
@@ -7103,7 +7159,11 @@ inline bool Server::dispatch_request(Request &req, Response &res,
const auto &handler = x.second;
if (matcher->match(req)) {
handler(req, res);
req.matched_route = matcher->pattern();
if (!pre_request_handler_ ||
pre_request_handler_(req, res) != HandlerResponse::Handled) {
handler(req, res);
}
return true;
}
}
@@ -7230,7 +7290,11 @@ inline bool Server::dispatch_request_for_content_reader(
const auto &handler = x.second;
if (matcher->match(req)) {
handler(req, res, content_reader);
req.matched_route = matcher->pattern();
if (!pre_request_handler_ ||
pre_request_handler_(req, res) != HandlerResponse::Handled) {
handler(req, res, content_reader);
}
return true;
}
}
@@ -7318,8 +7382,9 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
}
// Setup `is_connection_closed` method
req.is_connection_closed = [&]() {
return !detail::is_socket_alive(strm.socket());
auto sock = strm.socket();
req.is_connection_closed = [sock]() {
return !detail::is_socket_alive(sock);
};
// Routing
@@ -9200,7 +9265,7 @@ inline SSLSocketStream::SSLSocketStream(
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
write_timeout_usec_(write_timeout_usec),
max_timeout_msec_(max_timeout_msec), start_time(start_time) {
max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
}
@@ -9306,7 +9371,7 @@ inline socket_t SSLSocketStream::socket() const { return sock_; }
inline time_t SSLSocketStream::duration() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start_time)
std::chrono::steady_clock::now() - start_time_)
.count();
}
+2 -2
View File
@@ -87,7 +87,7 @@ if get_option('cpp-httplib_compile')
soversion: version.split('.')[0] + '.' + version.split('.')[1],
install: true
)
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1], version: version)
import('pkgconfig').generate(
lib,
@@ -98,7 +98,7 @@ if get_option('cpp-httplib_compile')
)
else
install_headers('httplib.h')
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.')
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.', version: version)
import('pkgconfig').generate(
name: 'cpp-httplib',
+173 -3
View File
@@ -43,6 +43,10 @@ const int PORT = 1234;
const string LONG_QUERY_VALUE = string(25000, '@');
const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE;
const string TOO_LONG_QUERY_VALUE = string(35000, '@');
const string TOO_LONG_QUERY_URL =
"/too-long-query-value?key=" + TOO_LONG_QUERY_VALUE;
const std::string JSON_DATA = "{\"hello\":\"world\"}";
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
@@ -70,7 +74,6 @@ static void read_file(const std::string &path, std::string &out) {
fs.read(&out[0], static_cast<std::streamsize>(size));
}
#ifndef _WIN32
class UnixSocketTest : public ::testing::Test {
protected:
void TearDown() override { std::remove(pathname_.c_str()); }
@@ -167,6 +170,7 @@ TEST_F(UnixSocketTest, abstract) {
}
#endif
#ifndef _WIN32
TEST(SocketStream, wait_writable_UNIX) {
int fds[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
@@ -2259,7 +2263,7 @@ TEST(NoContentTest, ContentLength) {
}
}
TEST(RoutingHandlerTest, PreRoutingHandler) {
TEST(RoutingHandlerTest, PreAndPostRoutingHandlers) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
@@ -2350,6 +2354,63 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
}
}
TEST(RequestHandlerTest, PreRequestHandler) {
auto route_path = "/user/:user";
Server svr;
svr.Get("/hi", [](const Request &, Response &res) {
res.set_content("hi", "text/plain");
});
svr.Get(route_path, [](const Request &req, Response &res) {
res.set_content(req.path_params.at("user"), "text/plain");
});
svr.set_pre_request_handler([&](const Request &req, Response &res) {
if (req.matched_route == route_path) {
auto user = req.path_params.at("user");
if (user != "john") {
res.status = StatusCode::Forbidden_403;
res.set_content("error", "text/html");
return Server::HandlerResponse::Handled;
}
}
return Server::HandlerResponse::Unhandled;
});
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);
{
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("hi", res->body);
}
{
auto res = cli.Get("/user/john");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("john", res->body);
}
{
auto res = cli.Get("/user/invalid-user");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::Forbidden_403, res->status);
EXPECT_EQ("error", res->body);
}
}
TEST(InvalidFormatTest, StatusCode) {
Server svr;
@@ -2867,6 +2928,11 @@ protected:
EXPECT_EQ(LONG_QUERY_URL, req.target);
EXPECT_EQ(LONG_QUERY_VALUE, req.get_param_value("key"));
})
.Get("/too-long-query-value",
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(TOO_LONG_QUERY_URL, req.target);
EXPECT_EQ(TOO_LONG_QUERY_VALUE, req.get_param_value("key"));
})
.Get("/array-param",
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(3u, req.get_param_value_count("array"));
@@ -3655,6 +3721,13 @@ TEST_F(ServerTest, LongQueryValue) {
EXPECT_EQ(StatusCode::UriTooLong_414, res->status);
}
TEST_F(ServerTest, TooLongQueryValue) {
auto res = cli_.Get(TOO_LONG_QUERY_URL.c_str());
ASSERT_FALSE(res);
EXPECT_EQ(Error::Read, res.error());
}
TEST_F(ServerTest, TooLongHeader) {
Request req;
req.method = "GET";
@@ -5716,6 +5789,41 @@ TEST(KeepAliveTest, ReadTimeout) {
EXPECT_EQ("b", resb->body);
}
TEST(KeepAliveTest, MaxCount) {
size_t keep_alive_max_count = 3;
Server svr;
svr.set_keep_alive_max_count(keep_alive_max_count);
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_keep_alive(true);
for (size_t i = 0; i < 5; i++) {
auto result = cli.Get("/hi");
ASSERT_TRUE(result);
EXPECT_EQ(StatusCode::OK_200, result->status);
if (i == keep_alive_max_count - 1) {
EXPECT_EQ("close", result->get_header_value("Connection"));
} else {
EXPECT_FALSE(result->has_header("Connection"));
}
}
}
TEST(KeepAliveTest, Issue1041) {
Server svr;
svr.set_keep_alive_timeout(3);
@@ -7902,6 +8010,68 @@ TEST(MultipartFormDataTest, ContentLength) {
ASSERT_TRUE(send_request(1, req, &response));
ASSERT_EQ("200", response.substr(9, 3));
}
TEST(MultipartFormDataTest, AccessPartHeaders) {
auto handled = false;
Server svr;
svr.Post("/test", [&](const Request &req, Response &) {
ASSERT_EQ(2u, req.files.size());
auto it = req.files.begin();
ASSERT_EQ("text1", it->second.name);
ASSERT_EQ("text1", it->second.content);
ASSERT_EQ(1, it->second.headers.count("Content-Length"));
auto content_length = it->second.headers.find("CONTENT-length");
ASSERT_EQ("5", content_length->second);
ASSERT_EQ(3, it->second.headers.size());
++it;
ASSERT_EQ("text2", it->second.name);
ASSERT_EQ("text2", it->second.content);
auto &headers = it->second.headers;
ASSERT_EQ(3, headers.size());
auto custom_header = headers.find("x-whatever");
ASSERT_TRUE(custom_header != headers.end());
ASSERT_NE("customvalue", custom_header->second);
ASSERT_EQ("CustomValue", custom_header->second);
ASSERT_TRUE(headers.find("X-Test") == headers.end()); // text1 header
handled = true;
});
thread t = thread([&] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
ASSERT_TRUE(handled);
});
svr.wait_until_ready();
auto req = "POST /test HTTP/1.1\r\n"
"Content-Type: multipart/form-data;boundary=--------\r\n"
"Content-Length: 232\r\n"
"\r\n----------\r\n"
"Content-Disposition: form-data; name=\"text1\"\r\n"
"Content-Length: 5\r\n"
"X-Test: 1\r\n"
"\r\n"
"text1"
"\r\n----------\r\n"
"Content-Disposition: form-data; name=\"text2\"\r\n"
"Content-Type: text/plain\r\n"
"X-Whatever: CustomValue\r\n"
"\r\n"
"text2"
"\r\n------------\r\n"
"That should be disregarded. Not even read";
std::string response;
ASSERT_TRUE(send_request(1, req, &response));
ASSERT_EQ("200", response.substr(9, 3));
}
#endif
TEST(TaskQueueTest, IncreaseAtomicInteger) {
@@ -8623,7 +8793,7 @@ TEST(MaxTimeoutTest, ContentStream) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(MaxTimeoutTest, ContentStreamSSL) {
time_t timeout = 2000;
time_t threshold = 500; // SSL_shutdown is slow on some operating systems.
time_t threshold = 1200; // SSL_shutdown is slow on some operating systems.
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);