From 1f072d6858f98e2717f50cffca4acb17e663267d Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
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 <boost/shared_ptr.hpp>
+
 #include <Swiften/Base/URL.h>
 #include <Swiften/Base/SafeString.h>
 
 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> 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 <Swiften/Network/HTTPConnectProxiedConnection.h>
 
 #include <iostream>
+#include <utility>
+
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
 
+#include <Swiften/Base/foreach.h>
 #include <Swiften/Base/Algorithm.h>
 #include <Swiften/Base/Log.h>
 #include <Swiften/Base/String.h>
 #include <Swiften/Base/ByteArray.h>
 #include <Swiften/Network/HostAddressPort.h>
 #include <Swiften/Network/ConnectionFactory.h>
+#include <Swiften/Network/HTTPTrafficFilter.h>
 #include <Swiften/StringCodecs/Base64.h>
 
 using namespace Swift;
@@ -40,6 +45,9 @@ HTTPConnectProxiedConnection::HTTPConnectProxiedConnection(
 			authPassword_(authPassword) {
 }
 
+void HTTPConnectProxiedConnection::setHTTPTrafficFilter(boost::shared_ptr<HTTPTrafficFilter> 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<std::pair<std::string, std::string> >& 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<std::string, std::string>(headerLine.substr(0, splitIndex), headerLine.substr(splitIndex + 1)));
+		}
+	}
+}
+
+void HTTPConnectProxiedConnection::sendHTTPRequest(const std::string& statusLine, std::vector<std::pair<std::string, std::string> >& headerFields) {
+	typedef std::pair<std::string, std::string> 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<SafeByteArray> data) {
-	SWIFT_LOG(debug) << byteArrayToString(ByteArray(data->begin(), data->end())) << std::endl;
-	std::vector<std::string> 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<std::pair<std::string, std::string> > headerFields;
+
+	std::string::size_type headerEnd = dataString.find("\r\n\r\n", 0);
+
+	parseHTTPHeader(dataString.substr(0, headerEnd), statusLine, headerFields);
+
+	if (trafficFilter_) {
+		std::vector<std::pair<std::string, std::string> > 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<std::string> tmp = String::split(statusLine, ' ');
 	if (tmp.size() > 1) {
 		try {
 			int status = boost::lexical_cast<int>(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 <boost/shared_ptr.hpp>
+
 #include <Swiften/Base/API.h>
 #include <Swiften/Network/ProxiedConnection.h>
 
@@ -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<HTTPTrafficFilter> 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<SafeByteArray> data);
 
+			void sendHTTPRequest(const std::string& statusLine, std::vector<std::pair<std::string, std::string> >& headerFields);
+			void parseHTTPHeader(const std::string& data, std::string& statusLine, std::vector<std::pair<std::string, std::string> >& headerFields);
+
 		private:
 			SafeByteArray authID_;
 			SafeByteArray authPassword_;
+			boost::shared_ptr<HTTPTrafficFilter> 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> 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> httpTrafficFilter) : resolver_(resolver), connectionFactory_(connectionFactory), timerFactory_(timerFactory), proxyHost_(proxyHost), proxyPort_(proxyPort), authID_(authID), authPassword_(authPassword),  httpTrafficFilter_(httpTrafficFilter) {
 }
 
 boost::shared_ptr<Connection> 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> httpTrafficFilter = boost::shared_ptr<HTTPTrafficFilter>());
+			HTTPConnectProxiedConnectionFactory(DomainNameResolver* resolver, ConnectionFactory* connectionFactory, TimerFactory* timerFactory, const std::string& proxyHost, int proxyPort, const SafeString& authID, const SafeString& authPassword, boost::shared_ptr<HTTPTrafficFilter> httpTrafficFilter = boost::shared_ptr<HTTPTrafficFilter>());
 
 			virtual boost::shared_ptr<Connection> createConnection();
 
@@ -35,5 +37,6 @@ namespace Swift {
 			int proxyPort_;
 			SafeString authID_;
 			SafeString authPassword_;
+			boost::shared_ptr<HTTPTrafficFilter> 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 <Swiften/Network/HTTPTrafficFilter.h>
+
+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 <vector>
+#include <string>
+#include <utility>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Base/API.h>
+
+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<std::pair<std::string, std::string> > filterHTTPResponseHeader(const std::vector<std::pair<std::string, std::string> >& /* 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 <Swiften/Network/Connection.h>
 #include <Swiften/Network/ConnectionFactory.h>
 #include <Swiften/Network/HTTPConnectProxiedConnection.h>
+#include <Swiften/Network/HTTPTrafficFilter.h>
 #include <Swiften/Network/HostAddressPort.h>
 #include <Swiften/Network/StaticDomainNameResolver.h>
 #include <Swiften/Network/DummyTimerFactory.h>
 #include <Swiften/EventLoop/DummyEventLoop.h>
+#include <Swiften/Base/Log.h>
 
 using namespace Swift;
 
+namespace {
+	class ExampleHTTPTrafficFilter : public HTTPTrafficFilter {
+		public:
+			ExampleHTTPTrafficFilter() {}
+			virtual ~ExampleHTTPTrafficFilter() {}
+
+			virtual std::vector<std::pair<std::string, std::string> > filterHTTPResponseHeader(const std::vector<std::pair<std::string, std::string> >& response) {
+				filterResponses.push_back(response);
+				SWIFT_LOG(debug) << std::endl;
+				return filterResponseReturn;
+			}
+
+			std::vector<std::vector<std::pair<std::string, std::string> > > filterResponses;
+
+			std::vector<std::pair<std::string, std::string> > 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<ExampleHTTPTrafficFilter> httpTrafficFilter = boost::make_shared<ExampleHTTPTrafficFilter>();
+
+			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<std::string, std::string>("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<size_t>(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<HTTPConnectProxiedConnection> c = HTTPConnectProxiedConnection::create(resolver, connectionFactory, timerFactory, proxyHost, proxyPort, "", "");
-- 
cgit v0.10.2-6-g49f6