| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed 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 libcore.java.net; |
| |
| import com.google.mockwebserver.Dispatcher; |
| import com.google.mockwebserver.MockResponse; |
| import com.google.mockwebserver.MockWebServer; |
| import com.google.mockwebserver.RecordedRequest; |
| import com.google.mockwebserver.SocketPolicy; |
| |
| import com.android.okhttp.AndroidShimResponseCache; |
| import com.android.okhttp.internal.Platform; |
| import com.android.okhttp.internal.tls.TrustRootIndex; |
| |
| import junit.framework.TestCase; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.Authenticator; |
| import java.net.CacheRequest; |
| import java.net.CacheResponse; |
| import java.net.CookieHandler; |
| import java.net.CookieManager; |
| import java.net.HttpRetryException; |
| import java.net.HttpURLConnection; |
| import java.net.InetAddress; |
| import java.net.PasswordAuthentication; |
| import java.net.ProtocolException; |
| import java.net.Proxy; |
| import java.net.ProxySelector; |
| import java.net.ResponseCache; |
| import java.net.Socket; |
| import java.net.SocketAddress; |
| import java.net.SocketException; |
| import java.net.SocketTimeoutException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.net.UnknownHostException; |
| import java.nio.channels.SocketChannel; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.GZIPOutputStream; |
| import javax.net.SocketFactory; |
| import javax.net.ssl.HandshakeCompletedListener; |
| import javax.net.ssl.HostnameVerifier; |
| import javax.net.ssl.HttpsURLConnection; |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLHandshakeException; |
| import javax.net.ssl.SSLParameters; |
| import javax.net.ssl.SSLSession; |
| import javax.net.ssl.SSLSocket; |
| import javax.net.ssl.SSLSocketFactory; |
| import javax.net.ssl.TrustManager; |
| import javax.net.ssl.X509TrustManager; |
| import libcore.java.security.TestKeyStore; |
| import libcore.javax.net.ssl.TestSSLContext; |
| |
| import tests.net.DelegatingSocketFactory; |
| |
| import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; |
| import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; |
| import static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; |
| import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; |
| import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.fail; |
| |
| public final class URLConnectionTest extends TestCase { |
| |
| private MockWebServer server; |
| private AndroidShimResponseCache cache; |
| private String hostName; |
| private List<TestSSLContext> testSSLContextsToClose; |
| |
| @Override protected void setUp() throws Exception { |
| super.setUp(); |
| server = new MockWebServer(); |
| hostName = server.getHostName(); |
| testSSLContextsToClose = new ArrayList<>(); |
| } |
| |
| @Override protected void tearDown() throws Exception { |
| ResponseCache.setDefault(null); |
| Authenticator.setDefault(null); |
| System.clearProperty("proxyHost"); |
| System.clearProperty("proxyPort"); |
| System.clearProperty("http.proxyHost"); |
| System.clearProperty("http.proxyPort"); |
| System.clearProperty("https.proxyHost"); |
| System.clearProperty("https.proxyPort"); |
| server.shutdown(); |
| server = null; |
| if (cache != null) { |
| cache.delete(); |
| cache = null; |
| } |
| for (TestSSLContext testSSLContext : testSSLContextsToClose) { |
| testSSLContext.close(); |
| } |
| super.tearDown(); |
| } |
| |
| public void testRequestHeaderValidation() throws Exception { |
| // Android became more strict after M about which characters were allowed in request header |
| // names and values: previously almost anything was allowed if it didn't contain \0. |
| |
| assertForbiddenRequestHeaderName(null); |
| assertForbiddenRequestHeaderName(""); |
| assertForbiddenRequestHeaderName("\n"); |
| assertForbiddenRequestHeaderName("a\nb"); |
| assertForbiddenRequestHeaderName("\u0000"); |
| assertForbiddenRequestHeaderName("\r"); |
| assertForbiddenRequestHeaderName("\t"); |
| assertForbiddenRequestHeaderName("\u001f"); |
| assertForbiddenRequestHeaderName("\u007f"); |
| assertForbiddenRequestHeaderName("\u0080"); |
| assertForbiddenRequestHeaderName("\ud83c\udf69"); |
| |
| assertEquals(null, setAndReturnRequestHeaderValue(null)); |
| assertEquals("", setAndReturnRequestHeaderValue("")); |
| assertForbiddenRequestHeaderValue("\u0000"); |
| |
| // Workaround for http://b/26422335 , http://b/26889631 , http://b/27606665 : |
| // allow (but strip) trailing \n, \r and \r\n |
| // assertForbiddenRequestHeaderValue("\r"); |
| // End of workaround |
| |
| // '\t' in header values can either be (a) forbidden or (b) allowed. |
| // The original version of Android N (API 23) implemented behavior |
| // (a), but OEMs can backport a fix that changes the behavior to (b). |
| // Therefore, this test has been relaxed for Android N CTS to allow |
| // either behavior. It is planned that future versions of Android only |
| // allow behavior (b). |
| try { |
| // throws IAE in case (a), passes in case (b) |
| assertEquals("a valid\tvalue", setAndReturnRequestHeaderValue("a valid\tvalue")); |
| } catch (IllegalArgumentException tolerated) { |
| // verify case (a) |
| assertForbiddenRequestHeaderValue("\t"); |
| } |
| assertForbiddenRequestHeaderValue("\u001f"); |
| assertForbiddenRequestHeaderValue("\u007f"); |
| |
| // For http://b/28867041 the allowed character range was changed. |
| // assertForbiddenRequestHeaderValue("\u0080"); |
| // assertForbiddenRequestHeaderValue("\ud83c\udf69"); |
| assertEquals("\u0080", setAndReturnRequestHeaderValue("\u0080")); |
| assertEquals("\ud83c\udf69", setAndReturnRequestHeaderValue("\ud83c\udf69")); |
| |
| // Workaround for http://b/26422335 , http://b/26889631 , http://b/27606665 : |
| // allow (but strip) trailing \n, \r and \r\n |
| assertEquals("", setAndReturnRequestHeaderValue("\n")); |
| assertEquals("a", setAndReturnRequestHeaderValue("a\n")); |
| assertEquals("", setAndReturnRequestHeaderValue("\r")); |
| assertEquals("a", setAndReturnRequestHeaderValue("a\r")); |
| assertEquals("", setAndReturnRequestHeaderValue("\r\n")); |
| assertEquals("a", setAndReturnRequestHeaderValue("a\r\n")); |
| assertForbiddenRequestHeaderValue("a\nb"); |
| assertForbiddenRequestHeaderValue("\nb"); |
| assertForbiddenRequestHeaderValue("a\rb"); |
| assertForbiddenRequestHeaderValue("\rb"); |
| assertForbiddenRequestHeaderValue("a\r\nb"); |
| assertForbiddenRequestHeaderValue("\r\nb"); |
| // End of workaround |
| } |
| |
| private static void assertForbiddenRequestHeaderName(String name) throws Exception { |
| URL url = new URL("http://www.google.com/"); |
| HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); |
| try { |
| urlConnection.addRequestProperty(name, "value"); |
| fail("Expected exception"); |
| } catch (IllegalArgumentException expected) { |
| } catch (NullPointerException expectedIfNull) { |
| assertTrue(name == null); |
| } |
| } |
| |
| private static void assertForbiddenRequestHeaderValue(String value) throws Exception { |
| URL url = new URL("http://www.google.com/"); |
| HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); |
| try { |
| urlConnection.addRequestProperty("key", value); |
| fail("Expected exception"); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| private static String setAndReturnRequestHeaderValue(String value) throws Exception { |
| URL url = new URL("http://www.google.com/"); |
| HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); |
| urlConnection.addRequestProperty("key", value); |
| return urlConnection.getRequestProperty("key"); |
| } |
| |
| public void testRequestHeaders() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| urlConnection.addRequestProperty("D", "e"); |
| urlConnection.addRequestProperty("D", "f"); |
| assertEquals("f", urlConnection.getRequestProperty("D")); |
| assertEquals("f", urlConnection.getRequestProperty("d")); |
| Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties(); |
| assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); |
| assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d"))); |
| try { |
| requestHeaders.put("G", Arrays.asList("h")); |
| fail("Modified an unmodifiable view."); |
| } catch (UnsupportedOperationException expected) { |
| } |
| try { |
| requestHeaders.get("D").add("i"); |
| fail("Modified an unmodifiable view."); |
| } catch (UnsupportedOperationException expected) { |
| } |
| try { |
| urlConnection.setRequestProperty(null, "j"); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| try { |
| urlConnection.addRequestProperty(null, "k"); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| urlConnection.setRequestProperty("NullValue", null); // should fail silently! |
| assertNull(urlConnection.getRequestProperty("NullValue")); |
| urlConnection.addRequestProperty("AnotherNullValue", null); // should fail silently! |
| assertNull(urlConnection.getRequestProperty("AnotherNullValue")); |
| |
| urlConnection.getResponseCode(); |
| RecordedRequest request = server.takeRequest(); |
| assertContains(request.getHeaders(), "D: e"); |
| assertContains(request.getHeaders(), "D: f"); |
| assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); |
| assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); |
| assertContainsNoneMatching(request.getHeaders(), "G:.*"); |
| assertContainsNoneMatching(request.getHeaders(), "null:.*"); |
| |
| try { |
| urlConnection.addRequestProperty("N", "o"); |
| fail("Set header after connect"); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| urlConnection.setRequestProperty("P", "q"); |
| fail("Set header after connect"); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| urlConnection.getRequestProperties(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| public void testGetRequestPropertyReturnsLastValue() throws Exception { |
| server.play(); |
| HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| urlConnection.addRequestProperty("A", "value1"); |
| urlConnection.addRequestProperty("A", "value2"); |
| assertEquals("value2", urlConnection.getRequestProperty("A")); |
| } |
| |
| public void testResponseHeaders() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse() |
| .setStatus("HTTP/1.0 200 Fantastic") |
| .addHeader("A: c") |
| .addHeader("B: d") |
| .addHeader("A: e") |
| .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); |
| server.play(); |
| |
| HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals(200, urlConnection.getResponseCode()); |
| assertEquals("Fantastic", urlConnection.getResponseMessage()); |
| assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null)); |
| Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields(); |
| assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null)); |
| assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A"))); |
| assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a"))); |
| try { |
| responseHeaders.put("N", Arrays.asList("o")); |
| fail("Modified an unmodifiable view."); |
| } catch (UnsupportedOperationException expected) { |
| } |
| try { |
| responseHeaders.get("A").add("f"); |
| fail("Modified an unmodifiable view."); |
| } catch (UnsupportedOperationException expected) { |
| } |
| assertEquals("A", urlConnection.getHeaderFieldKey(0)); |
| assertEquals("c", urlConnection.getHeaderField(0)); |
| assertEquals("B", urlConnection.getHeaderFieldKey(1)); |
| assertEquals("d", urlConnection.getHeaderField(1)); |
| assertEquals("A", urlConnection.getHeaderFieldKey(2)); |
| assertEquals("e", urlConnection.getHeaderField(2)); |
| } |
| |
| public void testGetErrorStreamOnSuccessfulRequest() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertNull(connection.getErrorStream()); |
| } |
| |
| public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception { |
| server.enqueue(new MockResponse().setResponseCode(404).setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE)); |
| } |
| |
| // Check that if we don't read to the end of a response, the next request on the |
| // recycled connection doesn't get the unread tail of the first request's response. |
| // http://code.google.com/p/android/issues/detail?id=2939 |
| public void test_2939() throws Exception { |
| MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); |
| |
| server.enqueue(response); |
| server.enqueue(response); |
| server.play(); |
| |
| assertContent("ABCDE", server.getUrl("/").openConnection(), 5); |
| assertContent("ABCDE", server.getUrl("/").openConnection(), 5); |
| } |
| |
| // Check that we recognize a few basic mime types by extension. |
| // http://code.google.com/p/android/issues/detail?id=10100 |
| public void test_10100() throws Exception { |
| assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); |
| assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); |
| } |
| |
| public void testConnectionsArePooled() throws Exception { |
| MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); |
| |
| server.enqueue(response); |
| server.enqueue(response); |
| server.enqueue(response); |
| server.play(); |
| |
| assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); |
| assertEquals(2, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testChunkedConnectionsArePooled() throws Exception { |
| MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); |
| |
| server.enqueue(response); |
| server.enqueue(response); |
| server.enqueue(response); |
| server.play(); |
| |
| assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); |
| assertEquals(2, server.takeRequest().getSequenceNumber()); |
| } |
| |
| /** |
| * Test that connections are added to the pool as soon as the response has |
| * been consumed. |
| */ |
| public void testConnectionsArePooledWithoutExplicitDisconnect() throws Exception { |
| server.enqueue(new MockResponse().setBody("ABC")); |
| server.enqueue(new MockResponse().setBody("DEF")); |
| server.play(); |
| |
| URLConnection connection1 = server.getUrl("/").openConnection(); |
| assertEquals("ABC", readAscii(connection1.getInputStream(), Integer.MAX_VALUE)); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| URLConnection connection2 = server.getUrl("/").openConnection(); |
| assertEquals("DEF", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testServerClosesSocket() throws Exception { |
| testServerClosesSocket(DISCONNECT_AT_END); |
| } |
| |
| public void testServerShutdownInput() throws Exception { |
| testServerClosesSocket(SHUTDOWN_INPUT_AT_END); |
| } |
| |
| private void testServerClosesSocket(SocketPolicy socketPolicy) throws Exception { |
| server.enqueue(new MockResponse() |
| .setBody("This connection won't pool properly") |
| .setSocketPolicy(socketPolicy)); |
| server.enqueue(new MockResponse().setBody("This comes after a busted connection")); |
| server.play(); |
| |
| assertContent("This connection won't pool properly", server.getUrl("/a").openConnection()); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertContent("This comes after a busted connection", server.getUrl("/b").openConnection()); |
| // sequence number 0 means the HTTP socket connection was not reused |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testServerShutdownOutput() throws Exception { |
| // This test causes MockWebServer to log a "connection failed" stack trace |
| |
| // Setting the server workerThreads to 1 ensures the responses are generated in the order |
| // the requests are accepted by the server. Without this the second and third requests made |
| // by the client (the request for "/b" and the retry for "/b" when the bad socket is |
| // detected) can be handled by the server out of order leading to test failure. |
| server.setWorkerThreads(1); |
| server.enqueue(new MockResponse() |
| .setBody("Output shutdown after this response") |
| .setSocketPolicy(SHUTDOWN_OUTPUT_AT_END)); |
| server.enqueue(new MockResponse().setBody("This response will fail to write")); |
| server.enqueue(new MockResponse().setBody("This comes after a busted connection")); |
| server.play(); |
| |
| assertContent("Output shutdown after this response", server.getUrl("/a").openConnection()); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertContent("This comes after a busted connection", server.getUrl("/b").openConnection()); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| } |
| |
| enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } |
| |
| public void test_chunkedUpload_byteByByte() throws Exception { |
| doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); |
| } |
| |
| public void test_chunkedUpload_smallBuffers() throws Exception { |
| doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); |
| } |
| |
| public void test_chunkedUpload_largeBuffers() throws Exception { |
| doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); |
| } |
| |
| public void test_fixedLengthUpload_byteByByte() throws Exception { |
| doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); |
| } |
| |
| public void test_fixedLengthUpload_smallBuffers() throws Exception { |
| doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); |
| } |
| |
| public void test_fixedLengthUpload_largeBuffers() throws Exception { |
| doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); |
| } |
| |
| private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { |
| int n = 512*1024; |
| server.setBodyLimit(0); |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); |
| conn.setDoOutput(true); |
| conn.setRequestMethod("POST"); |
| if (uploadKind == TransferKind.CHUNKED) { |
| conn.setChunkedStreamingMode(-1); |
| } else { |
| conn.setFixedLengthStreamingMode(n); |
| } |
| OutputStream out = conn.getOutputStream(); |
| if (writeKind == WriteKind.BYTE_BY_BYTE) { |
| for (int i = 0; i < n; ++i) { |
| out.write('x'); |
| } |
| } else { |
| byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024]; |
| Arrays.fill(buf, (byte) 'x'); |
| for (int i = 0; i < n; i += buf.length) { |
| out.write(buf, 0, Math.min(buf.length, n - i)); |
| } |
| } |
| out.close(); |
| assertEquals(200, conn.getResponseCode()); |
| RecordedRequest request = server.takeRequest(); |
| assertEquals(n, request.getBodySize()); |
| if (uploadKind == TransferKind.CHUNKED) { |
| assertTrue(request.getChunkSizes().size() > 0); |
| } else { |
| assertTrue(request.getChunkSizes().isEmpty()); |
| } |
| } |
| |
| public void testGetResponseCodeNoResponseBody() throws Exception { |
| server.enqueue(new MockResponse() |
| .addHeader("abc: def")); |
| server.play(); |
| |
| URL url = server.getUrl("/"); |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
| conn.setDoInput(false); |
| assertEquals("def", conn.getHeaderField("abc")); |
| assertEquals(200, conn.getResponseCode()); |
| try { |
| conn.getInputStream(); |
| fail(); |
| } catch (ProtocolException expected) { |
| } |
| } |
| |
| public void testConnectViaHttps() throws IOException, InterruptedException { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| |
| assertContent("this response comes via HTTPS", connection); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); |
| assertEquals("TLSv1.2", request.getSslProtocol()); |
| } |
| |
| public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); |
| server.enqueue(new MockResponse().setBody("another response via HTTPS")); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(clientSocketFactory); |
| assertContent("this response comes via HTTPS", connection); |
| |
| connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(clientSocketFactory); |
| assertContent("another response via HTTPS", connection); |
| |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testConnectViaHttpsReusingConnectionsDifferentFactories() |
| throws IOException, InterruptedException { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); |
| server.enqueue(new MockResponse().setBody("another response via HTTPS")); |
| server.play(); |
| |
| // install a custom SSL socket factory so the server can be authorized |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| assertContent("this response comes via HTTPS", connection); |
| |
| connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| try { |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE); |
| fail("without an SSL socket factory, the connection should fail"); |
| } catch (SSLException expected) { |
| } |
| } |
| |
| /** |
| * Verify that we don't retry connections on certificate verification errors. |
| * |
| * http://code.google.com/p/android/issues/detail?id=13178 |
| */ |
| public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException { |
| TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(), |
| TestKeyStore.getServer()); |
| testSSLContextsToClose.add(testSSLContext); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse()); // unused |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| try { |
| connection.getInputStream(); |
| fail(); |
| } catch (SSLHandshakeException expected) { |
| assertTrue(expected.getCause() instanceof CertificateException); |
| } |
| assertEquals(0, server.getRequestCount()); |
| } |
| |
| public void testConnectViaProxy_emptyPath() throws Exception { |
| // expected normalization http://android -> http://android/ per b/30107354 |
| checkConnectViaProxy( |
| ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY, "http://android.com", |
| "http://android.com/", "android.com"); |
| } |
| |
| public void testConnectViaProxy_complexUrlWithNoPath() throws Exception { |
| checkConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY, |
| "http://android.com:8080?height=100&width=42", |
| "http://android.com:8080/?height=100&width=42", |
| "android.com:8080"); |
| } |
| |
| public void testConnectViaProxyUsingProxyArg() throws Exception { |
| checkConnectViaProxy(ProxyConfig.CREATE_ARG); |
| } |
| |
| public void testConnectViaProxyUsingProxySystemProperty() throws Exception { |
| checkConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); |
| } |
| |
| public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception { |
| checkConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); |
| } |
| |
| private void checkConnectViaProxy(ProxyConfig proxyConfig) throws Exception { |
| checkConnectViaProxy(proxyConfig, |
| "http://android.com/foo", "http://android.com/foo", "android.com"); |
| } |
| |
| private void checkConnectViaProxy(ProxyConfig proxyConfig, String urlString, |
| String expectedUrlInRequestLine, String expectedHost) throws Exception { |
| MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); |
| server.enqueue(mockResponse); |
| server.play(); |
| |
| URL url = new URL(urlString); |
| HttpURLConnection connection = proxyConfig.connect(server, url); |
| assertContent("this response comes via a proxy", connection); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertEquals("GET " + expectedUrlInRequestLine + " HTTP/1.1", request.getRequestLine()); |
| assertContains(request.getHeaders(), "Host: " + expectedHost); |
| } |
| |
| public void testContentDisagreesWithContentLengthHeader() throws IOException { |
| server.enqueue(new MockResponse() |
| .setBody("abc\r\nYOU SHOULD NOT SEE THIS") |
| .clearHeaders() |
| .addHeader("Content-Length: 3")); |
| server.play(); |
| |
| assertContent("abc", server.getUrl("/").openConnection()); |
| } |
| |
| public void testContentDisagreesWithChunkedHeader() throws IOException { |
| MockResponse mockResponse = new MockResponse(); |
| mockResponse.setChunkedBody("abc", 3); |
| ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); |
| bytesOut.write(mockResponse.getBody()); |
| bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes()); |
| mockResponse.setBody(bytesOut.toByteArray()); |
| mockResponse.clearHeaders(); |
| mockResponse.addHeader("Transfer-encoding: chunked"); |
| |
| server.enqueue(mockResponse); |
| server.play(); |
| |
| assertContent("abc", server.getUrl("/").openConnection()); |
| } |
| |
| public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { |
| testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); |
| } |
| |
| public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { |
| // https should not use http proxy |
| testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); |
| } |
| |
| private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); |
| server.play(); |
| |
| URL url = server.getUrl("/foo"); |
| HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| |
| assertContent("this response comes via HTTPS", connection); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); |
| } |
| |
| |
| public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception { |
| testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); |
| } |
| |
| /** |
| * We weren't honoring all of the appropriate proxy system properties when |
| * connecting via HTTPS. http://b/3097518 |
| */ |
| public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { |
| testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY); |
| } |
| |
| public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { |
| testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); |
| } |
| |
| /** |
| * We were verifying the wrong hostname when connecting to an HTTPS site |
| * through a proxy. http://b/3097277 |
| */ |
| private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); |
| server.enqueue(new MockResponse() |
| .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) |
| .clearHeaders()); |
| server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); |
| server.play(); |
| |
| URL url = new URL("https://android.com/foo"); |
| HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.setHostnameVerifier(hostnameVerifier); |
| |
| assertContent("this response comes via a secure proxy", connection); |
| |
| RecordedRequest connect = server.takeRequest(); |
| assertEquals("Connect line failure on proxy", |
| "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); |
| assertContains(connect.getHeaders(), "Host: android.com:443"); |
| |
| RecordedRequest get = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); |
| assertContains(get.getHeaders(), "Host: android.com"); |
| assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); |
| } |
| |
| |
| /** |
| * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912 |
| */ |
| public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| |
| initResponseCache(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); |
| |
| // The inclusion of a body in the response to the CONNECT is key to reproducing b/6754912. |
| MockResponse badProxyResponse = new MockResponse() |
| .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) |
| .clearHeaders() |
| .setBody("bogus proxy connect response content"); |
| |
| server.enqueue(badProxyResponse); |
| server.enqueue(new MockResponse().setBody("response")); |
| |
| server.play(); |
| |
| URL url = new URL("https://android.com/foo"); |
| ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY; |
| HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.setHostnameVerifier(new RecordingHostnameVerifier()); |
| assertContent("response", connection); |
| |
| RecordedRequest connect = server.takeRequest(); |
| assertEquals("CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); |
| assertContains(connect.getHeaders(), "Host: android.com:443"); |
| } |
| |
| private void initResponseCache() throws IOException { |
| String tmp = System.getProperty("java.io.tmpdir"); |
| File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); |
| cache = AndroidShimResponseCache.create(cacheDir, Integer.MAX_VALUE); |
| ResponseCache.setDefault(cache); |
| } |
| |
| /** |
| * Test Etag headers are returned correctly when a client-side cache is not installed. |
| * https://code.google.com/p/android/issues/detail?id=108949 |
| */ |
| public void testEtagHeaders_uncached() throws Exception { |
| final String etagValue1 = "686897696a7c876b7e"; |
| final String body1 = "Response with etag 1"; |
| final String etagValue2 = "686897696a7c876b7f"; |
| final String body2 = "Response with etag 2"; |
| |
| server.enqueue( |
| new MockResponse() |
| .setBody(body1) |
| .setHeader("Content-Type", "text/plain") |
| .setHeader("Etag", etagValue1)); |
| server.enqueue( |
| new MockResponse() |
| .setBody(body2) |
| .setHeader("Content-Type", "text/plain") |
| .setHeader("Etag", etagValue2)); |
| server.play(); |
| |
| URL url = server.getUrl("/"); |
| HttpURLConnection connection1 = (HttpURLConnection) url.openConnection(); |
| assertEquals(etagValue1, connection1.getHeaderField("Etag")); |
| assertContent(body1, connection1); |
| connection1.disconnect(); |
| |
| // Discard the server-side record of the request made. |
| server.takeRequest(); |
| |
| HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); |
| assertEquals(etagValue2, connection2.getHeaderField("Etag")); |
| assertContent(body2, connection2); |
| connection2.disconnect(); |
| |
| // Check the client did not cache. |
| RecordedRequest request = server.takeRequest(); |
| assertNull(request.getHeader("If-None-Match")); |
| } |
| |
| /** |
| * Test Etag headers are returned correctly when a client-side cache is installed and the server |
| * data is unchanged. |
| * https://code.google.com/p/android/issues/detail?id=108949 |
| */ |
| public void testEtagHeaders_cachedWithServerHit() throws Exception { |
| final String etagValue = "686897696a7c876b7e"; |
| final String body = "Response with etag"; |
| |
| server.enqueue( |
| new MockResponse() |
| .setBody(body) |
| .setResponseCode(HttpURLConnection.HTTP_OK) |
| .setHeader("Content-Type", "text/plain") |
| .setHeader("Etag", etagValue)); |
| |
| server.enqueue( |
| new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); |
| server.play(); |
| |
| initResponseCache(); |
| |
| URL url = server.getUrl("/"); |
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| assertEquals(etagValue, connection.getHeaderField("Etag")); |
| assertContent(body, connection); |
| connection.disconnect(); |
| |
| // Discard the server-side record of the request made. |
| server.takeRequest(); |
| |
| // Confirm the cached body is returned along with the original etag header. |
| HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); |
| assertEquals(etagValue, cachedConnection.getHeaderField("Etag")); |
| assertContent(body, cachedConnection); |
| cachedConnection.disconnect(); |
| |
| // Check the client formatted the request correctly. |
| RecordedRequest request = server.takeRequest(); |
| assertEquals(etagValue, request.getHeader("If-None-Match")); |
| } |
| |
| /** |
| * Test Etag headers are returned correctly when a client-side cache is installed and the server |
| * data has changed. |
| * https://code.google.com/p/android/issues/detail?id=108949 |
| */ |
| public void testEtagHeaders_cachedWithServerMiss() throws Exception { |
| final String etagValue1 = "686897696a7c876b7e"; |
| final String body1 = "Response with etag 1"; |
| final String etagValue2 = "686897696a7c876b7f"; |
| final String body2 = "Response with etag 2"; |
| |
| server.enqueue( |
| new MockResponse() |
| .setBody(body1) |
| .setResponseCode(HttpURLConnection.HTTP_OK) |
| .setHeader("Content-Type", "text/plain") |
| .setHeader("Etag", etagValue1)); |
| |
| server.enqueue( |
| new MockResponse() |
| .setBody(body2) |
| .setResponseCode(HttpURLConnection.HTTP_OK) |
| .setHeader("Content-Type", "text/plain") |
| .setHeader("Etag", etagValue2)); |
| |
| server.play(); |
| |
| initResponseCache(); |
| |
| URL url = server.getUrl("/"); |
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| assertEquals(etagValue1, connection.getHeaderField("Etag")); |
| assertContent(body1, connection); |
| connection.disconnect(); |
| |
| // Discard the server-side record of the request made. |
| server.takeRequest(); |
| |
| // Confirm the new body is returned along with the new etag header. |
| HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); |
| assertEquals(etagValue2, cachedConnection.getHeaderField("Etag")); |
| assertContent(body2, cachedConnection); |
| cachedConnection.disconnect(); |
| |
| // Check the client formatted the request correctly. |
| RecordedRequest request = server.takeRequest(); |
| assertEquals(etagValue1, request.getHeader("If-None-Match")); |
| } |
| |
| /** |
| * Test which headers are sent unencrypted to the HTTP proxy. |
| */ |
| public void testProxyConnectIncludesProxyHeadersOnly() |
| throws IOException, InterruptedException { |
| Authenticator.setDefault(new SimpleAuthenticator()); |
| RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); |
| server.enqueue(new MockResponse() |
| .setResponseCode(407) |
| .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); |
| server.enqueue(new MockResponse() |
| .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) |
| .clearHeaders()); |
| server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); |
| server.play(); |
| |
| URL url = new URL("https://android.com/foo"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( |
| server.toProxyAddress()); |
| connection.addRequestProperty("Private", "Secret"); |
| connection.addRequestProperty("User-Agent", "baz"); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.setHostnameVerifier(hostnameVerifier); |
| assertContent("encrypted response from the origin server", connection); |
| |
| // connect1 and connect2 are tunnel requests which potentially tunnel multiple requests; |
| // Thus we can't expect its headers to exactly match those of the wrapped request. |
| // See https://github.com/square/okhttp/commit/457fb428a729c50c562822571ea9b13e689648f3 |
| |
| { |
| RecordedRequest connect1 = server.takeRequest(); |
| List<String> headers = connect1.getHeaders(); |
| assertContainsNoneMatching(headers, "Private.*"); |
| assertContainsNoneMatching(headers, "Proxy\\-Authorization.*"); |
| assertHeaderPresent(connect1, "User-Agent"); |
| assertContains(headers, "Host: android.com:443"); |
| assertContains(headers, "Proxy-Connection: Keep-Alive"); |
| } |
| |
| { |
| RecordedRequest connect2 = server.takeRequest(); |
| List<String> headers = connect2.getHeaders(); |
| assertContainsNoneMatching(headers, "Private.*"); |
| assertHeaderPresent(connect2, "Proxy-Authorization"); |
| assertHeaderPresent(connect2, "User-Agent"); |
| assertContains(headers, "Host: android.com:443"); |
| assertContains(headers, "Proxy-Connection: Keep-Alive"); |
| } |
| |
| RecordedRequest get = server.takeRequest(); |
| assertContains(get.getHeaders(), "Private: Secret"); |
| assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); |
| } |
| |
| public void testProxyAuthenticateOnConnect() throws Exception { |
| Authenticator.setDefault(new SimpleAuthenticator()); |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); |
| server.enqueue(new MockResponse() |
| .setResponseCode(407) |
| .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); |
| server.enqueue(new MockResponse() |
| .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) |
| .clearHeaders()); |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| |
| URL url = new URL("https://android.com/foo"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( |
| server.toProxyAddress()); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.setHostnameVerifier(new RecordingHostnameVerifier()); |
| assertContent("A", connection); |
| |
| RecordedRequest connect1 = server.takeRequest(); |
| assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine()); |
| assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*"); |
| |
| RecordedRequest connect2 = server.takeRequest(); |
| assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine()); |
| assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic " |
| + SimpleAuthenticator.BASE_64_CREDENTIALS); |
| |
| RecordedRequest get = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); |
| assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*"); |
| } |
| |
| // Don't disconnect after building a tunnel with CONNECT |
| // http://code.google.com/p/android/issues/detail?id=37221 |
| public void testProxyWithConnectionClose() throws IOException { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); |
| server.enqueue(new MockResponse() |
| .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) |
| .clearHeaders()); |
| server.enqueue(new MockResponse().setBody("this response comes via a proxy")); |
| server.play(); |
| |
| URL url = new URL("https://android.com/foo"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( |
| server.toProxyAddress()); |
| connection.setRequestProperty("Connection", "close"); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.setHostnameVerifier(new RecordingHostnameVerifier()); |
| |
| assertContent("this response comes via a proxy", connection); |
| } |
| |
| public void testDisconnectedConnection() throws IOException { |
| server.enqueue(new MockResponse() |
| .throttleBody(2, 100, TimeUnit.MILLISECONDS) |
| .setBody("ABCD")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| InputStream in = connection.getInputStream(); |
| assertEquals('A', (char) in.read()); |
| connection.disconnect(); |
| try { |
| // Reading 'B' may succeed if it's buffered. |
| in.read(); |
| // But 'C' shouldn't be buffered (the response is throttled) and this should fail. |
| in.read(); |
| fail("Expected a connection closed exception"); |
| } catch (IOException expected) { |
| } |
| } |
| |
| // http://b/33763156 |
| public void testDisconnectDuringConnect_getInputStream() throws IOException { |
| checkDisconnectDuringConnect(HttpURLConnection::getInputStream); |
| } |
| |
| // http://b/33763156 |
| public void testDisconnectDuringConnect_getOutputStream() throws IOException { |
| checkDisconnectDuringConnect(HttpURLConnection::getOutputStream); |
| } |
| |
| // http://b/33763156 |
| public void testDisconnectDuringConnect_getResponseCode() throws IOException { |
| checkDisconnectDuringConnect(HttpURLConnection::getResponseCode); |
| } |
| |
| // http://b/33763156 |
| public void testDisconnectDuringConnect_getResponseMessage() throws IOException { |
| checkDisconnectDuringConnect(HttpURLConnection::getResponseMessage); |
| } |
| |
| interface ConnectStrategy { |
| /** |
| * Causes the given {@code connection}, which was previously disconnected, |
| * to initiate the connection. |
| */ |
| void connect(HttpURLConnection connection) throws IOException; |
| } |
| |
| // http://b/33763156 |
| private void checkDisconnectDuringConnect(ConnectStrategy connectStrategy) throws IOException { |
| server.enqueue(new MockResponse().setBody("This should never be sent")); |
| server.play(); |
| |
| final AtomicReference<HttpURLConnection> connectionHolder = new AtomicReference<>(); |
| class DisconnectingCookieHandler extends CookieManager { |
| @Override |
| public Map<String, List<String>> get(URI uri, Map<String, List<String>> map) |
| throws IOException { |
| Map<String, List<String>> result = super.get(uri, map); |
| connectionHolder.get().disconnect(); |
| return result; |
| } |
| } |
| CookieHandler defaultCookieHandler = CookieHandler.getDefault(); |
| try { |
| CookieHandler.setDefault(new DisconnectingCookieHandler()); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connectionHolder.set(connection); |
| try { |
| connectStrategy.connect(connection); |
| fail(); |
| } catch (IOException expected) { |
| assertEquals("Canceled", expected.getMessage()); |
| } finally { |
| connection.disconnect(); |
| } |
| } finally { |
| CookieHandler.setDefault(defaultCookieHandler); |
| } |
| } |
| |
| public void testDisconnectBeforeConnect() throws IOException { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.disconnect(); |
| |
| assertContent("A", connection); |
| assertEquals(200, connection.getResponseCode()); |
| } |
| |
| public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException { |
| server.enqueue(new MockResponse() |
| .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) |
| .addHeader("Content-Encoding: gzip")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| try { |
| assertEquals(200, connection.getResponseCode()); |
| } finally { |
| connection.disconnect(); |
| } |
| } |
| |
| public void testDefaultRequestProperty() throws Exception { |
| URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A"); |
| assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")); |
| } |
| |
| /** |
| * Reads {@code count} characters from the stream. If the stream is |
| * exhausted before {@code count} characters can be read, the remaining |
| * characters are returned and the stream is closed. |
| */ |
| private String readAscii(InputStream in, int count) throws IOException { |
| StringBuilder result = new StringBuilder(); |
| for (int i = 0; i < count; i++) { |
| int value = in.read(); |
| if (value == -1) { |
| in.close(); |
| break; |
| } |
| result.append((char) value); |
| } |
| return result.toString(); |
| } |
| |
| public void testMarkAndResetWithContentLengthHeader() throws IOException { |
| testMarkAndReset(TransferKind.FIXED_LENGTH); |
| } |
| |
| public void testMarkAndResetWithChunkedEncoding() throws IOException { |
| testMarkAndReset(TransferKind.CHUNKED); |
| } |
| |
| public void testMarkAndResetWithNoLengthHeaders() throws IOException { |
| testMarkAndReset(TransferKind.END_OF_STREAM); |
| } |
| |
| private void testMarkAndReset(TransferKind transferKind) throws IOException { |
| MockResponse response = new MockResponse(); |
| transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); |
| server.enqueue(response); |
| server.enqueue(response); |
| server.play(); |
| |
| InputStream in = server.getUrl("/").openConnection().getInputStream(); |
| assertFalse("This implementation claims to support mark().", in.markSupported()); |
| in.mark(5); |
| assertEquals("ABCDE", readAscii(in, 5)); |
| try { |
| in.reset(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); |
| assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection()); |
| } |
| |
| /** |
| * We've had a bug where we forget the HTTP response when we see response |
| * code 401. This causes a new HTTP request to be issued for every call into |
| * the URLConnection. |
| */ |
| public void testUnauthorizedResponseHandling() throws IOException { |
| MockResponse response = new MockResponse() |
| .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") |
| .setResponseCode(401) // UNAUTHORIZED |
| .setBody("Unauthorized"); |
| server.enqueue(response); |
| server.enqueue(response); |
| server.enqueue(response); |
| server.play(); |
| |
| URL url = server.getUrl("/"); |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
| |
| assertEquals(401, conn.getResponseCode()); |
| assertEquals(401, conn.getResponseCode()); |
| assertEquals(401, conn.getResponseCode()); |
| assertEquals(1, server.getRequestCount()); |
| } |
| |
| public void testNonHexChunkSize() throws IOException { |
| server.enqueue(new MockResponse() |
| .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") |
| .clearHeaders() |
| .addHeader("Transfer-encoding: chunked")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| try { |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE); |
| fail(); |
| } catch (IOException e) { |
| } |
| } |
| |
| public void testMissingChunkBody() throws IOException { |
| server.enqueue(new MockResponse() |
| .setBody("5") |
| .clearHeaders() |
| .addHeader("Transfer-encoding: chunked") |
| .setSocketPolicy(DISCONNECT_AT_END)); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| try { |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE); |
| fail(); |
| } catch (IOException e) { |
| } |
| } |
| |
| /** |
| * This test checks whether connections are gzipped by default. This |
| * behavior in not required by the API, so a failure of this test does not |
| * imply a bug in the implementation. |
| */ |
| public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse() |
| .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) |
| .addHeader("Content-Encoding: gzip")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| assertNull(connection.getContentEncoding()); |
| assertEquals(-1, connection.getContentLength()); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertContains(request.getHeaders(), "Accept-Encoding: gzip"); |
| } |
| |
| public void testClientConfiguredGzipContentEncoding() throws Exception { |
| byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")); |
| server.enqueue(new MockResponse() |
| .setBody(bodyBytes) |
| .addHeader("Content-Encoding: gzip") |
| .addHeader("Content-Length: " + bodyBytes.length)); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| connection.addRequestProperty("Accept-Encoding", "gzip"); |
| InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); |
| assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); |
| assertEquals(bodyBytes.length, connection.getContentLength()); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertContains(request.getHeaders(), "Accept-Encoding: gzip"); |
| assertEquals("gzip", connection.getContentEncoding()); |
| } |
| |
| public void testGzipAndConnectionReuseWithFixedLength() throws Exception { |
| testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); |
| } |
| |
| public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { |
| testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); |
| } |
| |
| public void testClientConfiguredCustomContentEncoding() throws Exception { |
| server.enqueue(new MockResponse() |
| .setBody("ABCDE") |
| .addHeader("Content-Encoding: custom")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| connection.addRequestProperty("Accept-Encoding", "custom"); |
| assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertContains(request.getHeaders(), "Accept-Encoding: custom"); |
| } |
| |
| /** |
| * Test a bug where gzip input streams weren't exhausting the input stream, |
| * which corrupted the request that followed. |
| * http://code.google.com/p/android/issues/detail?id=7059 |
| */ |
| private void testClientConfiguredGzipContentEncodingAndConnectionReuse( |
| TransferKind transferKind) throws Exception { |
| MockResponse responseOne = new MockResponse(); |
| responseOne.addHeader("Content-Encoding: gzip"); |
| transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); |
| server.enqueue(responseOne); |
| MockResponse responseTwo = new MockResponse(); |
| transferKind.setBody(responseTwo, "two (identity)", 5); |
| server.enqueue(responseTwo); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| connection.addRequestProperty("Accept-Encoding", "gzip"); |
| InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); |
| assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| |
| connection = server.getUrl("/").openConnection(); |
| assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| } |
| |
| /** |
| * Test that HEAD requests don't have a body regardless of the response |
| * headers. http://code.google.com/p/android/issues/detail?id=24672 |
| */ |
| public void testHeadAndContentLength() throws Exception { |
| server.enqueue(new MockResponse() |
| .clearHeaders() |
| .addHeader("Content-Length: 100")); |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| |
| HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection1.setRequestMethod("HEAD"); |
| assertEquals("100", connection1.getHeaderField("Content-Length")); |
| assertContent("", connection1); |
| |
| HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("A", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); |
| |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertEquals(1, server.takeRequest().getSequenceNumber()); |
| } |
| |
| /** |
| * Test that request body chunking works. This test has been relaxed from treating |
| * the {@link java.net.HttpURLConnection#setChunkedStreamingMode(int)} |
| * chunk length as being fixed because OkHttp no longer guarantees |
| * the fixed chunk size. Instead, we check that chunking takes place |
| * and we force the chunk size with flushes. |
| */ |
| public void testSetChunkedStreamingMode() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| // Later releases of Android ignore the value for chunkLength if it is > 0 and default to |
| // a fixed chunkLength. During the change-over period while the chunkLength indicates the |
| // chunk buffer size (inc. header) the chunkLength has to be >= 8. This enables the flush() |
| // to dictate the size of the chunks. |
| urlConnection.setChunkedStreamingMode(50 /* chunkLength */); |
| urlConnection.setDoOutput(true); |
| OutputStream outputStream = urlConnection.getOutputStream(); |
| String outputString = "ABCDEFGH"; |
| byte[] outputBytes = outputString.getBytes("US-ASCII"); |
| int targetChunkSize = 3; |
| for (int i = 0; i < outputBytes.length; i += targetChunkSize) { |
| int count = i + targetChunkSize < outputBytes.length ? 3 : outputBytes.length - i; |
| outputStream.write(outputBytes, i, count); |
| outputStream.flush(); |
| } |
| assertEquals(200, urlConnection.getResponseCode()); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertEquals(outputString, new String(request.getBody(), "US-ASCII")); |
| assertEquals(Arrays.asList(3, 3, 2), request.getChunkSizes()); |
| } |
| |
| public void testAuthenticateWithFixedLengthStreaming() throws Exception { |
| testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); |
| } |
| |
| public void testAuthenticateWithChunkedStreaming() throws Exception { |
| testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); |
| } |
| |
| private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { |
| MockResponse pleaseAuthenticate = new MockResponse() |
| .setResponseCode(401) |
| .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") |
| .setBody("Please authenticate."); |
| server.enqueue(pleaseAuthenticate); |
| server.play(); |
| |
| Authenticator.setDefault(new SimpleAuthenticator()); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setDoOutput(true); |
| byte[] requestBody = { 'A', 'B', 'C', 'D' }; |
| if (streamingMode == StreamingMode.FIXED_LENGTH) { |
| connection.setFixedLengthStreamingMode(requestBody.length); |
| } else if (streamingMode == StreamingMode.CHUNKED) { |
| connection.setChunkedStreamingMode(0); |
| } |
| OutputStream outputStream = connection.getOutputStream(); |
| outputStream.write(requestBody); |
| outputStream.close(); |
| try { |
| connection.getInputStream(); |
| fail(); |
| } catch (HttpRetryException expected) { |
| } |
| |
| // no authorization header for the request... |
| RecordedRequest request = server.takeRequest(); |
| assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); |
| assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); |
| } |
| |
| public void testSetValidRequestMethod() throws Exception { |
| server.play(); |
| assertValidRequestMethod("GET"); |
| assertValidRequestMethod("DELETE"); |
| assertValidRequestMethod("HEAD"); |
| assertValidRequestMethod("OPTIONS"); |
| assertValidRequestMethod("POST"); |
| assertValidRequestMethod("PUT"); |
| assertValidRequestMethod("TRACE"); |
| } |
| |
| private void assertValidRequestMethod(String requestMethod) throws Exception { |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setRequestMethod(requestMethod); |
| assertEquals(requestMethod, connection.getRequestMethod()); |
| } |
| |
| public void testSetInvalidRequestMethodLowercase() throws Exception { |
| server.play(); |
| assertInvalidRequestMethod("get"); |
| } |
| |
| public void testSetInvalidRequestMethodConnect() throws Exception { |
| server.play(); |
| assertInvalidRequestMethod("CONNECT"); |
| } |
| |
| private void assertInvalidRequestMethod(String requestMethod) throws Exception { |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| try { |
| connection.setRequestMethod(requestMethod); |
| fail(); |
| } catch (ProtocolException expected) { |
| } |
| } |
| |
| public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception { |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| try { |
| connection.setFixedLengthStreamingMode(-2); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| public void testCanSetNegativeChunkedStreamingMode() throws Exception { |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setChunkedStreamingMode(-2); |
| } |
| |
| public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| try { |
| connection.setFixedLengthStreamingMode(1); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| try { |
| connection.setChunkedStreamingMode(1); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setChunkedStreamingMode(1); |
| try { |
| connection.setFixedLengthStreamingMode(1); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setFixedLengthStreamingMode(1); |
| try { |
| connection.setChunkedStreamingMode(1); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| public void testSecureFixedLengthStreaming() throws Exception { |
| testSecureStreamingPost(StreamingMode.FIXED_LENGTH); |
| } |
| |
| public void testSecureChunkedStreaming() throws Exception { |
| testSecureStreamingPost(StreamingMode.CHUNKED); |
| } |
| |
| /** |
| * Users have reported problems using HTTPS with streaming request bodies. |
| * http://code.google.com/p/android/issues/detail?id=12860 |
| */ |
| private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setBody("Success!")); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.setDoOutput(true); |
| byte[] requestBody = { 'A', 'B', 'C', 'D' }; |
| if (streamingMode == StreamingMode.FIXED_LENGTH) { |
| connection.setFixedLengthStreamingMode(requestBody.length); |
| } else if (streamingMode == StreamingMode.CHUNKED) { |
| connection.setChunkedStreamingMode(0); |
| } |
| OutputStream outputStream = connection.getOutputStream(); |
| outputStream.write(requestBody); |
| outputStream.close(); |
| assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertEquals("POST / HTTP/1.1", request.getRequestLine()); |
| if (streamingMode == StreamingMode.FIXED_LENGTH) { |
| assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes()); |
| } else if (streamingMode == StreamingMode.CHUNKED) { |
| assertEquals(Arrays.asList(4), request.getChunkSizes()); |
| } |
| assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); |
| } |
| |
| enum StreamingMode { |
| FIXED_LENGTH, CHUNKED |
| } |
| |
| public void testAuthenticateWithPost() throws Exception { |
| MockResponse pleaseAuthenticate = new MockResponse() |
| .setResponseCode(401) |
| .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") |
| .setBody("Please authenticate."); |
| // fail auth three times... |
| server.enqueue(pleaseAuthenticate); |
| server.enqueue(pleaseAuthenticate); |
| server.enqueue(pleaseAuthenticate); |
| // ...then succeed the fourth time |
| server.enqueue(new MockResponse().setBody("Successful auth!")); |
| server.play(); |
| |
| Authenticator.setDefault(new SimpleAuthenticator()); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setDoOutput(true); |
| byte[] requestBody = { 'A', 'B', 'C', 'D' }; |
| OutputStream outputStream = connection.getOutputStream(); |
| outputStream.write(requestBody); |
| outputStream.close(); |
| assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| // no authorization header for the first request... |
| RecordedRequest request = server.takeRequest(); |
| assertContainsNoneMatching(request.getHeaders(), "Authorization: .*"); |
| |
| // ...but the three requests that follow include an authorization header |
| for (int i = 0; i < 3; i++) { |
| request = server.takeRequest(); |
| assertEquals("POST / HTTP/1.1", request.getRequestLine()); |
| assertContains(request.getHeaders(), "Authorization: Basic " |
| + SimpleAuthenticator.BASE_64_CREDENTIALS); |
| assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); |
| } |
| } |
| |
| public void testAuthenticateWithGet() throws Exception { |
| MockResponse pleaseAuthenticate = new MockResponse() |
| .setResponseCode(401) |
| .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") |
| .setBody("Please authenticate."); |
| // fail auth three times... |
| server.enqueue(pleaseAuthenticate); |
| server.enqueue(pleaseAuthenticate); |
| server.enqueue(pleaseAuthenticate); |
| // ...then succeed the fourth time |
| server.enqueue(new MockResponse().setBody("Successful auth!")); |
| server.play(); |
| |
| SimpleAuthenticator authenticator = new SimpleAuthenticator(); |
| Authenticator.setDefault(authenticator); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType); |
| assertEquals(server.getPort(), authenticator.requestingPort); |
| assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite); |
| assertEquals("protected area", authenticator.requestingPrompt); |
| assertEquals("http", authenticator.requestingProtocol); |
| assertEquals("Basic", authenticator.requestingScheme); |
| |
| // no authorization header for the first request... |
| RecordedRequest request = server.takeRequest(); |
| assertContainsNoneMatching(request.getHeaders(), "Authorization: .*"); |
| |
| // ...but the three requests that follow requests include an authorization header |
| for (int i = 0; i < 3; i++) { |
| request = server.takeRequest(); |
| assertEquals("GET / HTTP/1.1", request.getRequestLine()); |
| assertContains(request.getHeaders(), "Authorization: Basic " |
| + SimpleAuthenticator.BASE_64_CREDENTIALS); |
| } |
| } |
| |
| // bug 11473660 |
| public void testAuthenticateWithLowerCaseHeadersAndScheme() throws Exception { |
| MockResponse pleaseAuthenticate = new MockResponse() |
| .setResponseCode(401) |
| .addHeader("www-authenticate: basic realm=\"protected area\"") |
| .setBody("Please authenticate."); |
| // fail auth three times... |
| server.enqueue(pleaseAuthenticate); |
| server.enqueue(pleaseAuthenticate); |
| server.enqueue(pleaseAuthenticate); |
| // ...then succeed the fourth time |
| server.enqueue(new MockResponse().setBody("Successful auth!")); |
| server.play(); |
| |
| SimpleAuthenticator authenticator = new SimpleAuthenticator(); |
| Authenticator.setDefault(authenticator); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType); |
| assertEquals(server.getPort(), authenticator.requestingPort); |
| assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite); |
| assertEquals("protected area", authenticator.requestingPrompt); |
| assertEquals("http", authenticator.requestingProtocol); |
| assertEquals("basic", authenticator.requestingScheme); |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=19081 |
| public void testAuthenticateWithCommaSeparatedAuthenticationMethods() throws Exception { |
| server.enqueue(new MockResponse() |
| .setResponseCode(401) |
| .addHeader("WWW-Authenticate: Scheme1 realm=\"a\", Basic realm=\"b\", " |
| + "Scheme3 realm=\"c\"") |
| .setBody("Please authenticate.")); |
| server.enqueue(new MockResponse().setBody("Successful auth!")); |
| server.play(); |
| |
| SimpleAuthenticator authenticator = new SimpleAuthenticator(); |
| authenticator.expectedPrompt = "b"; |
| Authenticator.setDefault(authenticator); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); |
| assertContains(server.takeRequest().getHeaders(), |
| "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS); |
| assertEquals("Basic", authenticator.requestingScheme); |
| } |
| |
| public void testAuthenticateWithMultipleAuthenticationHeaders() throws Exception { |
| server.enqueue(new MockResponse() |
| .setResponseCode(401) |
| .addHeader("WWW-Authenticate: Scheme1 realm=\"a\"") |
| .addHeader("WWW-Authenticate: Basic realm=\"b\"") |
| .addHeader("WWW-Authenticate: Scheme3 realm=\"c\"") |
| .setBody("Please authenticate.")); |
| server.enqueue(new MockResponse().setBody("Successful auth!")); |
| server.play(); |
| |
| SimpleAuthenticator authenticator = new SimpleAuthenticator(); |
| authenticator.expectedPrompt = "b"; |
| Authenticator.setDefault(authenticator); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); |
| assertContains(server.takeRequest().getHeaders(), |
| "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS); |
| assertEquals("Basic", authenticator.requestingScheme); |
| } |
| |
| public void testRedirectedWithChunkedEncoding() throws Exception { |
| testRedirected(TransferKind.CHUNKED, true); |
| } |
| |
| public void testRedirectedWithContentLengthHeader() throws Exception { |
| testRedirected(TransferKind.FIXED_LENGTH, true); |
| } |
| |
| public void testRedirectedWithNoLengthHeaders() throws Exception { |
| testRedirected(TransferKind.END_OF_STREAM, false); |
| } |
| |
| private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { |
| MockResponse response = new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: /foo"); |
| transferKind.setBody(response, "This page has moved!", 10); |
| server.enqueue(response); |
| server.enqueue(new MockResponse().setBody("This is the new location!")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| assertEquals("This is the new location!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| RecordedRequest first = server.takeRequest(); |
| assertEquals("GET / HTTP/1.1", first.getRequestLine()); |
| RecordedRequest retry = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); |
| if (reuse) { |
| assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); |
| } |
| } |
| |
| public void testRedirectedOnHttps() throws IOException, InterruptedException { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: /foo") |
| .setBody("This page has moved!")); |
| server.enqueue(new MockResponse().setBody("This is the new location!")); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| assertEquals("This is the new location!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| RecordedRequest first = server.takeRequest(); |
| assertEquals("GET / HTTP/1.1", first.getRequestLine()); |
| RecordedRequest retry = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); |
| assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); |
| } |
| |
| public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: http://anyhost/foo") |
| .setBody("This page has moved!")); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| assertEquals("This page has moved!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| } |
| |
| public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: https://anyhost/foo") |
| .setBody("This page has moved!")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("This page has moved!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| } |
| |
| public void testRedirectToAnotherOriginServer() throws Exception { |
| MockWebServer server2 = new MockWebServer(); |
| server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); |
| server2.play(); |
| |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: " + server2.getUrl("/").toString()) |
| .setBody("This page has moved!")); |
| server.enqueue(new MockResponse().setBody("This is the first server again!")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| assertEquals("This is the 2nd server!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| assertEquals(server2.getUrl("/"), connection.getURL()); |
| |
| // make sure the first server was careful to recycle the connection |
| assertEquals("This is the first server again!", |
| readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); |
| |
| RecordedRequest first = server.takeRequest(); |
| assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort()); |
| RecordedRequest second = server2.takeRequest(); |
| assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort()); |
| RecordedRequest third = server.takeRequest(); |
| assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); |
| |
| server2.shutdown(); |
| } |
| |
| // http://b/27590872 - assert we do not throw a runtime exception if a server responds with |
| // a location that cannot be represented directly by URI. |
| public void testRedirectWithInvalidRedirectUrl() throws Exception { |
| // The first server hosts a redirect to a second. We need two so that the ProxySelector |
| // installed is used for the redirect. Otherwise the second request will be handled via the |
| // existing keep-alive connection. |
| server.play(); |
| |
| MockWebServer server2 = new MockWebServer(); |
| server2.play(); |
| |
| String targetPath = "/target"; |
| // The "%0" in the suffix is invalid without a second digit. |
| String invalidSuffix = "?foo=%0&bar=%00"; |
| |
| String redirectPath = server2.getUrl(targetPath).toString(); |
| String invalidRedirectUri = redirectPath + invalidSuffix; |
| |
| // Redirect to the invalid URI. |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: " + invalidRedirectUri)); |
| |
| server2.enqueue(new MockResponse().setBody("Target")); |
| |
| // Assert the target URI is actually invalid. |
| try { |
| new URI(invalidRedirectUri); |
| fail("Target URL is expected to be invalid"); |
| } catch (URISyntaxException expected) {} |
| |
| // The ProxySelector requires a URI object, which forces the HttpURLConnectionImpl to create |
| // a URI object containing a string based on the redirect address, regardless of what it is |
| // using internally to hold the target address. |
| ProxySelector originalSelector = ProxySelector.getDefault(); |
| final List<URI> proxySelectorUris = new ArrayList<>(); |
| ProxySelector.setDefault(new ProxySelector() { |
| @Override |
| public List<Proxy> select(URI uri) { |
| if (uri.getScheme().equals("http")) { |
| // Ignore socks proxy lookups. |
| proxySelectorUris.add(uri); |
| } |
| return Collections.singletonList(Proxy.NO_PROXY); |
| } |
| |
| @Override |
| public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { |
| // no-op |
| } |
| }); |
| |
| try { |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("Target", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| // Inspect the redirect request to see what request was actually made. |
| RecordedRequest actualRequest = server2.takeRequest(); |
| assertEquals(targetPath + invalidSuffix, actualRequest.getPath()); |
| |
| // The first URI will be the initial request. We want to inspect the redirect. |
| URI uri = proxySelectorUris.get(1); |
| // The proxy is selected by Address alone (not the whole target URI). |
| // In OkHttp, HttpEngine.createAddress() converts to an Address and the |
| // RouteSelector converts back to address.url(). |
| assertEquals(server2.getUrl("/").toString(), uri.toString()); |
| } finally { |
| ProxySelector.setDefault(originalSelector); |
| server2.shutdown(); |
| } |
| } |
| |
| public void testInstanceFollowsRedirects() throws Exception { |
| testInstanceFollowsRedirects("http://www.google.com/"); |
| testInstanceFollowsRedirects("https://www.google.com/"); |
| } |
| |
| private void testInstanceFollowsRedirects(String spec) throws Exception { |
| URL url = new URL(spec); |
| HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); |
| urlConnection.setInstanceFollowRedirects(true); |
| assertTrue(urlConnection.getInstanceFollowRedirects()); |
| urlConnection.setInstanceFollowRedirects(false); |
| assertFalse(urlConnection.getInstanceFollowRedirects()); |
| } |
| |
| public void testFollowRedirects() throws Exception { |
| testFollowRedirects("http://www.google.com/"); |
| testFollowRedirects("https://www.google.com/"); |
| } |
| |
| private void testFollowRedirects(String spec) throws Exception { |
| URL url = new URL(spec); |
| boolean originalValue = HttpURLConnection.getFollowRedirects(); |
| try { |
| HttpURLConnection.setFollowRedirects(false); |
| assertFalse(HttpURLConnection.getFollowRedirects()); |
| |
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| assertFalse(connection.getInstanceFollowRedirects()); |
| |
| HttpURLConnection.setFollowRedirects(true); |
| assertTrue(HttpURLConnection.getFollowRedirects()); |
| |
| HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); |
| assertTrue(connection2.getInstanceFollowRedirects()); |
| } finally { |
| HttpURLConnection.setFollowRedirects(originalValue); |
| } |
| } |
| |
| public void testResponse300MultipleChoiceWithPost() throws Exception { |
| // Chrome doesn't follow the redirect, but Firefox and the RI both do |
| testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE); |
| } |
| |
| public void testResponse301MovedPermanentlyWithPost() throws Exception { |
| testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM); |
| } |
| |
| public void testResponse302MovedTemporarilyWithPost() throws Exception { |
| testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP); |
| } |
| |
| public void testResponse303SeeOtherWithPost() throws Exception { |
| testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER); |
| } |
| |
| private void testResponseRedirectedWithPost(int redirectCode) throws Exception { |
| server.enqueue(new MockResponse() |
| .setResponseCode(redirectCode) |
| .addHeader("Location: /page2") |
| .setBody("This page has moved!")); |
| server.enqueue(new MockResponse().setBody("Page 2")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection(); |
| connection.setDoOutput(true); |
| byte[] requestBody = { 'A', 'B', 'C', 'D' }; |
| OutputStream outputStream = connection.getOutputStream(); |
| outputStream.write(requestBody); |
| outputStream.close(); |
| assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| assertTrue(connection.getDoOutput()); |
| |
| RecordedRequest page1 = server.takeRequest(); |
| assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine()); |
| assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); |
| |
| RecordedRequest page2 = server.takeRequest(); |
| assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); |
| } |
| |
| public void testResponse305UseProxy() throws Exception { |
| server.play(); |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_USE_PROXY) |
| .addHeader("Location: " + server.getUrl("/")) |
| .setBody("This page has moved!")); |
| server.enqueue(new MockResponse().setBody("Proxy Response")); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection(); |
| // Fails on the RI, which gets "Proxy Response" |
| assertEquals("This page has moved!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| RecordedRequest page1 = server.takeRequest(); |
| assertEquals("GET /foo HTTP/1.1", page1.getRequestLine()); |
| assertEquals(1, server.getRequestCount()); |
| } |
| |
| public void testHttpsWithCustomTrustManager() throws Exception { |
| RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); |
| RecordingTrustManager trustManager = new RecordingTrustManager(); |
| SSLContext sc = SSLContext.getInstance("TLS"); |
| sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); |
| |
| HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); |
| HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); |
| SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); |
| HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); |
| try { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setBody("ABC")); |
| server.enqueue(new MockResponse().setBody("DEF")); |
| server.enqueue(new MockResponse().setBody("GHI")); |
| server.play(); |
| |
| URL url = server.getUrl("/"); |
| assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); |
| assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); |
| assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); |
| |
| assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls); |
| assertEquals(Arrays.asList("checkServerTrusted [" |
| + "CN=Local Host 3, " |
| + "CN=Test Intermediate Certificate Authority 2, " |
| + "CN=Test Root Certificate Authority 1" |
| + "] ECDHE_RSA"), |
| trustManager.calls); |
| } finally { |
| HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); |
| HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); |
| } |
| } |
| |
| /** |
| * Test that the timeout period is honored. The connect timeout is applied to each socket |
| * connection attempt. If a hostname resolves to multiple IPs HttpURLConnection will wait the |
| * full timeout for each. |
| */ |
| public void testConnectTimeouts() throws IOException { |
| // During CTS tests we are limited in what host names we can depend on and unfortunately |
| // DNS lookups are not pluggable through standard APIs. During manual testing you should be |
| // able to change this to any name that can be resolved to multiple IPs and it should still |
| // work. |
| String hostName = "localhost"; |
| int expectedConnectionAttempts = InetAddress.getAllByName(hostName).length; |
| int perSocketTimeout = 1000; |
| final int[] socketCreationCount = new int[1]; |
| final int[] socketConnectTimeouts = new int[expectedConnectionAttempts]; |
| |
| // It is difficult to force socket timeouts reliably so we replace the default SocketFactory |
| // and have it create Sockets that always time out when connect(SocketAddress, int) is |
| // called. |
| SocketFactory originalDefault = SocketFactory.getDefault(); |
| try { |
| // Override the default SocketFactory so we can intercept socket creation. |
| SocketFactory.setDefault(new DelegatingSocketFactory(originalDefault) { |
| @Override |
| protected Socket configureSocket(Socket socket) throws IOException { |
| final int attemptNumber = socketCreationCount[0]++; |
| Socket socketWrapper = new DelegatingSocket(socket) { |
| @Override |
| public void connect(SocketAddress endpoint, int timeout) |
| throws IOException { |
| socketConnectTimeouts[attemptNumber] = timeout; |
| throw new SocketTimeoutException("Simulated timeout after " + timeout); |
| } |
| }; |
| return socketWrapper; |
| } |
| }); |
| |
| URLConnection urlConnection = new URL("http://" + hostName + "/").openConnection(); |
| urlConnection.setConnectTimeout(perSocketTimeout); |
| urlConnection.getInputStream(); |
| fail(); |
| } catch (SocketTimeoutException e) { |
| assertEquals(expectedConnectionAttempts, socketCreationCount[0]); |
| for (int i = 0; i < expectedConnectionAttempts; i++) { |
| assertEquals(perSocketTimeout, socketConnectTimeouts[i]); |
| } |
| } finally { |
| SocketFactory.setDefault(originalDefault); |
| } |
| } |
| |
| public void testReadTimeouts() throws IOException { |
| /* |
| * This relies on the fact that MockWebServer doesn't close the |
| * connection after a response has been sent. This causes the client to |
| * try to read more bytes than are sent, which results in a timeout. |
| */ |
| MockResponse timeout = new MockResponse() |
| .setBody("ABC") |
| .clearHeaders() |
| .addHeader("Content-Length: 4"); |
| server.enqueue(timeout); |
| server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive |
| server.play(); |
| |
| URLConnection urlConnection = server.getUrl("/").openConnection(); |
| urlConnection.setReadTimeout(1000); |
| InputStream in = urlConnection.getInputStream(); |
| assertEquals('A', in.read()); |
| assertEquals('B', in.read()); |
| assertEquals('C', in.read()); |
| try { |
| in.read(); // if Content-Length was accurate, this would return -1 immediately |
| fail(); |
| } catch (SocketTimeoutException expected) { |
| } |
| } |
| |
| public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| urlConnection.setRequestProperty("Transfer-encoding", "chunked"); |
| urlConnection.setDoOutput(true); |
| urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); |
| assertEquals(200, urlConnection.getResponseCode()); |
| |
| RecordedRequest request = server.takeRequest(); |
| assertEquals("ABC", new String(request.getBody(), "UTF-8")); |
| } |
| |
| public void testConnectionCloseInRequest() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); |
| a.setRequestProperty("Connection", "close"); |
| assertEquals(200, a.getResponseCode()); |
| |
| HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals(200, b.getResponseCode()); |
| |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertEquals("When connection: close is used, each request should get its own connection", |
| 0, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testConnectionCloseInResponse() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse().addHeader("Connection: close")); |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals(200, a.getResponseCode()); |
| |
| HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals(200, b.getResponseCode()); |
| |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertEquals("When connection: close is used, each request should get its own connection", |
| 0, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { |
| MockResponse response = new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) |
| .addHeader("Location: /foo") |
| .addHeader("Connection: close"); |
| server.enqueue(response); |
| server.enqueue(new MockResponse().setBody("This is the new location!")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| assertEquals("This is the new location!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| assertEquals(0, server.takeRequest().getSequenceNumber()); |
| assertEquals("When connection: close is used, each request should get its own connection", |
| 0, server.takeRequest().getSequenceNumber()); |
| } |
| |
| public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { |
| server.enqueue(new MockResponse() |
| .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) |
| .setBody("This body is not allowed!")); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| assertEquals("This body is not allowed!", |
| readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| } |
| |
| public void testSingleByteReadIsSigned() throws IOException { |
| server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); |
| server.play(); |
| |
| URLConnection connection = server.getUrl("/").openConnection(); |
| InputStream in = connection.getInputStream(); |
| assertEquals(254, in.read()); |
| assertEquals(255, in.read()); |
| assertEquals(-1, in.read()); |
| } |
| |
| public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException { |
| testFlushAfterStreamTransmitted(TransferKind.CHUNKED); |
| } |
| |
| public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException { |
| testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); |
| } |
| |
| public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { |
| testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); |
| } |
| |
| /** |
| * We explicitly permit apps to close the upload stream even after it has |
| * been transmitted. We also permit flush so that buffered streams can |
| * do a no-op flush when they are closed. http://b/3038470 |
| */ |
| private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { |
| server.enqueue(new MockResponse().setBody("abc")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setDoOutput(true); |
| byte[] upload = "def".getBytes("UTF-8"); |
| |
| if (transferKind == TransferKind.CHUNKED) { |
| connection.setChunkedStreamingMode(0); |
| } else if (transferKind == TransferKind.FIXED_LENGTH) { |
| connection.setFixedLengthStreamingMode(upload.length); |
| } |
| |
| OutputStream out = connection.getOutputStream(); |
| out.write(upload); |
| assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| |
| out.flush(); // dubious but permitted |
| try { |
| out.write("ghi".getBytes("UTF-8")); |
| fail(); |
| } catch (IOException expected) { |
| } |
| } |
| |
| public void testGetHeadersThrows() throws IOException { |
| server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| try { |
| connection.getInputStream(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| |
| try { |
| connection.getInputStream(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| } |
| |
| public void testReadTimeoutsOnRecycledConnections() throws Exception { |
| server.enqueue(new MockResponse().setBody("ABC")); |
| server.play(); |
| |
| // The request should work once and then fail |
| URLConnection connection = server.getUrl("").openConnection(); |
| // Read timeout of a day, sure to cause the test to timeout and fail. |
| connection.setReadTimeout(24 * 3600 * 1000); |
| InputStream input = connection.getInputStream(); |
| assertEquals("ABC", readAscii(input, Integer.MAX_VALUE)); |
| input.close(); |
| try { |
| connection = server.getUrl("").openConnection(); |
| // Set the read timeout back to 100ms, this request will time out |
| // because we've only enqueued one response. |
| connection.setReadTimeout(100); |
| connection.getInputStream(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| } |
| |
| /** |
| * This test goes through the exhaustive set of interesting ASCII characters |
| * because most of those characters are interesting in some way according to |
| * RFC 2396 and RFC 2732. http://b/1158780 |
| * After M, Android's HttpURLConnection started canonicalizing hostnames to lower case, IDN |
| * encoding and being more strict about invalid characters. |
| */ |
| public void testUrlCharacterMapping() throws Exception { |
| server.setDispatcher(new Dispatcher() { |
| @Override public MockResponse dispatch(RecordedRequest request) |
| throws InterruptedException { |
| return new MockResponse(); |
| } |
| }); |
| server.play(); |
| |
| // alphanum |
| testUrlToUriMapping("abzABZ09", "abzabz09", "abzABZ09", "abzABZ09", "abzABZ09"); |
| testUrlToRequestMapping("abzABZ09", "abzABZ09", "abzABZ09"); |
| |
| // control characters |
| |
| // On JB-MR2 and below, we would allow a host containing \u0000 |
| // and then generate a request with a Host header that violated RFC2616. |
| // We now reject such hosts. |
| // |
| // The ideal behaviour here is to be "lenient" about the host and rewrite |
| // it, but attempting to do so introduces a new range of incompatible |
| // behaviours. |
| testUrlToUriMapping("\u0000", null, "%00", "%00", "%00"); // RI fails this |
| testUrlToRequestMapping("\u0000", "%00", "%00"); |
| |
| testUrlToUriMapping("\u0001", null, "%01", "%01", "%01"); |
| testUrlToRequestMapping("\u0001", "%01", "%01"); |
| |
| testUrlToUriMapping("\u001f", null, "%1F", "%1F", "%1F"); |
| testUrlToRequestMapping("\u001f", "%1F", "%1F"); |
| |
| // ascii characters |
| testUrlToUriMapping("%20", null, "%20", "%20", "%20"); |
| testUrlToRequestMapping("%20", "%20", "%20"); |
| testUrlToUriMapping(" ", null, "%20", "%20", "%20"); |
| testUrlToRequestMapping(" ", "%20", "%20"); |
| testUrlToUriMapping("!", "!", "!", "!", "!"); |
| testUrlToRequestMapping("!", "!", "!"); |
| testUrlToUriMapping("\"", null, "%22", "%22", "%22"); |
| testUrlToRequestMapping("\"", "%22", "%22"); |
| testUrlToUriMapping("#", null, null, null, "%23"); |
| testUrlToRequestMapping("#", null, null); |
| testUrlToUriMapping("$", "$", "$", "$", "$"); |
| testUrlToRequestMapping("$", "$", "$"); |
| testUrlToUriMapping("&", "&", "&", "&", "&"); |
| testUrlToRequestMapping("&", "&", "&"); |
| |
| // http://b/30405333 - upstream OkHttp encodes single quote (') as %27 in query parameters |
| // but this breaks iTunes remote apps: iTunes currently does not accept %27 so we have a |
| // local patch to retain the historic Android behavior of not encoding single quote. |
| testUrlToUriMapping("'", "'", "'", "'", "'"); |
| testUrlToRequestMapping("'", "'", "'"); |
| |
| testUrlToUriMapping("(", "(", "(", "(", "("); |
| testUrlToRequestMapping("(", "(", "("); |
| testUrlToUriMapping(")", ")", ")", ")", ")"); |
| testUrlToRequestMapping(")", ")", ")"); |
| testUrlToUriMapping("*", "*", "*", "*", "*"); |
| testUrlToRequestMapping("*", "*", "*"); |
| testUrlToUriMapping("+", "+", "+", "+", "+"); |
| testUrlToRequestMapping("+", "+", "+"); |
| testUrlToUriMapping(",", ",", ",", ",", ","); |
| testUrlToRequestMapping(",", ",", ","); |
| testUrlToUriMapping("-", "-", "-", "-", "-"); |
| testUrlToRequestMapping("-", "-", "-"); |
| testUrlToUriMapping(".", null, ".", ".", "."); |
| testUrlToRequestMapping(".", ".", "."); |
| testUrlToUriMapping(".foo", ".foo", ".foo", ".foo", ".foo"); |
| testUrlToRequestMapping(".foo", ".foo", ".foo"); |
| testUrlToUriMapping("/", null, "/", "/", "/"); |
| testUrlToRequestMapping("/", "/", "/"); |
| testUrlToUriMapping(":", null, ":", ":", ":"); |
| testUrlToRequestMapping(":", ":", ":"); |
| testUrlToUriMapping(";", ";", ";", ";", ";"); |
| testUrlToRequestMapping(";", ";", ";"); |
| testUrlToUriMapping("<", null, "%3C", "%3C", "%3C"); |
| testUrlToRequestMapping("<", "%3C", "%3C"); |
| testUrlToUriMapping("=", "=", "=", "=", "="); |
| testUrlToRequestMapping("=", "=", "="); |
| testUrlToUriMapping(">", null, "%3E", "%3E", "%3E"); |
| testUrlToRequestMapping(">", "%3E", "%3E"); |
| testUrlToUriMapping("?", null, null, "?", "?"); |
| testUrlToRequestMapping("?", null, "?"); |
| testUrlToUriMapping("@", null, "@", "@", "@"); |
| testUrlToRequestMapping("@", "@", "@"); |
| testUrlToUriMapping("[", null, null, null, "%5B"); |
| testUrlToRequestMapping("[", null, null); |
| testUrlToUriMapping("\\", null, null, null, "%5C"); |
| testUrlToRequestMapping("\\", null, null); |
| testUrlToUriMapping("]", null, null, null, "%5D"); |
| testUrlToRequestMapping("]", null, null); |
| testUrlToUriMapping("^", null, "%5E", null, "%5E"); |
| testUrlToRequestMapping("^", "%5E", null); |
| testUrlToUriMapping("_", "_", "_", "_", "_"); |
| testUrlToRequestMapping("_", "_", "_"); |
| testUrlToUriMapping("`", null, "%60", null, "%60"); |
| testUrlToRequestMapping("`", "%60", null); |
| testUrlToUriMapping("{", null, "%7B", null, "%7B"); |
| testUrlToRequestMapping("{", "%7B", null); |
| testUrlToUriMapping("|", null, "%7C", null, "%7C"); |
| testUrlToRequestMapping("|", "%7C", null); |
| testUrlToUriMapping("}", null, "%7D", null, "%7D"); |
| testUrlToRequestMapping("}", "%7D", null); |
| testUrlToUriMapping("~", "~", "~", "~", "~"); |
| testUrlToRequestMapping("~", "~", "~"); |
| testUrlToUriMapping("\u007f", null, "%7F", "%7F", "%7F"); |
| testUrlToRequestMapping("\u007f", "%7F", "%7F"); |
| |
| // beyond ASCII |
| |
| // 0x80 is the code point for the Euro sign in CP1252 (but not 8859-15 or Unicode). |
| // Unicode code point 0x80 is a control character and maps to {0xC2, 0x80} in UTF-8. |
| // 0x80 is outside of the ASCII range and is not supported by IDN in hostnames. |
| testUrlToUriMapping("\u0080", null, "%C2%80", "%C2%80", "%C2%80"); |
| testUrlToRequestMapping("\u0080", "%C2%80", "%C2%80"); |
| |
| // More complicated transformations for the authorities below. |
| |
| // 0x20AC is the code point for the Euro sign in Unicode. |
| // Unicode code point 0x20AC maps to {0xE2, 0x82, 0xAC} in UTF-8 |
| // 0x20AC is not supported by all registrars but there are some legacy domains that |
| // use it and Android currently supports IDN conversion for it. |
| testUrlToUriMapping("\u20ac", null /* skip */, "%E2%82%AC", "%E2%82%AC", "%E2%82%AC"); |
| testUrlToUriMappingAuthority("http://host\u20ac.tld/", "http://xn--host-yv7a.tld/"); |
| testUrlToRequestMapping("\u20ac", "%E2%82%AC", "%E2%82%AC"); |
| |
| // UTF-16 {0xD842, 0xDF9F} -> Unicode 0x20B9F (a Kanji character) |
| // Unicode code point 0x20B9F maps to {0xF0, 0xA0, 0xAE, 0x9F} in UTF-8 |
| // IDN can deal with this code point. |
| testUrlToUriMapping("\ud842\udf9f", null /* skip */, "%F0%A0%AE%9F", "%F0%A0%AE%9F", |
| "%F0%A0%AE%9F"); |
| testUrlToUriMappingAuthority("http://host\uD842\uDF9F.tld/", "http://xn--host-ov06c.tld/"); |
| testUrlToRequestMapping("\ud842\udf9f", "%F0%A0%AE%9F", "%F0%A0%AE%9F"); |
| } |
| |
| private void testUrlToUriMappingAuthority(String urlString, String expectedUriString) |
| throws Exception { |
| URI authorityUri = backdoorUrlToUri(new URL(urlString)); |
| assertEquals(expectedUriString, authorityUri.toString()); |
| } |
| |
| /** |
| * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI, |
| * HttpURLConnection recovers from URLs with unescaped but unsupported URI |
| * characters like '{' and '|' by escaping these characters. |
| */ |
| private URI backdoorUrlToUri(URL url) throws Exception { |
| final AtomicReference<URI> uriReference = new AtomicReference<URI>(); |
| |
| ResponseCache.setDefault(new ResponseCache() { |
| @Override public CacheRequest put(URI uri, URLConnection connection) |
| throws IOException { |
| return null; |
| } |
| @Override public CacheResponse get(URI uri, String requestMethod, |
| Map<String, List<String>> requestHeaders) throws IOException { |
| uriReference.set(uri); |
| throw new UnsupportedOperationException(); |
| } |
| }); |
| |
| try { |
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| connection.getResponseCode(); |
| } catch (Exception expected) { |
| } |
| |
| return uriReference.get(); |
| } |
| |
| /* |
| * Test the request that would be made by making an actual request a MockWebServer and capturing |
| * the request made. |
| * |
| * Any "as" values that are null are not tested. |
| */ |
| private void testUrlToRequestMapping( |
| String string, String asFile, String asQuery) throws Exception { |
| if (asFile != null) { |
| URL fileUrl = server.getUrl("/file" + string + "/#discarded"); |
| HttpURLConnection urlConnection = (HttpURLConnection) fileUrl.openConnection(); |
| // Bypass the cache. |
| urlConnection.setUseCaches(false); |
| |
| assertEquals(200, urlConnection.getResponseCode()); |
| assertEquals("/file" + asFile + "/", server.takeRequest().getPath()); |
| } |
| if (asQuery != null) { |
| URL queryUrl = server.getUrl("/file?q" + string + "=x#discarded"); |
| HttpURLConnection urlConnection = (HttpURLConnection) queryUrl.openConnection(); |
| // Bypass the cache. |
| urlConnection.setUseCaches(false); |
| |
| assertEquals(200, urlConnection.getResponseCode()); |
| assertEquals("/file?q" + asQuery + "=x", server.takeRequest().getPath()); |
| } |
| } |
| |
| /* |
| * Test the request that would be made by looking at the URI presented to the cache. This |
| * includes the likely host name that would be used if a request were made. The cache throws an |
| * exception so no request is actually made. |
| * |
| * Any "as" values that are null are not tested. |
| */ |
| private void testUrlToUriMapping(String string, String asAuthority, String asFile, |
| String asQuery, String asFragment) throws Exception { |
| if (asAuthority != null) { |
| URI authorityUri = backdoorUrlToUri(new URL("http://host" + string + ".tld/")); |
| assertEquals("http://host" + asAuthority + ".tld/", authorityUri.toString()); |
| } |
| if (asFile != null) { |
| URI fileUri = backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")); |
| assertEquals("http://host.tld/file" + asFile + "/", fileUri.toString()); |
| } |
| if (asQuery != null) { |
| URI queryUri = backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")); |
| assertEquals("http://host.tld/file?q" + asQuery + "=x", queryUri.toString()); |
| } |
| assertEquals("http://host.tld/file#" + asFragment + "-x", |
| backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString()); |
| } |
| |
| public void testHostWithNul() throws Exception { |
| URL url = new URL("http://host\u0000/"); |
| try { |
| url.openStream(); |
| fail(); |
| } catch (UnknownHostException expected) {} |
| } |
| |
| /** |
| * Don't explode if the cache returns a null body. http://b/3373699 |
| */ |
| public void testResponseCacheReturnsNullOutputStream() throws Exception { |
| final AtomicBoolean aborted = new AtomicBoolean(); |
| ResponseCache.setDefault(new ResponseCache() { |
| @Override public CacheResponse get(URI uri, String requestMethod, |
| Map<String, List<String>> requestHeaders) throws IOException { |
| return null; |
| } |
| @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { |
| return new CacheRequest() { |
| @Override public void abort() { |
| aborted.set(true); |
| } |
| @Override public OutputStream getBody() throws IOException { |
| return null; |
| } |
| }; |
| } |
| }); |
| |
| server.enqueue(new MockResponse().setBody("abcdef")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| InputStream in = connection.getInputStream(); |
| assertEquals("abc", readAscii(in, 3)); |
| in.close(); |
| assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here |
| } |
| |
| |
| /** |
| * http://code.google.com/p/android/issues/detail?id=14562 |
| */ |
| public void testReadAfterLastByte() throws Exception { |
| server.enqueue(new MockResponse() |
| .setBody("ABC") |
| .clearHeaders() |
| .addHeader("Connection: close") |
| .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| InputStream in = connection.getInputStream(); |
| assertEquals("ABC", readAscii(in, 3)); |
| assertEquals(-1, in.read()); |
| assertEquals(-1, in.read()); // throws IOException in Gingerbread |
| } |
| |
| public void testGetContent() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| InputStream in = (InputStream) connection.getContent(); |
| assertEquals("A", readAscii(in, Integer.MAX_VALUE)); |
| } |
| |
| public void testGetContentOfType() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| try { |
| connection.getContent(null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| try { |
| connection.getContent(new Class[] { null }); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| assertNull(connection.getContent(new Class[] { getClass() })); |
| connection.disconnect(); |
| } |
| |
| public void testGetOutputStreamOnGetFails() throws Exception { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| try { |
| connection.getOutputStream(); |
| fail(); |
| } catch (ProtocolException expected) { |
| } |
| } |
| |
| public void testGetOutputAfterGetInputStreamFails() throws Exception { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setDoOutput(true); |
| try { |
| connection.getInputStream(); |
| connection.getOutputStream(); |
| fail(); |
| } catch (ProtocolException expected) { |
| } |
| } |
| |
| public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.connect(); |
| try { |
| connection.setDoOutput(true); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| connection.setDoInput(true); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| connection.disconnect(); |
| } |
| |
| public void testLastModified() throws Exception { |
| server.enqueue(new MockResponse() |
| .addHeader("Last-Modified", "Wed, 27 Nov 2013 11:26:00 GMT") |
| .setBody("Hello")); |
| server.play(); |
| |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.connect(); |
| |
| assertEquals(1385551560000L, connection.getLastModified()); |
| assertEquals(1385551560000L, connection.getHeaderFieldDate("Last-Modified", -1)); |
| } |
| |
| public void testClientSendsContentLength() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| connection.setDoOutput(true); |
| OutputStream out = connection.getOutputStream(); |
| out.write(new byte[] { 'A', 'B', 'C' }); |
| out.close(); |
| assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); |
| RecordedRequest request = server.takeRequest(); |
| assertContains(request.getHeaders(), "Content-Length: 3"); |
| } |
| |
| public void testGetContentLengthConnects() throws Exception { |
| server.enqueue(new MockResponse().setBody("ABC")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals(3, connection.getContentLength()); |
| connection.disconnect(); |
| } |
| |
| public void testGetContentTypeConnects() throws Exception { |
| server.enqueue(new MockResponse() |
| .addHeader("Content-Type: text/plain") |
| .setBody("ABC")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("text/plain", connection.getContentType()); |
| connection.disconnect(); |
| } |
| |
| public void testGetContentEncodingConnects() throws Exception { |
| server.enqueue(new MockResponse() |
| .addHeader("Content-Encoding: identity") |
| .setBody("ABC")); |
| server.play(); |
| HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals("identity", connection.getContentEncoding()); |
| connection.disconnect(); |
| } |
| |
| // http://b/4361656 |
| public void testUrlContainsQueryButNoPath() throws Exception { |
| server.enqueue(new MockResponse().setBody("A")); |
| server.play(); |
| URL url = new URL("http", server.getHostName(), server.getPort(), "?query"); |
| assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE)); |
| RecordedRequest request = server.takeRequest(); |
| assertEquals("GET /?query HTTP/1.1", request.getRequestLine()); |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=20442 |
| public void testInputStreamAvailableWithChunkedEncoding() throws Exception { |
| testInputStreamAvailable(TransferKind.CHUNKED); |
| } |
| |
| public void testInputStreamAvailableWithContentLengthHeader() throws Exception { |
| testInputStreamAvailable(TransferKind.FIXED_LENGTH); |
| } |
| |
| public void testInputStreamAvailableWithNoLengthHeaders() throws Exception { |
| testInputStreamAvailable(TransferKind.END_OF_STREAM); |
| } |
| |
| private void testInputStreamAvailable(TransferKind transferKind) throws IOException { |
| String body = "ABCDEFGH"; |
| MockResponse response = new MockResponse(); |
| transferKind.setBody(response, body, 4); |
| server.enqueue(response); |
| server.play(); |
| URLConnection connection = server.getUrl("/").openConnection(); |
| InputStream in = connection.getInputStream(); |
| for (int i = 0; i < body.length(); i++) { |
| assertTrue(in.available() >= 0); |
| assertEquals(body.charAt(i), in.read()); |
| } |
| assertEquals(0, in.available()); |
| assertEquals(-1, in.read()); |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=28095 |
| public void testInvalidIpv4Address() throws Exception { |
| try { |
| URI uri = new URI("http://1111.111.111.111/index.html"); |
| uri.toURL().openConnection().connect(); |
| fail(); |
| } catch (UnknownHostException expected) { |
| } |
| } |
| |
| public void testConnectIpv6() throws Exception { |
| server.enqueue(new MockResponse().setBody("testConnectIpv6 body")); |
| server.play(); |
| URL url = new URL("http://[::1]:" + server.getPort() + "/"); |
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| assertContent("testConnectIpv6 body", connection); |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=16895 |
| public void testUrlWithSpaceInHost() throws Exception { |
| URLConnection urlConnection = new URL("http://and roid.com/").openConnection(); |
| try { |
| urlConnection.getInputStream(); |
| fail(); |
| } catch (UnknownHostException expected) { |
| } |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=16895 |
| public void testUrlWithSpaceInHostViaHttpProxy() throws Exception { |
| server.enqueue(new MockResponse()); |
| server.play(); |
| URLConnection urlConnection = new URL("http://and roid.com/") |
| .openConnection(server.toProxyAddress()); |
| |
| try { |
| // This test is to check that a NullPointerException is not thrown. |
| urlConnection.getInputStream(); |
| fail(); // the RI makes a bogus proxy request for "GET http://and roid.com/ HTTP/1.1" |
| } catch (UnknownHostException expected) { |
| } |
| } |
| |
| /** Checks that if the first TLS handshake fails, no fallback is attempted. */ |
| private void checkNoFallbackOnFailedHandshake(SSLSocketFactory clientSocketFactory, |
| SSLSocketFactory serverSocketFactory, |
| String... expectedProtocols) |
| throws Exception { |
| server.useHttps(serverSocketFactory, false); |
| server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); |
| server.enqueue(new MockResponse().setBody("This required fallbacks")); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| // Keeps track of the client sockets created so that we can interrogate them. |
| final boolean disableFallbackScsv = true; |
| FallbackTestClientSocketFactory fallbackTestClientSocketFactory = |
| new FallbackTestClientSocketFactory(clientSocketFactory, disableFallbackScsv); |
| connection.setSSLSocketFactory(fallbackTestClientSocketFactory); |
| try { |
| connection.getInputStream().read(); |
| fail(); |
| } catch (SSLHandshakeException expected) { |
| } |
| List<SSLSocket> createdSockets = fallbackTestClientSocketFactory.getCreatedSockets(); |
| assertEquals(1, createdSockets.size()); |
| assertSslSocket((TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0), |
| false /* expectedWasFallbackScsvSet */, expectedProtocols); |
| } |
| |
| public void testNoSslFallback_specifiedProtocols() throws Exception { |
| String[] enabledProtocols = { "TLSv1.2", "TLSv1.1" }; |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| SSLSocketFactory serverSocketFactory = |
| new LimitedProtocolsSocketFactory( |
| testSSLContext.serverContext.getSocketFactory(), |
| enabledProtocols); |
| SSLSocketFactory clientSocketFactory = new LimitedProtocolsSocketFactory( |
| testSSLContext.clientContext.getSocketFactory(), enabledProtocols); |
| checkNoFallbackOnFailedHandshake(clientSocketFactory, serverSocketFactory, |
| enabledProtocols); |
| } |
| |
| public void testNoSslFallback_defaultProtocols() throws Exception { |
| // Will need to be updated if the enabled protocols in Android's SSLSocketFactory change |
| String[] expectedEnabledProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1" }; |
| |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| SSLSocketFactory serverSocketFactory = testSSLContext.serverContext.getSocketFactory(); |
| SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory(); |
| checkNoFallbackOnFailedHandshake(clientSocketFactory, serverSocketFactory, |
| expectedEnabledProtocols); |
| } |
| |
| private static void assertSslSocket(TlsFallbackDisabledScsvSSLSocket socket, |
| boolean expectedWasFallbackScsvSet, String... expectedEnabledProtocols) { |
| Set<String> enabledProtocols = |
| new HashSet<String>(Arrays.asList(socket.getEnabledProtocols())); |
| Set<String> expectedProtocolsSet = new HashSet<String>(Arrays.asList(expectedEnabledProtocols)); |
| assertEquals(expectedProtocolsSet, enabledProtocols); |
| assertEquals(expectedWasFallbackScsvSet, socket.wasTlsFallbackScsvSet()); |
| } |
| |
| public void testInspectSslBeforeConnect() throws Exception { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| assertNotNull(connection.getHostnameVerifier()); |
| try { |
| connection.getLocalCertificates(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| connection.getServerCertificates(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| connection.getCipherSuite(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| connection.getPeerPrincipal(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| /** |
| * Test that we can inspect the SSL session after connect(). |
| * http://code.google.com/p/android/issues/detail?id=24431 |
| */ |
| public void testInspectSslAfterConnect() throws Exception { |
| TestSSLContext testSSLContext = createDefaultTestSSLContext(); |
| server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse()); |
| server.play(); |
| |
| HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); |
| connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); |
| connection.connect(); |
| try { |
| assertNotNull(connection.getHostnameVerifier()); |
| assertNull(connection.getLocalCertificates()); |
| assertNotNull(connection.getServerCertificates()); |
| assertNotNull(connection.getCipherSuite()); |
| assertNotNull(connection.getPeerPrincipal()); |
| } finally { |
| connection.disconnect(); |
| } |
| } |
| |
| /** |
| * Checks that OkHttp's certificate pinning logic is not used for the common case |
| * of HttpsUrlConnections. |
| * |
| * <p>OkHttp 2.7 introduced logic for Certificate Pinning. We deliberately don't |
| * expose any API surface that would interact with OkHttp's implementation because |
| * Android has its own API / implementation for certificate pinning. We can't |
| * easily test that there is *no* code path that would invoke OkHttp's certificate |
| * pinning logic, so this test only covers the *common* code path of a |
| * HttpsURLConnection as a sanity check. |
| * |
| * <p>To check whether OkHttp performs certificate pinning under the hood, this |
| * test disables two {@link Platform} methods. In OkHttp 2.7.5, these two methods |
| * are exclusively used in relation to certificate pinning. Android only provides |
| * the minimal implementation of these methods to get OkHttp's tests to pass, so |
| * they should never be invoked outside of OkHttp's tests. |
| */ |
| public void testTrustManagerAndTrustRootIndex_unusedForHttpsConnection() throws Exception { |
| Platform platform = Platform.getAndSetForTest(new PlatformWithoutTrustManager()); |
| try { |
| testConnectViaHttps(); |
| } finally { |
| Platform.getAndSetForTest(platform); |
| } |
| } |
| |
| /** |
| * Similar to {@link #testTrustManagerAndTrustRootIndex_unusedForHttpsConnection()}, |
| * but for the HTTP case. In the HTTP case, no certificate or trust management |
| * related logic should ever be involved at all, so some pretty basic things must |
| * be going wrong in order for this test to (unexpectedly) invoke the corresponding |
| * Platform methods. |
| */ |
| public void testTrustManagerAndTrustRootIndex_unusedForHttpConnection() throws Exception { |
| Platform platform = Platform.getAndSetForTest(new PlatformWithoutTrustManager()); |
| try { |
| server.enqueue(new MockResponse().setBody("response").setResponseCode(200)); |
| server.play(); |
| HttpURLConnection urlConnection = |
| (HttpURLConnection) server.getUrl("/").openConnection(); |
| assertEquals(200, urlConnection.getResponseCode()); |
| } finally { |
| Platform.getAndSetForTest(platform); |
| } |
| } |
| |
| /** |
| * A {@link Platform} that doesn't support two methods that, in OkHttp 2.7.5, |
| * are exclusively used to provide custom CertificatePinning. |
| */ |
| static class PlatformWithoutTrustManager extends Platform { |
| @Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { |
| throw new AssertionError("Unexpected call"); |
| } |
| @Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { |
| throw new AssertionError("Unexpected call"); |
| } |
| } |
| |
| /** |
| * Returns a gzipped copy of {@code bytes}. |
| */ |
| public byte[] gzip(byte[] bytes) throws IOException { |
| ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); |
| OutputStream gzippedOut = new GZIPOutputStream(bytesOut); |
| gzippedOut.write(bytes); |
| gzippedOut.close(); |
| return bytesOut.toByteArray(); |
| } |
| |
| /** |
| * Reads at most {@code limit} characters from {@code in} and asserts that |
| * content equals {@code expected}. |
| */ |
| private void assertContent(String expected, URLConnection connection, int limit) |
| throws IOException { |
| connection.connect(); |
| assertEquals(expected, readAscii(connection.getInputStream(), limit)); |
| ((HttpURLConnection) connection).disconnect(); |
| } |
| |
| private void assertContent(String expected, URLConnection connection) throws IOException { |
| assertContent(expected, connection, Integer.MAX_VALUE); |
| } |
| |
| private static void assertHeaderPresent(RecordedRequest request, String headerName) { |
| assertNotNull(headerName + " missing: " + request.getHeaders(), |
| request.getHeader(headerName)); |
| } |
| |
| private void assertContains(List<String> list, String value) { |
| assertTrue(list.toString(), list.contains(value)); |
| } |
| |
| private void assertContainsNoneMatching(List<String> list, String pattern) { |
| for (String header : list) { |
| if (header.matches(pattern)) { |
| fail("Header " + header + " matches " + pattern); |
| } |
| } |
| } |
| |
| private Set<String> newSet(String... elements) { |
| return new HashSet<String>(Arrays.asList(elements)); |
| } |
| |
| private TestSSLContext createDefaultTestSSLContext() { |
| TestSSLContext result = TestSSLContext.create(); |
| testSSLContextsToClose.add(result); |
| return result; |
| } |
| |
| enum TransferKind { |
| CHUNKED() { |
| @Override void setBody(MockResponse response, byte[] content, int chunkSize) |
| throws IOException { |
| response.setChunkedBody(content, chunkSize); |
| } |
| }, |
| FIXED_LENGTH() { |
| @Override void setBody(MockResponse response, byte[] content, int chunkSize) { |
| response.setBody(content); |
| } |
| }, |
| END_OF_STREAM() { |
| @Override void setBody(MockResponse response, byte[] content, int chunkSize) { |
| response.setBody(content); |
| response.setSocketPolicy(DISCONNECT_AT_END); |
| for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { |
| if (h.next().startsWith("Content-Length:")) { |
| h.remove(); |
| break; |
| } |
| } |
| } |
| }; |
| |
| abstract void setBody(MockResponse response, byte[] content, int chunkSize) |
| throws IOException; |
| |
| void setBody(MockResponse response, String content, int chunkSize) throws IOException { |
| setBody(response, content.getBytes("UTF-8"), chunkSize); |
| } |
| } |
| |
| enum ProxyConfig { |
| NO_PROXY() { |
| @Override public HttpURLConnection connect(MockWebServer server, URL url) |
| throws IOException { |
| return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); |
| } |
| }, |
| |
| CREATE_ARG() { |
| @Override public HttpURLConnection connect(MockWebServer server, URL url) |
| throws IOException { |
| return (HttpURLConnection) url.openConnection(server.toProxyAddress()); |
| } |
| }, |
| |
| PROXY_SYSTEM_PROPERTY() { |
| @Override public HttpURLConnection connect(MockWebServer server, URL url) |
| throws IOException { |
| System.setProperty("proxyHost", "localhost"); |
| System.setProperty("proxyPort", Integer.toString(server.getPort())); |
| return (HttpURLConnection) url.openConnection(); |
| } |
| }, |
| |
| HTTP_PROXY_SYSTEM_PROPERTY() { |
| @Override public HttpURLConnection connect(MockWebServer server, URL url) |
| throws IOException { |
| System.setProperty("http.proxyHost", "localhost"); |
| System.setProperty("http.proxyPort", Integer.toString(server.getPort())); |
| return (HttpURLConnection) url.openConnection(); |
| } |
| }, |
| |
| HTTPS_PROXY_SYSTEM_PROPERTY() { |
| @Override public HttpURLConnection connect(MockWebServer server, URL url) |
| throws IOException { |
| System.setProperty("https.proxyHost", "localhost"); |
| System.setProperty("https.proxyPort", Integer.toString(server.getPort())); |
| return (HttpURLConnection) url.openConnection(); |
| } |
| }; |
| |
| public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException; |
| } |
| |
| private static class RecordingTrustManager implements X509TrustManager { |
| private final List<String> calls = new ArrayList<String>(); |
| |
| public X509Certificate[] getAcceptedIssuers() { |
| calls.add("getAcceptedIssuers"); |
| return new X509Certificate[] {}; |
| } |
| |
| public void checkClientTrusted(X509Certificate[] chain, String authType) |
| throws CertificateException { |
| calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); |
| } |
| |
| public void checkServerTrusted(X509Certificate[] chain, String authType) |
| throws CertificateException { |
| calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); |
| } |
| |
| private String certificatesToString(X509Certificate[] certificates) { |
| List<String> result = new ArrayList<String>(); |
| for (X509Certificate certificate : certificates) { |
| result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); |
| } |
| return result.toString(); |
| } |
| } |
| |
| private static class RecordingHostnameVerifier implements HostnameVerifier { |
| private final List<String> calls = new ArrayList<String>(); |
| |
| public boolean verify(String hostname, SSLSession session) { |
| calls.add("verify " + hostname); |
| return true; |
| } |
| } |
| |
| private static class SimpleAuthenticator extends Authenticator { |
| /** base64("username:password") */ |
| private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ="; |
| |
| private String expectedPrompt; |
| private RequestorType requestorType; |
| private int requestingPort; |
| private InetAddress requestingSite; |
| private String requestingPrompt; |
| private String requestingProtocol; |
| private String requestingScheme; |
| |
| protected PasswordAuthentication getPasswordAuthentication() { |
| requestorType = getRequestorType(); |
| requestingPort = getRequestingPort(); |
| requestingSite = getRequestingSite(); |
| requestingPrompt = getRequestingPrompt(); |
| requestingProtocol = getRequestingProtocol(); |
| requestingScheme = getRequestingScheme(); |
| return (expectedPrompt == null || expectedPrompt.equals(requestingPrompt)) |
| ? new PasswordAuthentication("username", "password".toCharArray()) |
| : null; |
| } |
| } |
| |
| /** |
| * An SSLSocketFactory that delegates all calls. |
| */ |
| private static class DelegatingSSLSocketFactory extends SSLSocketFactory { |
| |
| protected final SSLSocketFactory delegate; |
| |
| public DelegatingSSLSocketFactory(SSLSocketFactory delegate) { |
| this.delegate = delegate; |
| } |
| |
| @Override |
| public String[] getDefaultCipherSuites() { |
| return delegate.getDefaultCipherSuites(); |
| } |
| |
| @Override |
| public String[] getSupportedCipherSuites() { |
| return delegate.getSupportedCipherSuites(); |
| } |
| |
| @Override |
| public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) |
| throws IOException { |
| return (SSLSocket) delegate.createSocket(s, host, port, autoClose); |
| } |
| |
| @Override |
| public SSLSocket createSocket() throws IOException { |
| return (SSLSocket) delegate.createSocket(); |
| } |
| |
| @Override |
| public SSLSocket createSocket(String host, int port) |
| throws IOException, UnknownHostException { |
| return (SSLSocket) delegate.createSocket(host, port); |
| } |
| |
| @Override |
| public SSLSocket createSocket(String host, int port, InetAddress localHost, |
| int localPort) throws IOException, UnknownHostException { |
| return (SSLSocket) delegate.createSocket(host, port, localHost, localPort); |
| } |
| |
| @Override |
| public SSLSocket createSocket(InetAddress host, int port) throws IOException { |
| return (SSLSocket) delegate.createSocket(host, port); |
| } |
| |
| @Override |
| public SSLSocket createSocket(InetAddress address, int port, |
| InetAddress localAddress, int localPort) throws IOException { |
| return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); |
| } |
| |
| } |
| |
| /** |
| * An SSLSocketFactory that delegates calls but limits the enabled protocols for any created |
| * sockets. |
| */ |
| private static class LimitedProtocolsSocketFactory extends DelegatingSSLSocketFactory { |
| |
| private final String[] protocols; |
| |
| private LimitedProtocolsSocketFactory(SSLSocketFactory delegate, String... protocols) { |
| super(delegate); |
| this.protocols = protocols; |
| } |
| |
| @Override |
| public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) |
| throws IOException { |
| SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose); |
| socket.setEnabledProtocols(protocols); |
| return socket; |
| } |
| |
| @Override |
| public SSLSocket createSocket() throws IOException { |
| SSLSocket socket = (SSLSocket) delegate.createSocket(); |
| socket.setEnabledProtocols(protocols); |
| return socket; |
| } |
| |
| @Override |
| public SSLSocket createSocket(String host, int port) |
| throws IOException, UnknownHostException { |
| SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); |
| socket.setEnabledProtocols(protocols); |
| return socket; |
| } |
| |
| @Override |
| public SSLSocket createSocket(String host, int port, InetAddress localHost, |
| int localPort) throws IOException, UnknownHostException { |
| SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort); |
| socket.setEnabledProtocols(protocols); |
| return socket; |
| } |
| |
| @Override |
| public SSLSocket createSocket(InetAddress host, int port) throws IOException { |
| SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); |
| socket.setEnabledProtocols(protocols); |
| return socket; |
| } |
| |
| @Override |
| public SSLSocket createSocket(InetAddress address, int port, |
| InetAddress localAddress, int localPort) throws IOException { |
| SSLSocket socket = |
| (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); |
| socket.setEnabledProtocols(protocols); |
| return socket; |
| } |
| } |
| |
| /** |
| * A Socket that forwards all calls to public or protected methods, except for those |
| * that Socket inherits from Object, to a delegate. |
| */ |
| private static abstract class DelegatingSocket extends Socket { |
| private final Socket delegate; |
| |
| public DelegatingSocket(Socket delegate) { |
| if (delegate == null) { |
| throw new NullPointerException(); |
| } |
| this.delegate = delegate; |
| } |
| |
| @Override public void bind(SocketAddress bindpoint) throws IOException { delegate.bind(bindpoint); } |
| @Override public void close() throws IOException { delegate.close(); } |
| @Override public void connect(SocketAddress endpoint) throws IOException { delegate.connect(endpoint); } |
| @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { delegate.connect(endpoint, timeout); } |
| @Override public SocketChannel getChannel() { return delegate.getChannel(); } |
| @Override public FileDescriptor getFileDescriptor$() { return delegate.getFileDescriptor$(); } |
| @Override public InetAddress getInetAddress() { return delegate.getInetAddress(); } |
| @Override public InputStream getInputStream() throws IOException { return delegate.getInputStream(); } |
| @Override public boolean getKeepAlive() throws SocketException { return delegate.getKeepAlive(); } |
| @Override public InetAddress getLocalAddress() { return delegate.getLocalAddress(); } |
| @Override public int getLocalPort() { return delegate.getLocalPort(); } |
| @Override public SocketAddress getLocalSocketAddress() { return delegate.getLocalSocketAddress(); } |
| @Override public boolean getOOBInline() throws SocketException { return delegate.getOOBInline(); } |
| @Override public OutputStream getOutputStream() throws IOException { return delegate.getOutputStream(); } |
| @Override public int getPort() { return delegate.getPort(); } |
| @Override public int getReceiveBufferSize() throws SocketException { return delegate.getReceiveBufferSize(); } |
| @Override public SocketAddress getRemoteSocketAddress() { return delegate.getRemoteSocketAddress(); } |
| @Override public boolean getReuseAddress() throws SocketException { return delegate.getReuseAddress(); } |
| @Override public int getSendBufferSize() throws SocketException { return delegate.getSendBufferSize(); } |
| @Override public int getSoLinger() throws SocketException { return delegate.getSoLinger(); } |
| @Override public int getSoTimeout() throws SocketException { return delegate.getSoTimeout(); } |
| @Override public boolean getTcpNoDelay() throws SocketException { return delegate.getTcpNoDelay(); } |
| @Override public int getTrafficClass() throws SocketException { return delegate.getTrafficClass(); } |
| @Override public boolean isBound() { return delegate.isBound(); } |
| @Override public boolean isClosed() { return delegate.isClosed(); } |
| @Override public boolean isConnected() { return delegate.isConnected(); } |
| @Override public boolean isInputShutdown() { return delegate.isInputShutdown(); } |
| @Override public boolean isOutputShutdown() { return delegate.isOutputShutdown(); } |
| @Override public void sendUrgentData(int data) throws IOException { delegate.sendUrgentData(data); } |
| @Override public void setKeepAlive(boolean on) throws SocketException { delegate.setKeepAlive(on); } |
| @Override public void setOOBInline(boolean on) throws SocketException { delegate.setOOBInline(on); } |
| @Override public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { delegate.setPerformancePreferences(connectionTime, latency, bandwidth); } |
| @Override public void setReceiveBufferSize(int size) throws SocketException { delegate.setReceiveBufferSize(size); } |
| @Override public void setReuseAddress(boolean on) throws SocketException { delegate.setReuseAddress(on); } |
| @Override public void setSendBufferSize(int size) throws SocketException { delegate.setSendBufferSize(size); } |
| @Override public void setSoLinger(boolean on, int linger) throws SocketException { delegate.setSoLinger(on, linger); } |
| @Override public void setSoTimeout(int timeout) throws SocketException { delegate.setSoTimeout(timeout); } |
| @Override public void setTcpNoDelay(boolean on) throws SocketException { delegate.setTcpNoDelay(on); } |
| @Override public void setTrafficClass(int tc) throws SocketException { delegate.setTrafficClass(tc); } |
| @Override public void shutdownInput() throws IOException { delegate.shutdownInput(); } |
| @Override public void shutdownOutput() throws IOException { delegate.shutdownOutput(); } |
| @Override public String toString() { return delegate.toString(); } |
| } |
| |
| /** |
| * An {@link javax.net.ssl.SSLSocket} that delegates all calls. |
| */ |
| private static abstract class DelegatingSSLSocket extends SSLSocket { |
| protected final SSLSocket delegate; |
| |
| public DelegatingSSLSocket(SSLSocket delegate) { |
| this.delegate = delegate; |
| } |
| |
| @Override public void shutdownInput() throws IOException { |
| delegate.shutdownInput(); |
| } |
| |
| @Override public void shutdownOutput() throws IOException { |
| delegate.shutdownOutput(); |
| } |
| |
| @Override public String[] getSupportedCipherSuites() { |
| return delegate.getSupportedCipherSuites(); |
| } |
| |
| @Override public String[] getEnabledCipherSuites() { |
| return delegate.getEnabledCipherSuites(); |
| } |
| |
| @Override public void setEnabledCipherSuites(String[] suites) { |
| delegate.setEnabledCipherSuites(suites); |
| } |
| |
| @Override public String[] getSupportedProtocols() { |
| return delegate.getSupportedProtocols(); |
| } |
| |
| @Override public String[] getEnabledProtocols() { |
| return delegate.getEnabledProtocols(); |
| } |
| |
| @Override public void setEnabledProtocols(String[] protocols) { |
| delegate.setEnabledProtocols(protocols); |
| } |
| |
| @Override public SSLSession getSession() { |
| return delegate.getSession(); |
| } |
| |
| @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { |
| delegate.addHandshakeCompletedListener(listener); |
| } |
| |
| @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { |
| delegate.removeHandshakeCompletedListener(listener); |
| } |
| |
| @Override public void startHandshake() throws IOException { |
| delegate.startHandshake(); |
| } |
| |
| @Override public void setUseClientMode(boolean mode) { |
| delegate.setUseClientMode(mode); |
| } |
| |
| @Override public boolean getUseClientMode() { |
| return delegate.getUseClientMode(); |
| } |
| |
| @Override public void setNeedClientAuth(boolean need) { |
| delegate.setNeedClientAuth(need); |
| } |
| |
| @Override public void setWantClientAuth(boolean want) { |
| delegate.setWantClientAuth(want); |
| } |
| |
| @Override public boolean getNeedClientAuth() { |
| return delegate.getNeedClientAuth(); |
| } |
| |
| @Override public boolean getWantClientAuth() { |
| return delegate.getWantClientAuth(); |
| } |
| |
| @Override public void setEnableSessionCreation(boolean flag) { |
| delegate.setEnableSessionCreation(flag); |
| } |
| |
| @Override public boolean getEnableSessionCreation() { |
| return delegate.getEnableSessionCreation(); |
| } |
| |
| @Override public SSLParameters getSSLParameters() { |
| return delegate.getSSLParameters(); |
| } |
| |
| @Override public void setSSLParameters(SSLParameters p) { |
| delegate.setSSLParameters(p); |
| } |
| |
| @Override public void close() throws IOException { |
| delegate.close(); |
| } |
| |
| @Override public InetAddress getInetAddress() { |
| return delegate.getInetAddress(); |
| } |
| |
| @Override public InputStream getInputStream() throws IOException { |
| return delegate.getInputStream(); |
| } |
| |
| @Override public boolean getKeepAlive() throws SocketException { |
| return delegate.getKeepAlive(); |
| } |
| |
| @Override public InetAddress getLocalAddress() { |
| return delegate.getLocalAddress(); |
| } |
| |
| @Override public int getLocalPort() { |
| return delegate.getLocalPort(); |
| } |
| |
| @Override public OutputStream getOutputStream() throws IOException { |
| return delegate.getOutputStream(); |
| } |
| |
| @Override public int getPort() { |
| return delegate.getPort(); |
| } |
| |
| @Override public int getSoLinger() throws SocketException { |
| return delegate.getSoLinger(); |
| } |
| |
| @Override public int getReceiveBufferSize() throws SocketException { |
| return delegate.getReceiveBufferSize(); |
| } |
| |
| @Override public int getSendBufferSize() throws SocketException { |
| return delegate.getSendBufferSize(); |
| } |
| |
| @Override public int getSoTimeout() throws SocketException { |
| return delegate.getSoTimeout(); |
| } |
| |
| @Override public boolean getTcpNoDelay() throws SocketException { |
| return delegate.getTcpNoDelay(); |
| } |
| |
| @Override public void setKeepAlive(boolean keepAlive) throws SocketException { |
| delegate.setKeepAlive(keepAlive); |
| } |
| |
| @Override public void setSendBufferSize(int size) throws SocketException { |
| delegate.setSendBufferSize(size); |
| } |
| |
| @Override public void setReceiveBufferSize(int size) throws SocketException { |
| delegate.setReceiveBufferSize(size); |
| } |
| |
| @Override public void setSoLinger(boolean on, int timeout) throws SocketException { |
| delegate.setSoLinger(on, timeout); |
| } |
| |
| @Override public void setSoTimeout(int timeout) throws SocketException { |
| delegate.setSoTimeout(timeout); |
| } |
| |
| @Override public void setTcpNoDelay(boolean on) throws SocketException { |
| delegate.setTcpNoDelay(on); |
| } |
| |
| @Override public String toString() { |
| return delegate.toString(); |
| } |
| |
| @Override public SocketAddress getLocalSocketAddress() { |
| return delegate.getLocalSocketAddress(); |
| } |
| |
| @Override public SocketAddress getRemoteSocketAddress() { |
| return delegate.getRemoteSocketAddress(); |
| } |
| |
| @Override public boolean isBound() { |
| return delegate.isBound(); |
| } |
| |
| @Override public boolean isConnected() { |
| return delegate.isConnected(); |
| } |
| |
| @Override public boolean isClosed() { |
| return delegate.isClosed(); |
| } |
| |
| @Override public void bind(SocketAddress localAddr) throws IOException { |
| delegate.bind(localAddr); |
| } |
| |
| @Override public void connect(SocketAddress remoteAddr) throws IOException { |
| delegate.connect(remoteAddr); |
| } |
| |
| @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException { |
| delegate.connect(remoteAddr, timeout); |
| } |
| |
| @Override public boolean isInputShutdown() { |
| return delegate.isInputShutdown(); |
| } |
| |
| @Override public boolean isOutputShutdown() { |
| return delegate.isOutputShutdown(); |
| } |
| |
| @Override public void setReuseAddress(boolean reuse) throws SocketException { |
| delegate.setReuseAddress(reuse); |
| } |
| |
| @Override public boolean getReuseAddress() throws SocketException { |
| return delegate.getReuseAddress(); |
| } |
| |
| @Override public void setOOBInline(boolean oobinline) throws SocketException { |
| delegate.setOOBInline(oobinline); |
| } |
| |
| @Override public boolean getOOBInline() throws SocketException { |
| return delegate.getOOBInline(); |
| } |
| |
| @Override public void setTrafficClass(int value) throws SocketException { |
| delegate.setTrafficClass(value); |
| } |
| |
| @Override public int getTrafficClass() throws SocketException { |
| return delegate.getTrafficClass(); |
| } |
| |
| @Override public void sendUrgentData(int value) throws IOException { |
| delegate.sendUrgentData(value); |
| } |
| |
| @Override public SocketChannel getChannel() { |
| return delegate.getChannel(); |
| } |
| |
| @Override public void setPerformancePreferences(int connectionTime, int latency, |
| int bandwidth) { |
| delegate.setPerformancePreferences(connectionTime, latency, bandwidth); |
| } |
| } |
| |
| /** |
| * An SSLSocketFactory that delegates calls. It keeps a record of any sockets created. |
| * If {@link #disableTlsFallbackScsv} is set to {@code true} then sockets created by the |
| * delegate are wrapped with ones that will not accept the {@link #TLS_FALLBACK_SCSV} cipher, |
| * thus bypassing server-side fallback checks on platforms that support it. Unfortunately this |
| * wrapping will disable any reflection-based calls to SSLSocket from Platform. |
| */ |
| private static class FallbackTestClientSocketFactory extends DelegatingSSLSocketFactory { |
| /** |
| * The cipher suite used during TLS connection fallback to indicate a fallback. |
| * See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 |
| */ |
| public static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"; |
| |
| private final boolean disableTlsFallbackScsv; |
| private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>(); |
| |
| public FallbackTestClientSocketFactory(SSLSocketFactory delegate, |
| boolean disableTlsFallbackScsv) { |
| super(delegate); |
| this.disableTlsFallbackScsv = disableTlsFallbackScsv; |
| } |
| |
| @Override public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) |
| throws IOException { |
| SSLSocket socket = super.createSocket(s, host, port, autoClose); |
| if (disableTlsFallbackScsv) { |
| socket = new TlsFallbackDisabledScsvSSLSocket(socket); |
| } |
| createdSockets.add(socket); |
| return socket; |
| } |
| |
| @Override public SSLSocket createSocket() throws IOException { |
| SSLSocket socket = super.createSocket(); |
| if (disableTlsFallbackScsv) { |
| socket = new TlsFallbackDisabledScsvSSLSocket(socket); |
| } |
| createdSockets.add(socket); |
| return socket; |
| } |
| |
| @Override public SSLSocket createSocket(String host,int port) throws IOException { |
| SSLSocket socket = super.createSocket(host, port); |
| if (disableTlsFallbackScsv) { |
| socket = new TlsFallbackDisabledScsvSSLSocket(socket); |
| } |
| createdSockets.add(socket); |
| return socket; |
| } |
| |
| @Override public SSLSocket createSocket(String host,int port, InetAddress localHost, |
| int localPort) throws IOException { |
| SSLSocket socket = super.createSocket(host, port, localHost, localPort); |
| if (disableTlsFallbackScsv) { |
| socket = new TlsFallbackDisabledScsvSSLSocket(socket); |
| } |
| createdSockets.add(socket); |
| return socket; |
| } |
| |
| @Override public SSLSocket createSocket(InetAddress host,int port) throws IOException { |
| SSLSocket socket = super.createSocket(host, port); |
| if (disableTlsFallbackScsv) { |
| socket = new TlsFallbackDisabledScsvSSLSocket(socket); |
| } |
| createdSockets.add(socket); |
| return socket; |
| } |
| |
| @Override public SSLSocket createSocket(InetAddress address,int port, |
| InetAddress localAddress, int localPort) throws IOException { |
| SSLSocket socket = super.createSocket(address, port, localAddress, localPort); |
| if (disableTlsFallbackScsv) { |
| socket = new TlsFallbackDisabledScsvSSLSocket(socket); |
| } |
| createdSockets.add(socket); |
| return socket; |
| } |
| |
| public List<SSLSocket> getCreatedSockets() { |
| return createdSockets; |
| } |
| } |
| |
| private static class TlsFallbackDisabledScsvSSLSocket extends DelegatingSSLSocket { |
| |
| private boolean tlsFallbackScsvSet; |
| |
| public TlsFallbackDisabledScsvSSLSocket(SSLSocket socket) { |
| super(socket); |
| } |
| |
| @Override public void setEnabledCipherSuites(String[] suites) { |
| List<String> enabledCipherSuites = new ArrayList<String>(suites.length); |
| for (String suite : suites) { |
| if (suite.equals(FallbackTestClientSocketFactory.TLS_FALLBACK_SCSV)) { |
| // Record that an attempt was made to set TLS_FALLBACK_SCSV, but don't actually |
| // set it. |
| tlsFallbackScsvSet = true; |
| } else { |
| enabledCipherSuites.add(suite); |
| } |
| } |
| delegate.setEnabledCipherSuites( |
| enabledCipherSuites.toArray(new String[enabledCipherSuites.size()])); |
| } |
| |
| public boolean wasTlsFallbackScsvSet() { |
| return tlsFallbackScsvSet; |
| } |
| } |
| } |