Allow addr_map_ values to be hostnames, not just IP literals (#2515)

addr_map_ only accepted IP literals as mapped values; a non-IP value was
passed as the `ip` argument and rejected by getaddrinfo's AI_NUMERICHOST
path. The lookup in ClientImpl::create_client_socket and
WebSocketClient::connect now checks the mapped value with
detail::is_ip_address: IP literals keep the existing AI_NUMERICHOST path,
while hostnames are passed as the connect host so they get resolved.

host_ is never touched, so it keeps supplying the Host header and SNI in
both cases.

is_ip_address() moved into the non-SSL detail block so that both client
code paths can use it without CPPHTTPLIB_SSL_ENABLED.

This also fixes the documented Unix domain socket client example, where
the mapped value is a socket path: it previously took the AI_NUMERICHOST
path and never reached the AF_UNIX branch in create_socket.
This commit is contained in:
Kim, Hyuk authored and GitHub committed 2026-08-01 14:29:20 -04:00
1 parent 447b9c4a29
commit 49b921b52d
2 files changed
+135 -17

No files matched your search

+38 -17
View File
@@ -2452,7 +2452,8 @@ protected:
std::thread::id socket_requests_are_from_thread_ = std::thread::id();
bool socket_should_be_closed_when_request_is_done_ = false;
// Hostname-IP map
// Hostname to connection target map. The value is an IP literal or another
// hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_;
// Default headers
@@ -4021,7 +4022,8 @@ private:
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
std::string interface_;
// Hostname-IP map
// Hostname to connection target map. The value is an IP literal or another
// hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_;
#ifdef CPPHTTPLIB_SSL_ENABLED
@@ -9096,6 +9098,13 @@ inline bool perform_websocket_handshake(Stream &strm, Request &req,
selected_subprotocol);
}
inline bool is_ip_address(const std::string &host) {
struct in_addr addr4;
struct in6_addr addr6;
return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
}
} // namespace detail
/*
@@ -9279,13 +9288,6 @@ inline std::string SHA_512(const std::string &s) {
}
#endif
inline bool is_ip_address(const std::string &host) {
struct in_addr addr4;
struct in6_addr addr6;
return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
}
template <typename T>
inline bool process_server_socket_ssl(
const std::atomic<socket_t> &svr_sock, tls::session_t session,
@@ -12991,13 +12993,23 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
write_timeout_sec_, write_timeout_usec_, interface_, error);
}
// Check is custom IP specified for host_
// Check is custom IP or hostname specified for host_.
// An IP literal goes to the ip argument, which keeps create_socket's
// AI_NUMERICHOST path; a hostname goes to the host argument so that it is
// resolved. Either way host_ still supplies the Host header and SNI.
auto connect_host = host_;
std::string ip;
auto it = addr_map_.find(host_);
if (it != addr_map_.end()) { ip = it->second; }
if (it != addr_map_.end() && !it->second.empty()) {
if (detail::is_ip_address(it->second)) {
ip = it->second;
} else {
connect_host = it->second;
}
}
return detail::create_client_socket(
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
connect_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);
@@ -20859,16 +20871,25 @@ inline bool WebSocketClient::connect() {
if (!is_valid_) { return false; }
shutdown_and_close();
// Check is custom IP specified for host_.
// host_ stays the identity used for the Host header and for SNI, while ip
// only redirects where the socket connects.
// Check is custom IP or hostname specified for host_.
// host_ stays the identity used for the Host header and for SNI, while the
// mapped value only redirects where the socket connects. An IP literal goes
// to the ip argument, which keeps create_socket's AI_NUMERICHOST path; a
// hostname goes to the host argument so that it is resolved.
auto connect_host = host_;
std::string ip;
auto it = addr_map_.find(host_);
if (it != addr_map_.end()) { ip = it->second; }
if (it != addr_map_.end() && !it->second.empty()) {
if (detail::is_ip_address(it->second)) {
ip = it->second;
} else {
connect_host = it->second;
}
}
Error error;
sock_ = detail::create_client_socket(
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
connect_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);