Index: .classpath =================================================================== --- .classpath (revision 494409) +++ .classpath (working copy) @@ -1,10 +1,11 @@ - - - - + + + + + - + Index: build.xml =================================================================== --- build.xml (revision 522919) +++ build.xml (working copy) @@ -264,6 +264,7 @@ + Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnection.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnection.java (revision 515170) +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnection.java (working copy) @@ -17,7 +17,6 @@ package org.apache.harmony.luni.internal.net.www.protocol.http; -import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -33,8 +32,6 @@ import java.net.Proxy; import java.net.ProxySelector; import java.net.ResponseCache; -import java.net.Socket; -import java.net.SocketAddress; import java.net.SocketPermission; import java.net.URI; import java.net.URISyntaxException; @@ -74,6 +71,8 @@ private int httpVersion = 1; // Assume HTTP/1.1 + protected HttpConnection connection; + private InputStream is; private InputStream uis; @@ -78,8 +77,6 @@ private InputStream uis; - protected Socket socket; - private OutputStream socketOut; private OutputStream cacheOut; @@ -130,8 +127,12 @@ @Override public void close() throws IOException { - bytesRemaining = 0; - closeSocket(); + if(bytesRemaining > 0) { + bytesRemaining = 0; + disconnect(true); // Should close the socket if client hasn't read all the data + } else { + disconnect(false); + } /* * if user has set useCache to true and cache exists, aborts it when * closing @@ -153,6 +154,7 @@ @Override public int read() throws IOException { if (bytesRemaining <= 0) { + disconnect(false); return -1; } int result = is.read(); @@ -162,6 +164,9 @@ cacheOut.write(result); } bytesRemaining--; + if (bytesRemaining <= 0) { + disconnect(false); + } return result; } @@ -176,6 +181,7 @@ throw new ArrayIndexOutOfBoundsException(); } if (bytesRemaining <= 0) { + disconnect(false); return -1; } if (length > bytesRemaining) { @@ -190,6 +196,9 @@ cacheOut.write(buf, offset, result); } } + if (bytesRemaining <= 0) { + disconnect(false); + } return result; } @@ -195,6 +204,7 @@ public long skip(int amount) throws IOException { if (bytesRemaining <= 0) { + disconnect(false); return -1; } if (amount > bytesRemaining) { @@ -204,6 +214,9 @@ if (result > 0) { bytesRemaining -= result; } + if (bytesRemaining <= 0) { + disconnect(false); + } return result; } } @@ -219,8 +232,12 @@ @Override public void close() throws IOException { + if(!atEnd && available() > 0) { + disconnect(true); + } else { + disconnect(false); + } atEnd = true; - closeSocket(); // if user has set useCache to true and cache exists, abort if (useCaches && null != cacheRequest) { cacheRequest.abort(); @@ -261,6 +278,7 @@ readChunkSize(); } if (atEnd) { + disconnect(false); return -1; } bytesRemaining--; @@ -286,6 +304,7 @@ readChunkSize(); } if (atEnd) { + disconnect(false); return -1; } if (length > bytesRemaining) { @@ -305,6 +324,7 @@ public long skip(int amount) throws IOException { if (atEnd) { + disconnect(false); return -1; } if (bytesRemaining <= 0) { @@ -428,6 +448,7 @@ } sendCache(closed); } + disconnect(false); } @Override @@ -585,7 +606,7 @@ return; } // socket to be used for connection - Socket socket = null; + connection = null; // try to determine: to use the proxy or not if (proxy != null) { // try to make the connection to the proxy @@ -591,7 +612,7 @@ // try to make the connection to the proxy // specified in constructor. // IOException will be thrown in the case of failure - socket = getHTTPConnection(proxy); + connection = getHTTPConnection(proxy); } else { // Use system-wide ProxySelect to select proxy list, // then try to connect via elements in the proxy list. @@ -604,7 +625,7 @@ continue; } try { - socket = getHTTPConnection(selectedProxy); + connection = getHTTPConnection(selectedProxy); proxy = selectedProxy; break; // connected } catch (IOException e) { @@ -614,12 +635,12 @@ } } } - if (socket == null) { + if (connection == null) { // make direct connection - socket = getHTTPConnection(null); + connection = getHTTPConnection(null); } - socket.setSoTimeout(getReadTimeout()); - setUpTransportIO(socket); + connection.setSoTimeout(getReadTimeout()); + setUpTransportIO(connection); connected = true; } @@ -624,42 +645,17 @@ } /** - * Returns connected socket to be used for this HTTP connection. TODO: - * implement persistent connections. + * Returns connected socket to be used for this HTTP connection. */ - protected Socket getHTTPConnection(Proxy proxy) throws IOException { - Socket socket; + protected HttpConnection getHTTPConnection(Proxy proxy) throws IOException { + HttpConnection connection; if (proxy == null || proxy.type() == Proxy.Type.DIRECT) { - this.proxy = null; // not using proxy - socket = new Socket(); - socket.connect(new InetSocketAddress(getHostName(), getHostPort()), - getConnectTimeout()); - } else if (proxy.type() == Proxy.Type.HTTP) { - socket = new Socket(); - - SocketAddress proxyAddr = proxy.address(); - - if (!(proxyAddr instanceof InetSocketAddress)) { - throw new IllegalArgumentException(Msg.getString( - "K0316", proxyAddr.getClass())); //$NON-NLS-1$ - } - - InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; - - if( iProxyAddr.getAddress() == null ) { - // Resolve proxy, see HARMONY-3113 - socket.connect(new InetSocketAddress((iProxyAddr.getHostName()), - iProxyAddr.getPort()), getConnectTimeout()); - } else { - socket.connect(iProxyAddr, getConnectTimeout()); - } + this.proxy = null; // not using proxy + connection = HttpConnectionManager.getDefault().getConnection(uri, getConnectTimeout()); } else { - // using SOCKS proxy - socket = new Socket(proxy); - socket.connect(new InetSocketAddress(getHostName(), getHostPort()), - getConnectTimeout()); + connection = HttpConnectionManager.getDefault().getConnection(uri, proxy, getConnectTimeout()); } - return socket; + return connection; } /** @@ -665,13 +661,12 @@ /** * Sets up the data streams used to send request[s] and read response[s]. * - * @param socket - * socket to be used for connection + * @param connection + * HttpConnection to be used */ - protected void setUpTransportIO(Socket socket) throws IOException { - this.socket = socket; - socketOut = socket.getOutputStream(); - is = new BufferedInputStream(socket.getInputStream()); + protected void setUpTransportIO(HttpConnection connection) throws IOException { + socketOut = connection.getOutputStream(); + is = connection.getInputStream(); } // Tries to get head and body from cache, return true if has got this time @@ -721,16 +716,17 @@ */ @Override public void disconnect() { - try { - closeSocket(); - } catch (IOException e) { - } + disconnect(true); } - void closeSocket() throws IOException { - if (is != null) { - is.close(); + private void disconnect(boolean closeSocket) { + if(closeSocket && connection != null) { + connection.closeSocketAndStreams(); + } else if (connection != null) { + HttpConnectionManager.getDefault().returnConnectionToPool(connection); + connection = null; } + connection = null; } protected void endRequest() throws IOException { @@ -1070,7 +1066,7 @@ if (method == HEAD || (responseCode >= 100 && responseCode < 200) || responseCode == HTTP_NO_CONTENT || responseCode == HTTP_NOT_MODIFIED) { - closeSocket(); + disconnect(); uis = new LimitedInputStream(0); } putToCache(); @@ -1163,8 +1159,7 @@ // if we are doing output make sure the appropriate headers are sent if (os != null) { if (reqHeader.get("Content-Type") == null) { //$NON-NLS-1$ - output - .append("Content-Type: application/x-www-form-urlencoded\r\n"); //$NON-NLS-1$ + output.append("Content-Type: application/x-www-form-urlencoded\r\n"); //$NON-NLS-1$ } if (os.isCached()) { if (reqHeader.get("Content-Length") == null) { //$NON-NLS-1$ @@ -1198,8 +1193,7 @@ * if both setFixedLengthStreamingMode and * content-length are set, use fixedContentLength first */ - output - .append((fixedContentLength >= 0) ? String + output.append((fixedContentLength >= 0) ? String .valueOf(fixedContentLength) : reqHeader.get(i)); } else { @@ -1381,7 +1375,7 @@ // drop everything and reconnect, might not be required for // HTTP/1.1 endRequest(); - closeSocket(); + disconnect(); connected = false; String credentials = getAuthorizationCredentials(challenge); if (credentials == null) { @@ -1404,7 +1398,7 @@ // drop everything and reconnect, might not be required for // HTTP/1.1 endRequest(); - closeSocket(); + disconnect(); connected = false; String credentials = getAuthorizationCredentials(challenge); if (credentials == null) { @@ -1450,7 +1444,7 @@ hostPort = -1; } endRequest(); - closeSocket(); + disconnect(); connected = false; continue; } @@ -1467,7 +1461,7 @@ * authorization challenge * * @param challenge - * @return + * @return authorization credentials * @throws IOException */ private String getAuthorizationCredentials(String challenge) @@ -1517,4 +1511,4 @@ } } } -} +} \ No newline at end of file Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConfiguration.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConfiguration.java +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConfiguration.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.luni.internal.net.www.protocol.http; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.net.URI; + +import org.apache.harmony.luni.util.Msg; + +/** + * An HttpConfiguration contains all the details needed to create an http connection + * and to compare whether or not two connections are the same. An HttpConfiguration + * will either consist of a Proxy or a port number (int) + * and host name (String) or all three, depending on whether or not a + * Proxy is used and the type of Proxy it is. + * + * HttpConfiguration is used as a key by HttpConnectionManager + * to retrieve HttpConnections from its connection pool. + */ +public class HttpConfiguration { + + private Proxy proxy; + private int hostPort; + private String hostName; + private URI uri; + + public HttpConfiguration(URI uri) { + this.uri = uri; + this.hostName = uri.getHost(); + this.hostPort = uri.getPort(); + if(hostPort == -1) { + if(uri.getScheme().equals("https")) { //$NON-NLS-1$ + hostPort = 443; + } else { + hostPort = 80; + } + } + } + + public HttpConfiguration(URI uri, Proxy proxy) { + this.uri = uri; + this.proxy = proxy; + if (proxy.type() == Proxy.Type.HTTP) { + SocketAddress proxyAddr = proxy.address(); + if (!(proxyAddr instanceof InetSocketAddress)) { + throw new IllegalArgumentException(Msg.getString( + "K0316", proxyAddr.getClass())); //$NON-NLS-1$ + } + InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; + this.hostName = iProxyAddr.getHostName(); + this.hostPort = iProxyAddr.getPort(); + } else { + // using SOCKS proxy + this.hostName = uri.getHost(); + this.hostPort = uri.getPort(); + if(hostPort == -1) { + if(uri.getScheme().equals("https")) { //$NON-NLS-1$ + hostPort = 443; + } else { + hostPort = 80; + } + } + } + this.uri = uri; + SocketAddress proxyAddr = proxy.address(); + if (!(proxyAddr instanceof InetSocketAddress)) { + throw new IllegalArgumentException(Msg.getString( + "K0316", proxyAddr.getClass())); //$NON-NLS-1$ + } + InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; + this.hostName = iProxyAddr.getHostName(); + this.hostPort = iProxyAddr.getPort(); + } + + /** + * Returns true if this configuration uses a Proxy + */ + public boolean usesProxy() { + return proxy != null; + } + + /** + * Returns the Proxy for this configuration, or null if a proxy + * is not used + */ + public Proxy getProxy() { + return proxy; + } + + /** + * Returns the host name for this configuration, or null if an http Proxy is used + */ + public String getHostName() { + return hostName; + } + + /** + * Returns the port for this configuration, or 0 if an http Proxy is used + */ + public int getHostPort() { + return hostPort; + } + + @Override + public boolean equals(Object arg0) { + if(!(arg0 instanceof HttpConfiguration)) { + return false; + } else { + HttpConfiguration config = (HttpConfiguration)arg0; + if(config.proxy != null && proxy != null) { + return config.proxy.equals(proxy) && uri.equals(config.uri); + } + return uri.equals(config.uri); + } + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + +} \ No newline at end of file Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.luni.internal.net.www.protocol.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.SocketException; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.harmony.luni.internal.nls.Messages; + +/** + * An HttpConnection represents a persistent http or https connection and contains + * various utility methods to access that connection. + */ +public class HttpConnection { + + private boolean usingSecureSocket = false; + + private Socket socket; + private SSLSocket sslSocket; + + private InputStream inputStream; + private OutputStream outputStream; + private InputStream sslInputStream; + private OutputStream sslOutputStream; + + private HttpConfiguration config; + + public HttpConnection(HttpConfiguration config, int connectTimeout) throws IOException { + this.config = config; + String hostName = config.getHostName(); + int hostPort = config.getHostPort(); + Proxy proxy = config.getProxy(); + if(proxy == null || proxy.type() == Proxy.Type.HTTP) { + socket = new Socket(); + } else { + socket = new Socket(proxy); + } + socket.connect(new InetSocketAddress(hostName, hostPort), connectTimeout); + } + + public void closeSocketAndStreams() { + if(usingSecureSocket) { + if (null != sslOutputStream) { + OutputStream temp = sslOutputStream; + sslOutputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != sslInputStream) { + InputStream temp = sslInputStream; + sslInputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != sslSocket) { + Socket temp = sslSocket; + sslSocket = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + } + if (null != outputStream) { + OutputStream temp = outputStream; + outputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != inputStream) { + InputStream temp = inputStream; + inputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != socket) { + Socket temp = socket; + socket = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + } + + public void setSoTimeout(int readTimeout) throws SocketException { + socket.setSoTimeout(readTimeout); + } + + public OutputStream getOutputStream() throws IOException { + if(usingSecureSocket) { + if (sslOutputStream == null) { + sslOutputStream = sslSocket.getOutputStream(); + } + return sslOutputStream; + } else if(outputStream == null) { + outputStream = socket.getOutputStream(); + } + return outputStream; + } + + public InputStream getInputStream() throws IOException { + if(usingSecureSocket) { + if (sslInputStream == null) { + sslInputStream = sslSocket.getInputStream(); + } + return sslInputStream; + } else if(inputStream == null) { + inputStream = socket.getInputStream(); + } + return inputStream; + } + + public HttpConfiguration getHttpConfiguration() { + return config; + } + + public SSLSocket getSecureSocket(SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier) throws IOException { + if(!usingSecureSocket) { + String hostName = config.getHostName(); + int port = config.getHostPort(); + // create the wrapper over connected socket + sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, + hostName, port, true); + sslSocket.setUseClientMode(true); + sslSocket.startHandshake(); + if (!hostnameVerifier.verify(hostName, sslSocket.getSession())) { + throw new IOException(Messages.getString("luni.02", hostName)); //$NON-NLS-1$ + } + usingSecureSocket = true; + } + return sslSocket; + } + + Socket getSocket() { + return socket; + } + +} \ No newline at end of file Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnectionManager.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnectionManager.java +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnectionManager.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.luni.internal.net.www.protocol.http; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URI; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.harmony.luni.util.PriviAction; + +/** + * HttpConnectionManager manages a pool of HttpConnections + * that are not currently in use and is used to get hold of persistent HttpConnections. + * Clients should return an HttpConnection to the pool after use by calling + * returnConnectionToPool + * + * Two system properties affect the behaviour of this class - http.maxConnections + * and http.keepAlive. http.keepAlive determines whether + * or not connections should be persisted and http.maxConnections + * determines the maximum number of connections to each individual host that + * should be kept in the pool. + */ +public class HttpConnectionManager { + + // The maximum number of connections to any location + private static int maxConnections = 5; + + // Keeps connections alive if true + private static boolean keepAlive = true; + + private static HttpConnectionManager defaultConnectionManager; + private ConnectionPool pool = new ConnectionPool(); + + /** + * Returns the default connection manager + */ + public static HttpConnectionManager getDefault() { + if(defaultConnectionManager == null) { + defaultConnectionManager = new HttpConnectionManager(); + } + return defaultConnectionManager; + } + + public HttpConnection getConnection(URI uri, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(uri); + return pool.getHttpConnection(config, connectTimeout); + } + + public HttpConnection getConnection(URI uri, Proxy proxy, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(uri, proxy); + return pool.getHttpConnection(config, connectTimeout); + } + + public void returnConnectionToPool(HttpConnection connection) { + checkSystemProperties(); + pool.returnConnection(connection); + } + + public int numFreeConnections() { + return pool.numFreeConnections(); + } + + private void checkSystemProperties() { + String httpMaxConnections = AccessController.doPrivileged(new PriviAction("http.maxConnections")); + String httpKeepAlive = AccessController.doPrivileged(new PriviAction("http.keepAlive")); + if(httpMaxConnections != null) { + maxConnections = Integer.parseInt(httpMaxConnections); + } + if(httpKeepAlive != null) { + keepAlive = Boolean.parseBoolean(httpKeepAlive); + if(!keepAlive) { + pool.clear(); + } + } + } + + private static class ConnectionPool { + + private Map> freeConnectionMap = new HashMap>(); // Map of free Sockets + + public synchronized void clear() { + for (Iterator> iter = freeConnectionMap.values().iterator(); iter.hasNext();) { + List connections = iter.next(); + for (Iterator iterator = connections.iterator(); iterator.hasNext();) { + HttpConnection connection = iterator.next(); + connection.closeSocketAndStreams(); + } + } + freeConnectionMap.clear(); + } + + public synchronized void returnConnection(HttpConnection connection) { + if(!connection.getSocket().isClosed() && keepAlive) { + HttpConfiguration config = connection.getHttpConfiguration(); + List connections = freeConnectionMap.get(config); + if(connections == null) { + connections = new ArrayList(); + freeConnectionMap.put(config, connections); + } + if(connections.size() < HttpConnectionManager.maxConnections) { + if(!connections.contains(connection)) { + connections.add(connection); + } + } else { + connection.closeSocketAndStreams(); + } + } else { + // Make sure all streams are closed etc. + connection.closeSocketAndStreams(); + } + } + + public synchronized HttpConnection getHttpConnection(HttpConfiguration config, int connectTimeout) throws IOException { + List connections = freeConnectionMap.get(config); + if(keepAlive && connections == null) { + connections = new ArrayList(); + freeConnectionMap.put(config, connections); + } + if(!keepAlive || connections.isEmpty()) { + HttpConnection connection = new HttpConnection(config, connectTimeout); + return connection; + } else { + HttpConnection connection = connections.get(0); + connections.remove(0); + if(!connection.getSocket().isClosed()) { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkConnect(connection.getSocket().getInetAddress().getHostName(), connection.getSocket().getPort()); + } + return connection; + } else { + return getHttpConnection(config, connectTimeout); + } + } + } + + public int numFreeConnections() { + int numFree = 0; + for (Iterator> iter = freeConnectionMap.values().iterator(); iter.hasNext();) { + List connections = iter.next(); + numFree += connections.size(); + } + return numFree; + } + } + + public void reset() { + pool.clear(); + } + +} \ No newline at end of file Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (revision 494409) +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (working copy) @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.harmony.luni.internal.net.www.protocol.https; import java.io.IOException; @@ -22,7 +21,6 @@ import java.io.OutputStream; import java.net.ProtocolException; import java.net.Proxy; -import java.net.Socket; import java.net.URL; import java.security.Permission; import java.security.Principal; @@ -387,7 +385,7 @@ responseMessage, responseCode)); } // if there are some remaining data in the stream - read it out - InputStream is = socket.getInputStream(); + InputStream is = connection.getInputStream(); while (is.available() != 0) { is.read(); } @@ -397,7 +395,8 @@ super.connect(); } if (!makingSSLTunnel) { - setUpTransportIO(wrapConnection(socket)); + sslSocket = connection.getSecureSocket(getSSLSocketFactory(), getHostnameVerifier()); + setUpTransportIO(connection); } } @@ -420,21 +419,5 @@ return super.requestString(); } - /** - * Create the secure socket over the connected socket and verify remote - * hostname. - */ - private Socket wrapConnection(Socket socket) throws IOException { - String hostname = url.getHost(); - // create the wrapper over connected socket - sslSocket = (SSLSocket) getSSLSocketFactory().createSocket(socket, - hostname, url.getPort(), true); - sslSocket.setUseClientMode(true); - sslSocket.startHandshake(); - if (!getHostnameVerifier().verify(hostname, sslSocket.getSession())) { - throw new IOException(Messages.getString("luni.02", hostname)); //$NON-NLS-1$ - } - return sslSocket; - } } } Index: src/test/impl/org/apache/harmony/tests/internal/net/www/protocol/http/PersistenceTest.java =================================================================== --- src/test/impl/org/apache/harmony/tests/internal/net/www/protocol/http/PersistenceTest.java +++ src/test/impl/org/apache/harmony/tests/internal/net/www/protocol/http/PersistenceTest.java @@ -0,0 +1,485 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.tests.internal.net.www.protocol.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.HttpURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import junit.framework.TestCase; + +import org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection; +import org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnectionManager; + +import tests.support.Support_Jetty; + +/** + * Tests for HttpURLConnection persistence. + * These tests depends on internal implementation. + */ +public class PersistenceTest extends TestCase { + + private static final boolean DEBUG = false; + + private final static Object bound = new Object(); + + private static int port; + + static { + // run-once set up + try { + port = Support_Jetty.startDefaultHttpServer(); + } catch (Exception e) { + fail("Exception during setup jetty : " + e.getMessage()); + } + } + + static class MockServer extends Thread { + ServerSocket serverSocket; + boolean accepted = false; + boolean started = false; + + public MockServer(String name) throws IOException { + super(name); + serverSocket = new ServerSocket(0); + serverSocket.setSoTimeout(5000); + } + + public int port() { + return serverSocket.getLocalPort(); + } + + @Override + public void run() { + try { + synchronized (bound) { + started = true; + bound.notify(); + } + try { + serverSocket.accept().close(); + accepted = true; + } catch (SocketTimeoutException ignore) { + } + serverSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + static class MockHTTPServer extends MockServer { + // HTTP response codes + static final int OK_CODE = 200; + static final int NOT_FOUND_CODE = 404; + // how many times persistent connection will be used + // by server + int persUses; + // result code to be sent to client + int responseCode; + // response content to be sent to client + String response = ""; + // client's POST message + String clientPost = "Hello from client!"; + + public MockHTTPServer(String name, int persUses) throws IOException { + this(name, persUses, OK_CODE); + } + + public MockHTTPServer(String name, int persUses, + int responseCode) throws IOException { + super(name); + this.persUses = persUses; + this.responseCode = responseCode; + } + + public int port() { + return serverSocket.getLocalPort(); + } + + @Override + public void run() { + try { + synchronized (bound) { + started = true; + bound.notify(); + } + InputStream is = null; + Socket client = null; + try { + client = serverSocket.accept(); + accepted = true; + for (int i=0; i 0)) { + if (bytik == '\r') { + bytik = is.read(); + } + if (wasEOL && (bytik == '\n')) { + break; + } + wasEOL = (bytik == '\n'); + buff[num++] = (byte) bytik; + } + //int num = is.read(buff); + String message = new String(buff, 0, num); + if (DEBUG) { + System.out.println("---- Server got request: ----\n" + + message + "-----------------------------"); + } + + // Act as Server (not Proxy) side + if (message.startsWith("POST")) { + // client connection sent some data + // if the data was not read with header + if (DEBUG) { + System.out.println( + "---- Server read client's data: ----"); + } + num = is.read(buff); + message = new String(buff, 0, num); + if (DEBUG) { + System.out.println("'" + message + "'"); + System.out.println( + "------------------------------------"); + } + // check the received data + assertEquals(clientPost, message); + } + + client.getOutputStream().write(( + "HTTP/1.1 " + responseCode + " OK\n" + + "Content-type: text/html\n" + + "Content-length: " + + response.length() + "\n\n" + + response).getBytes()); + + if (responseCode != OK_CODE) { + // wait while test case check closed connection + // and interrupt this thread + try { + while (!isInterrupted()) { + Thread.sleep(1000); + } + } catch (Exception ignore) { } + } + } + } catch (SocketTimeoutException ignore) { + ignore.printStackTrace(); + } finally { + if (is != null) { + is.close(); + } + if (client != null) { + client.close(); + } + serverSocket.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public void setUp() { + if (DEBUG) { + System.out.println("\n=============================="); + System.out.println("===== Execution: "+getName()); + System.out.println("=============================="); + } + } + + /** + * Test that an HTTP connection persists + */ + public void testConnectionsPersist() throws IOException, InterruptedException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + c.getOutputStream().close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + OutputStream os = c.getOutputStream(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + os.close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + httpServer.join(); + } + + /** + * Test that multiple HTTP connections persist + */ + public void testMultipleConnectionsPersist() throws IOException, InterruptedException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + MockServer httpServer2 = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer2.start(); + synchronized(bound) { + if (!httpServer2.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + OutputStream os = c.getOutputStream(); + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer2.port()).openConnection(); + c2.setDoOutput(true); + c2.setRequestMethod("POST"); + OutputStream os2 = c2.getOutputStream(); + os.close(); + os2.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + + c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + os = c.getOutputStream(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + c2 = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer2.port()).openConnection(); + c2.setDoOutput(true); + c2.setRequestMethod("POST"); + os2 = c2.getOutputStream(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + os.close(); + os2.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + httpServer.join(); + httpServer2.join(); + } + + /** + * Test that a closed HTTP connection is not kept in the pool of live connections + * @throws URISyntaxException + */ + public void testForcedClosure() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpConnection connection = HttpConnectionManager.getDefault().getConnection(new URI("http://127.0.0.1:" + httpServer.port()), 1000); + HttpConnectionManager.getDefault().returnConnectionToPool(connection); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + c.getOutputStream(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + c.disconnect(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + } + + /** + * Test that a connection is closed if the client does not read all the data + * @throws Exception + */ + public void testIncorrectUsage() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); // get the input stream but don't finish reading it + is.close(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + } + + /** + * Test that a connection is closed in case of unsuccessful connection. + * Here client gets NOT_FOUND response. + */ + public void testConnectionNonPersistence() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for NOT FOUND checking", 1, + MockHTTPServer.NOT_FOUND_CODE); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + int initialFreeConnections + = HttpConnectionManager.getDefault().numFreeConnections(); + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + try { + c.getInputStream(); + fail("Expected IOException was not thrown"); + } catch (IOException expected) { + // expected + } finally { + httpServer.interrupt(); + } + assertEquals("Unsuccessful connection was not closed", + initialFreeConnections, + HttpConnectionManager.getDefault().numFreeConnections()); + } + + /** + * Test that a connection is not closed if the client does read all the data + * @throws Exception + */ + public void testCorrectUsage() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); + byte[] buffer = new byte[128]; + int totalBytes = 0; + int bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + totalBytes += bytesRead; + } + is.close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c2.setDoOutput(true); + c2.setRequestMethod("GET"); + InputStream is2 = c2.getInputStream(); + byte[] buffer2 = new byte[128]; + int totalBytes2 = 0; + int bytesRead2 = 0; + while((bytesRead2 = is2.read(buffer2)) > 0){ + totalBytes2 += bytesRead2; + } + is2.close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + assertEquals(totalBytes, totalBytes2); + } + + /** + * Test that the http.keepAlive system property has the required effect on persistent connections + */ + public void testKeepAliveSystemProperty() throws IOException, InterruptedException { + System.setProperty("http.keepAlive", "false"); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + OutputStream os = c.getOutputStream(); + os.close(); + assertEquals(0, HttpConnectionManager.getDefault().numFreeConnections()); + httpServer.join(); + System.setProperty("http.keepAlive", "true"); + } + + /** + * Test that the http.maxConnections system property has the required effect on persistent connections + * @throws Exception + */ + public void testMaxConnectionsSystemProperty() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + System.setProperty("http.maxConnections", "2"); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); + c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is2 = c.getInputStream(); + c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is3 = c.getInputStream(); + byte[] buffer = new byte[128]; + while(is.read(buffer) > 0){ + } + while(is2.read(buffer) > 0){ + } + while(is3.read(buffer) > 0){ + } + is.close(); + is2.close(); + is3.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + } + +} \ No newline at end of file Index: src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java =================================================================== --- src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java +++ src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java @@ -0,0 +1,828 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.harmony.tests.internal.net.www.protocol.http; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.PasswordAuthentication; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URL; +import java.security.Permission; +import java.util.ArrayList; + +import junit.framework.TestCase; + +/** + * Tests for HttpURLConnection class constructors and methods. + */ +public class HttpURLConnectionTest extends TestCase { + + private static final boolean DEBUG = false; + + private final static Object bound = new Object(); + + static class MockServer extends Thread { + ServerSocket serverSocket; + boolean accepted = false; + boolean started = false; + + public MockServer(String name) throws IOException { + super(name); + serverSocket = new ServerSocket(0); + serverSocket.setSoTimeout(5000); + } + + public int port() { + return serverSocket.getLocalPort(); + } + + @Override + public void run() { + try { + synchronized (bound) { + started = true; + bound.notify(); + } + try { + serverSocket.accept().close(); + accepted = true; + } catch (SocketTimeoutException ignore) { + } + serverSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + static class MockHTTPServer extends MockServer { + // HTTP response codes + static final int OK_CODE = 200; + static final int NOT_FOUND_CODE = 404; + // how many times persistent connection will be used + // by server + int persUses; + // result code to be sent to client + int responseCode; + // response content to be sent to client + String response = ""; + // client's POST message + String clientPost = "Hello from client!"; + + public MockHTTPServer(String name, int persUses) throws IOException { + this(name, persUses, OK_CODE); + } + + public MockHTTPServer(String name, int persUses, + int responseCode) throws IOException { + super(name); + this.persUses = persUses; + this.responseCode = responseCode; + } + + public int port() { + return serverSocket.getLocalPort(); + } + + @Override + public void run() { + try { + synchronized (bound) { + started = true; + bound.notify(); + } + InputStream is = null; + Socket client = null; + try { + client = serverSocket.accept(); + accepted = true; + for (int i=0; i 0)) { + if (bytik == '\r') { + bytik = is.read(); + } + if (wasEOL && (bytik == '\n')) { + break; + } + wasEOL = (bytik == '\n'); + buff[num++] = (byte) bytik; + } + //int num = is.read(buff); + String message = new String(buff, 0, num); + if (DEBUG) { + System.out.println("---- Server got request: ----\n" + + message + "-----------------------------"); + } + + // Act as Server (not Proxy) side + if (message.startsWith("POST")) { + // client connection sent some data + // if the data was not read with header + if (DEBUG) { + System.out.println( + "---- Server read client's data: ----"); + } + num = is.read(buff); + message = new String(buff, 0, num); + if (DEBUG) { + System.out.println("'" + message + "'"); + System.out.println( + "------------------------------------"); + } + // check the received data + assertEquals(clientPost, message); + } + + client.getOutputStream().write(( + "HTTP/1.1 " + responseCode + " OK\n" + + "Content-type: text/html\n" + + "Content-length: " + + response.length() + "\n\n" + + response).getBytes()); + + if (responseCode != OK_CODE) { + // wait while test case check closed connection + // and interrupt this thread + try { + while (!isInterrupted()) { + Thread.sleep(1000); + } + } catch (Exception ignore) { } + } + } + } catch (SocketTimeoutException ignore) { + ignore.printStackTrace(); + } finally { + if (is != null) { + is.close(); + } + if (client != null) { + client.close(); + } + serverSocket.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + static class MockProxyServer extends MockServer { + + boolean acceptedAuthorizedRequest; + + public MockProxyServer(String name) throws Exception { + super(name); + } + + @Override + public void run() { + try { + Socket socket = serverSocket.accept(); + socket.setSoTimeout(5000); + byte[] buff = new byte[1024]; + int num = socket.getInputStream().read(buff); + socket.getOutputStream().write(( + "HTTP/1.0 407 Proxy authentication required\n" + + "Proxy-authenticate: Basic realm=\"remotehost\"\n\n") + .getBytes()); + num = socket.getInputStream().read(buff); + if (num == -1) { + // this connection was closed, create new one: + socket = serverSocket.accept(); + socket.setSoTimeout(5000); + num = socket.getInputStream().read(buff); + } + String request = new String(buff, 0, num); + acceptedAuthorizedRequest = + request.toLowerCase().indexOf("proxy-authorization:") > 0; + if (acceptedAuthorizedRequest) { + socket.getOutputStream().write(( + "HTTP/1.1 200 OK\n\n").getBytes()); + } + } catch (IOException e) { + } + } + } + + public void setUp() { + if (DEBUG) { + System.out.println("\n=============================="); + System.out.println("===== Execution: "+getName()); + System.out.println("=============================="); + } + } + + /** + * ProxySelector implementation used in the test. + */ + static class TestProxySelector extends ProxySelector { + // proxy port + private int proxy_port; + // server port + private int server_port; + + /** + * Creates proxy selector instance. + * Selector will return the proxy, only if the connection + * is made to localhost:server_port. Otherwise it will + * return NO_PROXY. + * Address of the returned proxy will be localhost:proxy_port. + */ + public TestProxySelector(int server_port, int proxy_port) { + this.server_port = server_port; + this.proxy_port = proxy_port; + } + + @Override + public java.util.List select(URI uri) { + Proxy proxy = Proxy.NO_PROXY; + if (("localhost".equals(uri.getHost())) + && (server_port == uri.getPort())) { + proxy = new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", proxy_port)); + } + ArrayList result = new ArrayList(); + result.add(proxy); + return result; + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + // do nothing + } + } + + /** + * @tests org.apache.harmony.luni.internal.net.www.http.getOutputStream() + */ + public void testGetOutputStream() throws Exception { + // Regression for HARMONY-482 + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + //use new String("POST") instead of simple "POST" to obtain other + //object instances then those that are in HttpURLConnection classes + c.setRequestMethod(new String("POST")); + c.getOutputStream(); + httpServer.join(); + } + + + /** + * Test checks if the proxy specified in openConnection + * method will be used for connection to the server + */ + public void testUsingProxy() throws Exception { + // Regression for HARMONY-570 + MockServer server = new MockServer("server"); + MockServer proxy = new MockServer("proxy"); + + URL url = new URL("http://localhost:" + server.port()); + + HttpURLConnection connection = (HttpURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", + proxy.port()))); + connection.setConnectTimeout(2000); + connection.setReadTimeout(2000); + + server.start(); + synchronized(bound) { + if (!server.started) bound.wait(5000); + } + proxy.start(); + synchronized(bound) { + if (!proxy.started) bound.wait(5000); + } + + connection.connect(); + + // wait while server and proxy run + server.join(); + proxy.join(); + + assertTrue("Connection does not use proxy", connection.usingProxy()); + assertTrue("Proxy server was not used", proxy.accepted); + + HttpURLConnection huc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY); + assertFalse(huc.usingProxy()); + } + + /** + * Test checks if the proxy provided by proxy selector + * will be used for connection to the server + */ + public void testUsingProxySelector() throws Exception { + // Regression for HARMONY-570 + MockServer server = new MockServer("server"); + MockServer proxy = new MockServer("proxy"); + + URL url = new URL("http://localhost:" + server.port()); + + // keep default proxy selector + ProxySelector defPS = ProxySelector.getDefault(); + // replace selector + ProxySelector.setDefault( + new TestProxySelector(server.port(), proxy.port())); + + try { + HttpURLConnection connection = + (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(2000); + connection.setReadTimeout(2000); + + server.start(); + synchronized(bound) { + if (!server.started) bound.wait(5000); + } + proxy.start(); + synchronized(bound) { + if (!proxy.started) bound.wait(5000); + } + connection.connect(); + + // wait while server and proxy run + server.join(); + proxy.join(); + + assertTrue("Connection does not use proxy", + connection.usingProxy()); + assertTrue("Proxy server was not used", proxy.accepted); + } finally { + // restore default proxy selector + ProxySelector.setDefault(defPS); + } + } + + public void testProxyAuthorization() throws Exception { + // Set up test Authenticator + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication( + "user", "password".toCharArray()); + } + }); + + try { + MockProxyServer proxy = new MockProxyServer("ProxyServer"); + + URL url = new URL("http://remotehost:55555/requested.data"); + HttpURLConnection connection = + (HttpURLConnection) url.openConnection( + new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", proxy.port()))); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + proxy.start(); + + connection.connect(); + assertEquals("unexpected response code", + 200, connection.getResponseCode()); + proxy.join(); + assertTrue("Connection did not send proxy authorization request", + proxy.acceptedAuthorizedRequest); + } finally { + // remove previously set authenticator + Authenticator.setDefault(null); + } + } + + /** + * Test that a connection is not closed if the client reads all the data + * but not closes input stream. read until -1. + */ + public void testConnectionPersistence() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + InputStream is = c.getInputStream(); + byte[] buffer = new byte[128]; + int totalBytes = 0; + int bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer, 0, bytesRead) + "'"); + } + totalBytes += bytesRead; + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[128]; + totalBytes = 0; + bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer, 0, bytesRead) + "'"); + totalBytes += bytesRead; + } + } + } + + /** + * Test that a connection is not closed if the client reads all the data + * but not closes input stream. read() not receives -1. + */ + public void testConnectionPersistence2() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is not closed if it firstly does POST, + * and then does GET requests. + */ + public void testConnectionPersistence3() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setDoOutput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + c.getOutputStream().write(httpServer.clientPost.getBytes()); + + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is not closed if it firstly does GET, + * and then does POST requests. + */ + public void testConnectionPersistence4() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoOutput(true); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + c2.getOutputStream().write(httpServer.clientPost.getBytes()); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is not closed if it does POST for 2 times. + */ + public void testConnectionPersistence5() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + c.setDoOutput(true); + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + c.getOutputStream().write(httpServer.clientPost.getBytes()); + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoOutput(true); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + c2.getOutputStream().write(httpServer.clientPost.getBytes()); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection made through proxy will be reused + * for connection establishing without proxy. + */ + public void testProxiedConnectionPersistence() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://some.host:1234") + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", + httpServer.port()))); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + c.setDoOutput(true); + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + c.getOutputStream().write(httpServer.clientPost.getBytes()); + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://some.host:1234").openConnection(); + c2.setDoOutput(true); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + c2.getOutputStream().write(httpServer.clientPost.getBytes()); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + public void testSecurityManager() throws MalformedURLException, IOException, InterruptedException { + try { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + MySecurityManager sm = new MySecurityManager(); + System.setSecurityManager(sm); + + // Check that a first connection calls checkConnect + try { + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + c.connect(); + fail("Should have thrown a SecurityException upon connection"); + + } catch (SecurityException e) { + } + + // Now create a connection properly + System.setSecurityManager(null); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + InputStream is = c.getInputStream(); + byte[] buffer = new byte[128]; + int totalBytes = 0; + int bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer, 0, bytesRead) + "'"); + } + totalBytes += bytesRead; + } + + // Now check that a second connection also calls checkConnect + System.setSecurityManager(sm); + try { + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[128]; + totalBytes = 0; + bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer, 0, bytesRead) + "'"); + totalBytes += bytesRead; + } + } + fail("Expected a SecurityException to be thrown"); + } catch (SecurityException e) { + } + } finally { + System.setSecurityManager(null); + } + } + + private static class MySecurityManager extends SecurityManager { + + @Override + public void checkConnect(String host, int port) { + throw new SecurityException(); + } + + @Override + public void checkConnect(String host, int port, Object context) { + throw new SecurityException(); + } + + @Override + public void checkPermission(Permission permission) { + // allows a new security manager to be set + } + + } + +} \ No newline at end of file Index: src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java =================================================================== --- src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java +++ src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java @@ -0,0 +1,1440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.tests.internal.net.www.protocol.https; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.Proxy; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.Arrays; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Implementation independent test for HttpsURLConnection. + * The test needs certstore file placed in system classpath + * and named as "key_store." + the type of the + * default KeyStore installed in the system in lower case. + *
+ * For example: if default KeyStore type in the system is BKS + * (i.e. java.security file sets up the property keystore.type=BKS), + * thus classpath should point to the directory with "key_store.bks" + * file. + *
+ * This certstore file should contain self-signed certificate + * generated by keytool utility in a usual way. + *
+ * The password to the certstore should be "password" (without quotes). + */ +public class HttpsURLConnectionTest extends TestCase { + + // the password to the store + private static final String KS_PASSWORD = "password"; + + // turn on/off logging + private static final boolean DO_LOG = false; + + // read/connection timeout value + private static final int TIMEOUT = 5000; + + // OK response code + private static final int OK_CODE = 200; + + // Not Found response code + private static final int NOT_FOUND_CODE = 404; + + // Proxy authentication required response code + private static final int AUTHENTICATION_REQUIRED_CODE = 407; + + // fields keeping the system values of corresponding properties + private static String systemKeyStoreType; + + private static String systemKeyStore; + + private static String systemKeyStorePassword; + + private static String systemTrustStoreType; + + private static String systemTrustStore; + + private static String systemTrustStorePassword; + + /** + * Checks that HttpsURLConnection's default SSLSocketFactory is operable. + */ + public void testGetDefaultSSLSocketFactory() throws Exception { + // set up the properties defining the default values needed by SSL stuff + setUpStoreProperties(); + + try { + SSLSocketFactory defaultSSLSF = HttpsURLConnection + .getDefaultSSLSocketFactory(); + ServerSocket ss = new ServerSocket(0); + Socket s = defaultSSLSF + .createSocket("localhost", ss.getLocalPort()); + ss.accept(); + s.close(); + ss.close(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Checks if HTTPS connection performs initial SSL handshake with the + * server working over SSL, sends encrypted HTTP request, + * and receives expected HTTP response. After HTTPS session if finished + * test checks connection state parameters established by + * HttpsURLConnection. + */ + public void testHttpsConnection() throws Throwable { + // set up the properties defining the default values needed by SSL stuff + setUpStoreProperties(); + + try { + // create the SSL server socket acting as a server + SSLContext ctx = getContext(); + ServerSocket ss = ctx.getServerSocketFactory() + .createServerSocket(0); + + // create the HostnameVerifier to check hostname verification + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create url connection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + // perform the interaction between the peers + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + + // check the connection state + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Checks if HTTPS connection performs initial SSL handshake with the + * server working over SSL, sends encrypted HTTP request, + * and receives expected HTTP response. After that it checks that the + * established connection is persistent. + * After HTTPS session if finished + * test checks connection state parameters established by + * HttpsURLConnection. + */ + public void testHttpsPersistentConnection() throws Throwable { + // set up the properties defining the default values needed by SSL stuff + setUpStoreProperties(); + + try { + // create the SSL server socket acting as a server + SSLContext ctx = getContext(); + ServerSocket ss = ctx.getServerSocketFactory() + .createServerSocket(0); + + // create the HostnameVerifier to check hostname verification + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create url connection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + // perform the interaction between the peers + SSLSocket peerSocket = (SSLSocket) doPersistentInteraction(connection, ss); + + // check the connection state + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests the behaviour of HTTPS connection in case of unavailability + * of requested resource. + */ + public void testHttpsConnection_Not_Found_Response() throws Throwable { + // set up the properties defining the default values needed by SSL stuff + setUpStoreProperties(); + + try { + // create the SSL server socket acting as a server + SSLContext ctx = getContext(); + ServerSocket ss = ctx.getServerSocketFactory() + .createServerSocket(0); + + // create the HostnameVerifier to check hostname verification + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create url connection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + try { + doInteraction(connection, ss, NOT_FOUND_CODE); + fail("Expected exception was not thrown."); + } catch (FileNotFoundException e) { + if (DO_LOG) { + System.out.println("Expected exception was thrown: " + + e.getMessage()); + } + } + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests possibility to set up the default SSLSocketFactory + * to be used by HttpsURLConnection. + */ + public void testSetDefaultSSLSocketFactory() throws Throwable { + // create the SSLServerSocket which will be used by server side + SSLContext ctx = getContext(); + SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory() + .createServerSocket(0); + + SSLSocketFactory socketFactory = (SSLSocketFactory) ctx + .getSocketFactory(); + // set up the factory as default + HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); + // check the result + assertSame("Default SSLSocketFactory differs from expected", + socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory()); + + // create the HostnameVerifier to check hostname verification + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + TestHostnameVerifier hnv_late = new TestHostnameVerifier(); + // late initialization: should not be used for created connection + HttpsURLConnection.setDefaultHostnameVerifier(hnv_late); + + // perform the interaction between the peers + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + // check the connection state + checkConnectionStateParameters(connection, peerSocket); + // check the verification process + assertTrue("Hostname verification was not done", hnv.verified); + assertFalse( + "Hostname verification should not be done by this verifier", + hnv_late.verified); + // check the used SSLSocketFactory + assertSame("Default SSLSocketFactory should be used", + HttpsURLConnection.getDefaultSSLSocketFactory(), connection + .getSSLSocketFactory()); + + // should silently exit + connection.connect(); + } + + /** + * Tests possibility to set up the SSLSocketFactory + * to be used by HttpsURLConnection. + */ + public void testSetSSLSocketFactory() throws Throwable { + // create the SSLServerSocket which will be used by server side + SSLContext ctx = getContext(); + SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory() + .createServerSocket(0); + + // create the HostnameVerifier to check hostname verification + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + SSLSocketFactory socketFactory = (SSLSocketFactory) ctx + .getSocketFactory(); + connection.setSSLSocketFactory(socketFactory); + + TestHostnameVerifier hnv_late = new TestHostnameVerifier(); + // late initialization: should not be used for created connection + HttpsURLConnection.setDefaultHostnameVerifier(hnv_late); + + // perform the interaction between the peers + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + // check the connection state + checkConnectionStateParameters(connection, peerSocket); + // check the verification process + assertTrue("Hostname verification was not done", hnv.verified); + assertFalse( + "Hostname verification should not be done by this verifier", + hnv_late.verified); + // check the used SSLSocketFactory + assertNotSame("Default SSLSocketFactory should not be used", + HttpsURLConnection.getDefaultSSLSocketFactory(), connection + .getSSLSocketFactory()); + assertSame("Result differs from expected", socketFactory, connection + .getSSLSocketFactory()); + + // should silently exit + connection.connect(); + } + + /** + * Tests the behaviour of HttpsURLConnection in case of retrieving + * of the connection state parameters before connection has been made. + */ + public void testUnconnectedStateParameters() throws Throwable { + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:55555"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + try { + connection.getCipherSuite(); + fail("Expected IllegalStateException was not thrown"); + } catch (IllegalStateException e) {} + try { + connection.getPeerPrincipal(); + fail("Expected IllegalStateException was not thrown"); + } catch (IllegalStateException e) {} + try { + connection.getLocalPrincipal(); + fail("Expected IllegalStateException was not thrown"); + } catch (IllegalStateException e) {} + + try { + connection.getServerCertificates(); + fail("Expected IllegalStateException was not thrown"); + } catch (IllegalStateException e) {} + try { + connection.getLocalCertificates(); + fail("Expected IllegalStateException was not thrown"); + } catch (IllegalStateException e) {} + } + + /** + * Tests if setHostnameVerifier() method replaces default verifier. + */ + public void testSetHostnameVerifier() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + SSLServerSocket ss = (SSLServerSocket) getContext() + .getServerSocketFactory().createServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + TestHostnameVerifier hnv_late = new TestHostnameVerifier(); + // replace default verifier + connection.setHostnameVerifier(hnv_late); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + assertTrue("Hostname verification was not done", hnv_late.verified); + assertFalse( + "Hostname verification should not be done by this verifier", + hnv.verified); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests the behaviour in case of sending the data to the server. + */ + public void test_doOutput() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + SSLServerSocket ss = (SSLServerSocket) getContext() + .getServerSocketFactory().createServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + connection.setDoOutput(true); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests the behaviour in case of sending the data to the server + * over persistent connection. + */ + public void testPersistence_doOutput() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + SSLServerSocket ss = (SSLServerSocket) getContext() + .getServerSocketFactory().createServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + connection.setDoOutput(true); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) + doPersistentInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. + */ + public void testProxyConnection() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55556/requested.data"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. + * Checks that persistent connection to the host exists and can + * be used no in spite of explicit Proxy specifying. + */ + public void testPersistentProxyConnection() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55556/requested.data"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doPersistentInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. + * Proxy server needs authentication. + */ + public void testProxyAuthConnection() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + Authenticator.setDefault(new Authenticator() { + + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("user", "password" + .toCharArray()); + } + }); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55555/requested.data"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. + * 2 HTTPS connections are opened for one URL. For the first time + * the connection is opened through one proxy, + * for the second time through another. + */ + public void testConsequentProxyConnection() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55555/requested.data"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // create another SSLServerSocket which will be used by server side + ss = new ServerSocket(0); + + connection = (HttpsURLConnection) url.openConnection(new Proxy( + Proxy.Type.HTTP, new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + peerSocket = (SSLSocket) doInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. + * Proxy server needs authentication. + * Client sends data to the server. + */ + public void testProxyAuthConnection_doOutput() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + Authenticator.setDefault(new Authenticator() { + + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("user", "password" + .toCharArray()); + } + }); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55554/requested.data"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + connection.setDoOutput(true); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, + OK_CODE); + checkConnectionStateParameters(connection, peerSocket); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. + * Proxy server needs authentication but client fails to authenticate + * (Authenticator was not set up in the system). + */ + public void testProxyAuthConnectionFailed() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55555/requested.data"); + HttpURLConnection connection = (HttpURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + try { + doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE); + } catch (IOException e) { + // SSL Tunnelling failed + if (DO_LOG) { + System.out.println("Got expected IOException: " + + e.getMessage()); + } + } + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests the behaviour of HTTPS connection in case of unavailability + * of requested resource. + */ + public void testProxyConnection_Not_Found_Response() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpURLConnection connection = (HttpURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + try { + doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND + fail("Expected exception was not thrown."); + } catch (FileNotFoundException e) { + if (DO_LOG) { + System.out.println("Expected exception was thrown: " + + e.getMessage()); + } + } + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + // --------------------------------------------------------------------- + // ------------------------ Staff Methods ------------------------------ + // --------------------------------------------------------------------- + + /** + * Log the name of the test case to be executed. + */ + public void setUp() throws Exception { + if (DO_LOG) { + System.out.println(); + System.out.println("------------------------"); + System.out.println("------ " + getName()); + System.out.println("------------------------"); + } + } + + /** + * Checks the HttpsURLConnection getter's values and compares + * them with actual corresponding values of remote peer. + */ + public static void checkConnectionStateParameters( + HttpsURLConnection clientConnection, SSLSocket serverPeer) + throws Exception { + SSLSession session = serverPeer.getSession(); + + assertEquals(session.getCipherSuite(), clientConnection + .getCipherSuite()); + + assertEquals(session.getLocalPrincipal(), clientConnection + .getPeerPrincipal()); + + assertEquals(session.getPeerPrincipal(), clientConnection + .getLocalPrincipal()); + + Certificate[] serverCertificates = clientConnection + .getServerCertificates(); + Certificate[] localCertificates = session.getLocalCertificates(); + assertTrue("Server certificates differ from expected", Arrays.equals( + serverCertificates, localCertificates)); + + localCertificates = clientConnection.getLocalCertificates(); + serverCertificates = session.getPeerCertificates(); + assertTrue("Local certificates differ from expected", Arrays.equals( + serverCertificates, localCertificates)); + } + + /** + * Returns the file name of the key/trust store. The key store file + * (named as "key_store." + extension equals to the default KeyStore + * type installed in the system in lower case) is searched in classpath. + * @throws AssertionFailedError if property was not set + * or file does not exist. + */ + private static String getKeyStoreFileName() throws Exception { + String ksFileName = "org/apache/harmony/luni/tests/key_store." + + KeyStore.getDefaultType().toLowerCase(); + URL url = ClassLoader.getSystemClassLoader().getResource(ksFileName); + assertNotNull("Expected KeyStore file: '" + ksFileName + + "' for default KeyStore of type '" + + KeyStore.getDefaultType() + "' does not exist.", url); + return new File(url.toURI()).getAbsolutePath(); + } + + /** + * Builds and returns the context used for secure socket creation. + */ + private static SSLContext getContext() throws Exception { + String type = KeyStore.getDefaultType(); + SSLContext ctx; + + String keyStore = getKeyStoreFileName(); + File keyStoreFile = new File(keyStore); + + FileInputStream fis = new FileInputStream(keyStoreFile); + + KeyStore ks = KeyStore.getInstance(type); + ks.load(fis, KS_PASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory + .getDefaultAlgorithm()); + kmf.init(ks, KS_PASSWORD.toCharArray()); + + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + + ctx = SSLContext.getInstance("TLSv1"); + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return ctx; + } + + /** + * Sets up the properties pointing to the key store and trust store + * and used as default values by JSSE staff. This is needed to test + * HTTPS behaviour in the case of default SSL Socket Factories. + */ + private static void setUpStoreProperties() throws Exception { + String type = KeyStore.getDefaultType(); + + systemKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType"); + systemKeyStore = System.getProperty("javax.net.ssl.keyStore"); + systemKeyStorePassword = System + .getProperty("javax.net.ssl.keyStorePassword"); + + systemTrustStoreType = System + .getProperty("javax.net.ssl.trustStoreType"); + systemTrustStore = System.getProperty("javax.net.ssl.trustStore"); + systemTrustStorePassword = System + .getProperty("javax.net.ssl.trustStorePassword"); + + System.setProperty("javax.net.ssl.keyStoreType", type); + System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); + System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); + + System.setProperty("javax.net.ssl.trustStoreType", type); + System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); + System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); + } + + /** + * Rolls back the values of system properties. + */ + private static void tearDownStoreProperties() { + if (systemKeyStoreType == null) { + System.clearProperty("javax.net.ssl.keyStoreType"); + } else { + System + .setProperty("javax.net.ssl.keyStoreType", + systemKeyStoreType); + } + if (systemKeyStore == null) { + System.clearProperty("javax.net.ssl.keyStore"); + } else { + System.setProperty("javax.net.ssl.keyStore", systemKeyStore); + } + if (systemKeyStorePassword == null) { + System.clearProperty("javax.net.ssl.keyStorePassword"); + } else { + System.setProperty("javax.net.ssl.keyStorePassword", + systemKeyStorePassword); + } + + if (systemTrustStoreType == null) { + System.clearProperty("javax.net.ssl.trustStoreType"); + } else { + System.setProperty("javax.net.ssl.trustStoreType", + systemTrustStoreType); + } + if (systemTrustStore == null) { + System.clearProperty("javax.net.ssl.trustStore"); + } else { + System.setProperty("javax.net.ssl.trustStore", systemTrustStore); + } + if (systemTrustStorePassword == null) { + System.clearProperty("javax.net.ssl.trustStorePassword"); + } else { + System.setProperty("javax.net.ssl.trustStorePassword", + systemTrustStorePassword); + } + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket). + */ + public static Socket doInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket) throws Throwable { + return doInteraction(clientConnection, serverSocket, OK_CODE, false, false); + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket). Server will response with specified + * response code. + */ + public static Socket doInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket, final int responseCode) + throws Throwable { + return doInteraction(clientConnection, serverSocket, responseCode, + false, false); + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket) over persistent connection. + */ + public static Socket doPersistentInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket) throws Throwable { + return doInteraction(clientConnection, serverSocket, OK_CODE, + false, true); + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket) over persistent connection. + * Server will response with specified response code. + */ + public static Socket doPersistentInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket, final int responseCode) + throws Throwable { + return doInteraction(clientConnection, serverSocket, responseCode, + false, true); + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket). Server will response with specified + * response code. + * @param doAuthentication specifies + * if the server needs client authentication. + */ + public static Socket doInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket, final int responseCode, + final boolean doAuthentication, + final boolean checkPersistence) throws Throwable { + + // set up the connection + clientConnection.setDoInput(true); + clientConnection.setConnectTimeout(TIMEOUT); + clientConnection.setReadTimeout(TIMEOUT); + + ServerWork server = new ServerWork(serverSocket, responseCode, + doAuthentication, checkPersistence); + + ClientConnectionWork client = new ClientConnectionWork(clientConnection); + + server.start(); + client.start(); + + client.join(); + if (client.thrown != null) { + if (responseCode != OK_CODE) { // not OK response expected + // it is probably expected exception, keep it as is + throw client.thrown; + } + if ((client.thrown instanceof SocketTimeoutException) + && (server.thrown != null)) { + // server's exception is more informative in this case + throw new Exception(server.thrown); + } else { + throw new Exception(client.thrown); + } + } + + if (checkPersistence) { + ClientConnectionWork client2 = + new ClientConnectionWork((HttpURLConnection) + clientConnection.getURL().openConnection()); + client2.start(); + client2.join(); + if (client2.thrown != null) { + if (responseCode != OK_CODE) { // not OK response expected + // it is probably expected exception, keep it as is + throw client2.thrown; + } + if ((client2.thrown instanceof SocketTimeoutException) + && (server.thrown != null)) { + // server's exception is more informative in this case + throw new Exception(server.thrown); + } else { + throw new Exception(client2.thrown); + } + } + } + + server.join(); + + if (server.thrown != null) { + throw server.thrown; + } + return server.peerSocket; + } + + /** + * The host name verifier used in test. + */ + static class TestHostnameVerifier implements HostnameVerifier { + + boolean verified = false; + + public boolean verify(String hostname, SSLSession session) { + if (DO_LOG) { + System.out.println("***> verification " + hostname + " " + + session.getPeerHost()); + } + verified = true; + return true; + } + } + + /** + * The base class for mock Client and Server. + */ + static class Work extends Thread { + + /** + * The header of OK HTTP response. + */ + static String responseHead = "HTTP/1.1 200 OK\n"; + + /** + * The content of the response. + */ + static String plainResponseContent = "\n" + + "Plain Response Content\n" + + ""; + + /** + * The tail of the response. + */ + static String plainResponseTail = "Content-type: text/html\n" + + "Content-length: " + plainResponseContent.length() + "\n\n" + + plainResponseContent; + + /** + * The response message to be sent in plain (HTTP) format. + */ + static String plainResponse = responseHead + plainResponseTail; + + /** + * The content of the response to be sent during HTTPS session. + */ + static String httpsResponseContent = "\n" + + "HTTPS Response Content\n" + + ""; + + /** + * The tail of the response to be sent during HTTPS session. + */ + static String httpsResponseTail = "Content-type: text/html\n" + + "Content-length: " + httpsResponseContent.length() + "\n\n" + + httpsResponseContent; + + /** + * The response requiring client's proxy authentication. + */ + static String respAuthenticationRequired = "HTTP/1.0 407 Proxy authentication required\n" + + "Proxy-authenticate: Basic realm=\"localhost\"\n\n"; + + /** + * The data to be posted by client to the server. + */ + static String clientsData = "_.-^ Client's Data ^-._"; + + /** + * The exception thrown during peers interaction. + */ + protected Throwable thrown; + + /** + * The print stream used for debug log. + * If it is null debug info will not be printed. + */ + private PrintStream out = new PrintStream(System.out); + + /** + * Prints log message. + */ + public synchronized void log(String message) { + if (DO_LOG && (out != null)) { + System.out.println("[" + getName() + "]: " + message); + } + } + } + + /** + * The class used for server side works. + */ + static class ServerWork extends Work { + + // the server socket used for connection + private ServerSocket serverSocket; + + // the socket connected with client peer + private Socket peerSocket; + + // indicates if the server acts as proxy server + private boolean actAsProxy; + + // indicates if the server needs proxy authentication + private boolean needProxyAuthentication; + + // do we check for connection persistence + private boolean checkPersistence; + + // response code to be send to the client peer + private int responseCode; + + /** + * Creates the thread acting as a server side. + */ + public ServerWork(ServerSocket serverSocket) { + // the server does not require proxy authentication + // and sends OK_CODE (OK) response code + this(serverSocket, OK_CODE, false, false); + } + + /** + * Creates the thread acting as a server side. + * @param serverSocket the server socket to be used during connection + * @param responseCode the response code to be sent to the client + * @param needProxyAuthentication + * indicates if the server needs proxy authentication + */ + public ServerWork(ServerSocket serverSocket, int responseCode, + boolean needProxyAuthentication, boolean checkPersistence) { + this.serverSocket = serverSocket; + this.responseCode = responseCode; + this.needProxyAuthentication = needProxyAuthentication; + this.checkPersistence = checkPersistence; + // will act as a proxy server if the specified server socket + // is not a secure server socket + if (serverSocket instanceof SSLServerSocket) { + // demand client to send its certificate + ((SSLServerSocket) serverSocket).setNeedClientAuth(true); + // work as a HTTPS server, not as HTTP proxy + this.actAsProxy = false; + } else { + this.actAsProxy = true; + } + this.actAsProxy = !(serverSocket instanceof SSLServerSocket); + setName(this.actAsProxy ? "Proxy Server" : "Server"); + } + + /** + * Closes the connection. + */ + public void closeSocket(Socket socket) { + try { + socket.getInputStream().close(); + } catch (IOException e) {} + try { + socket.getOutputStream().close(); + } catch (IOException e) {} + try { + socket.close(); + } catch (IOException e) {} + } + + /** + * Performs the actual server work. + * If some exception occurs during the work it will be + * stored in the thrown field. + */ + public void run() { + // the buffer used for reading the messages + byte[] buff = new byte[2048]; + // the number of bytes read into the buffer + int num; + try { + // configure the server socket to avoid blocking + serverSocket.setSoTimeout(TIMEOUT); + // accept client connection + peerSocket = serverSocket.accept(); + // configure the client connection to avoid blocking + peerSocket.setSoTimeout(TIMEOUT); + log("Client connection ACCEPTED"); + + InputStream is = peerSocket.getInputStream(); + OutputStream os = peerSocket.getOutputStream(); + + // how many times established connection will be used + int number_of_uses = checkPersistence ? 2 : 1; + for (int it=0; it 0); + } + + if (peerSocket instanceof SSLSocket) { + // it will be so if we are have second iteration + // over persistent connection + os.write(("HTTP/1.1 " + OK_CODE + + "\n" + httpsResponseTail).getBytes()); + log("Sent OK RESPONSE over SSL"); + } else { + // The content of this response will reach proxied + // HTTPUC but will not reach proxied HTTPSUC + // In case of HTTP connection it will be the final + // message, in case of HTTPS connection this message + // will just indicate that connection with remote + // host has been done + // (i.e. SSL tunnel has been established). + os.write(plainResponse.getBytes()); + log("Sent OK RESPONSE"); + } + + if (message.startsWith("CONNECT")) { // request for SSL tunnel + log("Perform SSL Handshake..."); + // create sslSocket acting as a remote server peer + SSLSocket sslSocket = (SSLSocket) getContext() + .getSocketFactory().createSocket(peerSocket, + "localhost", peerSocket.getPort(), true); // do autoclose + sslSocket.setUseClientMode(false); + // demand client authentication + sslSocket.setNeedClientAuth(true); + sslSocket.startHandshake(); + peerSocket = sslSocket; + is = peerSocket.getInputStream(); + os = peerSocket.getOutputStream(); + + // read the HTTP request sent by secure connection + // (HTTPS request) + num = is.read(buff); + message = new String(buff, 0, num); + log("[Remote Server] Request from SSL tunnel:\n" + message); + log("------------------"); + + if (message.startsWith("POST")) { + // client connection sent some data + log("[Remote Server] try to read client data"); + num = is.read(buff); + message = new String(buff, 0, num); + log("[Remote Server] client's data: '" + message + "'"); + // check the received data + assertEquals(clientsData, message); + } + + log("[Remote Server] Sending the response by SSL tunnel.."); + // send the response with specified response code + os.write(("HTTP/1.1 " + responseCode + + "\n" + httpsResponseTail).getBytes()); + } + log("Work is DONE"); + }; + } catch (Throwable e) { + if (DO_LOG) { + e.printStackTrace(); + } + thrown = e; + } finally { + closeSocket(peerSocket); + try { + serverSocket.close(); + } catch (IOException e) {} + } + } + } + + /** + * The class used for client side works. It could be used to test + * both HttpURLConnection and HttpsURLConnection. + */ + static class ClientConnectionWork extends Work { + + // connection to be used to contact the server side + private HttpURLConnection connection; + + /** + * Creates the thread acting as a client side. + * @param connection connection to be used to contact the server side + */ + public ClientConnectionWork(HttpURLConnection connection) { + this.connection = connection; + setName("Client Connection"); + log("Created over connection: " + connection.getClass()); + } + + /** + * Performs the actual client work. + * If some exception occurs during the work it will be + * stored in the thrown field. + */ + public void run() { + try { + log("Opening the connection.."); + connection.connect(); + log("Connection has been ESTABLISHED, using proxy: " + + connection.usingProxy()); + if (connection.getDoOutput()) { + // connection configured to post data, do so + connection.getOutputStream().write(clientsData.getBytes()); + } + // read the content of HTTP(s) response + InputStream is = connection.getInputStream(); + log("Input Stream obtained: "+is.getClass()); + byte[] buff = new byte[2048]; + int num = 0; + int byt = 0; + while ((num < buff.length) && (is.available() > 0) + && ((byt = is.read()) != -1)) { + buff[num++] = (byte) byt; + } + String message = new String(buff, 0, num); + log("Got content:\n" + message); + log("------------------"); + log("Response code: " + connection.getResponseCode()); + + if (connection instanceof HttpsURLConnection) { + assertEquals(httpsResponseContent, message); + } else { + assertEquals(plainResponseContent, message); + } + } catch (Throwable e) { + if (DO_LOG) { + e.printStackTrace(); + } + thrown = e; + } + } + } + + public static junit.framework.Test suite() { + return new TestSuite(HttpsURLConnectionTest.class); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } +}