From 1f072d6858f98e2717f50cffca4acb17e663267d Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Mon, 16 Mar 2015 12:56:22 +0100 Subject: Add ability to modify HTTP CONNECT proxy initialization This patch adds HTTPTrafficFilter and integrates it into HTTPConnectProxiedConnection. This allows the HTTP CONNECT proxy initialization process to be customized. Test-Information: Added a unit test that verifies the new functionality. Change-Id: I0b93c319fb205487b8be65717276cd0dd38851a3 diff --git a/Swiften/Client/ClientOptions.h b/Swiften/Client/ClientOptions.h index 884b1bb..20e7443 100644 --- a/Swiften/Client/ClientOptions.h +++ b/Swiften/Client/ClientOptions.h @@ -1,15 +1,19 @@ /* - * Copyright (c) 2011 Isode Limited. + * Copyright (c) 2011-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once +#include + #include #include namespace Swift { + class HTTPTrafficFilter; + struct ClientOptions { enum UseTLS { NeverUseTLS, @@ -134,5 +138,11 @@ namespace Swift { */ SafeString boshHTTPConnectProxyAuthID; SafeString boshHTTPConnectProxyAuthPassword; + + /** + * This can be initialized with a custom HTTPTrafficFilter, which allows HTTP CONNECT + * proxy initialization to be customized. + */ + boost::shared_ptr httpTrafficFilter; }; } diff --git a/Swiften/Client/CoreClient.cpp b/Swiften/Client/CoreClient.cpp index 4416ee4..842488d 100644 --- a/Swiften/Client/CoreClient.cpp +++ b/Swiften/Client/CoreClient.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2014 Isode Limited. + * Copyright (c) 2010-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -96,7 +96,7 @@ void CoreClient::connect(const ClientOptions& o) { std::string proxyHostname = o.manualProxyHostname.empty() ? systemHTTPConnectProxy.getAddress().toString() : o.manualProxyHostname; int proxyPort = o.manualProxyPort == -1 ? systemHTTPConnectProxy.getPort() : o.manualProxyPort; SWIFT_LOG(debug) << "Proxy: " << proxyHostname << ":" << proxyPort << std::endl; - proxyConnectionFactories.push_back(new HTTPConnectProxiedConnectionFactory(networkFactories->getDomainNameResolver(), networkFactories->getConnectionFactory(), networkFactories->getTimerFactory(), proxyHostname, proxyPort)); + proxyConnectionFactories.push_back(new HTTPConnectProxiedConnectionFactory(networkFactories->getDomainNameResolver(), networkFactories->getConnectionFactory(), networkFactories->getTimerFactory(), proxyHostname, proxyPort, o.httpTrafficFilter)); useDirectConnection = false; break; } diff --git a/Swiften/Network/HTTPConnectProxiedConnection.cpp b/Swiften/Network/HTTPConnectProxiedConnection.cpp index ead48e9..fc5a6c6 100644 --- a/Swiften/Network/HTTPConnectProxiedConnection.cpp +++ b/Swiften/Network/HTTPConnectProxiedConnection.cpp @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2011-2012 Isode Limited. + * Copyright (c) 2011-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -14,15 +14,20 @@ #include #include +#include + #include #include +#include +#include #include #include #include #include #include #include +#include #include using namespace Swift; @@ -40,6 +45,9 @@ HTTPConnectProxiedConnection::HTTPConnectProxiedConnection( authPassword_(authPassword) { } +void HTTPConnectProxiedConnection::setHTTPTrafficFilter(boost::shared_ptr trafficFilter) { + trafficFilter_ = trafficFilter; +} void HTTPConnectProxiedConnection::initializeProxy() { std::stringstream connect; @@ -58,9 +66,58 @@ void HTTPConnectProxiedConnection::initializeProxy() { write(data); } +void HTTPConnectProxiedConnection::parseHTTPHeader(const std::string& data, std::string& statusLine, std::vector >& headerFields) { + std::istringstream dataStream(data); + + // parse status line + std::getline(dataStream, statusLine); + + // parse fields + std::string headerLine; + std::string::size_type splitIndex; + while (std::getline(dataStream, headerLine) && headerLine != "\r") { + splitIndex = headerLine.find(':', 0); + if (splitIndex != std::string::npos) { + headerFields.push_back(std::pair(headerLine.substr(0, splitIndex), headerLine.substr(splitIndex + 1))); + } + } +} + +void HTTPConnectProxiedConnection::sendHTTPRequest(const std::string& statusLine, std::vector >& headerFields) { + typedef std::pair HTTPHeaderField; + std::stringstream request; + + request << statusLine << "\r\n"; + foreach (const HTTPHeaderField& field, headerFields) { + request << field.first << ":" << field.second << "\r\n"; + } + request << "\r\n"; + write(createSafeByteArray(request.str())); +} + void HTTPConnectProxiedConnection::handleProxyInitializeData(boost::shared_ptr data) { - SWIFT_LOG(debug) << byteArrayToString(ByteArray(data->begin(), data->end())) << std::endl; - std::vector tmp = String::split(byteArrayToString(ByteArray(data->begin(), data->end())), ' '); + std::string dataString = byteArrayToString(ByteArray(data->begin(), data->end())); + SWIFT_LOG(debug) << data << std::endl; + + std::string statusLine; + std::vector > headerFields; + + std::string::size_type headerEnd = dataString.find("\r\n\r\n", 0); + + parseHTTPHeader(dataString.substr(0, headerEnd), statusLine, headerFields); + + if (trafficFilter_) { + std::vector > newHeaderFields = trafficFilter_->filterHTTPResponseHeader(headerFields); + if (!newHeaderFields.empty()) { + std::stringstream statusLine; + statusLine << "CONNECT " << getServer().getAddress().toString() << ":" << getServer().getPort(); + sendHTTPRequest(statusLine.str(), newHeaderFields); + SWIFT_LOG(debug) << "send HTTP request from traffic filter" << std::endl; + return; + } + } + + std::vector tmp = String::split(statusLine, ' '); if (tmp.size() > 1) { try { int status = boost::lexical_cast(tmp[1]); diff --git a/Swiften/Network/HTTPConnectProxiedConnection.h b/Swiften/Network/HTTPConnectProxiedConnection.h index 5b28242..7d83863 100644 --- a/Swiften/Network/HTTPConnectProxiedConnection.h +++ b/Swiften/Network/HTTPConnectProxiedConnection.h @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2011-2012 Isode Limited. + * Copyright (c) 2011-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -13,6 +13,8 @@ #pragma once +#include + #include #include @@ -21,6 +23,7 @@ namespace Swift { class ConnectionFactory; class EventLoop; class TimerFactory; + class HTTPTrafficFilter; class SWIFTEN_API HTTPConnectProxiedConnection : public ProxiedConnection { public: @@ -30,14 +33,20 @@ namespace Swift { return ref(new HTTPConnectProxiedConnection(resolver, connectionFactory, timerFactory, proxyHost, proxyPort, authID, authPassword)); } + void setHTTPTrafficFilter(boost::shared_ptr trafficFilter); + private: HTTPConnectProxiedConnection(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, const SafeString& authID, const SafeString& authPassword); virtual void initializeProxy(); virtual void handleProxyInitializeData(boost::shared_ptr data); + void sendHTTPRequest(const std::string& statusLine, std::vector >& headerFields); + void parseHTTPHeader(const std::string& data, std::string& statusLine, std::vector >& headerFields); + private: SafeByteArray authID_; SafeByteArray authPassword_; + boost::shared_ptr trafficFilter_; }; } diff --git a/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp b/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp index e50c5d0..91b241e 100644 --- a/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp +++ b/Swiften/Network/HTTPConnectProxiedConnectionFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Isode Limited. + * Copyright (c) 2012-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -16,15 +16,17 @@ namespace Swift { -HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort) : resolver_(resolver), connectionFactory_(connectionFactory), timerFactory_(timerFactory), proxyHost_(proxyHost), proxyPort_(proxyPort), authID_(""), authPassword_("") { +HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, boost::shared_ptr httpTrafficFilter) : resolver_(resolver), connectionFactory_(connectionFactory), timerFactory_(timerFactory), proxyHost_(proxyHost), proxyPort_(proxyPort), authID_(""), authPassword_(""), httpTrafficFilter_(httpTrafficFilter) { } -HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, const SafeString& authID, const SafeString& authPassword) : resolver_(resolver), connectionFactory_(connectionFactory), timerFactory_(timerFactory), proxyHost_(proxyHost), proxyPort_(proxyPort), authID_(authID), authPassword_(authPassword) { +HTTPConnectProxiedConnectionFactory::HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, const SafeString& authID, const SafeString& authPassword, boost::shared_ptr httpTrafficFilter) : resolver_(resolver), connectionFactory_(connectionFactory), timerFactory_(timerFactory), proxyHost_(proxyHost), proxyPort_(proxyPort), authID_(authID), authPassword_(authPassword), httpTrafficFilter_(httpTrafficFilter) { } boost::shared_ptr HTTPConnectProxiedConnectionFactory::createConnection() { - return HTTPConnectProxiedConnection::create(resolver_, connectionFactory_, timerFactory_, proxyHost_, proxyPort_, authID_, authPassword_); + HTTPConnectProxiedConnection::ref proxyConnection = HTTPConnectProxiedConnection::create(resolver_, connectionFactory_, timerFactory_, proxyHost_, proxyPort_, authID_, authPassword_); + proxyConnection->setHTTPTrafficFilter(httpTrafficFilter_); + return proxyConnection; } } diff --git a/Swiften/Network/HTTPConnectProxiedConnectionFactory.h b/Swiften/Network/HTTPConnectProxiedConnectionFactory.h index e7a4283..b4ddd4e 100644 --- a/Swiften/Network/HTTPConnectProxiedConnectionFactory.h +++ b/Swiften/Network/HTTPConnectProxiedConnectionFactory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Isode Limited. + * Copyright (c) 2012-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -20,10 +20,12 @@ namespace Swift { class DomainNameResolver; class TimerFactory; class EventLoop; + class HTTPTrafficFilter; + class HTTPConnectProxiedConnectionFactory : public ConnectionFactory { public: - HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort); - HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, const SafeString& authID, const SafeString& authPassword); + HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, boost::shared_ptr httpTrafficFilter = boost::shared_ptr()); + HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, const SafeString& authID, const SafeString& authPassword, boost::shared_ptr httpTrafficFilter = boost::shared_ptr()); virtual boost::shared_ptr createConnection(); @@ -35,5 +37,6 @@ namespace Swift { int proxyPort_; SafeString authID_; SafeString authPassword_; + boost::shared_ptr httpTrafficFilter_; }; } diff --git a/Swiften/Network/HTTPTrafficFilter.cpp b/Swiften/Network/HTTPTrafficFilter.cpp new file mode 100644 index 0000000..d40fbdf --- /dev/null +++ b/Swiften/Network/HTTPTrafficFilter.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include + +namespace Swift { + +HTTPTrafficFilter::~HTTPTrafficFilter() { + +} + +} diff --git a/Swiften/Network/HTTPTrafficFilter.h b/Swiften/Network/HTTPTrafficFilter.h new file mode 100644 index 0000000..da96c96 --- /dev/null +++ b/Swiften/Network/HTTPTrafficFilter.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include +#include +#include + +#include + +#include + +namespace Swift { + +class SWIFTEN_API HTTPTrafficFilter { + public: + virtual ~HTTPTrafficFilter(); + /** + * @brief This method is called by the HTTPConnectPRoxiedConnection on every incoming HTTP response. + * It can be used to insert additional HTTP requests into the HTTP CONNECT proxy initalization process. + * @return A vector of HTTP header fields to use in a new request. If an empty vector is returned, + * no new request will be send and the normal proxy logic continues. + */ + virtual std::vector > filterHTTPResponseHeader(const std::vector >& /* responseHeader */) = 0; +}; + +} diff --git a/Swiften/Network/SConscript b/Swiften/Network/SConscript index 284c1f4..c1f5c19 100644 --- a/Swiften/Network/SConscript +++ b/Swiften/Network/SConscript @@ -25,9 +25,9 @@ sourceList = [ "ConnectionServerFactory.cpp", "DummyConnection.cpp", "FakeConnection.cpp", - "ChainedConnector.cpp", - "Connector.cpp", - "Connection.cpp", + "ChainedConnector.cpp", + "Connector.cpp", + "Connection.cpp", "TimerFactory.cpp", "DummyTimerFactory.cpp", "BoostTimerFactory.cpp", @@ -53,6 +53,7 @@ sourceList = [ "NATTraversalForwardPortRequest.cpp", "NATTraversalRemovePortForwardingRequest.cpp", "NATTraversalInterface.cpp", + "HTTPTrafficFilter.cpp", ] if myenv.get("unbound", False) : diff --git a/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp b/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp index 7b9a5e6..fb6914e 100644 --- a/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp +++ b/Swiften/Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Isode Limited. + * Copyright (c) 2010-2015 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -17,13 +17,33 @@ #include #include #include +#include #include #include #include #include +#include using namespace Swift; +namespace { + class ExampleHTTPTrafficFilter : public HTTPTrafficFilter { + public: + ExampleHTTPTrafficFilter() {} + virtual ~ExampleHTTPTrafficFilter() {} + + virtual std::vector > filterHTTPResponseHeader(const std::vector >& response) { + filterResponses.push_back(response); + SWIFT_LOG(debug) << std::endl; + return filterResponseReturn; + } + + std::vector > > filterResponses; + + std::vector > filterResponseReturn; + }; +} + class HTTPConnectProxiedConnectionTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(HTTPConnectProxiedConnectionTest); CPPUNIT_TEST(testConnect_CreatesConnectionToProxy); @@ -35,6 +55,7 @@ class HTTPConnectProxiedConnectionTest : public CppUnit::TestFixture { CPPUNIT_TEST(testWrite_AfterConnect); CPPUNIT_TEST(testDisconnect_AfterConnectRequest); CPPUNIT_TEST(testDisconnect_AfterConnect); + CPPUNIT_TEST(testTrafficFilter); CPPUNIT_TEST_SUITE_END(); public: @@ -168,6 +189,43 @@ class HTTPConnectProxiedConnectionTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(!disconnectedError); } + void testTrafficFilter() { + HTTPConnectProxiedConnection::ref testling(createTestling()); + + boost::shared_ptr httpTrafficFilter = boost::make_shared(); + + testling->setHTTPTrafficFilter(httpTrafficFilter); + connect(testling, HostAddressPort(HostAddress("2.2.2.2"), 2345)); + + // set a default response so the server response is answered by the traffic filter + httpTrafficFilter->filterResponseReturn.clear(); + httpTrafficFilter->filterResponseReturn.push_back(std::pair("Authorization", "Negotiate a87421000492aa874209af8bc028")); + + connectionFactory->connections[0]->dataWritten.clear(); + + connectionFactory->connections[0]->onDataRead(createSafeByteArrayRef( + "HTTP/1.0 401 Unauthorized\r\n" + "WWW-Authenticate: Negotiate\r\n" + "\r\n")); + eventLoop->processEvents(); + + // verify that the traffic filter got called and answered with its response + CPPUNIT_ASSERT_EQUAL(static_cast(1), httpTrafficFilter->filterResponses.size()); + CPPUNIT_ASSERT_EQUAL(std::string("WWW-Authenticate"), httpTrafficFilter->filterResponses[0][0].first); + + // remove the default response from the traffic filter + httpTrafficFilter->filterResponseReturn.clear(); + eventLoop->processEvents(); + + // verify that the traffic filter answer is send over the wire + CPPUNIT_ASSERT_EQUAL(createByteArray("CONNECT 2.2.2.2:2345\r\nAuthorization:Negotiate a87421000492aa874209af8bc028\r\n\r\n"), connectionFactory->connections[0]->dataWritten); + + // verify that after without the default response, the traffic filter is skipped, authentication proceeds and traffic goes right through + connectionFactory->connections[0]->dataWritten.clear(); + testling->write(createSafeByteArray("abcdef")); + CPPUNIT_ASSERT_EQUAL(createByteArray("abcdef"), connectionFactory->connections[0]->dataWritten); + } + private: HTTPConnectProxiedConnection::ref createTestling() { boost::shared_ptr c = HTTPConnectProxiedConnection::create(resolver, connectionFactory, timerFactory, proxyHost, proxyPort, "", ""); -- cgit v0.10.2-6-g49f6