From 73f9f0ee5dca1485a8f35ff67848be43a7d75978 Mon Sep 17 00:00:00 2001
From: Thilo Cestonaro <thilo@cestona.ro>
Date: Wed, 4 May 2011 19:00:08 +0200
Subject: some SOCKS5ProxiedConnection unittests fixed disconnecting when SOCKS
 Proxy response with an unsupported auth method

License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php

diff --git a/Swiften/Network/SOCKS5ProxiedConnection.cpp b/Swiften/Network/SOCKS5ProxiedConnection.cpp
index 163e23a..838bcd2 100644
--- a/Swiften/Network/SOCKS5ProxiedConnection.cpp
+++ b/Swiften/Network/SOCKS5ProxiedConnection.cpp
@@ -126,6 +126,7 @@ void SOCKS5ProxiedConnection::handleDataRead(boost::shared_ptr<SafeByteArray> da
 						connection_->write(socksConnect);
 						break;
 					default:
+						disconnect();
 						onConnectFinished(true);
 						break;
 				}
diff --git a/Swiften/Network/UnitTest/SOCKS5ProxiedConnectionTest.cpp b/Swiften/Network/UnitTest/SOCKS5ProxiedConnectionTest.cpp
new file mode 100644
index 0000000..c820221
--- /dev/null
+++ b/Swiften/Network/UnitTest/SOCKS5ProxiedConnectionTest.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2010-2011 Thilo Cestonaro
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/optional.hpp>
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Network/Connection.h>
+#include <Swiften/Network/ConnectionFactory.h>
+#include <Swiften/Network/SOCKS5ProxiedConnection.h>
+#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/EventLoop/DummyEventLoop.h>
+
+using namespace Swift;
+
+class SOCKS5ProxiedConnectionTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(SOCKS5ProxiedConnectionTest);
+		CPPUNIT_TEST(testConnect_CreatesConnectionToProxy);
+		CPPUNIT_TEST(testConnect_ConnectHandshake);
+		CPPUNIT_TEST(testConnect_ReceiveMalformedConnectResponse);
+		CPPUNIT_TEST(testConnect_ReceiveConnectResponseWithUnsupportedAuthMethod);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			proxyHost = HostAddressPort(HostAddress("1.1.1.1"), 1234);
+			host = HostAddressPort(HostAddress("2.2.2.2"), 2345);
+			eventLoop = new DummyEventLoop();
+			connectionFactory = new MockConnectionFactory(eventLoop);
+			connectFinished = false;
+			disconnected = false;
+		}
+
+		void tearDown() {
+			delete connectionFactory;
+			delete eventLoop;
+		}
+
+		void testConnect_CreatesConnectionToProxy() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+
+			testling->connect(host);
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connectionFactory->connections.size()));
+			CPPUNIT_ASSERT(connectionFactory->connections[0]->hostAddressPort);
+			CPPUNIT_ASSERT(proxyHost == *connectionFactory->connections[0]->hostAddressPort);
+			CPPUNIT_ASSERT(!connectFinished);
+		}
+
+		void testConnect_ConnectHandshake() {
+			ByteArray connectResponse;
+			ByteArray connectRequest;
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+
+			// response is always first byte 0x05 and second 0x00
+			connectResponse += 0x05;
+			connectResponse += 0x00;
+
+			// first part of the connection handshake
+			connectRequest += 0x05; // Version: SOCKS5 = 0x05
+			connectRequest += 0x01; // Number of authentication methods after this byte.
+			connectRequest += 0x00; // authmethod = noAuth = 0x00
+
+			CPPUNIT_ASSERT_EQUAL(connectRequest, connectionFactory->connections[0]->dataWritten);
+
+			// response 
+			connectionFactory->connections[0]->onDataRead(connectResponse);
+			eventLoop->processEvents();
+
+
+			// second part of the connection handshake
+			connectRequest += 0x05; // Version: SOCKS5 = 0x05
+			connectRequest += 0x01; // Number of authentication methods after this byte.
+			connectRequest += 0x00; // authmethod = noAuth = 0x00
+			connectRequest += 0x01; // IPv4
+			connectRequest += 0x02; // IP
+			connectRequest += 0x02; // IP
+			connectRequest += 0x02; // IP
+			connectRequest += 0x02; // IP
+			connectRequest += 0x09; // Port
+			connectRequest += 0x29; // Port
+			
+			CPPUNIT_ASSERT_EQUAL(connectRequest, connectionFactory->connections[0]->dataWritten);
+
+			// response 
+			connectionFactory->connections[0]->onDataRead(connectResponse);
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT(connectFinished);
+			CPPUNIT_ASSERT(!connectFinishedWithError);
+			CPPUNIT_ASSERT(dataRead.isEmpty());
+		}
+
+		void testConnect_ReceiveMalformedConnectResponse() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+
+			connectionFactory->connections[0]->onDataRead(ByteArray("FLOOP"));
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT(connectFinished);
+			CPPUNIT_ASSERT(connectFinishedWithError);
+			CPPUNIT_ASSERT(connectionFactory->connections[0]->disconnected);
+		}
+
+		void testConnect_ReceiveConnectResponseWithUnsupportedAuthMethod() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+
+			ByteArray connectResponse;
+			connectResponse += 0x05;
+			connectResponse += 0x01;
+
+			connectionFactory->connections[0]->onDataRead(connectResponse);
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT(connectFinished);
+			CPPUNIT_ASSERT(connectFinishedWithError);
+			CPPUNIT_ASSERT(connectionFactory->connections[0]->disconnected);
+		}
+
+		void testConnect_ReceiveConnectResponseWithFailure() {
+			ByteArray connectResponse;
+			ByteArray connectRequest;
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+
+			// response is always first byte 0x05 and second 0x00
+			connectResponse += 0x05;
+			connectResponse += 0x00;
+
+			// first part of the connection handshake
+			connectRequest += 0x05; // Version: SOCKS5 = 0x05
+			connectRequest += 0x01; // Number of authentication methods after this byte.
+			connectRequest += 0x00; // authmethod = noAuth = 0x00
+
+			CPPUNIT_ASSERT_EQUAL(connectRequest, connectionFactory->connections[0]->dataWritten);
+
+			// response 
+			connectionFactory->connections[0]->onDataRead(connectResponse);
+			eventLoop->processEvents();
+
+
+			// second part of the connection handshake
+			connectRequest += 0x05; // Version: SOCKS5 = 0x05
+			connectRequest += 0x01; // Number of authentication methods after this byte.
+			connectRequest += 0x00; // authmethod = noAuth = 0x00
+			connectRequest += 0x01; // IPv4
+			connectRequest += 0x02; // IP
+			connectRequest += 0x02; // IP
+			connectRequest += 0x02; // IP
+			connectRequest += 0x02; // IP
+			connectRequest += 0x09; // Port
+			connectRequest += 0x29; // Port
+			
+			CPPUNIT_ASSERT_EQUAL(connectRequest, connectionFactory->connections[0]->dataWritten);
+
+			// response 
+			connectResponse.clear();
+			connectResponse += 0x05;
+			connectResponse += 0x01; // general SOCKS failure
+			connectionFactory->connections[0]->onDataRead(connectResponse);
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT(connectFinished);
+			CPPUNIT_ASSERT(connectFinishedWithError);
+			CPPUNIT_ASSERT(connectionFactory->connections[0]->disconnected);
+		}
+
+/*
+		void testConnect_ReceiveDataAfterConnect() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+			connectionFactory->connections[0]->onDataRead(ByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+			eventLoop->processEvents();
+
+			connectionFactory->connections[0]->onDataRead(ByteArray("abcdef"));
+
+			CPPUNIT_ASSERT_EQUAL(ByteArray("abcdef"), dataRead);
+		}
+
+		void testWrite_AfterConnect() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+			connectionFactory->connections[0]->onDataRead(ByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+			eventLoop->processEvents();
+			connectionFactory->connections[0]->dataWritten.clear();
+
+			testling->write(ByteArray("abcdef"));
+
+			CPPUNIT_ASSERT_EQUAL(ByteArray("abcdef"), connectionFactory->connections[0]->dataWritten);
+		}
+
+		void testDisconnect_AfterConnectRequest() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+
+			testling->disconnect();
+
+			CPPUNIT_ASSERT(connectionFactory->connections[0]->disconnected);
+			CPPUNIT_ASSERT(disconnected);
+			CPPUNIT_ASSERT(!disconnectedError);
+		}
+
+		void testDisconnect_AfterConnect() {
+			SOCKS5ProxiedConnection::ref testling(createTestling());
+			testling->connect(HostAddressPort(HostAddress("2.2.2.2"), 2345));
+			eventLoop->processEvents();
+			connectionFactory->connections[0]->onDataRead(ByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+			eventLoop->processEvents();
+
+			testling->disconnect();
+
+			CPPUNIT_ASSERT(connectionFactory->connections[0]->disconnected);
+			CPPUNIT_ASSERT(disconnected);
+			CPPUNIT_ASSERT(!disconnectedError);
+		}
+*/
+	private:
+		SOCKS5ProxiedConnection::ref createTestling() {
+			boost::shared_ptr<SOCKS5ProxiedConnection> c = SOCKS5ProxiedConnection::create(connectionFactory, proxyHost);
+			c->onConnectFinished.connect(boost::bind(&SOCKS5ProxiedConnectionTest::handleConnectFinished, this, _1));
+			c->onDisconnected.connect(boost::bind(&SOCKS5ProxiedConnectionTest::handleDisconnected, this, _1));
+			c->onDataRead.connect(boost::bind(&SOCKS5ProxiedConnectionTest::handleDataRead, this, _1));
+			return c;
+		}
+
+		void handleConnectFinished(bool error) {
+			connectFinished = true;
+			connectFinishedWithError = error;
+		}
+
+		void handleDisconnected(const boost::optional<Connection::Error>& e) {
+			disconnected = true;
+			disconnectedError = e;
+		}
+
+		void handleDataRead(const ByteArray& d) {
+			dataRead += d;
+		}
+
+		struct MockConnection : public Connection {
+			public:
+				MockConnection(const std::vector<HostAddressPort>& failingPorts, EventLoop* eventLoop) : eventLoop(eventLoop), failingPorts(failingPorts), disconnected(false) {
+				}
+
+				void listen() { assert(false); }
+
+				void connect(const HostAddressPort& address) {
+					hostAddressPort = address;
+					bool fail = std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end();
+					eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), fail));
+				}
+
+				HostAddressPort getLocalAddress() const { return HostAddressPort(); }
+
+				void disconnect() { 
+					disconnected = true;
+					onDisconnected(boost::optional<Connection::Error>());
+				}
+				
+				void write(const ByteArray& d) { 
+					dataWritten += d;
+				}
+
+				EventLoop* eventLoop;
+				boost::optional<HostAddressPort> hostAddressPort;
+				std::vector<HostAddressPort> failingPorts;
+				ByteArray dataWritten;
+				bool disconnected;
+		};
+
+		struct MockConnectionFactory : public ConnectionFactory {
+			MockConnectionFactory(EventLoop* eventLoop) : eventLoop(eventLoop) {
+			}
+
+			boost::shared_ptr<Connection> createConnection() {
+				boost::shared_ptr<MockConnection> connection = boost::make_shared<MockConnection>(failingPorts, eventLoop);
+				connections.push_back(connection);
+				return connection;
+			}
+
+			EventLoop* eventLoop;
+			std::vector< boost::shared_ptr<MockConnection> > connections;
+			std::vector<HostAddressPort> failingPorts;
+		};
+
+	private:
+		HostAddressPort proxyHost;
+		HostAddressPort host;
+		DummyEventLoop* eventLoop;
+		MockConnectionFactory* connectionFactory;
+		std::vector< boost::shared_ptr<MockConnection> > connections;
+		bool connectFinished;
+		bool connectFinishedWithError;
+		bool disconnected;
+		boost::optional<Connection::Error> disconnectedError;
+		ByteArray dataRead;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SOCKS5ProxiedConnectionTest);
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 9e61fc6..151ab74 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -288,6 +288,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp"),
 			File("Network/UnitTest/BOSHConnectionTest.cpp"),
 			File("Network/UnitTest/BOSHConnectionPoolTest.cpp"),
+			File("Network/UnitTest/SOCKS5ProxiedConnectionTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp"),
-- 
cgit v0.10.2-6-g49f6