diff --git a/Net/include/Poco/Net/HTTPClientSession.h b/Net/include/Poco/Net/HTTPClientSession.h index 1c3ddb8b19..2e94187a3a 100644 --- a/Net/include/Poco/Net/HTTPClientSession.h +++ b/Net/include/Poco/Net/HTTPClientSession.h @@ -21,9 +21,11 @@ #include "Poco/Net/IPAddress.h" #include "Poco/Net/Net.h" #include "Poco/Net/HTTPSession.h" +#include "Poco/Net/HTTPSessionFactory.h" #include "Poco/Net/HTTPBasicCredentials.h" #include "Poco/Net/HTTPDigestCredentials.h" #include "Poco/Net/HTTPNTLMCredentials.h" +#include "Poco/Net/ProxyConfig.h" #include "Poco/Net/SocketAddress.h" #include "Poco/SharedPtr.h" #include @@ -32,7 +34,6 @@ namespace Poco::Net { - class HTTPRequest; class HTTPResponse; @@ -65,40 +66,6 @@ class Net_API HTTPClientSession: public HTTPSession /// set up a session through a proxy. { public: - enum ProxyAuthentication - { - PROXY_AUTH_NONE, /// No proxy authentication - PROXY_AUTH_HTTP_BASIC, /// HTTP Basic proxy authentication (default, if username and password are supplied) - PROXY_AUTH_HTTP_DIGEST, /// HTTP Digest proxy authentication - PROXY_AUTH_NTLM /// NTLMv2 proxy authentication - }; - - struct ProxyConfig - /// HTTP proxy server configuration. - { - ProxyConfig(): - port(HTTP_PORT), - authMethod(PROXY_AUTH_HTTP_BASIC) - { - } - - std::string host; - /// Proxy server host name or IP address. - Poco::UInt16 port; - /// Proxy server TCP port. - std::string username; - /// Proxy server username. - std::string password; - /// Proxy server password. - std::string nonProxyHosts; - /// A regular expression defining hosts for which the proxy should be bypassed, - /// e.g. "localhost|127\.0\.0\.1|192\.168\.0\.\d+". Can also be an empty - /// string to disable proxy bypassing. - - ProxyAuthentication authMethod; - /// The authentication method to use - HTTP Basic or NTLM. - }; - HTTPClientSession(); /// Creates an unconnected HTTPClientSession. @@ -167,8 +134,8 @@ class Net_API HTTPClientSession: public HTTPSession const SocketAddress& getSourceAddress6(); /// Returns the last IPV6 source address set with setSourceAddress - void setProxy(const std::string& host, Poco::UInt16 port = HTTPSession::HTTP_PORT); - /// Sets the proxy host name and port number. + void setProxy(const std::string& host, Poco::UInt16 port = HTTPSession::HTTP_PORT, const std::string& protocol = "http", bool tunnel = true); + /// Sets the proxy host name, port number, protocol (http or https) and tunnel behaviour. void setProxyHost(const std::string& host); /// Sets the host name of the proxy server. @@ -176,12 +143,24 @@ class Net_API HTTPClientSession: public HTTPSession void setProxyPort(Poco::UInt16 port); /// Sets the port number of the proxy server. + void setProxyProtocol(const std::string& protocol); + /// Sets the proxy protocol (http or https). + + void setProxyTunnel(bool tunnel); + /// If 'true' proxy will be used as tunnel. + const std::string& getProxyHost() const; /// Returns the proxy host name. Poco::UInt16 getProxyPort() const; /// Returns the proxy port number. + const std::string& getProxyProtocol() const; + /// Returns the proxy protocol. + + bool isProxyTunnel() const; + /// Returns 'true' if proxy is configured as tunnel. + void setProxyCredentials(const std::string& username, const std::string& password); /// Sets the username and password for proxy authentication. /// Only Basic authentication is supported. @@ -203,7 +182,7 @@ class Net_API HTTPClientSession: public HTTPSession void setProxyConfig(const ProxyConfig& config); /// Sets the proxy configuration. - const ProxyConfig& getProxyConfig() const; + [[nodiscard]] const ProxyConfig& getProxyConfig() const; /// Returns the proxy configuration. static void setGlobalProxyConfig(const ProxyConfig& config); @@ -216,7 +195,7 @@ class Net_API HTTPClientSession: public HTTPSession /// The global proxy configuration should be set at start up, before /// the first HTTPClientSession instance is created. - static const ProxyConfig& getGlobalProxyConfig(); + [[nodiscard]] static const ProxyConfig& getGlobalProxyConfig(); /// Returns the global proxy configuration. void setKeepAliveTimeout(const Poco::Timespan& timeout); @@ -307,11 +286,11 @@ class Net_API HTTPClientSession: public HTTPSession /// the request or response stream changes into /// fail or bad state, but not eof state). - virtual bool secure() const; + [[nodiscard]] virtual bool secure() const; /// Return true iff the session uses SSL or TLS, /// or false otherwise. - bool bypassProxy() const; + [[nodiscard]] bool bypassProxy() const; /// Returns true if the proxy should be bypassed /// for the current host. @@ -365,6 +344,9 @@ class Net_API HTTPClientSession: public HTTPSession /// Calls proxyConnect() and attaches the resulting StreamSocket /// to the HTTPClientSession. + HTTPSessionFactory _proxySessionFactory; + /// Factory to create HTTPClientSession to proxy. + private: using OStreamPtr = Poco::SharedPtr; using IStreamPtr = Poco::SharedPtr; @@ -390,8 +372,11 @@ class Net_API HTTPClientSession: public HTTPSession static ProxyConfig _globalProxyConfig; - HTTPClientSession(const HTTPClientSession&); - HTTPClientSession& operator = (const HTTPClientSession&); + void initProxySessionFactory(); + /// Registers the "http" protocol with _proxySessionFactory. + + HTTPClientSession(const HTTPClientSession&) = delete; + HTTPClientSession& operator = (const HTTPClientSession&) = delete; friend class WebSocket; }; @@ -424,6 +409,18 @@ inline Poco::UInt16 HTTPClientSession::getProxyPort() const } +inline const std::string& HTTPClientSession::getProxyProtocol() const +{ + return _proxyConfig.protocol; +} + + +[[nodiscard]] inline bool HTTPClientSession::isProxyTunnel() const +{ + return _proxyConfig.tunnel; +} + + inline const std::string& HTTPClientSession::getProxyUsername() const { return _proxyConfig.username; @@ -436,13 +433,13 @@ inline const std::string& HTTPClientSession::getProxyPassword() const } -inline const HTTPClientSession::ProxyConfig& HTTPClientSession::getProxyConfig() const +inline const ProxyConfig& HTTPClientSession::getProxyConfig() const { return _proxyConfig; } -inline const HTTPClientSession::ProxyConfig& HTTPClientSession::getGlobalProxyConfig() +inline const ProxyConfig& HTTPClientSession::getGlobalProxyConfig() { return _globalProxyConfig; } diff --git a/Net/include/Poco/Net/HTTPSessionFactory.h b/Net/include/Poco/Net/HTTPSessionFactory.h index 324d47afb8..60237254c5 100644 --- a/Net/include/Poco/Net/HTTPSessionFactory.h +++ b/Net/include/Poco/Net/HTTPSessionFactory.h @@ -19,7 +19,7 @@ #include "Poco/Net/Net.h" -#include "Poco/Net/HTTPClientSession.h" +#include "Poco/Net/ProxyConfig.h" #include "Poco/Mutex.h" #include "Poco/URI.h" #include "Poco/SingletonHolder.h" @@ -29,7 +29,7 @@ namespace Poco::Net { - +class HTTPClientSession; class HTTPSessionInstantiator; @@ -51,7 +51,7 @@ class Net_API HTTPSessionFactory HTTPSessionFactory(const std::string& proxyHost, Poco::UInt16 proxyPort); /// Creates the HTTPSessionFactory and sets the proxy host and port. - HTTPSessionFactory(const HTTPClientSession::ProxyConfig& proxyConfig); + HTTPSessionFactory(const ProxyConfig& proxyConfig); /// Creates the HTTPSessionFactory and sets the proxy configuration. ~HTTPSessionFactory(); @@ -96,10 +96,10 @@ class Net_API HTTPSessionFactory const std::string& proxyPassword() const; /// Returns the password for proxy authorization. - void setProxyConfig(const HTTPClientSession::ProxyConfig& proxyConfig); + void setProxyConfig(const ProxyConfig& proxyConfig); /// Sets the proxy configuration. - const HTTPClientSession::ProxyConfig& getProxyConfig() const; + const ProxyConfig& getProxyConfig() const; /// Returns the proxy configuration. static HTTPSessionFactory& defaultFactory(); @@ -121,7 +121,7 @@ class Net_API HTTPSessionFactory typedef std::map Instantiators; Instantiators _instantiators; - HTTPClientSession::ProxyConfig _proxyConfig; + ProxyConfig _proxyConfig; mutable Poco::FastMutex _mutex; }; @@ -154,7 +154,7 @@ inline const std::string& HTTPSessionFactory::proxyPassword() const } -inline const HTTPClientSession::ProxyConfig& HTTPSessionFactory::getProxyConfig() const +inline const ProxyConfig& HTTPSessionFactory::getProxyConfig() const { return _proxyConfig; } diff --git a/Net/include/Poco/Net/HTTPSessionInstantiator.h b/Net/include/Poco/Net/HTTPSessionInstantiator.h index 131a73b229..f8e603ce82 100644 --- a/Net/include/Poco/Net/HTTPSessionInstantiator.h +++ b/Net/include/Poco/Net/HTTPSessionInstantiator.h @@ -51,14 +51,14 @@ class Net_API HTTPSessionInstantiator /// Unregisters the factory with the global HTTPSessionFactory. protected: - void setProxyConfig(const HTTPClientSession::ProxyConfig& proxyConfig); + void setProxyConfig(const ProxyConfig& proxyConfig); /// Sets the proxy configuration. - const HTTPClientSession::ProxyConfig& getProxyConfig() const; + const ProxyConfig& getProxyConfig() const; /// Returns the proxy configuration. private: - HTTPClientSession::ProxyConfig _proxyConfig; + ProxyConfig _proxyConfig; friend class HTTPSessionFactory; }; @@ -67,7 +67,7 @@ class Net_API HTTPSessionInstantiator // // inlines // -inline const HTTPClientSession::ProxyConfig& HTTPSessionInstantiator::getProxyConfig() const +inline const ProxyConfig& HTTPSessionInstantiator::getProxyConfig() const { return _proxyConfig; } diff --git a/Net/include/Poco/Net/ProxyConfig.h b/Net/include/Poco/Net/ProxyConfig.h new file mode 100644 index 0000000000..fc0aa6bcb9 --- /dev/null +++ b/Net/include/Poco/Net/ProxyConfig.h @@ -0,0 +1,69 @@ +// +// ProxyConfig.h +// +// Library: Net +// Package: HTTP +// Module: ProxyConfig +// +// Definition of the ProxyConfig class. +// +// Copyright (c) 2026-2026, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + +#ifndef Net_ProxyConfig_INCLUDED +#define Net_ProxyConfig_INCLUDED + +#include "Poco/Net/Net.h" +#include "Poco/Net/HTTPSession.h" + +namespace Poco { +namespace Net { + + +enum class ProxyAuthentication +{ + None, /// No proxy authentication + Basic, /// HTTP Basic proxy authentication (default, if username and password are supplied) + Digest, /// HTTP Digest proxy authentication + NTLM /// NTLMv2 proxy authentication +}; + + +struct ProxyConfig + /// HTTP proxy server configuration. +{ + ProxyConfig() = default; + + std::string host; + /// Proxy server host name or IP address. + Poco::UInt16 port = HTTPSession::HTTP_PORT; + /// Proxy server TCP port. + std::string protocol = "http"; + /// Protocol to use (http or https). + bool tunnel = true; + /// Use proxy as tunnel (establish 2-way communication through CONNECT request). + /// If tunnel option is 'false' request will be sent directly to proxy without CONNECT request. + /// + /// Warning: Setting tunnel to false for HTTPS sessions means the TLS connection + /// terminates at the proxy, not at the destination server. The proxy will see + /// the request in plaintext. + std::string username; + /// Proxy server username. + std::string password; + /// Proxy server password. + std::string nonProxyHosts; + /// A regular expression defining hosts for which the proxy should be bypassed, + /// e.g. "localhost|127\.0\.0\.1|192\.168\.0\.\d+". Can also be an empty + /// string to disable proxy bypassing. + + ProxyAuthentication authMethod = ProxyAuthentication::Basic; + /// The authentication method to use - HTTP Basic or NTLM. +}; + + +} } // namespace Poco::Net + +#endif // Net_ProxyConfig_INCLUDED diff --git a/Net/src/HTTPClientSession.cpp b/Net/src/HTTPClientSession.cpp index 4d716cd22c..71ba7d9f01 100644 --- a/Net/src/HTTPClientSession.cpp +++ b/Net/src/HTTPClientSession.cpp @@ -13,6 +13,8 @@ #include "Poco/Net/HTTPClientSession.h" +#include "Poco/Net/HTTPSessionFactory.h" +#include "Poco/Net/HTTPSessionInstantiator.h" #include "Poco/Net/HTTPRequest.h" #include "Poco/Net/HTTPResponse.h" #include "Poco/Net/HTTPHeaderStream.h" @@ -29,12 +31,13 @@ using Poco::NumberFormatter; using Poco::IllegalStateException; +using Poco::InvalidArgumentException; namespace Poco::Net { -HTTPClientSession::ProxyConfig HTTPClientSession::_globalProxyConfig; +ProxyConfig HTTPClientSession::_globalProxyConfig; HTTPClientSession::HTTPClientSession(): @@ -49,6 +52,7 @@ HTTPClientSession::HTTPClientSession(): _responseReceived(false), _ntlmProxyAuthenticated(false) { + initProxySessionFactory(); } @@ -65,6 +69,7 @@ HTTPClientSession::HTTPClientSession(const StreamSocket& socket): _responseReceived(false), _ntlmProxyAuthenticated(false) { + initProxySessionFactory(); } @@ -81,6 +86,7 @@ HTTPClientSession::HTTPClientSession(const SocketAddress& address): _responseReceived(false), _ntlmProxyAuthenticated(false) { + initProxySessionFactory(); } @@ -97,12 +103,15 @@ HTTPClientSession::HTTPClientSession(const std::string& host, Poco::UInt16 port) _responseReceived(false), _ntlmProxyAuthenticated(false) { + initProxySessionFactory(); } HTTPClientSession::HTTPClientSession(const std::string& host, Poco::UInt16 port, const ProxyConfig& proxyConfig): _host(host), _port(port), + _sourceAddress4(IPAddress::wildcard(IPAddress::IPv4), 0), + _sourceAddress6(IPAddress::wildcard(IPAddress::IPv6), 0), _proxyConfig(proxyConfig), _keepAliveTimeout(DEFAULT_KEEP_ALIVE_TIMEOUT, 0), _reconnect(false), @@ -111,6 +120,7 @@ HTTPClientSession::HTTPClientSession(const std::string& host, Poco::UInt16 port, _responseReceived(false), _ntlmProxyAuthenticated(false) { + initProxySessionFactory(); } @@ -124,13 +134,28 @@ HTTPClientSession::HTTPClientSession(const StreamSocket& socket, const ProxyConf _reconnect(false), _mustReconnect(false), _expectResponseBody(false), - _responseReceived(false) + _responseReceived(false), + _ntlmProxyAuthenticated(false) { + initProxySessionFactory(); } HTTPClientSession::~HTTPClientSession() { + try + { + _proxySessionFactory.unregisterProtocol("http"); + } + catch (...) + { + } +} + + +void HTTPClientSession::initProxySessionFactory() +{ + _proxySessionFactory.registerProtocol("http", new HTTPSessionInstantiator); } @@ -185,14 +210,19 @@ const SocketAddress& HTTPClientSession::getSourceAddress6() } -void HTTPClientSession::setProxy(const std::string& host, Poco::UInt16 port) +void HTTPClientSession::setProxy(const std::string& host, Poco::UInt16 port, const std::string& protocol, bool tunnel) { + if (protocol != "http" && protocol != "https") + throw InvalidArgumentException("Protocol must be either http or https"); + if (!connected()) { _proxyConfig.host = host; _proxyConfig.port = port; + _proxyConfig.protocol = protocol; + _proxyConfig.tunnel = tunnel; } - else throw IllegalStateException("Cannot set the proxy host and port for an already connected session"); + else throw IllegalStateException("Cannot set the proxy host, port and protocol for an already connected session"); } @@ -214,6 +244,27 @@ void HTTPClientSession::setProxyPort(Poco::UInt16 port) } +void HTTPClientSession::setProxyProtocol(const std::string& protocol) +{ + if (protocol != "http" && protocol != "https") + throw InvalidArgumentException("Protocol must be either http or https"); + + if (!connected()) + _proxyConfig.protocol = protocol; + else + throw IllegalStateException("Cannot set the proxy protocol for an already connected session"); +} + + +void HTTPClientSession::setProxyTunnel(bool tunnel) +{ + if (!connected()) + _proxyConfig.tunnel = tunnel; + else + throw IllegalStateException("Cannot set the proxy tunnel for an already connected session"); +} + + void HTTPClientSession::setProxyCredentials(const std::string& username, const std::string& password) { _proxyConfig.username = username; @@ -235,12 +286,21 @@ void HTTPClientSession::setProxyPassword(const std::string& password) void HTTPClientSession::setProxyConfig(const ProxyConfig& config) { - _proxyConfig = config; + if (config.protocol != "http" && config.protocol != "https") + throw InvalidArgumentException("Protocol must be either http or https"); + + if (!connected()) + _proxyConfig = config; + else + throw IllegalStateException("Cannot set the proxy configuration for an already connected session"); } void HTTPClientSession::setGlobalProxyConfig(const ProxyConfig& config) { + if (config.protocol != "http" && config.protocol != "https") + throw InvalidArgumentException("Protocol must be either http or https"); + _globalProxyConfig = config; } @@ -474,8 +534,13 @@ std::string HTTPClientSession::proxyRequestPrefix() const { std::string result("http://"); result.append(_host); - result.append(":"); - NumberFormatter::append(result, _port); + // Do not append default port, since this may break some servers. + // One example of such server is GCS (Google Cloud Storage). + if (_port != HTTPSession::HTTP_PORT) + { + result.append(":"); + NumberFormatter::append(result, _port); + } return result; } @@ -501,16 +566,16 @@ void HTTPClientSession::proxyAuthenticateImpl(HTTPRequest& request, const ProxyC { switch (proxyConfig.authMethod) { - case PROXY_AUTH_NONE: + case ProxyAuthentication::None: break; - case PROXY_AUTH_HTTP_BASIC: + case ProxyAuthentication::Basic: _proxyBasicCreds.setUsername(proxyConfig.username); _proxyBasicCreds.setPassword(proxyConfig.password); _proxyBasicCreds.proxyAuthenticate(request); break; - case PROXY_AUTH_HTTP_DIGEST: + case ProxyAuthentication::Digest: if (HTTPCredentials::hasDigestCredentials(request)) { _proxyDigestCreds.updateProxyAuthInfo(request); @@ -523,7 +588,7 @@ void HTTPClientSession::proxyAuthenticateImpl(HTTPRequest& request, const ProxyC } break; - case PROXY_AUTH_NTLM: + case ProxyAuthentication::NTLM: if (_ntlmProxyAuthenticated) { _proxyNTLMCreds.updateProxyAuthInfo(request); @@ -581,25 +646,30 @@ void HTTPClientSession::sendChallengeRequest(const HTTPRequest& request, HTTPRes StreamSocket HTTPClientSession::proxyConnect() { - ProxyConfig emptyProxyConfig; - HTTPClientSession proxySession(getProxyHost(), getProxyPort(), emptyProxyConfig); - proxySession.setTimeout(getTimeout()); + URI proxyUri; + proxyUri.setScheme(getProxyProtocol()); + proxyUri.setHost(getProxyHost()); + proxyUri.setPort(getProxyPort()); + + SharedPtr proxySession (_proxySessionFactory.createClientSession(proxyUri)); + + proxySession->setTimeout(getTimeout()); std::string targetAddress(_host); targetAddress.append(":"); NumberFormatter::append(targetAddress, _port); HTTPRequest proxyRequest(HTTPRequest::HTTP_CONNECT, targetAddress, HTTPMessage::HTTP_1_1); HTTPResponse proxyResponse; proxyRequest.set(HTTPMessage::PROXY_CONNECTION, HTTPMessage::CONNECTION_KEEP_ALIVE); - proxyRequest.set(HTTPRequest::HOST, getHost()); - proxySession.proxyAuthenticateImpl(proxyRequest, _proxyConfig); - proxySession.setKeepAlive(true); - proxySession.setSourceAddress(_sourceAddress4); - proxySession.setSourceAddress(_sourceAddress6); - proxySession.sendRequest(proxyRequest); - proxySession.receiveResponse(proxyResponse); + proxyRequest.set(HTTPRequest::HOST, targetAddress); + proxySession->proxyAuthenticateImpl(proxyRequest, _proxyConfig); + proxySession->setKeepAlive(true); + proxySession->setSourceAddress(_sourceAddress4); + proxySession->setSourceAddress(_sourceAddress6); + proxySession->sendRequest(proxyRequest); + proxySession->receiveResponse(proxyResponse); if (proxyResponse.getStatus() != HTTPResponse::HTTP_OK) - throw HTTPException("Cannot establish proxy connection", proxyResponse.getReason()); - return proxySession.detachSocket(); + throw HTTPException("Cannot establish proxy connection (" + std::to_string(proxyResponse.getStatus()) + ")", proxyResponse.getReason()); + return proxySession->detachSocket(); } @@ -619,5 +689,4 @@ bool HTTPClientSession::bypassProxy() const else return false; } - } // namespace Poco::Net diff --git a/Net/src/HTTPSessionFactory.cpp b/Net/src/HTTPSessionFactory.cpp index fb723de535..d16ed5432c 100644 --- a/Net/src/HTTPSessionFactory.cpp +++ b/Net/src/HTTPSessionFactory.cpp @@ -37,7 +37,7 @@ HTTPSessionFactory::HTTPSessionFactory(const std::string& proxyHost, Poco::UInt1 } -HTTPSessionFactory::HTTPSessionFactory(const HTTPClientSession::ProxyConfig& proxyConfig): +HTTPSessionFactory::HTTPSessionFactory(const ProxyConfig& proxyConfig): _proxyConfig(proxyConfig) { } @@ -127,7 +127,7 @@ void HTTPSessionFactory::setProxyCredentials(const std::string& username, const } -void HTTPSessionFactory::setProxyConfig(const HTTPClientSession::ProxyConfig& proxyConfig) +void HTTPSessionFactory::setProxyConfig(const ProxyConfig& proxyConfig) { FastMutex::ScopedLock lock(_mutex); diff --git a/Net/src/HTTPSessionInstantiator.cpp b/Net/src/HTTPSessionInstantiator.cpp index 1908d8ae93..2cb71cc5c3 100644 --- a/Net/src/HTTPSessionInstantiator.cpp +++ b/Net/src/HTTPSessionInstantiator.cpp @@ -57,7 +57,7 @@ void HTTPSessionInstantiator::unregisterInstantiator() } -void HTTPSessionInstantiator::setProxyConfig(const HTTPClientSession::ProxyConfig& proxyConfig) +void HTTPSessionInstantiator::setProxyConfig(const ProxyConfig& proxyConfig) { _proxyConfig = proxyConfig; } diff --git a/Net/testsuite/src/HTTPClientSessionTest.cpp b/Net/testsuite/src/HTTPClientSessionTest.cpp index aed5893e54..d6bbf7e55c 100644 --- a/Net/testsuite/src/HTTPClientSessionTest.cpp +++ b/Net/testsuite/src/HTTPClientSessionTest.cpp @@ -24,10 +24,15 @@ using Poco::Net::HTTPClientSession; +using Poco::Net::HTTPSession; +using Poco::Net::ProxyConfig; +using Poco::Net::ProxyAuthentication; using Poco::Net::HTTPRequest; using Poco::Net::HTTPResponse; using Poco::Net::HTTPMessage; using Poco::StreamCopier; +using Poco::InvalidArgumentException; +using Poco::IllegalStateException; using Poco::File; using Poco::Path; @@ -294,7 +299,7 @@ void HTTPClientSessionTest::testProxyAuth() void HTTPClientSessionTest::testBypassProxy() { - HTTPClientSession::ProxyConfig proxyConfig; + ProxyConfig proxyConfig; proxyConfig.host = "proxy.domain.com"; proxyConfig.port = 80; proxyConfig.nonProxyHosts = "localhost|127\\.0\\.0\\.1"; @@ -354,6 +359,231 @@ void HTTPClientSessionTest::testExpectContinueFail() } +void HTTPClientSessionTest::testProxyConfig() +{ + ProxyConfig config; + assertTrue (config.host.empty()); + assertTrue (config.port == HTTPSession::HTTP_PORT); + assertTrue (config.protocol == "http"); + assertTrue (config.tunnel == true); + assertTrue (config.username.empty()); + assertTrue (config.password.empty()); + assertTrue (config.nonProxyHosts.empty()); + assertTrue (config.authMethod == ProxyAuthentication::Basic); +} + + +void HTTPClientSessionTest::testSetProxyProtocolValidation() +{ + HTTPClientSession s("www.example.com"); + + try + { + s.setProxy("proxy", 80, "ftp", true); + fail("must throw InvalidArgumentException"); + } + catch (InvalidArgumentException&) + { + } + + try + { + s.setProxy("proxy", 80, "", true); + fail("must throw InvalidArgumentException"); + } + catch (InvalidArgumentException&) + { + } + + try + { + s.setProxyProtocol("socks"); + fail("must throw InvalidArgumentException"); + } + catch (InvalidArgumentException&) + { + } + + s.setProxy("proxy", 80, "http", true); + assertTrue (s.getProxyProtocol() == "http"); + + s.setProxy("proxy", 80, "https", true); + assertTrue (s.getProxyProtocol() == "https"); +} + + +void HTTPClientSessionTest::testSetProxyConfigValidation() +{ + HTTPClientSession s("www.example.com"); + + ProxyConfig badConfig; + badConfig.host = "proxy"; + badConfig.protocol = "ftp"; + + try + { + s.setProxyConfig(badConfig); + fail("must throw InvalidArgumentException"); + } + catch (InvalidArgumentException&) + { + } + + ProxyConfig goodConfig; + goodConfig.host = "proxy"; + goodConfig.protocol = "http"; + s.setProxyConfig(goodConfig); + assertTrue (s.getProxyHost() == "proxy"); + + goodConfig.protocol = "https"; + s.setProxyConfig(goodConfig); + assertTrue (s.getProxyProtocol() == "https"); +} + + +void HTTPClientSessionTest::testProxySetters() +{ + HTTPClientSession s("www.example.com"); + + s.setProxyHost("proxy.example.com"); + assertTrue (s.getProxyHost() == "proxy.example.com"); + + s.setProxyPort(8080); + assertTrue (s.getProxyPort() == 8080); + + s.setProxyProtocol("https"); + assertTrue (s.getProxyProtocol() == "https"); + + s.setProxyTunnel(false); + assertTrue (s.isProxyTunnel() == false); + + s.setProxyCredentials("user", "pass"); + assertTrue (s.getProxyUsername() == "user"); + assertTrue (s.getProxyPassword() == "pass"); + + ProxyConfig config; + config.host = "other-proxy.com"; + config.port = 3128; + config.protocol = "http"; + config.tunnel = true; + config.username = "admin"; + config.password = "secret"; + s.setProxyConfig(config); + assertTrue (s.getProxyHost() == "other-proxy.com"); + assertTrue (s.getProxyPort() == 3128); + assertTrue (s.getProxyProtocol() == "http"); + assertTrue (s.isProxyTunnel() == true); + assertTrue (s.getProxyUsername() == "admin"); + assertTrue (s.getProxyPassword() == "secret"); +} + + +void HTTPClientSessionTest::testProxyRequestPrefix() +{ + HTTPTestServer srv; + + HTTPClientSession s1("www.somehost.com", 80); + s1.setProxy("127.0.0.1", srv.port()); + HTTPRequest request1(HTTPRequest::HTTP_GET, "/large"); + s1.sendRequest(request1); + HTTPResponse response1; + std::istream& rs1 = s1.receiveResponse(response1); + std::ostringstream ostr1; + StreamCopier::copyStream(rs1, ostr1); + std::string r1 = srv.lastRequest(); + // Port 80 should NOT appear in the prefix + assertTrue (r1.find("GET http://www.somehost.com/large") != std::string::npos); + assertTrue (r1.find(":80") == std::string::npos); + + HTTPTestServer srv2; + HTTPClientSession s2("www.somehost.com", 8080); + s2.setProxy("127.0.0.1", srv2.port()); + HTTPRequest request2(HTTPRequest::HTTP_GET, "/large"); + s2.sendRequest(request2); + HTTPResponse response2; + std::istream& rs2 = s2.receiveResponse(response2); + std::ostringstream ostr2; + StreamCopier::copyStream(rs2, ostr2); + std::string r2 = srv2.lastRequest(); + // Non-default port should appear in the prefix + assertTrue (r2.find("GET http://www.somehost.com:8080/large") != std::string::npos); +} + + +void HTTPClientSessionTest::testProxyNonTunnel() +{ + HTTPTestServer srv; + HTTPClientSession s("www.somehost.com"); + s.setProxy("127.0.0.1", srv.port(), "http", false); + HTTPRequest request(HTTPRequest::HTTP_GET, "/large"); + s.sendRequest(request); + HTTPResponse response; + std::istream& rs = s.receiveResponse(response); + assertTrue (response.getContentLength() == HTTPTestServer::LARGE_BODY.length()); + assertTrue (response.getContentType() == "text/plain"); + std::ostringstream ostr; + StreamCopier::copyStream(rs, ostr); + assertTrue (ostr.str() == HTTPTestServer::LARGE_BODY); + std::string r = srv.lastRequest(); + assertTrue (r.find("GET http://www.somehost.com/large") != std::string::npos); +} + + +void HTTPClientSessionTest::testGlobalProxyConfig() +{ + ProxyConfig globalConfig; + globalConfig.host = "global-proxy.example.com"; + globalConfig.port = 3128; + globalConfig.protocol = "http"; + HTTPClientSession::setGlobalProxyConfig(globalConfig); + + HTTPClientSession s1("www.example.com"); + assertTrue (s1.getProxyHost() == "global-proxy.example.com"); + assertTrue (s1.getProxyPort() == 3128); + + // Per-session config overrides global + ProxyConfig localConfig; + localConfig.host = "local-proxy.example.com"; + localConfig.port = 8080; + localConfig.protocol = "http"; + s1.setProxyConfig(localConfig); + assertTrue (s1.getProxyHost() == "local-proxy.example.com"); + assertTrue (s1.getProxyPort() == 8080); + + // Reset global config + ProxyConfig emptyConfig; + HTTPClientSession::setGlobalProxyConfig(emptyConfig); + + HTTPClientSession s2("www.example.com"); + assertTrue (s2.getProxyHost().empty()); +} + + +void HTTPClientSessionTest::testBypassProxyExtended() +{ + ProxyConfig config; + config.host = "proxy.domain.com"; + config.port = 80; + config.protocol = "https"; + config.tunnel = false; + config.nonProxyHosts = "localhost|127\\.0\\.0\\.1"; + + HTTPClientSession s1("127.0.0.1", 80); + s1.setProxyConfig(config); + assertTrue (s1.bypassProxy()); + + HTTPClientSession s2("www.appinf.com", 80); + s2.setProxyConfig(config); + assertTrue (!s2.bypassProxy()); + + // Empty nonProxyHosts disables bypass + config.nonProxyHosts = ""; + HTTPClientSession s3("127.0.0.1", 80); + s3.setProxyConfig(config); + assertTrue (!s3.bypassProxy()); +} + + void HTTPClientSessionTest::setUp() { } @@ -383,6 +613,14 @@ CppUnit::Test* HTTPClientSessionTest::suite() CppUnit_addTest(pSuite, HTTPClientSessionTest, testBypassProxy); CppUnit_addTest(pSuite, HTTPClientSessionTest, testExpectContinue); CppUnit_addTest(pSuite, HTTPClientSessionTest, testExpectContinueFail); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testProxyConfig); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testSetProxyProtocolValidation); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testSetProxyConfigValidation); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testProxySetters); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testProxyRequestPrefix); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testProxyNonTunnel); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testGlobalProxyConfig); + CppUnit_addTest(pSuite, HTTPClientSessionTest, testBypassProxyExtended); return pSuite; } diff --git a/Net/testsuite/src/HTTPClientSessionTest.h b/Net/testsuite/src/HTTPClientSessionTest.h index 7836327e6c..463931dc03 100644 --- a/Net/testsuite/src/HTTPClientSessionTest.h +++ b/Net/testsuite/src/HTTPClientSessionTest.h @@ -39,6 +39,14 @@ class HTTPClientSessionTest: public CppUnit::TestCase void testBypassProxy(); void testExpectContinue(); void testExpectContinueFail(); + void testProxyConfig(); + void testSetProxyProtocolValidation(); + void testSetProxyConfigValidation(); + void testProxySetters(); + void testProxyRequestPrefix(); + void testProxyNonTunnel(); + void testGlobalProxyConfig(); + void testBypassProxyExtended(); void setUp(); void tearDown(); diff --git a/Net/testsuite/src/HTTPTestServer.cpp b/Net/testsuite/src/HTTPTestServer.cpp index 401f6a0d06..cecaf74da1 100644 --- a/Net/testsuite/src/HTTPTestServer.cpp +++ b/Net/testsuite/src/HTTPTestServer.cpp @@ -140,7 +140,9 @@ std::string HTTPTestServer::handleRequest() const } else if (_lastRequest.substr(0, 10) == "GET /large" || _lastRequest.substr(0, 11) == "HEAD /large" || - _lastRequest.substr(0, 36) == "GET http://www.somehost.com:80/large") + _lastRequest.substr(0, 36) == "GET http://www.somehost.com:80/large" || + _lastRequest.substr(0, 33) == "GET http://www.somehost.com/large" || + _lastRequest.substr(0, 38) == "GET http://www.somehost.com:8080/large") { std::string body(LARGE_BODY); response.append("HTTP/1.0 200 OK\r\n"); diff --git a/NetSSL_OpenSSL/include/Poco/Net/HTTPSClientSession.h b/NetSSL_OpenSSL/include/Poco/Net/HTTPSClientSession.h index f0c2fd1e73..e037746f2b 100644 --- a/NetSSL_OpenSSL/include/Poco/Net/HTTPSClientSession.h +++ b/NetSSL_OpenSSL/include/Poco/Net/HTTPSClientSession.h @@ -152,8 +152,14 @@ class NetSSL_API HTTPSClientSession: public HTTPClientSession int read(char* buffer, std::streamsize length); private: - HTTPSClientSession(const HTTPSClientSession&); - HTTPSClientSession& operator = (const HTTPSClientSession&); + void initProxySessionFactory(); + /// Registers the "https" protocol with _proxySessionFactory using default context. + + void initProxySessionFactory(Context::Ptr pContext); + /// Registers the "https" protocol with _proxySessionFactory using given context. + + HTTPSClientSession(const HTTPSClientSession&) = delete; + HTTPSClientSession& operator = (const HTTPSClientSession&) = delete; Context::Ptr _pContext; Session::Ptr _pSession; diff --git a/NetSSL_OpenSSL/src/HTTPSClientSession.cpp b/NetSSL_OpenSSL/src/HTTPSClientSession.cpp index d487d39bbe..4cb53343c5 100644 --- a/NetSSL_OpenSSL/src/HTTPSClientSession.cpp +++ b/NetSSL_OpenSSL/src/HTTPSClientSession.cpp @@ -13,6 +13,7 @@ #include "Poco/Net/HTTPSClientSession.h" +#include "Poco/Net/HTTPSSessionInstantiator.h" #include "Poco/Net/SecureStreamSocket.h" #include "Poco/Net/SecureStreamSocketImpl.h" #include "Poco/Net/SSLManager.h" @@ -35,6 +36,7 @@ HTTPSClientSession::HTTPSClientSession(): _pContext(SSLManager::instance().defaultClientContext()) { setPort(HTTPS_PORT); + initProxySessionFactory(); } @@ -44,6 +46,7 @@ HTTPSClientSession::HTTPSClientSession(const SecureStreamSocket& socket, const s { setHost(host); setPort(port); + initProxySessionFactory(); } @@ -53,6 +56,7 @@ HTTPSClientSession::HTTPSClientSession(const SecureStreamSocket& socket, Session _pSession(pSession) { setPort(HTTPS_PORT); + initProxySessionFactory(); } @@ -62,6 +66,7 @@ HTTPSClientSession::HTTPSClientSession(const std::string& host, Poco::UInt16 por { setHost(host); setPort(port); + initProxySessionFactory(); } @@ -69,6 +74,7 @@ HTTPSClientSession::HTTPSClientSession(Context::Ptr pContext): HTTPClientSession(SecureStreamSocket(pContext)), _pContext(pContext) { + initProxySessionFactory(pContext); } @@ -77,6 +83,7 @@ HTTPSClientSession::HTTPSClientSession(Context::Ptr pContext, Session::Ptr pSess _pContext(pContext), _pSession(pSession) { + initProxySessionFactory(pContext); } @@ -86,6 +93,7 @@ HTTPSClientSession::HTTPSClientSession(const std::string& host, Poco::UInt16 por { setHost(host); setPort(port); + initProxySessionFactory(pContext); } @@ -96,11 +104,19 @@ HTTPSClientSession::HTTPSClientSession(const std::string& host, Poco::UInt16 por { setHost(host); setPort(port); + initProxySessionFactory(pContext); } HTTPSClientSession::~HTTPSClientSession() { + try + { + _proxySessionFactory.unregisterProtocol("https"); + } + catch (...) + { + } } @@ -126,23 +142,51 @@ X509Certificate HTTPSClientSession::serverCertificate() std::string HTTPSClientSession::proxyRequestPrefix() const { - return std::string(); + if (isProxyTunnel()) + return std::string(); + + std::string result("https://"); + result.append(getHost()); + // Do not append default port, since this may break some servers. + // One example of such server is GCS (Google Cloud Storage). + if (getPort() != HTTPS_PORT) + { + result.append(":"); + NumberFormatter::append(result, getPort()); + } + return result; } void HTTPSClientSession::proxyAuthenticate(HTTPRequest& request) { + if (!isProxyTunnel()) + { + proxyAuthenticateImpl(request, getProxyConfig()); + } } void HTTPSClientSession::connect(const SocketAddress& address) { - if (getProxyHost().empty() || bypassProxy()) + bool useProxy = !getProxyHost().empty() && !bypassProxy(); + + if (useProxy && isProxyTunnel()) + { + StreamSocket proxySocket(proxyConnect()); + SecureStreamSocket secureSocket = SecureStreamSocket::attach(proxySocket, getHost(), _pContext, _pSession); + attachSocket(secureSocket); + if (_pContext->sessionCacheEnabled()) + { + _pSession = secureSocket.currentSession(); + } + } + else { SecureStreamSocket sss(socket()); if (sss.getPeerHostName().empty()) { - sss.setPeerHostName(getHost()); + sss.setPeerHostName(useProxy ? getProxyHost() : getHost()); } if (_pContext->sessionCacheEnabled()) { @@ -154,16 +198,6 @@ void HTTPSClientSession::connect(const SocketAddress& address) _pSession = sss.currentSession(); } } - else - { - StreamSocket proxySocket(proxyConnect()); - SecureStreamSocket secureSocket = SecureStreamSocket::attach(proxySocket, getHost(), _pContext, _pSession); - attachSocket(secureSocket); - if (_pContext->sessionCacheEnabled()) - { - _pSession = secureSocket.currentSession(); - } - } } @@ -186,4 +220,16 @@ Session::Ptr HTTPSClientSession::sslSession() } +void HTTPSClientSession::initProxySessionFactory() +{ + _proxySessionFactory.registerProtocol("https", new HTTPSSessionInstantiator); +} + + +void HTTPSClientSession::initProxySessionFactory(Context::Ptr pContext) +{ + _proxySessionFactory.registerProtocol("https", new HTTPSSessionInstantiator(pContext)); +} + + } // namespace Poco::Net diff --git a/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.cpp b/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.cpp index aeeb4dc838..a6726163bd 100644 --- a/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.cpp +++ b/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.cpp @@ -494,6 +494,33 @@ void HTTPSClientSessionTest::testServerAbort() } +void HTTPSClientSessionTest::testProxyConfig() +{ + HTTPSClientSession s("www.example.com"); + assertTrue (s.isProxyTunnel() == true); + assertTrue (s.getProxyProtocol() == "http"); + assertTrue (s.getProxyHost().empty()); +} + + +void HTTPSClientSessionTest::testProxySetters() +{ + HTTPSClientSession s("www.example.com"); + + s.setProxy("proxy.example.com", 3128, "https", false); + assertTrue (s.getProxyHost() == "proxy.example.com"); + assertTrue (s.getProxyPort() == 3128); + assertTrue (s.getProxyProtocol() == "https"); + assertTrue (s.isProxyTunnel() == false); + + s.setProxyTunnel(true); + assertTrue (s.isProxyTunnel() == true); + + s.setProxyTunnel(false); + assertTrue (s.isProxyTunnel() == false); +} + + void HTTPSClientSessionTest::setUp() { } @@ -524,6 +551,8 @@ CppUnit::Test* HTTPSClientSessionTest::suite() CppUnit_addTest(pSuite, HTTPSClientSessionTest, testCachedSession); CppUnit_addTest(pSuite, HTTPSClientSessionTest, testUnknownContentLength); CppUnit_addTest(pSuite, HTTPSClientSessionTest, testServerAbort); + CppUnit_addTest(pSuite, HTTPSClientSessionTest, testProxyConfig); + CppUnit_addTest(pSuite, HTTPSClientSessionTest, testProxySetters); return pSuite; } diff --git a/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.h b/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.h index 7af7d4b92e..d2a67ff5f6 100644 --- a/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.h +++ b/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.h @@ -40,7 +40,8 @@ class HTTPSClientSessionTest: public CppUnit::TestCase void testCachedSession(); void testUnknownContentLength(); void testServerAbort(); - + void testProxyConfig(); + void testProxySetters(); void setUp(); void tearDown();