summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--PortingProgress.txt24
-rw-r--r--src/com/isode/stroke/network/HTTPConnectProxiedConnection.java27
-rw-r--r--src/com/isode/stroke/network/HTTPTrafficFilter.java5
-rw-r--r--src/com/isode/stroke/network/JavaNetworkEnviroment.java51
-rw-r--r--src/com/isode/stroke/network/NetworkInterface.java24
-rw-r--r--src/com/isode/stroke/network/PlatformDomainNameAddressQuery.java86
-rw-r--r--src/com/isode/stroke/network/ProxiedConnection.java26
-rw-r--r--test/com/isode/stroke/network/HTTPConnectProxiedConnectionTest.java477
8 files changed, 697 insertions, 23 deletions
diff --git a/PortingProgress.txt b/PortingProgress.txt
index 0f798e5..7a8a986 100644
--- a/PortingProgress.txt
+++ b/PortingProgress.txt
@@ -133,14 +133,22 @@ Network:
All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39 except for:
-GConfProxyProvider, UnixProxyProvider, WindowsProxyProvider, MacOSXProxyProvider -- Not Yet Ported!
-NetworkEnvironment, SolarisNetworkEnvironment, UnixNetworkEnvironment, WindowsNetworkEnvironment -- Not Yet Ported!
-HTTPConnectProxiedConnectionTest -- Not Yet Ported!
-HostNameOrAddress -- Not Yet Ported!
-PlatformNATTraversalWorker, MiniUPnPInterface, NATPMPInterface -- Not Yet Ported!
-PlatformDomainNameAddressQuery -- Not Yet Ported!
-PlatformDomainNameServiceQuery -- Constructor needs change.
-UnboundDomainNameResolver -- Not Yet Ported!
+GConfProxyProvider, UnixProxyProvider, WindowsProxyProvider, MacOSXProxyProvider
+-- No need to port. We already have a JavaProxyProvider.
+
+SolarisNetworkEnvironment, UnixNetworkEnvironment, WindowsNetworkEnvironment, PlatformNATTraversalWorker
+-- No need to port. A JavaNetworkEnviroment has been implemented.
+
+HostNameOrAddress -- No need to port. Just a utiltity method to allow .toString to be called on something
+that is either HostName or a String. We can do this in java with Object.toString().
+
+MiniUPnPInterface, NATPMPInterface -- Not yet ported. These are difficult to import, we are using libminiupnpc which we do not have
+a java equivalent for?
+
+PlatformDomainNameServiceQuery -- Constructor needs change. Swiften version has an extra field required in constructor that is used
+for swiften implementation but not in stroke so this is not needed.
+
+UnboundDomainNameResolver -- Not yet ported, uses unbound Library which we do not have a java equivalent for?
-----
Parser:
diff --git a/src/com/isode/stroke/network/HTTPConnectProxiedConnection.java b/src/com/isode/stroke/network/HTTPConnectProxiedConnection.java
index a85758c..d2915bd 100644
--- a/src/com/isode/stroke/network/HTTPConnectProxiedConnection.java
+++ b/src/com/isode/stroke/network/HTTPConnectProxiedConnection.java
@@ -4,7 +4,7 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
- * Copyright (c) 2011-2015 Isode Limited.
+ * Copyright (c) 2011-2016 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
@@ -19,6 +19,8 @@ package com.isode.stroke.network;
import com.isode.stroke.base.SafeByteArray;
import com.isode.stroke.stringcodecs.Base64;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Scanner;
import java.util.Vector;
@@ -28,6 +30,7 @@ public class HTTPConnectProxiedConnection extends ProxiedConnection {
private SafeByteArray authPassword_;
private HTTPTrafficFilter trafficFilter_;
private StringBuffer httpResponseBuffer_ = new StringBuffer("");
+ private final List<Pair> nextHTTPRequestHeaders_ = new ArrayList<Pair>();
public static class Pair {
String a;
@@ -51,6 +54,8 @@ public class HTTPConnectProxiedConnection extends ProxiedConnection {
}
protected void initializeProxy() {
+ httpResponseBuffer_.setLength(0);
+
StringBuffer connect = new StringBuffer();
connect.append("CONNECT ").append(getServer().getAddress().toString()).append(":").append(getServer().getPort()).append(" HTTP/1.1\r\n");
SafeByteArray data = new SafeByteArray(connect.toString());
@@ -62,8 +67,16 @@ public class HTTPConnectProxiedConnection extends ProxiedConnection {
data.append(Base64.encode(credentials));
data.append(new SafeByteArray("\r\n"));
}
+ else if (!nextHTTPRequestHeaders_.isEmpty()) {
+ for (Pair headerField : nextHTTPRequestHeaders_) {
+ data.append(headerField.a);
+ data.append(": ");
+ data.append(headerField.b);
+ data.append("\r\n");
+ }
+ nextHTTPRequestHeaders_.clear();
+ }
data.append(new SafeByteArray("\r\n"));
- //SWIFT_LOG(debug) << "HTTP Proxy send headers: " << byteArrayToString(ByteArray(data.begin(), data.end())) << std::endl;
write(data);
}
@@ -85,12 +98,12 @@ public class HTTPConnectProxiedConnection extends ProxiedConnection {
String statusLine = parseHTTPHeader(httpResponseBuffer_.substring(0, headerEnd), headerFields);
if (trafficFilter_ != null) {
- Vector<Pair> newHeaderFields = trafficFilter_.filterHTTPResponseHeader(headerFields);
+ Vector<Pair> newHeaderFields = trafficFilter_.filterHTTPResponseHeader(statusLine, headerFields);
if (!newHeaderFields.isEmpty()) {
- StringBuffer statusLines = new StringBuffer();
- statusLines.append("CONNECT ").append(getServer().getAddress().toString()).append(":").append(getServer().getPort());
- sendHTTPRequest(statusLines.toString(), newHeaderFields);
- return;
+ reconnect();
+ nextHTTPRequestHeaders_.clear();
+ nextHTTPRequestHeaders_.addAll(newHeaderFields);
+ return;
}
}
diff --git a/src/com/isode/stroke/network/HTTPTrafficFilter.java b/src/com/isode/stroke/network/HTTPTrafficFilter.java
index 86f0659..c9a039e 100644
--- a/src/com/isode/stroke/network/HTTPTrafficFilter.java
+++ b/src/com/isode/stroke/network/HTTPTrafficFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015 Isode Limited.
+ * Copyright (c) 2015-2016 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
@@ -18,8 +18,9 @@ public interface 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.
+ * @param statusLine status line from a HTTP header
* @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.
*/
- public Vector<HTTPConnectProxiedConnection.Pair> filterHTTPResponseHeader(final Vector<HTTPConnectProxiedConnection.Pair> responseHeader);
+ public Vector<HTTPConnectProxiedConnection.Pair> filterHTTPResponseHeader(String statusLine, final Vector<HTTPConnectProxiedConnection.Pair> responseHeader);
} \ No newline at end of file
diff --git a/src/com/isode/stroke/network/JavaNetworkEnviroment.java b/src/com/isode/stroke/network/JavaNetworkEnviroment.java
new file mode 100644
index 0000000..0113c57
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaNetworkEnviroment.java
@@ -0,0 +1,51 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.network;
+
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+/**
+ * Java implementation of {@link NetworkEnvironment}
+ */
+public class JavaNetworkEnviroment extends NetworkEnvironment {
+
+ /**
+ * Logger
+ */
+ private final Logger logger = Logger.getLogger(this.getClass().getName());
+
+ @Override
+ public Vector<NetworkInterface> getNetworkInterfaces() {
+ Vector<NetworkInterface> results = new Vector<NetworkInterface>();
+ try {
+ Enumeration<java.net.NetworkInterface> javaNIEnumeration =
+ java.net.NetworkInterface.getNetworkInterfaces();
+ if (javaNIEnumeration.hasMoreElements()) {
+ java.net.NetworkInterface javaNI = javaNIEnumeration.nextElement();
+ try {
+ NetworkInterface strokeNI = new NetworkInterface(javaNI);
+ results.add(strokeNI);
+ } catch (SocketException e) {
+ logger.warning("Error determining if "+javaNI+
+ " is loopback : "+e.getMessage());
+ }
+
+ }
+ }
+ catch (SocketException e) {
+ logger.warning("Error occured when getting network interfaces - "+e.getMessage());
+ }
+ return results;
+ }
+
+}
diff --git a/src/com/isode/stroke/network/NetworkInterface.java b/src/com/isode/stroke/network/NetworkInterface.java
index 5590837..1bffafc 100644
--- a/src/com/isode/stroke/network/NetworkInterface.java
+++ b/src/com/isode/stroke/network/NetworkInterface.java
@@ -4,7 +4,7 @@
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
/*
- * Copyright (c) 2015 Isode Limited.
+ * Copyright (c) 2015-2016 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
@@ -16,6 +16,9 @@
package com.isode.stroke.network;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.util.Enumeration;
import java.util.Vector;
public class NetworkInterface {
@@ -28,6 +31,25 @@ public class NetworkInterface {
this.name = name;
this.loopback = loopback;
}
+
+ /**
+ * Creates a {@link NetworkInterface} from a {@link java.net.NetworkInterface} including
+ * all addresses in the {@link java.net.NetworkInterface}
+ * @param javaNI The {@link java.net.NetworkInterface} to create the {@link NetworkInterface}
+ * from, should not be {@code null}.
+ * @throws SocketException If an I/O error occurs when trying to determine if it is
+ * a loop back interface.
+ */
+ public NetworkInterface(java.net.NetworkInterface javaNI) throws SocketException {
+ this.name = javaNI.getName();
+ this.loopback = javaNI.isLoopback();
+ Enumeration<InetAddress> addressEnumeration = javaNI.getInetAddresses();
+ while (addressEnumeration.hasMoreElements()) {
+ InetAddress inetAddress = addressEnumeration.nextElement();
+ HostAddress hostAddress = new HostAddress(inetAddress);
+ addAddress(hostAddress);
+ }
+ }
public void addAddress(final HostAddress address) {
addresses.add(address);
diff --git a/src/com/isode/stroke/network/PlatformDomainNameAddressQuery.java b/src/com/isode/stroke/network/PlatformDomainNameAddressQuery.java
new file mode 100644
index 0000000..24deb4d
--- /dev/null
+++ b/src/com/isode/stroke/network/PlatformDomainNameAddressQuery.java
@@ -0,0 +1,86 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.network;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.EventLoop;
+
+public class PlatformDomainNameAddressQuery extends DomainNameAddressQuery {
+
+ private final String host_;
+ private final EventLoop eventLoop_;
+
+ public PlatformDomainNameAddressQuery(String host,EventLoop eventLoop) {
+ host_ = host;
+ eventLoop_ = eventLoop;
+ }
+
+ private class QueryRunnable implements Runnable {
+
+ private final List<HostAddress> results_ =
+ Collections.synchronizedList(new ArrayList<HostAddress>());
+
+ @Override
+ public void run() {
+ try {
+ InetAddress[] inetAddresses = InetAddress.getAllByName(host_);
+ for (InetAddress address : inetAddresses) {
+ HostAddress result = new HostAddress(address);
+ results_.add(result);
+ }
+ } catch (UnknownHostException e) {
+ emitError();
+ }
+ emitResults();
+ }
+
+ private void emitError() {
+ eventLoop_.postEvent(new Callback() {
+
+ @Override
+ public void run() {
+ onResult.emit(new ArrayList<HostAddress>(),new DomainNameResolveError());
+ }
+
+ });
+ }
+
+ private void emitResults() {
+ eventLoop_.postEvent(new Callback() {
+
+ @Override
+ public void run() {
+ // For thread safety emit a copy of the results
+ List<HostAddress> resultCopy = new ArrayList<HostAddress>();
+ synchronized (results_) {
+ resultCopy.addAll(results_);
+ }
+ onResult.emit(results_,null);
+ }
+
+ });
+ }
+
+ }
+
+ @Override
+ public void run() {
+ Thread queryThread = new Thread(new QueryRunnable());
+ queryThread.setDaemon(true);
+ queryThread.run();
+ }
+
+}
diff --git a/src/com/isode/stroke/network/ProxiedConnection.java b/src/com/isode/stroke/network/ProxiedConnection.java
index a94fbc5..6f4c044 100644
--- a/src/com/isode/stroke/network/ProxiedConnection.java
+++ b/src/com/isode/stroke/network/ProxiedConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2015 Isode Limited.
+ * Copyright (c) 2012-2016 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
@@ -27,8 +27,8 @@ public abstract class ProxiedConnection extends Connection {
private HostAddressPort server_;
private Connector connector_;
private Connection connection_;
- private SignalConnection onDataReadConnection;
- private SignalConnection onDisconnectedConnection;
+ private SignalConnection onDataReadConnection_;
+ private SignalConnection onDisconnectedConnection_;
private SignalConnection onConnectFinishedConnection;
public ProxiedConnection(DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory, final String proxyHost, int proxyPort) {
@@ -45,8 +45,8 @@ public abstract class ProxiedConnection extends Connection {
try {
cancelConnector();
if (connection_ != null) {
- onDataReadConnection.disconnect();
- onDisconnectedConnection.disconnect();
+ onDataReadConnection_.disconnect();
+ onDisconnectedConnection_.disconnect();
}
if (connected_) {
System.err.println("Warning: Connection was still established.");
@@ -147,4 +147,20 @@ public abstract class ProxiedConnection extends Connection {
protected HostAddressPort getServer() {
return server_;
}
+
+ protected void reconnect() {
+ if (onDataReadConnection_ != null) {
+ onDataReadConnection_.disconnect();
+ onDataReadConnection_ = null;
+ }
+ if (onDisconnectedConnection_ != null) {
+ onDisconnectedConnection_.disconnect();
+ onDisconnectedConnection_ = null;
+ }
+ if (connected_) {
+ connection_.disconnect();
+ }
+ connect(server_);
+ }
+
} \ No newline at end of file
diff --git a/test/com/isode/stroke/network/HTTPConnectProxiedConnectionTest.java b/test/com/isode/stroke/network/HTTPConnectProxiedConnectionTest.java
new file mode 100644
index 0000000..1f228dc
--- /dev/null
+++ b/test/com/isode/stroke/network/HTTPConnectProxiedConnectionTest.java
@@ -0,0 +1,477 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.network;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.DummyEventLoop;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.network.Connection.Error;
+import com.isode.stroke.network.HTTPConnectProxiedConnection.Pair;
+import com.isode.stroke.signals.Slot1;
+
+/**
+ * Tests for {@link HTTPConnectProxiedConnection}
+ */
+public class HTTPConnectProxiedConnectionTest {
+
+ private final String proxyHost = "doo.bah";
+ private final int proxyPort = 1234;
+ private final HostAddressPort proxyHostAddress = new HostAddressPort(new HostAddress("1.1.1.1"), proxyPort);
+ private final HostAddressPort host = new HostAddressPort(new HostAddress("2.2.2.2"), 2345);
+ private final DummyEventLoop eventLoop = new DummyEventLoop();
+ private final StaticDomainNameResolver resolver = new StaticDomainNameResolver(eventLoop);
+ private final MockConnectionFactory connectionFactory = new MockConnectionFactory(eventLoop);
+ private final TimerFactory timerFactory = new DummyTimerFactory();
+ private boolean connectFinished = false;
+ private boolean connectFinishedWithError = false;
+ private boolean disconnected = false;
+ private Connection.Error disconnectedError = null;
+ private final ByteArray dataRead = new ByteArray();
+
+ private static Logger logger =
+ Logger.getLogger(HTTPConnectProxiedConnectionTest.class.getName());
+
+ @Before
+ public void setUp() {
+ resolver.addAddress(proxyHost, proxyHostAddress.getAddress());
+ }
+
+ @Test
+ public void testConnect_CreatesConnectionToProxy() {
+ HTTPConnectProxiedConnection testling = createTestling();
+
+ connect(testling, host);
+
+ assertEquals(1,connectionFactory.connections.size());
+ assertNotNull(connectionFactory.connections.get(0).hostAddressPort);
+ assertEquals(proxyHostAddress,connectionFactory.connections.get(0).hostAddressPort);
+ assertFalse(connectFinished);
+ }
+
+ @Test
+ public void testConnect_SendsConnectRequest() {
+ HTTPConnectProxiedConnection testling = createTestling();
+
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ assertEquals(new ByteArray("CONNECT 2.2.2.2:2345 HTTP/1.1\r\n\r\n"),
+ connectionFactory.connections.get(0).dataWritten);
+ }
+
+ @Test
+ public void testConnect_ReceiveConnectResponse() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+ eventLoop.processEvents();
+
+ assertTrue(connectFinished);
+ assertFalse(connectFinishedWithError);
+ assertTrue(dataRead.isEmpty());
+ }
+
+ @Test
+ public void testConnect_ReceiveConnectChunkedResponse() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 "));
+ eventLoop.processEvents();
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("200 Connection established\r\n\r\n"));
+ eventLoop.processEvents();
+
+ assertTrue(connectFinished);
+ assertFalse(connectFinishedWithError);
+ assertTrue(dataRead.isEmpty());
+ }
+
+ @Test
+ public void testConnect_ReceiveMalformedConnectResponse() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("FLOOP"));
+ eventLoop.processEvents();
+
+ assertTrue(connectFinished);
+ assertTrue(connectFinishedWithError);
+ assertTrue(connectionFactory.connections.get(0).disconnected);
+ }
+
+ @Test
+ public void testConnect_ReceiveErrorConnectResponse() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 401 Unauthorized\r\n\r\n"));
+ eventLoop.processEvents();
+
+ assertTrue(connectFinished);
+ assertTrue(connectFinishedWithError);
+ assertTrue(connectionFactory.connections.get(0).disconnected);
+ }
+
+ @Test
+ public void testConnect_ReceiveDataAfterConnect() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+ eventLoop.processEvents();
+
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("abcdef"));
+
+ assertEquals(new ByteArray("abcdef"),dataRead);
+ }
+
+ @Test
+ public void testWrite_AfterConnect() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+ eventLoop.processEvents();
+ connectionFactory.connections.get(0).dataWritten.clear();
+
+ testling.write(new SafeByteArray("abcdef"));
+
+ assertEquals(new ByteArray("abcdef"),connectionFactory.connections.get(0).dataWritten);
+ }
+
+ @Test
+ public void testDisconnect_AfterConnectRequest() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ testling.disconnect();
+
+ assertTrue(connectionFactory.connections.get(0).disconnected);
+ assertTrue(disconnected);
+ assertNull(disconnectedError);
+ }
+
+ @Test
+ public void testDisconnect_AfterConnect() {
+ HTTPConnectProxiedConnection testling = createTestling();
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 200 Connection established\r\n\r\n"));
+ eventLoop.processEvents();
+
+ testling.disconnect();
+
+ assertTrue(connectionFactory.connections.get(0).disconnected);
+ assertTrue(disconnected);
+ assertNull(disconnectedError);
+ }
+
+ @Test
+ public void testTrafficFilter() {
+ HTTPConnectProxiedConnection testling = createTestling();
+
+ ExampleHTTPTrafficFilter httpTrafficFilter = new ExampleHTTPTrafficFilter();
+
+ testling.setHTTPTrafficFilter(httpTrafficFilter);
+ connect(testling, new HostAddressPort(new 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.add(new Pair("Authorization", "Negotiate a87421000492aa874209af8bc028"));
+
+ connectionFactory.connections.get(0).dataWritten.clear();
+
+ // test chunked response
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("HTTP/1.0 401 Unauthorized\r\n"));
+ eventLoop.processEvents();
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray("WWW-Authenticate: Negotiate\r\n\r\n"));
+ eventLoop.processEvents();
+
+
+ // verify that the traffic filter got called and answered with its response
+ assertEquals(1,httpTrafficFilter.filterResponses.size());
+ assertEquals("WWW-Authenticate",httpTrafficFilter.filterResponses.get(0).get(0).a);
+
+ // remove the default response from the traffic filter
+ httpTrafficFilter.filterResponseReturn.clear();
+ eventLoop.processEvents();
+
+ // verify that the traffic filter answer is send over the wire
+ assertEquals(new ByteArray("CONNECT 2.2.2.2:2345 HTTP/1.1\r\nAuthorization: Negotiate a87421000492aa874209af8bc028\r\n\r\n"), connectionFactory.connections.get(1).dataWritten);
+
+ // verify that after without the default response, the traffic filter is skipped, authentication proceeds and traffic goes right through
+ connectionFactory.connections.get(1).dataWritten.clear();
+ testling.write(new SafeByteArray("abcdef"));
+ assertEquals(new ByteArray("abcdef"), connectionFactory.connections.get(1).dataWritten);
+ }
+
+ @Test
+ public void testTrafficFilterNoConnectionReuse() {
+ HTTPConnectProxiedConnection testling = createTestling();
+
+ ProxyAuthenticationHTTPTrafficFilter httpTrafficFilter = new ProxyAuthenticationHTTPTrafficFilter();
+ testling.setHTTPTrafficFilter(httpTrafficFilter);
+
+
+ connect(testling, new HostAddressPort(new HostAddress("2.2.2.2"), 2345));
+
+ // First HTTP CONNECT request assumes the proxy will work.
+ assertEquals(new ByteArray("CONNECT 2.2.2.2:2345 HTTP/1.1\r\n\r\n"),
+ connectionFactory.connections.get(0).dataWritten);
+
+ // First reply presents initiator with authentication options.
+ connectionFactory.connections.get(0).onDataRead.emit(new SafeByteArray(
+ "HTTP/1.0 407 ProxyAuthentication Required\r\n"
+ +"proxy-Authenticate: Negotiate\r\n"
+ +"Proxy-Authenticate: Kerberos\r\n"
+ +"proxy-Authenticate: NTLM\r\n"
+ +"\r\n"));
+ eventLoop.processEvents();
+ assertFalse(connectFinished);
+ assertFalse(connectFinishedWithError);
+
+ // The HTTP proxy responds with code 407, so the traffic filter should inject the authentication response on a new connection.
+ assertEquals(new ByteArray("CONNECT 2.2.2.2:2345 HTTP/1.1\r\nProxy-Authorization: "
+ + "NTLM TlRMTVNTUAABAAAAt7II4gkACQAxAAAACQAJACgAAAVNTUAADAAFASgKAAAAD0"
+ + "xBQlNNT0tFM1dPUktHUk9VUA==\r\n\r\n"),
+ connectionFactory.connections.get(1).dataWritten);
+
+ // The proxy responds with another authentication step.
+ connectionFactory.connections.get(1).onDataRead.emit(new SafeByteArray(
+ "HTTP/1.0 407 ProxyAuthentication Required\r\n"
+ +"Proxy-Authenticate: NTLM TlRMTVNTUAACAAAAEAAQADgAAAA1goriluCDYHcYI/sAAAAAAAAAA"
+ + "FQAVABIAAAABQLODgAAAA9TAFAASQBSAEkAVAAxAEIAAgAQAFMAUABJAFIASQBUADEAQgABABAAUw"
+ + "BQAEkAUgBJAFQAMQBCAAQAEABzAHAAaQByAGkAdAAxAGIAAwAQAHMAcABpAHIAaQB0ADEAYgAAAAAA"
+ + "\r\n\r\n"));
+ eventLoop.processEvents();
+ assertFalse(connectFinished);
+ assertFalse(connectFinishedWithError);
+
+ // Last HTTP request that should succeed. Further traffic will go over the connection of this request.
+ assertEquals(new ByteArray("CONNECT 2.2.2.2:2345 HTTP/1.1\r\nProxy-Authorization: "
+ + "NTLM TlRMTVNTUAADAAAAGAAYAHIAAAAYABgAigAAABIAEgBIAAAABgAGAFoAAAASABIVNT"
+ + "UAADAAYAAAABAAEACiAAAANYKI4gUBKAoAAAAPTABBAEIAUwBNAE8ASwBFADMAXwBxAGEAT"
+ + "ABBAEIAUwBNAE8ASwBFADMA0NKq8HYYhj8AAAAAAAAAAAAAAAAAAAAAOIiih3mR+AkyM4r99"
+ + "sy1mdFonCu2ILODro1WTTrJ4b4JcXEzUBA2Ig==\r\n\r\n"),
+ connectionFactory.connections.get(2).dataWritten);
+
+ connectionFactory.connections.get(2).onDataRead.emit(new SafeByteArray("HTTP/1.0 200 OK"
+ + "\r\n\r\n"));
+ eventLoop.processEvents();
+
+ // The HTTP CONNECT proxy initialization finished without error.
+ assertTrue(connectFinished);
+ assertFalse(connectFinishedWithError);
+
+ // Further traffic is written directly, without interception of the filter.
+ connectionFactory.connections.get(2).dataWritten.clear();
+ testling.write(new SafeByteArray("This is some basic data traffic."));
+ assertEquals(new ByteArray("This is some basic data traffic."),
+ connectionFactory.connections.get(2).dataWritten);
+ }
+
+ private void connect(HTTPConnectProxiedConnection connection, HostAddressPort to) {
+ connection.connect(to);
+ eventLoop.processEvents();
+ eventLoop.processEvents();
+ eventLoop.processEvents();
+ }
+
+ private HTTPConnectProxiedConnection createTestling() {
+ HTTPConnectProxiedConnection connection = HTTPConnectProxiedConnection.create(resolver,
+ connectionFactory, timerFactory, proxyHost, proxyPort,
+ new SafeByteArray(""), new SafeByteArray(""));
+ connection.onConnectFinished.connect(new Slot1<Boolean>() {
+
+ @Override
+ public void call(Boolean hadError) {
+ handleConnectFinished(hadError.booleanValue());
+ }
+
+ });
+ connection.onDisconnected.connect(new Slot1<Connection.Error>() {
+
+ @Override
+ public void call(Error error) {
+ handleDisconnected(error);
+ }
+
+ });
+ connection.onDataRead.connect(new Slot1<SafeByteArray>() {
+
+ @Override
+ public void call(SafeByteArray data) {
+ handleDataRead(data);
+ }
+
+ });
+ return connection;
+ }
+
+ private void handleConnectFinished(boolean hadError) {
+ connectFinished = true;
+ connectFinishedWithError = hadError;
+ }
+
+ private void handleDisconnected(Connection.Error error) {
+ disconnected = true;
+ disconnectedError = error;
+ }
+
+ private void handleDataRead(SafeByteArray data) {
+ dataRead.append(data);
+ }
+
+ private static class ExampleHTTPTrafficFilter implements HTTPTrafficFilter {
+
+ @Override
+ public Vector<Pair> filterHTTPResponseHeader(String statusLine, Vector<Pair> response) {
+ filterResponses.add(response);
+ logger.fine("\n");
+ return filterResponseReturn;
+ }
+
+ private Vector<Vector<Pair>> filterResponses = new Vector<Vector<Pair>>();
+
+ private Vector<Pair> filterResponseReturn = new Vector<Pair>();
+
+ }
+
+ public static class ProxyAuthenticationHTTPTrafficFilter implements HTTPTrafficFilter {
+
+ @Override
+ public Vector<Pair> filterHTTPResponseHeader(String statusLine, Vector<Pair> response) {
+ Vector<Pair> filterResponseReturn = new Vector<Pair>();
+ String[] rawStatusLineFields = statusLine.split("\\s+");
+ Vector<String> statusLineFields = new Vector(Arrays.asList(rawStatusLineFields));
+
+ int statusCode = Integer.valueOf(statusLineFields.get(1));
+ if (statusCode == 407) {
+ for (Pair field : response) {
+ if ("Proxy-Authenticate".equalsIgnoreCase(field.a)) {
+ if (field.b.length() >= 6 && field.b.startsWith(" NTLM ")) {
+ filterResponseReturn.add(new Pair("Proxy-Authorization",
+ "NTLM TlRMTVNTUAADAAAAGAAYAHIAAAAYABgAigAAABIAEgBIAAAABgAGAFo"
+ + "AAAASABIVNTUAADAAYAAAABAAEACiAAAANYKI4gUBKAoAAAAPTABBAEIAU"
+ + "wBNAE8ASwBFADMAXwBxAGEATABBAEIAUwBNAE8ASwBFADMA0NKq8HYYhj"
+ + "8AAAAAAAAAAAAAAAAAAAAAOIiih3mR+AkyM4r99sy1mdFonCu2ILODro1W"
+ + "TTrJ4b4JcXEzUBA2Ig=="));
+ return filterResponseReturn;
+ }
+ else if (field.b.length() >= 5 && field.b.startsWith(" NTLM")) {
+ filterResponseReturn.add(new Pair("Proxy-Authorization",
+ "NTLM TlRMTVNTUAABAAAAt7II4gkACQAxAAAACQAJACgAAAVNTUAADAAFASg"
+ + "KAAAAD0xBQlNNT0tFM1dPUktHUk9VUA=="));
+ return filterResponseReturn;
+ }
+ }
+ }
+
+ return filterResponseReturn;
+ }
+ else {
+ return new Vector<Pair>();
+ }
+ }
+
+ }
+
+ private static class MockConnection extends Connection {
+
+ private final EventLoop eventLoop;
+ private HostAddressPort hostAddressPort = null;
+ private final List<HostAddressPort> failingPorts;
+ private final ByteArray dataWritten = new ByteArray();
+ private boolean disconnected = false;
+
+ private MockConnection(Collection<? extends HostAddressPort> failingPorts,
+ EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ this.failingPorts = new ArrayList<HostAddressPort>(failingPorts);
+ }
+
+ @Override
+ public void listen() {
+ fail();
+ }
+
+ @Override
+ public void connect(HostAddressPort address) {
+ hostAddressPort = address;
+ final boolean fail = failingPorts.contains(address);
+ eventLoop.postEvent(new Callback() {
+
+ @Override
+ public void run() {
+ onConnectFinished.emit(fail);
+ }
+
+ });
+ }
+
+ @Override
+ public void disconnect() {
+ disconnected = true;
+ onDisconnected.emit(null);
+ }
+
+ @Override
+ public void write(SafeByteArray data) {
+ dataWritten.append(data);
+ }
+
+ @Override
+ public HostAddressPort getLocalAddress() {
+ return new HostAddressPort();
+ }
+
+ public HostAddressPort getRemoteAddress() {
+ return new HostAddressPort();
+ }
+
+ }
+
+ private static class MockConnectionFactory implements ConnectionFactory {
+
+ private final EventLoop eventLoop;
+ private final List<MockConnection> connections = new ArrayList<MockConnection>();
+ private final List<HostAddressPort> failingPorts = new ArrayList<HostAddressPort>();
+
+ private MockConnectionFactory(EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ }
+
+ @Override
+ public Connection createConnection() {
+ MockConnection connection = new MockConnection(failingPorts, eventLoop);
+ connections.add(connection);
+ logger.fine("New connection created\n");
+ return connection;
+ }
+
+ }
+
+}