mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd95e67c23 | |||
| 2e2e47bab1 | |||
| 59905c7f0d | |||
| 8d03ef1615 | |||
| 23a1d79a66 |
@@ -8,8 +8,8 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.30.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x001E00"
|
||||
#define CPPHTTPLIB_VERSION "0.30.1"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x001E01"
|
||||
|
||||
/*
|
||||
* Platform compatibility check
|
||||
@@ -205,7 +205,10 @@
|
||||
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
|
||||
#ifndef _SSIZE_T_DEFINED
|
||||
using ssize_t = __int64;
|
||||
#define _SSIZE_T_DEFINED
|
||||
#endif
|
||||
#endif // _MSC_VER
|
||||
|
||||
#ifndef S_ISREG
|
||||
@@ -2443,16 +2446,20 @@ namespace detail {
|
||||
|
||||
#if defined(_WIN32)
|
||||
inline std::wstring u8string_to_wstring(const char *s) {
|
||||
std::wstring ws;
|
||||
if (!s) { return std::wstring(); }
|
||||
|
||||
auto len = static_cast<int>(strlen(s));
|
||||
if (!len) { return std::wstring(); }
|
||||
|
||||
auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
|
||||
if (wlen > 0) {
|
||||
ws.resize(wlen);
|
||||
wlen = ::MultiByteToWideChar(
|
||||
CP_UTF8, 0, s, len,
|
||||
const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
|
||||
if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
|
||||
}
|
||||
if (!wlen) { return std::wstring(); }
|
||||
|
||||
std::wstring ws;
|
||||
ws.resize(wlen);
|
||||
wlen = ::MultiByteToWideChar(
|
||||
CP_UTF8, 0, s, len,
|
||||
const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
|
||||
if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
|
||||
return ws;
|
||||
}
|
||||
#endif
|
||||
@@ -4543,6 +4550,7 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service,
|
||||
|
||||
return ret;
|
||||
#elif TARGET_OS_MAC
|
||||
if (!node) { return EAI_NONAME; }
|
||||
// macOS implementation using CFHost API for asynchronous DNS resolution
|
||||
CFStringRef hostname_ref = CFStringCreateWithCString(
|
||||
kCFAllocatorDefault, node, kCFStringEncodingUTF8);
|
||||
@@ -8974,14 +8982,11 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
|
||||
strm, req, res,
|
||||
// Regular
|
||||
[&](const char *buf, size_t n) {
|
||||
// Prevent arithmetic overflow when checking sizes.
|
||||
// Avoid computing (req.body.size() + n) directly because
|
||||
// adding two unsigned `size_t` values can wrap around and
|
||||
// produce a small result instead of indicating overflow.
|
||||
// Instead, check using subtraction: ensure `n` does not
|
||||
// exceed the remaining capacity `max_size() - size()`.
|
||||
if (req.body.size() >= req.body.max_size() ||
|
||||
n > req.body.max_size() - req.body.size()) {
|
||||
// Limit decompressed body size to payload_max_length_ to protect
|
||||
// against "zip bomb" attacks where a small compressed payload
|
||||
// decompresses to a massive size.
|
||||
if (req.body.size() + n > payload_max_length_ ||
|
||||
req.body.size() + n > req.body.max_size()) {
|
||||
return false;
|
||||
}
|
||||
req.body.append(buf, n);
|
||||
|
||||
@@ -14086,3 +14086,67 @@ TEST_F(SSEIntegrationTest, LastEventIdSentOnReconnect) {
|
||||
EXPECT_EQ(received_last_event_ids[1], "event-0");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Issue2318Test, EmptyHostString) {
|
||||
{
|
||||
httplib::Client cli_empty("", PORT);
|
||||
auto res = cli_empty.Get("/");
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(httplib::Error::Connection, res.error());
|
||||
}
|
||||
{
|
||||
httplib::Client cli(" ", PORT);
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(httplib::Error::Connection, res.error());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
TEST(ZipBombProtectionTest, DecompressedSizeExceedsLimit) {
|
||||
Server svr;
|
||||
|
||||
// Set a small payload limit (1KB)
|
||||
svr.set_payload_max_length(1024);
|
||||
|
||||
svr.Post("/test", [&](const Request &req, Response &res) {
|
||||
res.set_content("Body size: " + std::to_string(req.body.size()),
|
||||
"text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Create data that compresses well but exceeds limit when decompressed
|
||||
// 8KB of repeated null bytes compresses to a very small size
|
||||
std::string original_data(8 * 1024, '\0');
|
||||
|
||||
// Compress the data using gzip
|
||||
std::string compressed_data;
|
||||
detail::gzip_compressor compressor;
|
||||
compressor.compress(original_data.data(), original_data.size(), true,
|
||||
[&](const char *data, size_t size) {
|
||||
compressed_data.append(data, size);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Verify compression worked (compressed should be much smaller)
|
||||
ASSERT_LT(compressed_data.size(), original_data.size());
|
||||
ASSERT_LT(compressed_data.size(), 1024u); // Compressed fits in limit
|
||||
|
||||
// Send compressed data with Content-Encoding: gzip
|
||||
Client cli(HOST, PORT);
|
||||
Headers headers = {{"Content-Encoding", "gzip"}};
|
||||
auto res =
|
||||
cli.Post("/test", headers, compressed_data, "application/octet-stream");
|
||||
|
||||
// Server should reject because decompressed size (8KB) exceeds limit (1KB)
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user