diff options
| author | Alex Clayton <alex.clayton@isode.com> | 2016-02-22 16:05:37 (GMT) | 
|---|---|---|
| committer | Alex Clayton <alex.clayton@isode.com> | 2016-02-29 12:10:44 (GMT) | 
| commit | d636d68c84229c82ff746c7697d2014ff4dd4477 (patch) | |
| tree | a534ffdb9696c68d21d1cec6624023795ef683d7 | |
| parent | 2de569d23468c94fdcf1adc336a580b053423fd7 (diff) | |
| download | stroke-d636d68c84229c82ff746c7697d2014ff4dd4477.zip stroke-d636d68c84229c82ff746c7697d2014ff4dd4477.tar.bz2 | |
Finish porting on Network Package
As per PortingProgress.txt finsh porting all the classes I can from the network
package.  This involved some updates as the tests and code had changed since
they existing classes had been imported.
I have added notes for the classes I did not port in PortingProgress explaining
why they were not ported.
Test-information:
All unit tests pass.
Change-Id: Ibb52ae409f1da9b72a4c1e590cd22835a1be95eb
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; +        } +         +    } + +} | 
 Swift
 Swift