|
@@ -2024,6 +2024,31 @@ inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf,
|
|
|
|
|
|
|
|
class decompressor;
|
|
class decompressor;
|
|
|
|
|
|
|
|
|
|
+enum class NoProxyKind {
|
|
|
|
|
+ Wildcard, // "*"
|
|
|
|
|
+ HostnameSuffix, // "example.com" or ".example.com"
|
|
|
|
|
+ IPv4Cidr, // "10.0.0.0/8" (or single IP, treated as /32)
|
|
|
|
|
+ IPv6Cidr, // "fe80::/10" (or single IP, treated as /128)
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address.
|
|
|
|
|
+// Lets one CIDR matcher cover both families.
|
|
|
|
|
+using IPBytes = std::array<uint8_t, 16>;
|
|
|
|
|
+
|
|
|
|
|
+struct NoProxyEntry {
|
|
|
|
|
+ NoProxyKind kind = NoProxyKind::Wildcard;
|
|
|
|
|
+ std::string hostname_pattern; // lowercased, leading/trailing dot stripped
|
|
|
|
|
+ IPBytes net{};
|
|
|
|
|
+ int prefix_bits = 0;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+struct NormalizedTarget {
|
|
|
|
|
+ std::string hostname; // lowercase; brackets and trailing dot removed
|
|
|
|
|
+ bool is_ipv4 = false;
|
|
|
|
|
+ bool is_ipv6 = false;
|
|
|
|
|
+ IPBytes ip{};
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
} // namespace detail
|
|
} // namespace detail
|
|
|
|
|
|
|
|
class ClientImpl {
|
|
class ClientImpl {
|
|
@@ -2240,6 +2265,7 @@ public:
|
|
|
void set_proxy_basic_auth(const std::string &username,
|
|
void set_proxy_basic_auth(const std::string &username,
|
|
|
const std::string &password);
|
|
const std::string &password);
|
|
|
void set_proxy_bearer_token_auth(const std::string &token);
|
|
void set_proxy_bearer_token_auth(const std::string &token);
|
|
|
|
|
+ void set_no_proxy(const std::vector<std::string> &patterns);
|
|
|
|
|
|
|
|
void set_logger(Logger logger);
|
|
void set_logger(Logger logger);
|
|
|
void set_error_logger(ErrorLogger error_logger);
|
|
void set_error_logger(ErrorLogger error_logger);
|
|
@@ -2265,16 +2291,19 @@ protected:
|
|
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
|
Response &res, bool &success, Error &error);
|
|
Response &res, bool &success, Error &error);
|
|
|
|
|
|
|
|
|
|
+ bool is_proxy_enabled_for_host(const std::string &host) const;
|
|
|
|
|
+
|
|
|
// All of:
|
|
// All of:
|
|
|
// shutdown_ssl
|
|
// shutdown_ssl
|
|
|
// shutdown_socket
|
|
// shutdown_socket
|
|
|
// close_socket
|
|
// close_socket
|
|
|
- // should ONLY be called when socket_mutex_ is locked.
|
|
|
|
|
- // Also, shutdown_ssl and close_socket should also NOT be called concurrently
|
|
|
|
|
- // with a DIFFERENT thread sending requests using that socket.
|
|
|
|
|
|
|
+ // disconnect
|
|
|
|
|
+ // should ONLY be called when socket_mutex_ is locked, and only when
|
|
|
|
|
+ // no other thread is using the socket.
|
|
|
virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
|
|
virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
|
|
|
void shutdown_socket(Socket &socket) const;
|
|
void shutdown_socket(Socket &socket) const;
|
|
|
void close_socket(Socket &socket);
|
|
void close_socket(Socket &socket);
|
|
|
|
|
+ void disconnect(bool gracefully);
|
|
|
|
|
|
|
|
bool process_request(Stream &strm, Request &req, Response &res,
|
|
bool process_request(Stream &strm, Request &req, Response &res,
|
|
|
bool close_connection, Error &error);
|
|
bool close_connection, Error &error);
|
|
@@ -2352,6 +2381,11 @@ protected:
|
|
|
std::string proxy_basic_auth_password_;
|
|
std::string proxy_basic_auth_password_;
|
|
|
std::string proxy_bearer_token_auth_token_;
|
|
std::string proxy_bearer_token_auth_token_;
|
|
|
|
|
|
|
|
|
|
+ std::vector<detail::NoProxyEntry> no_proxy_entries_;
|
|
|
|
|
+
|
|
|
|
|
+ mutable detail::NormalizedTarget host_normalized_;
|
|
|
|
|
+ mutable bool host_normalized_valid_ = false;
|
|
|
|
|
+
|
|
|
mutable std::mutex logger_mutex_;
|
|
mutable std::mutex logger_mutex_;
|
|
|
Logger logger_;
|
|
Logger logger_;
|
|
|
ErrorLogger error_logger_;
|
|
ErrorLogger error_logger_;
|
|
@@ -2612,6 +2646,7 @@ public:
|
|
|
void set_proxy_basic_auth(const std::string &username,
|
|
void set_proxy_basic_auth(const std::string &username,
|
|
|
const std::string &password);
|
|
const std::string &password);
|
|
|
void set_proxy_bearer_token_auth(const std::string &token);
|
|
void set_proxy_bearer_token_auth(const std::string &token);
|
|
|
|
|
+ void set_no_proxy(const std::vector<std::string> &patterns);
|
|
|
void set_logger(Logger logger);
|
|
void set_logger(Logger logger);
|
|
|
void set_error_logger(ErrorLogger error_logger);
|
|
void set_error_logger(ErrorLogger error_logger);
|
|
|
|
|
|
|
@@ -10513,6 +10548,176 @@ make_host_and_port_string_always_port(const std::string &host, int port) {
|
|
|
return prepare_host_string(host) + ":" + std::to_string(port);
|
|
return prepare_host_string(host) + ":" + std::to_string(port);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out);
|
|
|
|
|
+NormalizedTarget normalize_target(const std::string &host);
|
|
|
|
|
+bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits);
|
|
|
|
|
+bool host_matches_no_proxy(const NormalizedTarget &target,
|
|
|
|
|
+ const std::vector<NoProxyEntry> &entries);
|
|
|
|
|
+
|
|
|
|
|
+inline bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits) {
|
|
|
|
|
+ if (prefix_bits < 0 || prefix_bits > 128) { return false; }
|
|
|
|
|
+ if (prefix_bits == 0) { return true; }
|
|
|
|
|
+ int full_bytes = prefix_bits / 8;
|
|
|
|
|
+ int rem_bits = prefix_bits % 8;
|
|
|
|
|
+ if (full_bytes > 0 && std::memcmp(ip.data(), net.data(),
|
|
|
|
|
+ static_cast<size_t>(full_bytes)) != 0) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (rem_bits == 0) { return true; }
|
|
|
|
|
+ auto i = static_cast<size_t>(full_bytes);
|
|
|
|
|
+ auto mask = static_cast<uint8_t>(0xFFu << (8 - rem_bits));
|
|
|
|
|
+ return (ip[i] & mask) == (net[i] & mask);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
|
|
|
|
|
+ if (token.empty()) { return false; }
|
|
|
|
|
+
|
|
|
|
|
+ if (token == "*") {
|
|
|
|
|
+ out.kind = NoProxyKind::Wildcard;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto slash = token.find('/');
|
|
|
|
|
+ std::string addr_part =
|
|
|
|
|
+ (slash == std::string::npos) ? token : token.substr(0, slash);
|
|
|
|
|
+ std::string prefix_part =
|
|
|
|
|
+ (slash == std::string::npos) ? std::string() : token.substr(slash + 1);
|
|
|
|
|
+
|
|
|
|
|
+ // A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed;
|
|
|
|
|
+ // don't silently treat it as a /32 (or /128).
|
|
|
|
|
+ if (slash != std::string::npos && prefix_part.empty()) { return false; }
|
|
|
|
|
+
|
|
|
|
|
+ // Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the
|
|
|
|
|
+ // bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt
|
|
|
|
|
+ // when brackets are present.
|
|
|
|
|
+ bool bracketed = addr_part.size() >= 2 && addr_part.front() == '[' &&
|
|
|
|
|
+ addr_part.back() == ']';
|
|
|
|
|
+ if (bracketed) { addr_part = addr_part.substr(1, addr_part.size() - 2); }
|
|
|
|
|
+
|
|
|
|
|
+ if (!bracketed) {
|
|
|
|
|
+ struct in_addr v4;
|
|
|
|
|
+ if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) {
|
|
|
|
|
+ int prefix = 32;
|
|
|
|
|
+ if (!prefix_part.empty()) {
|
|
|
|
|
+ auto r = from_chars(prefix_part.data(),
|
|
|
|
|
+ prefix_part.data() + prefix_part.size(), prefix);
|
|
|
|
|
+ if (r.ec != std::errc{} ||
|
|
|
|
|
+ r.ptr != prefix_part.data() + prefix_part.size()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (prefix < 0 || prefix > 32) { return false; }
|
|
|
|
|
+ }
|
|
|
|
|
+ out.kind = NoProxyKind::IPv4Cidr;
|
|
|
|
|
+ std::memcpy(out.net.data(), &v4, sizeof(v4));
|
|
|
|
|
+ out.prefix_bits = prefix;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ struct in6_addr v6;
|
|
|
|
|
+ if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) {
|
|
|
|
|
+ int prefix = 128;
|
|
|
|
|
+ if (!prefix_part.empty()) {
|
|
|
|
|
+ auto r = from_chars(prefix_part.data(),
|
|
|
|
|
+ prefix_part.data() + prefix_part.size(), prefix);
|
|
|
|
|
+ if (r.ec != std::errc{} ||
|
|
|
|
|
+ r.ptr != prefix_part.data() + prefix_part.size()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (prefix < 0 || prefix > 128) { return false; }
|
|
|
|
|
+ }
|
|
|
|
|
+ out.kind = NoProxyKind::IPv6Cidr;
|
|
|
|
|
+ std::memcpy(out.net.data(), &v6, sizeof(v6));
|
|
|
|
|
+ out.prefix_bits = prefix;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Bracketed entries can only be IPv6. If the IPv6 parse above failed,
|
|
|
|
|
+ // the entry is malformed — don't fall through to the hostname branch.
|
|
|
|
|
+ if (bracketed) { return false; }
|
|
|
|
|
+
|
|
|
|
|
+ // A '/' on a non-IP token means a CIDR prefix without an address. Reject.
|
|
|
|
|
+ if (slash != std::string::npos) { return false; }
|
|
|
|
|
+ // Port-specific entries (host:port) are not supported.
|
|
|
|
|
+ if (token.find(':') != std::string::npos) { return false; }
|
|
|
|
|
+
|
|
|
|
|
+ std::string hostname = case_ignore::to_lower(token);
|
|
|
|
|
+ while (!hostname.empty() && hostname.front() == '.') {
|
|
|
|
|
+ hostname.erase(hostname.begin());
|
|
|
|
|
+ }
|
|
|
|
|
+ while (!hostname.empty() && hostname.back() == '.') {
|
|
|
|
|
+ hostname.pop_back();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hostname.empty()) { return false; }
|
|
|
|
|
+
|
|
|
|
|
+ out.kind = NoProxyKind::HostnameSuffix;
|
|
|
|
|
+ out.hostname_pattern = std::move(hostname);
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline NormalizedTarget normalize_target(const std::string &host) {
|
|
|
|
|
+ NormalizedTarget t;
|
|
|
|
|
+ std::string h = host;
|
|
|
|
|
+
|
|
|
|
|
+ if (h.size() >= 2 && h.front() == '[' && h.back() == ']') {
|
|
|
|
|
+ h = h.substr(1, h.size() - 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Strip a single trailing dot so "example.com." canonicalizes to
|
|
|
|
|
+ // "example.com".
|
|
|
|
|
+ if (!h.empty() && h.back() == '.') { h.pop_back(); }
|
|
|
|
|
+
|
|
|
|
|
+ t.hostname = case_ignore::to_lower(h);
|
|
|
|
|
+
|
|
|
|
|
+ if (!t.hostname.empty()) {
|
|
|
|
|
+ struct in_addr v4;
|
|
|
|
|
+ struct in6_addr v6;
|
|
|
|
|
+ if (inet_pton(AF_INET, t.hostname.c_str(), &v4) == 1) {
|
|
|
|
|
+ t.is_ipv4 = true;
|
|
|
|
|
+ std::memcpy(t.ip.data(), &v4, sizeof(v4));
|
|
|
|
|
+ } else if (inet_pton(AF_INET6, t.hostname.c_str(), &v6) == 1) {
|
|
|
|
|
+ t.is_ipv6 = true;
|
|
|
|
|
+ std::memcpy(t.ip.data(), &v6, sizeof(v6));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return t;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+inline bool host_matches_no_proxy(const NormalizedTarget &target,
|
|
|
|
|
+ const std::vector<NoProxyEntry> &entries) {
|
|
|
|
|
+ if (target.hostname.empty()) { return false; }
|
|
|
|
|
+ for (const auto &e : entries) {
|
|
|
|
|
+ switch (e.kind) {
|
|
|
|
|
+ case NoProxyKind::Wildcard: return true;
|
|
|
|
|
+ case NoProxyKind::IPv4Cidr:
|
|
|
|
|
+ if (target.is_ipv4 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case NoProxyKind::IPv6Cidr:
|
|
|
|
|
+ if (target.is_ipv6 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case NoProxyKind::HostnameSuffix:
|
|
|
|
|
+ if (target.is_ipv4 || target.is_ipv6) { break; }
|
|
|
|
|
+ if (target.hostname == e.hostname_pattern) { return true; }
|
|
|
|
|
+ // Dot-boundary suffix match: prevents "evilexample.com" from matching
|
|
|
|
|
+ // an entry of "example.com".
|
|
|
|
|
+ if (target.hostname.size() > e.hostname_pattern.size() + 1) {
|
|
|
|
|
+ auto offset = target.hostname.size() - e.hostname_pattern.size();
|
|
|
|
|
+ if (target.hostname[offset - 1] == '.' &&
|
|
|
|
|
+ target.hostname.compare(offset, e.hostname_pattern.size(),
|
|
|
|
|
+ e.hostname_pattern) == 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
template <typename T>
|
|
template <typename T>
|
|
|
inline bool check_and_write_headers(Stream &strm, Headers &headers,
|
|
inline bool check_and_write_headers(Stream &strm, Headers &headers,
|
|
|
T header_writer, Error &error) {
|
|
T header_writer, Error &error) {
|
|
@@ -12318,6 +12523,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
|
|
proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
|
|
proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
|
|
|
proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
|
|
proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
|
|
|
proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
|
|
proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
|
|
|
|
|
+ no_proxy_entries_ = rhs.no_proxy_entries_;
|
|
|
logger_ = rhs.logger_;
|
|
logger_ = rhs.logger_;
|
|
|
error_logger_ = rhs.error_logger_;
|
|
error_logger_ = rhs.error_logger_;
|
|
|
|
|
|
|
@@ -12333,8 +12539,25 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
|
|
#endif
|
|
#endif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+inline bool
|
|
|
|
|
+ClientImpl::is_proxy_enabled_for_host(const std::string &host) const {
|
|
|
|
|
+ if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
|
|
|
|
|
+ if (no_proxy_entries_.empty()) { return true; }
|
|
|
|
|
+ // host_ is const so its normalized form is invariant; cache it. The
|
|
|
|
|
+ // cross-host path (setup_redirect_client passing next_host) re-normalizes.
|
|
|
|
|
+ if (host == host_) {
|
|
|
|
|
+ if (!host_normalized_valid_) {
|
|
|
|
|
+ host_normalized_ = detail::normalize_target(host_);
|
|
|
|
|
+ host_normalized_valid_ = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return !detail::host_matches_no_proxy(host_normalized_, no_proxy_entries_);
|
|
|
|
|
+ }
|
|
|
|
|
+ auto target = detail::normalize_target(host);
|
|
|
|
|
+ return !detail::host_matches_no_proxy(target, no_proxy_entries_);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
|
inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
|
|
- if (!proxy_host_.empty() && proxy_port_ != -1) {
|
|
|
|
|
|
|
+ if (is_proxy_enabled_for_host(host_)) {
|
|
|
return detail::create_client_socket(
|
|
return detail::create_client_socket(
|
|
|
proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
|
|
proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
|
|
|
ipv6_v6only_, socket_options_, connection_timeout_sec_,
|
|
ipv6_v6only_, socket_options_, connection_timeout_sec_,
|
|
@@ -12406,6 +12629,12 @@ inline void ClientImpl::close_socket(Socket &socket) {
|
|
|
socket.sock = INVALID_SOCKET;
|
|
socket.sock = INVALID_SOCKET;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+inline void ClientImpl::disconnect(bool gracefully) {
|
|
|
|
|
+ shutdown_ssl(socket_, gracefully);
|
|
|
|
|
+ shutdown_socket(socket_);
|
|
|
|
|
+ close_socket(socket_);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
|
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
|
|
Response &res,
|
|
Response &res,
|
|
|
bool skip_100_continue) const {
|
|
bool skip_100_continue) const {
|
|
@@ -12477,14 +12706,8 @@ 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 like the other side has already closed the connection Also,
|
|
|
|
|
- // there cannot be any requests in flight from other threads since we
|
|
|
|
|
- // locked request_mutex_, so safe to close everything immediately
|
|
|
|
|
- const bool shutdown_gracefully = false;
|
|
|
|
|
- shutdown_ssl(socket_, shutdown_gracefully);
|
|
|
|
|
- shutdown_socket(socket_);
|
|
|
|
|
- close_socket(socket_);
|
|
|
|
|
|
|
+ // Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
|
|
|
|
|
+ disconnect(/*gracefully=*/false);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -12534,9 +12757,7 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
|
|
|
|
|
|
|
if (socket_should_be_closed_when_request_is_done_ || close_connection ||
|
|
if (socket_should_be_closed_when_request_is_done_ || close_connection ||
|
|
|
!ret) {
|
|
!ret) {
|
|
|
- shutdown_ssl(socket_, true);
|
|
|
|
|
- shutdown_socket(socket_);
|
|
|
|
|
- close_socket(socket_);
|
|
|
|
|
|
|
+ disconnect(/*gracefully=*/true);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -12649,11 +12870,7 @@ ClientImpl::open_stream(const std::string &method, const std::string &path,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
#endif
|
|
#endif
|
|
|
- if (!is_alive) {
|
|
|
|
|
- shutdown_ssl(socket_, false);
|
|
|
|
|
- shutdown_socket(socket_);
|
|
|
|
|
- close_socket(socket_);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!is_alive) { disconnect(/*gracefully=*/false); }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!is_alive) {
|
|
if (!is_alive) {
|
|
@@ -12945,7 +13162,7 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|
|
|
|
|
|
|
bool ret;
|
|
bool ret;
|
|
|
|
|
|
|
|
- if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
|
|
|
|
|
|
|
+ if (!is_ssl() && is_proxy_enabled_for_host(host_)) {
|
|
|
auto req2 = req;
|
|
auto req2 = req;
|
|
|
req2.path = "http://" +
|
|
req2.path = "http://" +
|
|
|
detail::make_host_and_port_string(host_, port_, false) +
|
|
detail::make_host_and_port_string(host_, port_, false) +
|
|
@@ -12969,9 +13186,7 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|
|
// to call it from a different thread since it's a thread-safety issue
|
|
// to call it from a different thread since it's a thread-safety issue
|
|
|
// to do these things to the socket if another thread is using the socket.
|
|
// to do these things to the socket if another thread is using the socket.
|
|
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
|
- shutdown_ssl(socket_, true);
|
|
|
|
|
- shutdown_socket(socket_);
|
|
|
|
|
- close_socket(socket_);
|
|
|
|
|
|
|
+ disconnect(/*gracefully=*/true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (300 < res.status && res.status < 400 && follow_location_) {
|
|
if (300 < res.status && res.status < 400 && follow_location_) {
|
|
@@ -12984,6 +13199,12 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|
|
res.status == StatusCode::ProxyAuthenticationRequired_407) &&
|
|
res.status == StatusCode::ProxyAuthenticationRequired_407) &&
|
|
|
req.authorization_count_ < 5) {
|
|
req.authorization_count_ < 5) {
|
|
|
auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
|
|
auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
|
|
|
|
|
+
|
|
|
|
|
+ // A 407 from a direct (NO_PROXY-bypassed) origin is meaningless and
|
|
|
|
|
+ // must not trigger a retry — that would let the origin extract the
|
|
|
|
|
+ // user's proxy digest credentials.
|
|
|
|
|
+ if (is_proxy && !is_proxy_enabled_for_host(host_)) { return ret; }
|
|
|
|
|
+
|
|
|
const auto &username =
|
|
const auto &username =
|
|
|
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
|
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
|
|
const auto &password =
|
|
const auto &password =
|
|
@@ -13151,13 +13372,13 @@ inline void ClientImpl::setup_redirect_client(ClientType &client) {
|
|
|
// host. This function is only called for cross-host redirects; same-host
|
|
// host. This function is only called for cross-host redirects; same-host
|
|
|
// redirects are handled directly in ClientImpl::redirect().
|
|
// redirects are handled directly in ClientImpl::redirect().
|
|
|
|
|
|
|
|
- // Setup proxy configuration (CRITICAL ORDER - proxy must be set
|
|
|
|
|
- // before proxy auth)
|
|
|
|
|
|
|
+ // Copy the proxy configuration unconditionally; the per-target bypass is
|
|
|
|
|
+ // re-evaluated at send time, so a later hop to a non-bypassed host can
|
|
|
|
|
+ // still use the proxy.
|
|
|
|
|
+ client.no_proxy_entries_ = no_proxy_entries_;
|
|
|
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
|
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
|
|
- // First set proxy host and port
|
|
|
|
|
client.set_proxy(proxy_host_, proxy_port_);
|
|
client.set_proxy(proxy_host_, proxy_port_);
|
|
|
|
|
|
|
|
- // Then set proxy authentication (order matters!)
|
|
|
|
|
if (!proxy_basic_auth_username_.empty()) {
|
|
if (!proxy_basic_auth_username_.empty()) {
|
|
|
client.set_proxy_basic_auth(proxy_basic_auth_username_,
|
|
client.set_proxy_basic_auth(proxy_basic_auth_username_,
|
|
|
proxy_basic_auth_password_);
|
|
proxy_basic_auth_password_);
|
|
@@ -13248,14 +13469,6 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!proxy_basic_auth_username_.empty() &&
|
|
|
|
|
- !proxy_basic_auth_password_.empty()) {
|
|
|
|
|
- if (!req.has_header("Proxy-Authorization")) {
|
|
|
|
|
- req.headers.insert(make_basic_authentication_header(
|
|
|
|
|
- proxy_basic_auth_username_, proxy_basic_auth_password_, true));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
if (!bearer_token_auth_token_.empty()) {
|
|
if (!bearer_token_auth_token_.empty()) {
|
|
|
if (!req.has_header("Authorization")) {
|
|
if (!req.has_header("Authorization")) {
|
|
|
req.headers.insert(make_bearer_token_authentication_header(
|
|
req.headers.insert(make_bearer_token_authentication_header(
|
|
@@ -13263,8 +13476,18 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!proxy_bearer_token_auth_token_.empty()) {
|
|
|
|
|
- if (!req.has_header("Proxy-Authorization")) {
|
|
|
|
|
|
|
+ // Proxy-Authorization is only sent when the proxy is actually used for
|
|
|
|
|
+ // this target — otherwise NO_PROXY-matched requests would leak proxy
|
|
|
|
|
+ // credentials directly to the destination server.
|
|
|
|
|
+ if (is_proxy_enabled_for_host(host_)) {
|
|
|
|
|
+ if (!proxy_basic_auth_username_.empty() &&
|
|
|
|
|
+ !proxy_basic_auth_password_.empty() &&
|
|
|
|
|
+ !req.has_header("Proxy-Authorization")) {
|
|
|
|
|
+ req.headers.insert(make_basic_authentication_header(
|
|
|
|
|
+ proxy_basic_auth_username_, proxy_basic_auth_password_, true));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!proxy_bearer_token_auth_token_.empty() &&
|
|
|
|
|
+ !req.has_header("Proxy-Authorization")) {
|
|
|
req.headers.insert(make_bearer_token_authentication_header(
|
|
req.headers.insert(make_bearer_token_authentication_header(
|
|
|
proxy_bearer_token_auth_token_, true));
|
|
proxy_bearer_token_auth_token_, true));
|
|
|
}
|
|
}
|
|
@@ -13574,7 +13797,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
|
|
|
|
|
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
if (is_ssl() && !expect_100_continue) {
|
|
if (is_ssl() && !expect_100_continue) {
|
|
|
- auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
|
|
|
|
|
|
|
+ auto is_proxy_enabled = is_proxy_enabled_for_host(host_);
|
|
|
if (!is_proxy_enabled) {
|
|
if (!is_proxy_enabled) {
|
|
|
if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
|
|
if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
|
|
|
error = Error::SSLPeerCouldBeClosed_;
|
|
error = Error::SSLPeerCouldBeClosed_;
|
|
@@ -14581,10 +14804,7 @@ inline void ClientImpl::stop() {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Otherwise, still holding the mutex, we can shut everything down ourselves
|
|
|
|
|
- shutdown_ssl(socket_, true);
|
|
|
|
|
- shutdown_socket(socket_);
|
|
|
|
|
- close_socket(socket_);
|
|
|
|
|
|
|
+ disconnect(/*gracefully=*/true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
inline std::string ClientImpl::host() const { return host_; }
|
|
inline std::string ClientImpl::host() const { return host_; }
|
|
@@ -14675,6 +14895,8 @@ inline void ClientImpl::set_interface(const std::string &intf) {
|
|
|
inline void ClientImpl::set_proxy(const std::string &host, int port) {
|
|
inline void ClientImpl::set_proxy(const std::string &host, int port) {
|
|
|
proxy_host_ = host;
|
|
proxy_host_ = host;
|
|
|
proxy_port_ = port;
|
|
proxy_port_ = port;
|
|
|
|
|
+ std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
|
|
|
+ disconnect(/*gracefully=*/true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
|
|
inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
|
|
@@ -14687,6 +14909,22 @@ inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
|
|
|
proxy_bearer_token_auth_token_ = token;
|
|
proxy_bearer_token_auth_token_ = token;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+inline void ClientImpl::set_no_proxy(const std::vector<std::string> &patterns) {
|
|
|
|
|
+ std::vector<detail::NoProxyEntry> parsed;
|
|
|
|
|
+ parsed.reserve(patterns.size());
|
|
|
|
|
+ for (const auto &p : patterns) {
|
|
|
|
|
+ auto trimmed = detail::trim_copy(p);
|
|
|
|
|
+ if (trimmed.empty()) { continue; }
|
|
|
|
|
+ detail::NoProxyEntry entry;
|
|
|
|
|
+ if (detail::parse_no_proxy_entry(trimmed, entry)) {
|
|
|
|
|
+ parsed.push_back(std::move(entry));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ no_proxy_entries_ = std::move(parsed);
|
|
|
|
|
+ std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
|
|
|
+ disconnect(/*gracefully=*/true);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
inline void ClientImpl::set_digest_auth(const std::string &username,
|
|
inline void ClientImpl::set_digest_auth(const std::string &username,
|
|
|
const std::string &password) {
|
|
const std::string &password) {
|
|
@@ -15388,6 +15626,9 @@ inline void Client::set_proxy_basic_auth(const std::string &username,
|
|
|
inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
|
|
inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
|
|
|
cli_->set_proxy_bearer_token_auth(token);
|
|
cli_->set_proxy_bearer_token_auth(token);
|
|
|
}
|
|
}
|
|
|
|
|
+inline void Client::set_no_proxy(const std::vector<std::string> &patterns) {
|
|
|
|
|
+ cli_->set_no_proxy(patterns);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
inline void Client::set_logger(Logger logger) {
|
|
inline void Client::set_logger(Logger logger) {
|
|
|
cli_->set_logger(std::move(logger));
|
|
cli_->set_logger(std::move(logger));
|
|
@@ -15617,7 +15858,7 @@ inline bool SSLClient::setup_proxy_connection(
|
|
|
Socket &socket,
|
|
Socket &socket,
|
|
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
|
Response &res, bool &success, Error &error) {
|
|
Response &res, bool &success, Error &error) {
|
|
|
- if (proxy_host_.empty() || proxy_port_ == -1) { return true; }
|
|
|
|
|
|
|
+ if (!is_proxy_enabled_for_host(host_)) { return true; }
|
|
|
|
|
|
|
|
if (!connect_with_proxy(socket, start_time, res, success, error)) {
|
|
if (!connect_with_proxy(socket, start_time, res, success, error)) {
|
|
|
return false;
|
|
return false;
|
|
@@ -15730,7 +15971,7 @@ inline bool SSLClient::connect_with_proxy(
|
|
|
inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
|
|
inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
|
|
|
if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
|
|
if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
|
|
|
|
|
|
|
|
- if (!proxy_host_.empty() && proxy_port_ != -1) { return true; }
|
|
|
|
|
|
|
+ if (is_proxy_enabled_for_host(host_)) { return true; }
|
|
|
|
|
|
|
|
if (!initialize_ssl(socket, error)) {
|
|
if (!initialize_ssl(socket, error)) {
|
|
|
shutdown_socket(socket);
|
|
shutdown_socket(socket);
|