You've already forked cpp-httplib
Resolve #2237
This commit is contained in:
79
httplib.h
79
httplib.h
@@ -1052,6 +1052,9 @@ private:
|
|||||||
|
|
||||||
ssize_t write_headers(Stream &strm, const Headers &headers);
|
ssize_t write_headers(Stream &strm, const Headers &headers);
|
||||||
|
|
||||||
|
std::string make_host_and_port_string(const std::string &host, int port,
|
||||||
|
bool is_ssl);
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
@@ -1719,8 +1722,6 @@ private:
|
|||||||
const std::string &boundary, const UploadFormDataItems &items,
|
const std::string &boundary, const UploadFormDataItems &items,
|
||||||
const FormDataProviderItems &provider_items) const;
|
const FormDataProviderItems &provider_items) const;
|
||||||
|
|
||||||
std::string adjust_host_string(const std::string &host) const;
|
|
||||||
|
|
||||||
virtual bool
|
virtual bool
|
||||||
process_socket(const Socket &socket,
|
process_socket(const Socket &socket,
|
||||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||||
@@ -7261,6 +7262,30 @@ inline bool RegexMatcher::match(Request &request) const {
|
|||||||
return std::regex_match(request.path, request.matches, regex_);
|
return std::regex_match(request.path, request.matches, regex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string make_host_and_port_string(const std::string &host, int port,
|
||||||
|
bool is_ssl) {
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
// Enclose IPv6 address in brackets (but not if already enclosed)
|
||||||
|
if (host.find(':') == std::string::npos ||
|
||||||
|
(!host.empty() && host[0] == '[')) {
|
||||||
|
// IPv4, hostname, or already bracketed IPv6
|
||||||
|
result = host;
|
||||||
|
} else {
|
||||||
|
// IPv6 address without brackets
|
||||||
|
result = "[" + host + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append port if not default
|
||||||
|
if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
|
||||||
|
; // do nothing
|
||||||
|
} else {
|
||||||
|
result += ":" + std::to_string(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// HTTP server implementation
|
// HTTP server implementation
|
||||||
@@ -8522,7 +8547,7 @@ inline ClientImpl::ClientImpl(const std::string &host, int port,
|
|||||||
const std::string &client_cert_path,
|
const std::string &client_cert_path,
|
||||||
const std::string &client_key_path)
|
const std::string &client_key_path)
|
||||||
: host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
|
: host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
|
||||||
host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)),
|
host_and_port_(detail::make_host_and_port_string(host_, port, is_ssl())),
|
||||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||||
|
|
||||||
inline ClientImpl::~ClientImpl() {
|
inline ClientImpl::~ClientImpl() {
|
||||||
@@ -8703,8 +8728,9 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||||
|
|
||||||
// Set this to false immediately - if it ever gets set to true by the end of
|
// Set this to false immediately - if it ever gets set to true by the end
|
||||||
// the request, we know another thread instructed us to close the socket.
|
// of the request, we know another thread instructed us to close the
|
||||||
|
// socket.
|
||||||
socket_should_be_closed_when_request_is_done_ = false;
|
socket_should_be_closed_when_request_is_done_ = false;
|
||||||
|
|
||||||
auto is_alive = false;
|
auto is_alive = false;
|
||||||
@@ -8720,10 +8746,10 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!is_alive) {
|
if (!is_alive) {
|
||||||
// Attempt to avoid sigpipe by shutting down non-gracefully if it seems
|
// Attempt to avoid sigpipe by shutting down non-gracefully if it
|
||||||
// like the other side has already closed the connection Also, there
|
// seems like the other side has already closed the connection Also,
|
||||||
// cannot be any requests in flight from other threads since we locked
|
// there cannot be any requests in flight from other threads since we
|
||||||
// request_mutex_, so safe to close everything immediately
|
// locked request_mutex_, so safe to close everything immediately
|
||||||
const bool shutdown_gracefully = false;
|
const bool shutdown_gracefully = false;
|
||||||
shutdown_ssl(socket_, shutdown_gracefully);
|
shutdown_ssl(socket_, shutdown_gracefully);
|
||||||
shutdown_socket(socket_);
|
shutdown_socket(socket_);
|
||||||
@@ -9027,7 +9053,8 @@ inline bool ClientImpl::create_redirect_client(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New method for robust client setup (based on basic_manual_redirect.cpp logic)
|
// New method for robust client setup (based on basic_manual_redirect.cpp
|
||||||
|
// logic)
|
||||||
template <typename ClientType>
|
template <typename ClientType>
|
||||||
inline void ClientImpl::setup_redirect_client(ClientType &client) {
|
inline void ClientImpl::setup_redirect_client(ClientType &client) {
|
||||||
// Copy basic settings first
|
// Copy basic settings first
|
||||||
@@ -9131,18 +9158,8 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
|||||||
// curl behavior)
|
// curl behavior)
|
||||||
if (address_family_ == AF_UNIX) {
|
if (address_family_ == AF_UNIX) {
|
||||||
req.set_header("Host", "localhost");
|
req.set_header("Host", "localhost");
|
||||||
} else if (is_ssl()) {
|
|
||||||
if (port_ == 443) {
|
|
||||||
req.set_header("Host", host_);
|
|
||||||
} else {
|
|
||||||
req.set_header("Host", host_and_port_);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (port_ == 80) {
|
req.set_header("Host", host_and_port_);
|
||||||
req.set_header("Host", host_);
|
|
||||||
} else {
|
|
||||||
req.set_header("Host", host_and_port_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9409,12 +9426,6 @@ inline Result ClientImpl::send_with_content_provider(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string
|
|
||||||
ClientImpl::adjust_host_string(const std::string &host) const {
|
|
||||||
if (host.find(':') != std::string::npos) { return "[" + host + "]"; }
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void ClientImpl::output_log(const Request &req,
|
inline void ClientImpl::output_log(const Request &req,
|
||||||
const Response &res) const {
|
const Response &res) const {
|
||||||
if (logger_) {
|
if (logger_) {
|
||||||
@@ -9538,8 +9549,8 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
|
|||||||
const FormDataProviderItems &provider_items) const {
|
const FormDataProviderItems &provider_items) const {
|
||||||
size_t cur_item = 0;
|
size_t cur_item = 0;
|
||||||
size_t cur_start = 0;
|
size_t cur_start = 0;
|
||||||
// cur_item and cur_start are copied to within the std::function and maintain
|
// cur_item and cur_start are copied to within the std::function and
|
||||||
// state between successive calls
|
// maintain state between successive calls
|
||||||
return [&, cur_item, cur_start](size_t offset,
|
return [&, cur_item, cur_start](size_t offset,
|
||||||
DataSink &sink) mutable -> bool {
|
DataSink &sink) mutable -> bool {
|
||||||
if (!offset && !items.empty()) {
|
if (!offset && !items.empty()) {
|
||||||
@@ -10251,8 +10262,8 @@ inline void ClientImpl::stop() {
|
|||||||
// If there is anything ongoing right now, the ONLY thread-safe thing we can
|
// If there is anything ongoing right now, the ONLY thread-safe thing we can
|
||||||
// do is to shutdown_socket, so that threads using this socket suddenly
|
// do is to shutdown_socket, so that threads using this socket suddenly
|
||||||
// discover they can't read/write any more and error out. Everything else
|
// discover they can't read/write any more and error out. Everything else
|
||||||
// (closing the socket, shutting ssl down) is unsafe because these actions are
|
// (closing the socket, shutting ssl down) is unsafe because these actions
|
||||||
// not thread-safe.
|
// are not thread-safe.
|
||||||
if (socket_requests_in_flight_ > 0) {
|
if (socket_requests_in_flight_ > 0) {
|
||||||
shutdown_socket(socket_);
|
shutdown_socket(socket_);
|
||||||
|
|
||||||
@@ -10889,7 +10900,8 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
|||||||
if (ca_cert_store) {
|
if (ca_cert_store) {
|
||||||
if (ctx_) {
|
if (ctx_) {
|
||||||
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
|
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
|
||||||
// Free memory allocated for old cert and use new store `ca_cert_store`
|
// Free memory allocated for old cert and use new store
|
||||||
|
// `ca_cert_store`
|
||||||
SSL_CTX_set_cert_store(ctx_, ca_cert_store);
|
SSL_CTX_set_cert_store(ctx_, ca_cert_store);
|
||||||
ca_cert_store_ = ca_cert_store;
|
ca_cert_store_ = ca_cert_store;
|
||||||
}
|
}
|
||||||
@@ -10914,7 +10926,8 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
|
|||||||
return is_valid() && ClientImpl::create_and_connect_socket(socket, error);
|
return is_valid() && ClientImpl::create_and_connect_socket(socket, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumes that socket_mutex_ is locked and that there are no requests in flight
|
// Assumes that socket_mutex_ is locked and that there are no requests in
|
||||||
|
// flight
|
||||||
inline bool SSLClient::connect_with_proxy(
|
inline bool SSLClient::connect_with_proxy(
|
||||||
Socket &socket,
|
Socket &socket,
|
||||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||||
|
|||||||
97
test/test.cc
97
test/test.cc
@@ -10319,6 +10319,103 @@ TEST(FileSystemTest, FileAndDirExistenceCheck) {
|
|||||||
EXPECT_TRUE(stat_dir.is_dir());
|
EXPECT_TRUE(stat_dir.is_dir());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MakeHostAndPortStringTest, VariousPatterns) {
|
||||||
|
// IPv4 with default HTTP port (80)
|
||||||
|
EXPECT_EQ("example.com",
|
||||||
|
detail::make_host_and_port_string("example.com", 80, false));
|
||||||
|
|
||||||
|
// IPv4 with default HTTPS port (443)
|
||||||
|
EXPECT_EQ("example.com",
|
||||||
|
detail::make_host_and_port_string("example.com", 443, true));
|
||||||
|
|
||||||
|
// IPv4 with non-default HTTP port
|
||||||
|
EXPECT_EQ("example.com:8080",
|
||||||
|
detail::make_host_and_port_string("example.com", 8080, false));
|
||||||
|
|
||||||
|
// IPv4 with non-default HTTPS port
|
||||||
|
EXPECT_EQ("example.com:8443",
|
||||||
|
detail::make_host_and_port_string("example.com", 8443, true));
|
||||||
|
|
||||||
|
// IPv6 with default HTTP port (80)
|
||||||
|
EXPECT_EQ("[::1]", detail::make_host_and_port_string("::1", 80, false));
|
||||||
|
|
||||||
|
// IPv6 with default HTTPS port (443)
|
||||||
|
EXPECT_EQ("[::1]", detail::make_host_and_port_string("::1", 443, true));
|
||||||
|
|
||||||
|
// IPv6 with non-default HTTP port
|
||||||
|
EXPECT_EQ("[::1]:8080",
|
||||||
|
detail::make_host_and_port_string("::1", 8080, false));
|
||||||
|
|
||||||
|
// IPv6 with non-default HTTPS port
|
||||||
|
EXPECT_EQ("[::1]:8443", detail::make_host_and_port_string("::1", 8443, true));
|
||||||
|
|
||||||
|
// IPv6 full address with default port
|
||||||
|
EXPECT_EQ("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
|
||||||
|
detail::make_host_and_port_string(
|
||||||
|
"2001:0db8:85a3:0000:0000:8a2e:0370:7334", 443, true));
|
||||||
|
|
||||||
|
// IPv6 full address with non-default port
|
||||||
|
EXPECT_EQ("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:9000",
|
||||||
|
detail::make_host_and_port_string(
|
||||||
|
"2001:0db8:85a3:0000:0000:8a2e:0370:7334", 9000, false));
|
||||||
|
|
||||||
|
// IPv6 localhost with non-default port
|
||||||
|
EXPECT_EQ("[::1]:3000",
|
||||||
|
detail::make_host_and_port_string("::1", 3000, false));
|
||||||
|
|
||||||
|
// IPv6 with zone ID (link-local address) with default port
|
||||||
|
EXPECT_EQ("[fe80::1%eth0]",
|
||||||
|
detail::make_host_and_port_string("fe80::1%eth0", 80, false));
|
||||||
|
|
||||||
|
// IPv6 with zone ID (link-local address) with non-default port
|
||||||
|
EXPECT_EQ("[fe80::1%eth0]:8080",
|
||||||
|
detail::make_host_and_port_string("fe80::1%eth0", 8080, false));
|
||||||
|
|
||||||
|
// Edge case: Port 443 with is_ssl=false (should add port)
|
||||||
|
EXPECT_EQ("example.com:443",
|
||||||
|
detail::make_host_and_port_string("example.com", 443, false));
|
||||||
|
|
||||||
|
// Edge case: Port 80 with is_ssl=true (should add port)
|
||||||
|
EXPECT_EQ("example.com:80",
|
||||||
|
detail::make_host_and_port_string("example.com", 80, true));
|
||||||
|
|
||||||
|
// IPv6 edge case: Port 443 with is_ssl=false (should add port)
|
||||||
|
EXPECT_EQ("[::1]:443", detail::make_host_and_port_string("::1", 443, false));
|
||||||
|
|
||||||
|
// IPv6 edge case: Port 80 with is_ssl=true (should add port)
|
||||||
|
EXPECT_EQ("[::1]:80", detail::make_host_and_port_string("::1", 80, true));
|
||||||
|
|
||||||
|
// Security fix: Already bracketed IPv6 should not get double brackets
|
||||||
|
EXPECT_EQ("[::1]", detail::make_host_and_port_string("[::1]", 80, false));
|
||||||
|
EXPECT_EQ("[::1]", detail::make_host_and_port_string("[::1]", 443, true));
|
||||||
|
EXPECT_EQ("[::1]:8080",
|
||||||
|
detail::make_host_and_port_string("[::1]", 8080, false));
|
||||||
|
EXPECT_EQ("[2001:db8::1]:8080",
|
||||||
|
detail::make_host_and_port_string("[2001:db8::1]", 8080, false));
|
||||||
|
EXPECT_EQ("[fe80::1%eth0]",
|
||||||
|
detail::make_host_and_port_string("[fe80::1%eth0]", 80, false));
|
||||||
|
EXPECT_EQ("[fe80::1%eth0]:8080",
|
||||||
|
detail::make_host_and_port_string("[fe80::1%eth0]", 8080, false));
|
||||||
|
|
||||||
|
// Edge case: Empty host (should return as-is)
|
||||||
|
EXPECT_EQ("", detail::make_host_and_port_string("", 80, false));
|
||||||
|
|
||||||
|
// Edge case: Colon in hostname (non-IPv6) - will be treated as IPv6
|
||||||
|
// This is a known limitation but shouldn't crash
|
||||||
|
EXPECT_EQ("[host:name]",
|
||||||
|
detail::make_host_and_port_string("host:name", 80, false));
|
||||||
|
|
||||||
|
// Port number edge cases (no validation, but should not crash)
|
||||||
|
EXPECT_EQ("example.com:0",
|
||||||
|
detail::make_host_and_port_string("example.com", 0, false));
|
||||||
|
EXPECT_EQ("example.com:-1",
|
||||||
|
detail::make_host_and_port_string("example.com", -1, false));
|
||||||
|
EXPECT_EQ("example.com:65535",
|
||||||
|
detail::make_host_and_port_string("example.com", 65535, false));
|
||||||
|
EXPECT_EQ("example.com:65536",
|
||||||
|
detail::make_host_and_port_string("example.com", 65536, false));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user