Compare commits

..

11 Commits

Author SHA1 Message Date
yhirose a5d4c143e5 Release v0.24.0 2025-07-29 19:47:48 -04:00
yhirose c0c36f021d Fix #2184, #2185 (#2190)
* Fix #2184, #2185

* Fix build error

* Update

* Update
2025-07-29 19:29:37 -04:00
yhirose 8e8a23e3d2 Fix #2187 2025-07-24 19:35:47 -04:00
yhirose 890a2dd85d Fix #2189 2025-07-24 17:04:59 -04:00
yhirose ca5fe354fb Release v0.23.1 2025-07-16 17:59:52 -04:00
Benjamin Gilbert dd98d2a24d build(meson): warn/fail on 32-bit machines (#2181)
On 32-bit Windows, meson setup fails with an unclear error:

    meson.build:25:16: ERROR: Could not get define 'CPPHTTPLIB_VERSION'

The actual problem is that httplib.h #errors out.

Have the Meson logic explicitly check for a 32-bit host and warn or error,
matching the check in httplib.h.  Phrase the Windows error in a way that
triggers WrapDB CI's unsupported architecture check.
2025-07-16 12:47:51 -04:00
yhirose 1f110b54d8 Chang #error to #warning for the 32-bit environment check except 32-bit Windows 2025-07-11 22:44:29 -04:00
yhirose 7b6867bcdf Fix #2021 (#2180) 2025-07-10 22:01:41 -04:00
yhirose 8b28577ec6 Resolve #366 2025-07-10 01:07:44 -04:00
yhirose 55b38907dc Resolve #1264 2025-07-10 00:58:52 -04:00
yhirose 53ea9e8bb4 Fix #2111 (#2179) 2025-07-10 00:47:45 -04:00
4 changed files with 411 additions and 74 deletions
+40 -1
View File
@@ -871,7 +871,7 @@ auto res = cli.Post(
httplib::Client cli(url, port);
// prints: 0 / 000 bytes => 50% complete
auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
auto res = cli.Get("/", [](size_t len, size_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)(len*100/total));
@@ -990,6 +990,25 @@ auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths
- `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters
- `false`: Sends paths as-is without encoding (useful for pre-encoded URLs)
### Performance Note for Local Connections
> [!WARNING]
> On Windows systems with improperly configured IPv6 settings, using "localhost" as the hostname may cause significant connection delays (up to 2 seconds per request) due to DNS resolution issues. This affects both client and server operations. For better performance when connecting to local services, use "127.0.0.1" instead of "localhost".
>
> See: https://github.com/yhirose/cpp-httplib/issues/366#issuecomment-593004264
```cpp
// May be slower on Windows due to DNS resolution delays
httplib::Client cli("localhost", 8080);
httplib::Server svr;
svr.listen("localhost", 8080);
// Faster alternative for local connections
httplib::Client cli("127.0.0.1", 8080);
httplib::Server svr;
svr.listen("127.0.0.1", 8080);
```
Compression
-----------
@@ -1065,6 +1084,8 @@ cli.set_address_family(AF_UNIX);
"my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
This library automatically sets the Host header to "localhost" for Unix socket connections, similar to curl's behavior:
URI Encoding/Decoding Utilities
-------------------------------
@@ -1141,6 +1162,24 @@ Serving HTTP on 0.0.0.0 port 80 ...
NOTE
----
### Regular Expression Stack Overflow
> [!CAUTION]
> When using complex regex patterns in route handlers, be aware that certain patterns may cause stack overflow during pattern matching. This is a known issue with `std::regex` implementations and affects the `dispatch_request()` method.
>
> ```cpp
> // This pattern can cause stack overflow with large input
> svr.Get(".*", handler);
> ```
>
> Consider using simpler patterns or path parameters to avoid this issue:
>
> ```cpp
> // Safer alternatives
> svr.Get("/users/:id", handler); // Path parameters
> svr.Get(R"(/api/v\d+/.*)", handler); // More specific patterns
> ```
### g++
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
+273 -69
View File
@@ -8,7 +8,8 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.23.0"
#define CPPHTTPLIB_VERSION "0.24.0"
#define CPPHTTPLIB_VERSION_NUM "0x001800"
/*
* Platform compatibility check
@@ -18,10 +19,10 @@
#error \
"cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler."
#elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ < 8
#error \
#warning \
"cpp-httplib doesn't support 32-bit platforms. Please use a 64-bit compiler."
#elif defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ < 8
#error \
#warning \
"cpp-httplib doesn't support platforms where size_t is less than 64 bits."
#endif
@@ -2029,7 +2030,7 @@ inline size_t get_header_value_u64(const Headers &headers,
inline size_t get_header_value_u64(const Headers &headers,
const std::string &key, size_t def,
size_t id) {
bool dummy = false;
auto dummy = false;
return get_header_value_u64(headers, key, def, id, dummy);
}
@@ -2281,6 +2282,10 @@ Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
cli_->set_write_timeout(duration);
}
inline void Client::set_max_timeout(time_t msec) {
cli_->set_max_timeout(msec);
}
template <class Rep, class Period>
inline void
Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
@@ -2296,15 +2301,19 @@ std::string hosted_at(const std::string &hostname);
void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
// JavaScript-style URL encoding/decoding functions
std::string encode_uri_component(const std::string &value);
std::string encode_uri(const std::string &value);
std::string decode_uri_component(const std::string &value);
std::string decode_uri(const std::string &value);
std::string encode_query_param(const std::string &value);
// RFC 3986 compliant URL component encoding/decoding functions
std::string encode_path_component(const std::string &component);
std::string decode_path_component(const std::string &component);
std::string encode_query_component(const std::string &component,
bool space_as_plus = true);
std::string decode_query_component(const std::string &component,
bool plus_as_space = true);
std::string append_query_params(const std::string &path, const Params &params);
@@ -2347,8 +2356,6 @@ private:
int ret_ = -1;
};
std::string decode_path(const std::string &s, bool convert_plus_to_space);
std::string trim_copy(const std::string &s);
void divide(
@@ -2849,43 +2856,6 @@ inline std::string encode_path(const std::string &s) {
return result;
}
inline std::string decode_path(const std::string &s,
bool convert_plus_to_space) {
std::string result;
for (size_t i = 0; i < s.size(); i++) {
if (s[i] == '%' && i + 1 < s.size()) {
if (s[i + 1] == 'u') {
auto val = 0;
if (from_hex_to_i(s, i + 2, 4, val)) {
// 4 digits Unicode codes
char buff[4];
size_t len = to_utf8(val, buff);
if (len > 0) { result.append(buff, len); }
i += 5; // 'u0000'
} else {
result += s[i];
}
} else {
auto val = 0;
if (from_hex_to_i(s, i + 1, 2, val)) {
// 2 digits hex codes
result += static_cast<char>(val);
i += 2; // '00'
} else {
result += s[i];
}
}
} else if (convert_plus_to_space && s[i] == '+') {
result += ' ';
} else {
result += s[i];
}
}
return result;
}
inline std::string file_extension(const std::string &path) {
std::smatch m;
thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
@@ -3215,6 +3185,23 @@ inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
template <bool Read>
inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
#ifdef __APPLE__
if (sock >= FD_SETSIZE) { return -1; }
fd_set fds, *rfds, *wfds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
rfds = (Read ? &fds : nullptr);
wfds = (Read ? nullptr : &fds);
timeval tv;
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
return handle_EINTR([&]() {
return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
});
#else
struct pollfd pfd;
pfd.fd = sock;
pfd.events = (Read ? POLLIN : POLLOUT);
@@ -3222,6 +3209,7 @@ inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
#endif
}
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
@@ -3234,6 +3222,36 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
time_t usec) {
#ifdef __APPLE__
if (sock >= FD_SETSIZE) { return Error::Connection; }
fd_set fdsr, fdsw;
FD_ZERO(&fdsr);
FD_ZERO(&fdsw);
FD_SET(sock, &fdsr);
FD_SET(sock, &fdsw);
timeval tv;
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
auto ret = handle_EINTR([&]() {
return select(static_cast<int>(sock + 1), &fdsr, &fdsw, nullptr, &tv);
});
if (ret == 0) { return Error::ConnectionTimeout; }
if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
auto error = 0;
socklen_t len = sizeof(error);
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char *>(&error), &len);
auto successful = res >= 0 && !error;
return successful ? Error::Success : Error::Connection;
}
return Error::Connection;
#else
struct pollfd pfd_read;
pfd_read.fd = sock;
pfd_read.events = POLLIN | POLLOUT;
@@ -3255,6 +3273,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
}
return Error::Connection;
#endif
}
inline bool is_socket_alive(socket_t sock) {
@@ -4561,7 +4580,7 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
case_ignore::equal(key, "Referer")) {
fn(key, val);
} else {
fn(key, decode_path(val, false));
fn(key, decode_path_component(val));
}
return true;
@@ -5209,9 +5228,9 @@ inline std::string params_to_query_str(const Params &params) {
for (auto it = params.begin(); it != params.end(); ++it) {
if (it != params.begin()) { query += "&"; }
query += it->first;
query += encode_query_component(it->first);
query += "=";
query += httplib::encode_uri_component(it->second);
query += encode_query_component(it->second);
}
return query;
}
@@ -5234,7 +5253,7 @@ inline void parse_query_text(const char *data, std::size_t size,
});
if (!key.empty()) {
params.emplace(decode_path(key, true), decode_path(val, true));
params.emplace(decode_query_component(key), decode_query_component(val));
}
});
}
@@ -5386,17 +5405,30 @@ inline bool parse_accept_header(const std::string &s,
return;
}
try {
accept_entry.quality = std::stod(quality_str);
// Check if quality is in valid range [0.0, 1.0]
if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
{
std::istringstream iss(quality_str);
iss >> accept_entry.quality;
// Check if conversion was successful and entire string was consumed
if (iss.fail() || !iss.eof()) {
has_invalid_entry = true;
return;
}
}
#else
try {
accept_entry.quality = std::stod(quality_str);
} catch (...) {
has_invalid_entry = true;
return;
}
#endif
// Check if quality is in valid range [0.0, 1.0]
if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
has_invalid_entry = true;
return;
}
} else {
// No quality parameter, use entire entry as media type
accept_entry.media_type = entry;
@@ -5544,7 +5576,7 @@ public:
std::smatch m2;
if (std::regex_match(it->second, m2, re_rfc5987_encoding)) {
file_.filename = decode_path(m2[1], false); // override...
file_.filename = decode_path_component(m2[1]); // override...
} else {
is_valid_ = false;
return false;
@@ -6450,9 +6482,154 @@ inline std::string decode_uri(const std::string &value) {
return result;
}
[[deprecated("Use encode_uri_component instead")]]
inline std::string encode_query_param(const std::string &value) {
return encode_uri_component(value);
inline std::string encode_path_component(const std::string &component) {
std::string result;
result.reserve(component.size() * 3);
for (size_t i = 0; i < component.size(); i++) {
auto c = static_cast<unsigned char>(component[i]);
// Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
result += static_cast<char>(c);
}
// Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
// "," / ";" / "="
else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
c == '=') {
result += static_cast<char>(c);
}
// Colon is allowed in path segments except first segment
else if (c == ':') {
result += static_cast<char>(c);
}
// @ is allowed in path
else if (c == '@') {
result += static_cast<char>(c);
} else {
result += '%';
char hex[3];
snprintf(hex, sizeof(hex), "%02X", c);
result.append(hex, 2);
}
}
return result;
}
inline std::string decode_path_component(const std::string &component) {
std::string result;
result.reserve(component.size());
for (size_t i = 0; i < component.size(); i++) {
if (component[i] == '%' && i + 1 < component.size()) {
if (component[i + 1] == 'u') {
// Unicode %uXXXX encoding
auto val = 0;
if (detail::from_hex_to_i(component, i + 2, 4, val)) {
// 4 digits Unicode codes
char buff[4];
size_t len = detail::to_utf8(val, buff);
if (len > 0) { result.append(buff, len); }
i += 5; // 'u0000'
} else {
result += component[i];
}
} else {
// Standard %XX encoding
auto val = 0;
if (detail::from_hex_to_i(component, i + 1, 2, val)) {
// 2 digits hex codes
result += static_cast<char>(val);
i += 2; // 'XX'
} else {
result += component[i];
}
}
} else {
result += component[i];
}
}
return result;
}
inline std::string encode_query_component(const std::string &component,
bool space_as_plus) {
std::string result;
result.reserve(component.size() * 3);
for (size_t i = 0; i < component.size(); i++) {
auto c = static_cast<unsigned char>(component[i]);
// Unreserved characters per RFC 3986
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
result += static_cast<char>(c);
}
// Space handling
else if (c == ' ') {
if (space_as_plus) {
result += '+';
} else {
result += "%20";
}
}
// Plus sign handling
else if (c == '+') {
if (space_as_plus) {
result += "%2B";
} else {
result += static_cast<char>(c);
}
}
// Query-safe sub-delimiters (excluding & and = which are query delimiters)
else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
c == '*' || c == ',' || c == ';') {
result += static_cast<char>(c);
}
// Colon and @ are allowed in query
else if (c == ':' || c == '@') {
result += static_cast<char>(c);
}
// Forward slash is allowed in query values
else if (c == '/') {
result += static_cast<char>(c);
}
// Question mark is allowed in query values (after first ?)
else if (c == '?') {
result += static_cast<char>(c);
} else {
result += '%';
char hex[3];
snprintf(hex, sizeof(hex), "%02X", c);
result.append(hex, 2);
}
}
return result;
}
inline std::string decode_query_component(const std::string &component,
bool plus_as_space) {
std::string result;
result.reserve(component.size());
for (size_t i = 0; i < component.size(); i++) {
if (component[i] == '%' && i + 2 < component.size()) {
std::string hex = component.substr(i + 1, 2);
char *end;
unsigned long value = std::strtoul(hex.c_str(), &end, 16);
if (end == hex.c_str() + 2) {
result += static_cast<char>(value);
i += 2;
} else {
result += component[i];
}
} else if (component[i] == '+' && plus_as_space) {
result += ' '; // + becomes space in form-urlencoded
} else {
result += component[i];
}
}
return result;
}
inline std::string append_query_params(const std::string &path,
@@ -7337,8 +7514,8 @@ inline bool Server::parse_request_line(const char *s, Request &req) const {
detail::divide(req.target, '?',
[&](const char *lhs_data, std::size_t lhs_size,
const char *rhs_data, std::size_t rhs_size) {
req.path = detail::decode_path(
std::string(lhs_data, lhs_size), false);
req.path =
decode_path_component(std::string(lhs_data, lhs_size));
detail::parse_query_text(rhs_data, rhs_size, req.params);
});
}
@@ -8001,6 +8178,16 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
res.version = "HTTP/1.1";
res.headers = default_headers_;
#ifdef __APPLE__
// Socket file descriptor exceeded FD_SETSIZE...
if (strm.socket() >= FD_SETSIZE) {
Headers dummy;
detail::read_headers(strm, dummy);
res.status = StatusCode::InternalServerError_500;
return write_response(strm, close_connection, req, res);
}
#endif
// Request line and headers
if (!parse_request_line(line_reader.ptr(), req) ||
!detail::read_headers(strm, req.headers)) {
@@ -8601,7 +8788,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
if (next_host.empty()) { next_host = host_; }
if (next_path.empty()) { next_path = "/"; }
auto path = detail::decode_path(next_path, true) + next_query;
auto path = decode_query_component(next_path, true) + next_query;
// Same host redirect - use current client
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
@@ -8785,7 +8972,11 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
}
if (!req.has_header("Host")) {
if (is_ssl()) {
// For Unix socket connections, use "localhost" as Host header (similar to
// curl behavior)
if (address_family_ == AF_UNIX) {
req.set_header("Host", "localhost");
} else if (is_ssl()) {
if (port_ == 443) {
req.set_header("Host", host_);
} else {
@@ -8885,15 +9076,28 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
{
detail::BufferStream bstrm;
const auto &path_with_query =
req.params.empty() ? req.path
: append_query_params(req.path, req.params);
// Extract path and query from req.path
std::string path_part, query_part;
auto query_pos = req.path.find('?');
if (query_pos != std::string::npos) {
path_part = req.path.substr(0, query_pos);
query_part = req.path.substr(query_pos + 1);
} else {
path_part = req.path;
query_part = "";
}
const auto &path =
path_encode_ ? detail::encode_path(path_with_query) : path_with_query;
// Encode path and query
auto path_with_query =
path_encode_ ? detail::encode_path(path_part) : path_part;
detail::write_request_line(bstrm, req.method, path);
detail::parse_query_text(query_part, req.params);
if (!req.params.empty()) {
path_with_query = append_query_params(path_with_query, req.params);
}
// Write request line and headers
detail::write_request_line(bstrm, req.method, path_with_query);
header_writer_(bstrm, req.headers);
// Flush buffer
+8
View File
@@ -18,6 +18,14 @@ project(
cxx = meson.get_compiler('cpp')
if cxx.sizeof('void *') != 8
if host_machine.system() == 'windows'
error('unsupported architecture: cpp-httplib doesn\'t support 32-bit Windows. Please use a 64-bit compiler.')
else
warning('cpp-httplib doesn\'t support 32-bit platforms. Please use a 64-bit compiler.')
endif
endif
# Check just in case downstream decides to edit the source
# and add a project version
version = meson.project_version()
+90 -4
View File
@@ -173,6 +173,48 @@ TEST_F(UnixSocketTest, abstract) {
}
#endif
TEST_F(UnixSocketTest, HostHeaderAutoSet) {
httplib::Server svr;
std::string received_host_header;
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
// Capture the Host header sent by the client
auto host_iter = req.headers.find("Host");
if (host_iter != req.headers.end()) {
received_host_header = host_iter->second;
}
res.set_content(content_, "text/plain");
});
std::thread t{[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
}};
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
ASSERT_TRUE(svr.is_running());
// Test that Host header is automatically set to "localhost" for Unix socket
// connections
httplib::Client cli{pathname_};
cli.set_address_family(AF_UNIX);
ASSERT_TRUE(cli.is_valid());
const auto &result = cli.Get(pattern_);
ASSERT_TRUE(result) << "error: " << result.error();
const auto &resp = result.value();
EXPECT_EQ(resp.status, StatusCode::OK_200);
EXPECT_EQ(resp.body, content_);
// Verify that Host header was automatically set to "localhost"
EXPECT_EQ(received_host_header, "localhost");
}
#ifndef _WIN32
TEST(SocketStream, wait_writable_UNIX) {
int fds[2];
@@ -259,9 +301,8 @@ TEST(StartupTest, WSAStartup) {
TEST(DecodePathTest, PercentCharacter) {
EXPECT_EQ(
detail::decode_path(
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)",
false),
decode_path_component(
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)"),
u8"descrip=Gastos áéíóúñÑ 6");
}
@@ -271,7 +312,7 @@ TEST(DecodePathTest, PercentCharacterNUL) {
expected.push_back('\0');
expected.push_back('x');
EXPECT_EQ(detail::decode_path("x%00x", false), expected);
EXPECT_EQ(decode_path_component("x%00x"), expected);
}
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
@@ -9900,6 +9941,51 @@ TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
}
}
TEST(RedirectTest, RedirectToUrlWithPlusInQueryParameters) {
Server svr;
svr.Get("/", [](const Request & /*req*/, Response &res) {
res.set_redirect(R"(/hello?key=AByz09+~-._%20%26%3F%C3%BC%2B)");
});
svr.Get("/hello", [](const Request &req, Response &res) {
res.set_content(req.get_param_value("key"), "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();
{
Client cli(HOST, PORT);
cli.set_follow_location(true);
auto res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("AByz09 ~-._ &?ü+", res->body);
}
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(RedirectTest, Issue2185_Online) {
SSLClient client("github.com");
client.set_follow_location(true);
auto res = client.Get("/Coollab-Art/Coollab/releases/download/1.1.1_UI-Scale/"
"Coollab-Windows.zip");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(9920427U, res->body.size());
}
#endif
TEST(VulnerabilityTest, CRLFInjection) {
Server svr;