1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.net;
18
19import com.google.mockwebserver.Dispatcher;
20import com.google.mockwebserver.MockResponse;
21import com.google.mockwebserver.MockWebServer;
22import com.google.mockwebserver.RecordedRequest;
23import com.google.mockwebserver.SocketPolicy;
24
25import com.android.okhttp.AndroidShimResponseCache;
26import com.android.okhttp.internal.Platform;
27import com.android.okhttp.internal.tls.TrustRootIndex;
28
29import junit.framework.TestCase;
30
31import java.io.ByteArrayOutputStream;
32import java.io.File;
33import java.io.FileDescriptor;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.OutputStream;
37import java.net.Authenticator;
38import java.net.CacheRequest;
39import java.net.CacheResponse;
40import java.net.CookieHandler;
41import java.net.CookieManager;
42import java.net.HttpRetryException;
43import java.net.HttpURLConnection;
44import java.net.InetAddress;
45import java.net.PasswordAuthentication;
46import java.net.ProtocolException;
47import java.net.Proxy;
48import java.net.ProxySelector;
49import java.net.ResponseCache;
50import java.net.Socket;
51import java.net.SocketAddress;
52import java.net.SocketException;
53import java.net.SocketTimeoutException;
54import java.net.URI;
55import java.net.URISyntaxException;
56import java.net.URL;
57import java.net.URLConnection;
58import java.net.UnknownHostException;
59import java.nio.channels.SocketChannel;
60import java.security.cert.CertificateException;
61import java.security.cert.X509Certificate;
62import java.util.ArrayList;
63import java.util.Arrays;
64import java.util.Collections;
65import java.util.HashSet;
66import java.util.Iterator;
67import java.util.List;
68import java.util.Map;
69import java.util.Set;
70import java.util.UUID;
71import java.util.concurrent.TimeUnit;
72import java.util.concurrent.atomic.AtomicBoolean;
73import java.util.concurrent.atomic.AtomicReference;
74import java.util.zip.GZIPInputStream;
75import java.util.zip.GZIPOutputStream;
76import javax.net.SocketFactory;
77import javax.net.ssl.HandshakeCompletedListener;
78import javax.net.ssl.HostnameVerifier;
79import javax.net.ssl.HttpsURLConnection;
80import javax.net.ssl.SSLContext;
81import javax.net.ssl.SSLException;
82import javax.net.ssl.SSLHandshakeException;
83import javax.net.ssl.SSLParameters;
84import javax.net.ssl.SSLSession;
85import javax.net.ssl.SSLSocket;
86import javax.net.ssl.SSLSocketFactory;
87import javax.net.ssl.TrustManager;
88import javax.net.ssl.X509TrustManager;
89import libcore.java.security.TestKeyStore;
90import libcore.javax.net.ssl.TestSSLContext;
91
92import tests.net.DelegatingSocketFactory;
93
94import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
95import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
96import static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
97import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
98import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
99import static org.junit.Assert.assertEquals;
100import static org.junit.Assert.fail;
101
102public final class URLConnectionTest extends TestCase {
103
104    private MockWebServer server;
105    private AndroidShimResponseCache cache;
106    private String hostName;
107    private List<TestSSLContext> testSSLContextsToClose;
108
109    @Override protected void setUp() throws Exception {
110        super.setUp();
111        server = new MockWebServer();
112        hostName = server.getHostName();
113        testSSLContextsToClose = new ArrayList<>();
114    }
115
116    @Override protected void tearDown() throws Exception {
117        ResponseCache.setDefault(null);
118        Authenticator.setDefault(null);
119        System.clearProperty("proxyHost");
120        System.clearProperty("proxyPort");
121        System.clearProperty("http.proxyHost");
122        System.clearProperty("http.proxyPort");
123        System.clearProperty("https.proxyHost");
124        System.clearProperty("https.proxyPort");
125        server.shutdown();
126        server = null;
127        if (cache != null) {
128            cache.delete();
129            cache = null;
130        }
131        for (TestSSLContext testSSLContext : testSSLContextsToClose) {
132            testSSLContext.close();
133        }
134        super.tearDown();
135    }
136
137    public void testRequestHeaderValidation() throws Exception {
138        // Android became more strict after M about which characters were allowed in request header
139        // names and values: previously almost anything was allowed if it didn't contain \0.
140
141        assertForbiddenRequestHeaderName(null);
142        assertForbiddenRequestHeaderName("");
143        assertForbiddenRequestHeaderName("\n");
144        assertForbiddenRequestHeaderName("a\nb");
145        assertForbiddenRequestHeaderName("\u0000");
146        assertForbiddenRequestHeaderName("\r");
147        assertForbiddenRequestHeaderName("\t");
148        assertForbiddenRequestHeaderName("\u001f");
149        assertForbiddenRequestHeaderName("\u007f");
150        assertForbiddenRequestHeaderName("\u0080");
151        assertForbiddenRequestHeaderName("\ud83c\udf69");
152
153        assertEquals(null, setAndReturnRequestHeaderValue(null));
154        assertEquals("", setAndReturnRequestHeaderValue(""));
155        assertForbiddenRequestHeaderValue("\u0000");
156
157        // Workaround for http://b/26422335 , http://b/26889631 , http://b/27606665 :
158        // allow (but strip) trailing \n, \r and \r\n
159        // assertForbiddenRequestHeaderValue("\r");
160        // End of workaround
161
162        // '\t' in header values can either be (a) forbidden or (b) allowed.
163        // The original version of Android N (API 23) implemented behavior
164        // (a), but OEMs can backport a fix that changes the behavior to (b).
165        // Therefore, this test has been relaxed for Android N CTS to allow
166        // either behavior. It is planned that future versions of Android only
167        // allow behavior (b).
168        try {
169            // throws IAE in case (a), passes in case (b)
170            assertEquals("a valid\tvalue", setAndReturnRequestHeaderValue("a valid\tvalue"));
171        } catch (IllegalArgumentException tolerated) {
172            // verify case (a)
173            assertForbiddenRequestHeaderValue("\t");
174        }
175        assertForbiddenRequestHeaderValue("\u001f");
176        assertForbiddenRequestHeaderValue("\u007f");
177
178        // For http://b/28867041 the allowed character range was changed.
179        // assertForbiddenRequestHeaderValue("\u0080");
180        // assertForbiddenRequestHeaderValue("\ud83c\udf69");
181        assertEquals("\u0080", setAndReturnRequestHeaderValue("\u0080"));
182        assertEquals("\ud83c\udf69", setAndReturnRequestHeaderValue("\ud83c\udf69"));
183
184        // Workaround for http://b/26422335 , http://b/26889631 , http://b/27606665 :
185        // allow (but strip) trailing \n, \r and \r\n
186        assertEquals("", setAndReturnRequestHeaderValue("\n"));
187        assertEquals("a", setAndReturnRequestHeaderValue("a\n"));
188        assertEquals("", setAndReturnRequestHeaderValue("\r"));
189        assertEquals("a", setAndReturnRequestHeaderValue("a\r"));
190        assertEquals("", setAndReturnRequestHeaderValue("\r\n"));
191        assertEquals("a", setAndReturnRequestHeaderValue("a\r\n"));
192        assertForbiddenRequestHeaderValue("a\nb");
193        assertForbiddenRequestHeaderValue("\nb");
194        assertForbiddenRequestHeaderValue("a\rb");
195        assertForbiddenRequestHeaderValue("\rb");
196        assertForbiddenRequestHeaderValue("a\r\nb");
197        assertForbiddenRequestHeaderValue("\r\nb");
198        // End of workaround
199    }
200
201    private static void assertForbiddenRequestHeaderName(String name) throws Exception {
202        URL url = new URL("http://www.google.com/");
203        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
204        try {
205            urlConnection.addRequestProperty(name, "value");
206            fail("Expected exception");
207        } catch (IllegalArgumentException expected) {
208        } catch (NullPointerException expectedIfNull) {
209            assertTrue(name == null);
210        }
211    }
212
213    private static void assertForbiddenRequestHeaderValue(String value) throws Exception {
214        URL url = new URL("http://www.google.com/");
215        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
216        try {
217            urlConnection.addRequestProperty("key", value);
218            fail("Expected exception");
219        } catch (IllegalArgumentException expected) {
220        }
221    }
222
223    private static String setAndReturnRequestHeaderValue(String value) throws Exception {
224        URL url = new URL("http://www.google.com/");
225        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
226        urlConnection.addRequestProperty("key", value);
227        return urlConnection.getRequestProperty("key");
228    }
229
230    public void testRequestHeaders() throws IOException, InterruptedException {
231        server.enqueue(new MockResponse());
232        server.play();
233
234        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
235        urlConnection.addRequestProperty("D", "e");
236        urlConnection.addRequestProperty("D", "f");
237        assertEquals("f", urlConnection.getRequestProperty("D"));
238        assertEquals("f", urlConnection.getRequestProperty("d"));
239        Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties();
240        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D")));
241        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d")));
242        try {
243            requestHeaders.put("G", Arrays.asList("h"));
244            fail("Modified an unmodifiable view.");
245        } catch (UnsupportedOperationException expected) {
246        }
247        try {
248            requestHeaders.get("D").add("i");
249            fail("Modified an unmodifiable view.");
250        } catch (UnsupportedOperationException expected) {
251        }
252        try {
253            urlConnection.setRequestProperty(null, "j");
254            fail();
255        } catch (NullPointerException expected) {
256        }
257        try {
258            urlConnection.addRequestProperty(null, "k");
259            fail();
260        } catch (NullPointerException expected) {
261        }
262        urlConnection.setRequestProperty("NullValue", null); // should fail silently!
263        assertNull(urlConnection.getRequestProperty("NullValue"));
264        urlConnection.addRequestProperty("AnotherNullValue", null);  // should fail silently!
265        assertNull(urlConnection.getRequestProperty("AnotherNullValue"));
266
267        urlConnection.getResponseCode();
268        RecordedRequest request = server.takeRequest();
269        assertContains(request.getHeaders(), "D: e");
270        assertContains(request.getHeaders(), "D: f");
271        assertContainsNoneMatching(request.getHeaders(), "NullValue.*");
272        assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*");
273        assertContainsNoneMatching(request.getHeaders(), "G:.*");
274        assertContainsNoneMatching(request.getHeaders(), "null:.*");
275
276        try {
277            urlConnection.addRequestProperty("N", "o");
278            fail("Set header after connect");
279        } catch (IllegalStateException expected) {
280        }
281        try {
282            urlConnection.setRequestProperty("P", "q");
283            fail("Set header after connect");
284        } catch (IllegalStateException expected) {
285        }
286        try {
287            urlConnection.getRequestProperties();
288            fail();
289        } catch (IllegalStateException expected) {
290        }
291    }
292
293    public void testGetRequestPropertyReturnsLastValue() throws Exception {
294        server.play();
295        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
296        urlConnection.addRequestProperty("A", "value1");
297        urlConnection.addRequestProperty("A", "value2");
298        assertEquals("value2", urlConnection.getRequestProperty("A"));
299    }
300
301    public void testResponseHeaders() throws IOException, InterruptedException {
302        server.enqueue(new MockResponse()
303                .setStatus("HTTP/1.0 200 Fantastic")
304                .addHeader("A: c")
305                .addHeader("B: d")
306                .addHeader("A: e")
307                .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
308        server.play();
309
310        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
311        assertEquals(200, urlConnection.getResponseCode());
312        assertEquals("Fantastic", urlConnection.getResponseMessage());
313        assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null));
314        Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields();
315        assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null));
316        assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A")));
317        assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a")));
318        try {
319            responseHeaders.put("N", Arrays.asList("o"));
320            fail("Modified an unmodifiable view.");
321        } catch (UnsupportedOperationException expected) {
322        }
323        try {
324            responseHeaders.get("A").add("f");
325            fail("Modified an unmodifiable view.");
326        } catch (UnsupportedOperationException expected) {
327        }
328        assertEquals("A", urlConnection.getHeaderFieldKey(0));
329        assertEquals("c", urlConnection.getHeaderField(0));
330        assertEquals("B", urlConnection.getHeaderFieldKey(1));
331        assertEquals("d", urlConnection.getHeaderField(1));
332        assertEquals("A", urlConnection.getHeaderFieldKey(2));
333        assertEquals("e", urlConnection.getHeaderField(2));
334    }
335
336    public void testGetErrorStreamOnSuccessfulRequest() throws Exception {
337        server.enqueue(new MockResponse().setBody("A"));
338        server.play();
339        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
340        assertNull(connection.getErrorStream());
341    }
342
343    public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception {
344        server.enqueue(new MockResponse().setResponseCode(404).setBody("A"));
345        server.play();
346        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
347        assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE));
348    }
349
350    // Check that if we don't read to the end of a response, the next request on the
351    // recycled connection doesn't get the unread tail of the first request's response.
352    // http://code.google.com/p/android/issues/detail?id=2939
353    public void test_2939() throws Exception {
354        MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8);
355
356        server.enqueue(response);
357        server.enqueue(response);
358        server.play();
359
360        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
361        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
362    }
363
364    // Check that we recognize a few basic mime types by extension.
365    // http://code.google.com/p/android/issues/detail?id=10100
366    public void test_10100() throws Exception {
367        assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg"));
368        assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf"));
369    }
370
371    public void testConnectionsArePooled() throws Exception {
372        MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR");
373
374        server.enqueue(response);
375        server.enqueue(response);
376        server.enqueue(response);
377        server.play();
378
379        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection());
380        assertEquals(0, server.takeRequest().getSequenceNumber());
381        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection());
382        assertEquals(1, server.takeRequest().getSequenceNumber());
383        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection());
384        assertEquals(2, server.takeRequest().getSequenceNumber());
385    }
386
387    public void testChunkedConnectionsArePooled() throws Exception {
388        MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5);
389
390        server.enqueue(response);
391        server.enqueue(response);
392        server.enqueue(response);
393        server.play();
394
395        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection());
396        assertEquals(0, server.takeRequest().getSequenceNumber());
397        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection());
398        assertEquals(1, server.takeRequest().getSequenceNumber());
399        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection());
400        assertEquals(2, server.takeRequest().getSequenceNumber());
401    }
402
403    /**
404     * Test that connections are added to the pool as soon as the response has
405     * been consumed.
406     */
407    public void testConnectionsArePooledWithoutExplicitDisconnect() throws Exception {
408        server.enqueue(new MockResponse().setBody("ABC"));
409        server.enqueue(new MockResponse().setBody("DEF"));
410        server.play();
411
412        URLConnection connection1 = server.getUrl("/").openConnection();
413        assertEquals("ABC", readAscii(connection1.getInputStream(), Integer.MAX_VALUE));
414        assertEquals(0, server.takeRequest().getSequenceNumber());
415        URLConnection connection2 = server.getUrl("/").openConnection();
416        assertEquals("DEF", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
417        assertEquals(1, server.takeRequest().getSequenceNumber());
418    }
419
420    public void testServerClosesSocket() throws Exception {
421        testServerClosesSocket(DISCONNECT_AT_END);
422    }
423
424    public void testServerShutdownInput() throws Exception {
425        testServerClosesSocket(SHUTDOWN_INPUT_AT_END);
426    }
427
428    private void testServerClosesSocket(SocketPolicy socketPolicy) throws Exception {
429        server.enqueue(new MockResponse()
430                .setBody("This connection won't pool properly")
431                .setSocketPolicy(socketPolicy));
432        server.enqueue(new MockResponse().setBody("This comes after a busted connection"));
433        server.play();
434
435        assertContent("This connection won't pool properly", server.getUrl("/a").openConnection());
436        assertEquals(0, server.takeRequest().getSequenceNumber());
437        assertContent("This comes after a busted connection", server.getUrl("/b").openConnection());
438        // sequence number 0 means the HTTP socket connection was not reused
439        assertEquals(0, server.takeRequest().getSequenceNumber());
440    }
441
442    public void testServerShutdownOutput() throws Exception {
443        // This test causes MockWebServer to log a "connection failed" stack trace
444
445        // Setting the server workerThreads to 1 ensures the responses are generated in the order
446        // the requests are accepted by the server. Without this the second and third requests made
447        // by the client (the request for "/b" and the retry for "/b" when the bad socket is
448        // detected) can be handled by the server out of order leading to test failure.
449        server.setWorkerThreads(1);
450        server.enqueue(new MockResponse()
451                .setBody("Output shutdown after this response")
452                .setSocketPolicy(SHUTDOWN_OUTPUT_AT_END));
453        server.enqueue(new MockResponse().setBody("This response will fail to write"));
454        server.enqueue(new MockResponse().setBody("This comes after a busted connection"));
455        server.play();
456
457        assertContent("Output shutdown after this response", server.getUrl("/a").openConnection());
458        assertEquals(0, server.takeRequest().getSequenceNumber());
459        assertContent("This comes after a busted connection", server.getUrl("/b").openConnection());
460        assertEquals(1, server.takeRequest().getSequenceNumber());
461        assertEquals(0, server.takeRequest().getSequenceNumber());
462    }
463
464    enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
465
466    public void test_chunkedUpload_byteByByte() throws Exception {
467        doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
468    }
469
470    public void test_chunkedUpload_smallBuffers() throws Exception {
471        doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
472    }
473
474    public void test_chunkedUpload_largeBuffers() throws Exception {
475        doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
476    }
477
478    public void test_fixedLengthUpload_byteByByte() throws Exception {
479        doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
480    }
481
482    public void test_fixedLengthUpload_smallBuffers() throws Exception {
483        doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
484    }
485
486    public void test_fixedLengthUpload_largeBuffers() throws Exception {
487        doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
488    }
489
490    private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception {
491        int n = 512*1024;
492        server.setBodyLimit(0);
493        server.enqueue(new MockResponse());
494        server.play();
495
496        HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection();
497        conn.setDoOutput(true);
498        conn.setRequestMethod("POST");
499        if (uploadKind == TransferKind.CHUNKED) {
500            conn.setChunkedStreamingMode(-1);
501        } else {
502            conn.setFixedLengthStreamingMode(n);
503        }
504        OutputStream out = conn.getOutputStream();
505        if (writeKind == WriteKind.BYTE_BY_BYTE) {
506            for (int i = 0; i < n; ++i) {
507                out.write('x');
508            }
509        } else {
510            byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024];
511            Arrays.fill(buf, (byte) 'x');
512            for (int i = 0; i < n; i += buf.length) {
513                out.write(buf, 0, Math.min(buf.length, n - i));
514            }
515        }
516        out.close();
517        assertEquals(200, conn.getResponseCode());
518        RecordedRequest request = server.takeRequest();
519        assertEquals(n, request.getBodySize());
520        if (uploadKind == TransferKind.CHUNKED) {
521            assertTrue(request.getChunkSizes().size() > 0);
522        } else {
523            assertTrue(request.getChunkSizes().isEmpty());
524        }
525    }
526
527    public void testGetResponseCodeNoResponseBody() throws Exception {
528        server.enqueue(new MockResponse()
529                .addHeader("abc: def"));
530        server.play();
531
532        URL url = server.getUrl("/");
533        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
534        conn.setDoInput(false);
535        assertEquals("def", conn.getHeaderField("abc"));
536        assertEquals(200, conn.getResponseCode());
537        try {
538            conn.getInputStream();
539            fail();
540        } catch (ProtocolException expected) {
541        }
542    }
543
544    public void testConnectViaHttps() throws IOException, InterruptedException {
545        TestSSLContext testSSLContext = createDefaultTestSSLContext();
546
547        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
548        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
549        server.play();
550
551        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
552        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
553
554        assertContent("this response comes via HTTPS", connection);
555
556        RecordedRequest request = server.takeRequest();
557        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
558        assertEquals("TLSv1.2", request.getSslProtocol());
559    }
560
561    public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
562        TestSSLContext testSSLContext = createDefaultTestSSLContext();
563        SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory();
564
565        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
566        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
567        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
568        server.play();
569
570        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
571        connection.setSSLSocketFactory(clientSocketFactory);
572        assertContent("this response comes via HTTPS", connection);
573
574        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
575        connection.setSSLSocketFactory(clientSocketFactory);
576        assertContent("another response via HTTPS", connection);
577
578        assertEquals(0, server.takeRequest().getSequenceNumber());
579        assertEquals(1, server.takeRequest().getSequenceNumber());
580    }
581
582    public void testConnectViaHttpsReusingConnectionsDifferentFactories()
583            throws IOException, InterruptedException {
584        TestSSLContext testSSLContext = createDefaultTestSSLContext();
585
586        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
587        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
588        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
589        server.play();
590
591        // install a custom SSL socket factory so the server can be authorized
592        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
593        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
594        assertContent("this response comes via HTTPS", connection);
595
596        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
597        try {
598            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
599            fail("without an SSL socket factory, the connection should fail");
600        } catch (SSLException expected) {
601        }
602    }
603
604    /**
605     * Verify that we don't retry connections on certificate verification errors.
606     *
607     * http://code.google.com/p/android/issues/detail?id=13178
608     */
609    public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
610        TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(),
611                                                              TestKeyStore.getServer());
612        testSSLContextsToClose.add(testSSLContext);
613
614        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
615        server.enqueue(new MockResponse()); // unused
616        server.play();
617
618        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
619        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
620        try {
621            connection.getInputStream();
622            fail();
623        } catch (SSLHandshakeException expected) {
624            assertTrue(expected.getCause() instanceof CertificateException);
625        }
626        assertEquals(0, server.getRequestCount());
627    }
628
629    public void testConnectViaProxy_emptyPath() throws Exception {
630        // expected normalization http://android -> http://android/ per b/30107354
631        checkConnectViaProxy(
632                ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY, "http://android.com",
633                "http://android.com/", "android.com");
634    }
635
636    public void testConnectViaProxy_complexUrlWithNoPath() throws Exception {
637        checkConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY,
638                "http://android.com:8080?height=100&width=42",
639                "http://android.com:8080/?height=100&width=42",
640                "android.com:8080");
641    }
642
643    public void testConnectViaProxyUsingProxyArg() throws Exception {
644        checkConnectViaProxy(ProxyConfig.CREATE_ARG);
645    }
646
647    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
648        checkConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
649    }
650
651    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
652        checkConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
653    }
654
655    private void checkConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
656        checkConnectViaProxy(proxyConfig,
657                "http://android.com/foo", "http://android.com/foo", "android.com");
658    }
659
660    private void checkConnectViaProxy(ProxyConfig proxyConfig, String urlString,
661            String expectedUrlInRequestLine, String expectedHost) throws Exception {
662        MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
663        server.enqueue(mockResponse);
664        server.play();
665
666        URL url = new URL(urlString);
667        HttpURLConnection connection = proxyConfig.connect(server, url);
668        assertContent("this response comes via a proxy", connection);
669
670        RecordedRequest request = server.takeRequest();
671        assertEquals("GET " + expectedUrlInRequestLine + " HTTP/1.1", request.getRequestLine());
672        assertContains(request.getHeaders(), "Host: " + expectedHost);
673    }
674
675    public void testContentDisagreesWithContentLengthHeader() throws IOException {
676        server.enqueue(new MockResponse()
677                .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
678                .clearHeaders()
679                .addHeader("Content-Length: 3"));
680        server.play();
681
682        assertContent("abc", server.getUrl("/").openConnection());
683    }
684
685    public void testContentDisagreesWithChunkedHeader() throws IOException {
686        MockResponse mockResponse = new MockResponse();
687        mockResponse.setChunkedBody("abc", 3);
688        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
689        bytesOut.write(mockResponse.getBody());
690        bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes());
691        mockResponse.setBody(bytesOut.toByteArray());
692        mockResponse.clearHeaders();
693        mockResponse.addHeader("Transfer-encoding: chunked");
694
695        server.enqueue(mockResponse);
696        server.play();
697
698        assertContent("abc", server.getUrl("/").openConnection());
699    }
700
701    public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
702        testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
703    }
704
705    public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
706        // https should not use http proxy
707        testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
708    }
709
710    private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception {
711        TestSSLContext testSSLContext = createDefaultTestSSLContext();
712
713        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
714        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
715        server.play();
716
717        URL url = server.getUrl("/foo");
718        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
719        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
720
721        assertContent("this response comes via HTTPS", connection);
722
723        RecordedRequest request = server.takeRequest();
724        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
725    }
726
727
728    public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
729        testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
730    }
731
732    /**
733     * We weren't honoring all of the appropriate proxy system properties when
734     * connecting via HTTPS. http://b/3097518
735     */
736    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
737        testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
738    }
739
740    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
741        testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
742    }
743
744    /**
745     * We were verifying the wrong hostname when connecting to an HTTPS site
746     * through a proxy. http://b/3097277
747     */
748    private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
749        TestSSLContext testSSLContext = createDefaultTestSSLContext();
750        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
751
752        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
753        server.enqueue(new MockResponse()
754                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
755                .clearHeaders());
756        server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
757        server.play();
758
759        URL url = new URL("https://android.com/foo");
760        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
761        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
762        connection.setHostnameVerifier(hostnameVerifier);
763
764        assertContent("this response comes via a secure proxy", connection);
765
766        RecordedRequest connect = server.takeRequest();
767        assertEquals("Connect line failure on proxy",
768                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
769        assertContains(connect.getHeaders(), "Host: android.com:443");
770
771        RecordedRequest get = server.takeRequest();
772        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
773        assertContains(get.getHeaders(), "Host: android.com");
774        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
775    }
776
777
778    /**
779     * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912
780     */
781    public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception {
782        TestSSLContext testSSLContext = createDefaultTestSSLContext();
783
784        initResponseCache();
785
786        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
787
788        // The inclusion of a body in the response to the CONNECT is key to reproducing b/6754912.
789        MockResponse badProxyResponse = new MockResponse()
790                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
791                .clearHeaders()
792                .setBody("bogus proxy connect response content");
793
794        server.enqueue(badProxyResponse);
795        server.enqueue(new MockResponse().setBody("response"));
796
797        server.play();
798
799        URL url = new URL("https://android.com/foo");
800        ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY;
801        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
802        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
803        connection.setHostnameVerifier(new RecordingHostnameVerifier());
804        assertContent("response", connection);
805
806        RecordedRequest connect = server.takeRequest();
807        assertEquals("CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
808        assertContains(connect.getHeaders(), "Host: android.com:443");
809    }
810
811    private void initResponseCache() throws IOException {
812        String tmp = System.getProperty("java.io.tmpdir");
813        File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
814        cache = AndroidShimResponseCache.create(cacheDir, Integer.MAX_VALUE);
815        ResponseCache.setDefault(cache);
816    }
817
818    /**
819     * Test Etag headers are returned correctly when a client-side cache is not installed.
820     * https://code.google.com/p/android/issues/detail?id=108949
821     */
822    public void testEtagHeaders_uncached() throws Exception {
823        final String etagValue1 = "686897696a7c876b7e";
824        final String body1 = "Response with etag 1";
825        final String etagValue2 = "686897696a7c876b7f";
826        final String body2 = "Response with etag 2";
827
828        server.enqueue(
829            new MockResponse()
830                .setBody(body1)
831                .setHeader("Content-Type", "text/plain")
832                .setHeader("Etag", etagValue1));
833        server.enqueue(
834            new MockResponse()
835                .setBody(body2)
836                .setHeader("Content-Type", "text/plain")
837                .setHeader("Etag", etagValue2));
838        server.play();
839
840        URL url = server.getUrl("/");
841        HttpURLConnection connection1 = (HttpURLConnection) url.openConnection();
842        assertEquals(etagValue1, connection1.getHeaderField("Etag"));
843        assertContent(body1, connection1);
844        connection1.disconnect();
845
846        // Discard the server-side record of the request made.
847        server.takeRequest();
848
849        HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
850        assertEquals(etagValue2, connection2.getHeaderField("Etag"));
851        assertContent(body2, connection2);
852        connection2.disconnect();
853
854        // Check the client did not cache.
855        RecordedRequest request = server.takeRequest();
856        assertNull(request.getHeader("If-None-Match"));
857    }
858
859    /**
860     * Test Etag headers are returned correctly when a client-side cache is installed and the server
861     * data is unchanged.
862     * https://code.google.com/p/android/issues/detail?id=108949
863     */
864    public void testEtagHeaders_cachedWithServerHit() throws Exception {
865        final String etagValue = "686897696a7c876b7e";
866        final String body = "Response with etag";
867
868        server.enqueue(
869            new MockResponse()
870                .setBody(body)
871                .setResponseCode(HttpURLConnection.HTTP_OK)
872                .setHeader("Content-Type", "text/plain")
873                .setHeader("Etag", etagValue));
874
875        server.enqueue(
876            new MockResponse()
877                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
878        server.play();
879
880        initResponseCache();
881
882        URL url = server.getUrl("/");
883        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
884        assertEquals(etagValue, connection.getHeaderField("Etag"));
885        assertContent(body, connection);
886        connection.disconnect();
887
888        // Discard the server-side record of the request made.
889        server.takeRequest();
890
891        // Confirm the cached body is returned along with the original etag header.
892        HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection();
893        assertEquals(etagValue, cachedConnection.getHeaderField("Etag"));
894        assertContent(body, cachedConnection);
895        cachedConnection.disconnect();
896
897        // Check the client formatted the request correctly.
898        RecordedRequest request = server.takeRequest();
899        assertEquals(etagValue, request.getHeader("If-None-Match"));
900    }
901
902    /**
903     * Test Etag headers are returned correctly when a client-side cache is installed and the server
904     * data has changed.
905     * https://code.google.com/p/android/issues/detail?id=108949
906     */
907    public void testEtagHeaders_cachedWithServerMiss() throws Exception {
908        final String etagValue1 = "686897696a7c876b7e";
909        final String body1 = "Response with etag 1";
910        final String etagValue2 = "686897696a7c876b7f";
911        final String body2 = "Response with etag 2";
912
913        server.enqueue(
914            new MockResponse()
915                .setBody(body1)
916                .setResponseCode(HttpURLConnection.HTTP_OK)
917                .setHeader("Content-Type", "text/plain")
918                .setHeader("Etag", etagValue1));
919
920        server.enqueue(
921            new MockResponse()
922                .setBody(body2)
923                .setResponseCode(HttpURLConnection.HTTP_OK)
924                .setHeader("Content-Type", "text/plain")
925                .setHeader("Etag", etagValue2));
926
927        server.play();
928
929        initResponseCache();
930
931        URL url = server.getUrl("/");
932        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
933        assertEquals(etagValue1, connection.getHeaderField("Etag"));
934        assertContent(body1, connection);
935        connection.disconnect();
936
937        // Discard the server-side record of the request made.
938        server.takeRequest();
939
940        // Confirm the new body is returned along with the new etag header.
941        HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection();
942        assertEquals(etagValue2, cachedConnection.getHeaderField("Etag"));
943        assertContent(body2, cachedConnection);
944        cachedConnection.disconnect();
945
946        // Check the client formatted the request correctly.
947        RecordedRequest request = server.takeRequest();
948        assertEquals(etagValue1, request.getHeader("If-None-Match"));
949    }
950
951    /**
952     * Test which headers are sent unencrypted to the HTTP proxy.
953     */
954    public void testProxyConnectIncludesProxyHeadersOnly()
955            throws IOException, InterruptedException {
956        Authenticator.setDefault(new SimpleAuthenticator());
957        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
958        TestSSLContext testSSLContext = createDefaultTestSSLContext();
959
960        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
961        server.enqueue(new MockResponse()
962                .setResponseCode(407)
963                .addHeader("Proxy-Authenticate: Basic realm=\"localhost\""));
964        server.enqueue(new MockResponse()
965                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
966                .clearHeaders());
967        server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
968        server.play();
969
970        URL url = new URL("https://android.com/foo");
971        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
972                server.toProxyAddress());
973        connection.addRequestProperty("Private", "Secret");
974        connection.addRequestProperty("User-Agent", "baz");
975        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
976        connection.setHostnameVerifier(hostnameVerifier);
977        assertContent("encrypted response from the origin server", connection);
978
979        // connect1 and connect2 are tunnel requests which potentially tunnel multiple requests;
980        // Thus we can't expect its headers to exactly match those of the wrapped request.
981        // See https://github.com/square/okhttp/commit/457fb428a729c50c562822571ea9b13e689648f3
982
983        {
984            RecordedRequest connect1 = server.takeRequest();
985            List<String> headers = connect1.getHeaders();
986            assertContainsNoneMatching(headers, "Private.*");
987            assertContainsNoneMatching(headers, "Proxy\\-Authorization.*");
988            assertHeaderPresent(connect1, "User-Agent");
989            assertContains(headers, "Host: android.com:443");
990            assertContains(headers, "Proxy-Connection: Keep-Alive");
991        }
992
993        {
994            RecordedRequest connect2 = server.takeRequest();
995            List<String> headers = connect2.getHeaders();
996            assertContainsNoneMatching(headers, "Private.*");
997            assertHeaderPresent(connect2, "Proxy-Authorization");
998            assertHeaderPresent(connect2, "User-Agent");
999            assertContains(headers, "Host: android.com:443");
1000            assertContains(headers, "Proxy-Connection: Keep-Alive");
1001        }
1002
1003        RecordedRequest get = server.takeRequest();
1004        assertContains(get.getHeaders(), "Private: Secret");
1005        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
1006    }
1007
1008    public void testProxyAuthenticateOnConnect() throws Exception {
1009        Authenticator.setDefault(new SimpleAuthenticator());
1010        TestSSLContext testSSLContext = createDefaultTestSSLContext();
1011        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
1012        server.enqueue(new MockResponse()
1013                .setResponseCode(407)
1014                .addHeader("Proxy-Authenticate: Basic realm=\"localhost\""));
1015        server.enqueue(new MockResponse()
1016                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
1017                .clearHeaders());
1018        server.enqueue(new MockResponse().setBody("A"));
1019        server.play();
1020
1021        URL url = new URL("https://android.com/foo");
1022        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
1023                server.toProxyAddress());
1024        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1025        connection.setHostnameVerifier(new RecordingHostnameVerifier());
1026        assertContent("A", connection);
1027
1028        RecordedRequest connect1 = server.takeRequest();
1029        assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine());
1030        assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*");
1031
1032        RecordedRequest connect2 = server.takeRequest();
1033        assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine());
1034        assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic "
1035                + SimpleAuthenticator.BASE_64_CREDENTIALS);
1036
1037        RecordedRequest get = server.takeRequest();
1038        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
1039        assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*");
1040    }
1041
1042    // Don't disconnect after building a tunnel with CONNECT
1043    // http://code.google.com/p/android/issues/detail?id=37221
1044    public void testProxyWithConnectionClose() throws IOException {
1045        TestSSLContext testSSLContext = createDefaultTestSSLContext();
1046        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
1047        server.enqueue(new MockResponse()
1048                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
1049                .clearHeaders());
1050        server.enqueue(new MockResponse().setBody("this response comes via a proxy"));
1051        server.play();
1052
1053        URL url = new URL("https://android.com/foo");
1054        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
1055                server.toProxyAddress());
1056        connection.setRequestProperty("Connection", "close");
1057        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1058        connection.setHostnameVerifier(new RecordingHostnameVerifier());
1059
1060        assertContent("this response comes via a proxy", connection);
1061    }
1062
1063    public void testDisconnectedConnection() throws IOException {
1064        server.enqueue(new MockResponse()
1065                .throttleBody(2, 100, TimeUnit.MILLISECONDS)
1066                .setBody("ABCD"));
1067        server.play();
1068
1069        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1070        InputStream in = connection.getInputStream();
1071        assertEquals('A', (char) in.read());
1072        connection.disconnect();
1073        try {
1074            // Reading 'B' may succeed if it's buffered.
1075            in.read();
1076            // But 'C' shouldn't be buffered (the response is throttled) and this should fail.
1077            in.read();
1078            fail("Expected a connection closed exception");
1079        } catch (IOException expected) {
1080        }
1081    }
1082
1083    // http://b/33763156
1084    public void testDisconnectDuringConnect_getInputStream() throws IOException {
1085        checkDisconnectDuringConnect(HttpURLConnection::getInputStream);
1086    }
1087
1088    // http://b/33763156
1089    public void testDisconnectDuringConnect_getOutputStream() throws IOException {
1090        checkDisconnectDuringConnect(HttpURLConnection::getOutputStream);
1091    }
1092
1093    // http://b/33763156
1094    public void testDisconnectDuringConnect_getResponseCode() throws IOException {
1095        checkDisconnectDuringConnect(HttpURLConnection::getResponseCode);
1096    }
1097
1098    // http://b/33763156
1099    public void testDisconnectDuringConnect_getResponseMessage() throws IOException {
1100        checkDisconnectDuringConnect(HttpURLConnection::getResponseMessage);
1101    }
1102
1103    interface ConnectStrategy {
1104        /**
1105         * Causes the given {@code connection}, which was previously disconnected,
1106         * to initiate the connection.
1107         */
1108        void connect(HttpURLConnection connection) throws IOException;
1109    }
1110
1111    // http://b/33763156
1112    private void checkDisconnectDuringConnect(ConnectStrategy connectStrategy) throws IOException {
1113        server.enqueue(new MockResponse().setBody("This should never be sent"));
1114        server.play();
1115
1116        final AtomicReference<HttpURLConnection> connectionHolder = new AtomicReference<>();
1117        class DisconnectingCookieHandler extends CookieManager {
1118            @Override
1119            public Map<String, List<String>> get(URI uri, Map<String, List<String>> map)
1120                    throws IOException {
1121                Map<String, List<String>> result = super.get(uri, map);
1122                connectionHolder.get().disconnect();
1123                return result;
1124            }
1125        }
1126        CookieHandler defaultCookieHandler = CookieHandler.getDefault();
1127        try {
1128            CookieHandler.setDefault(new DisconnectingCookieHandler());
1129            HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1130            connectionHolder.set(connection);
1131            try {
1132                connectStrategy.connect(connection);
1133                fail();
1134            } catch (IOException expected) {
1135                assertEquals("Canceled", expected.getMessage());
1136            } finally {
1137                connection.disconnect();
1138            }
1139        } finally {
1140            CookieHandler.setDefault(defaultCookieHandler);
1141        }
1142    }
1143
1144    public void testDisconnectBeforeConnect() throws IOException {
1145        server.enqueue(new MockResponse().setBody("A"));
1146        server.play();
1147
1148        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1149        connection.disconnect();
1150
1151        assertContent("A", connection);
1152        assertEquals(200, connection.getResponseCode());
1153    }
1154
1155    public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException {
1156        server.enqueue(new MockResponse()
1157                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
1158                .addHeader("Content-Encoding: gzip"));
1159        server.play();
1160
1161        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1162        try {
1163            assertEquals(200, connection.getResponseCode());
1164        } finally {
1165            connection.disconnect();
1166        }
1167    }
1168
1169    public void testDefaultRequestProperty() throws Exception {
1170        URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
1171        assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
1172    }
1173
1174    /**
1175     * Reads {@code count} characters from the stream. If the stream is
1176     * exhausted before {@code count} characters can be read, the remaining
1177     * characters are returned and the stream is closed.
1178     */
1179    private String readAscii(InputStream in, int count) throws IOException {
1180        StringBuilder result = new StringBuilder();
1181        for (int i = 0; i < count; i++) {
1182            int value = in.read();
1183            if (value == -1) {
1184                in.close();
1185                break;
1186            }
1187            result.append((char) value);
1188        }
1189        return result.toString();
1190    }
1191
1192    public void testMarkAndResetWithContentLengthHeader() throws IOException {
1193        testMarkAndReset(TransferKind.FIXED_LENGTH);
1194    }
1195
1196    public void testMarkAndResetWithChunkedEncoding() throws IOException {
1197        testMarkAndReset(TransferKind.CHUNKED);
1198    }
1199
1200    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
1201        testMarkAndReset(TransferKind.END_OF_STREAM);
1202    }
1203
1204    private void testMarkAndReset(TransferKind transferKind) throws IOException {
1205        MockResponse response = new MockResponse();
1206        transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
1207        server.enqueue(response);
1208        server.enqueue(response);
1209        server.play();
1210
1211        InputStream in = server.getUrl("/").openConnection().getInputStream();
1212        assertFalse("This implementation claims to support mark().", in.markSupported());
1213        in.mark(5);
1214        assertEquals("ABCDE", readAscii(in, 5));
1215        try {
1216            in.reset();
1217            fail();
1218        } catch (IOException expected) {
1219        }
1220        assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
1221        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
1222    }
1223
1224    /**
1225     * We've had a bug where we forget the HTTP response when we see response
1226     * code 401. This causes a new HTTP request to be issued for every call into
1227     * the URLConnection.
1228     */
1229    public void testUnauthorizedResponseHandling() throws IOException {
1230        MockResponse response = new MockResponse()
1231                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1232                .setResponseCode(401) // UNAUTHORIZED
1233                .setBody("Unauthorized");
1234        server.enqueue(response);
1235        server.enqueue(response);
1236        server.enqueue(response);
1237        server.play();
1238
1239        URL url = server.getUrl("/");
1240        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1241
1242        assertEquals(401, conn.getResponseCode());
1243        assertEquals(401, conn.getResponseCode());
1244        assertEquals(401, conn.getResponseCode());
1245        assertEquals(1, server.getRequestCount());
1246    }
1247
1248    public void testNonHexChunkSize() throws IOException {
1249        server.enqueue(new MockResponse()
1250                .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
1251                .clearHeaders()
1252                .addHeader("Transfer-encoding: chunked"));
1253        server.play();
1254
1255        URLConnection connection = server.getUrl("/").openConnection();
1256        try {
1257            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1258            fail();
1259        } catch (IOException e) {
1260        }
1261    }
1262
1263    public void testMissingChunkBody() throws IOException {
1264        server.enqueue(new MockResponse()
1265                .setBody("5")
1266                .clearHeaders()
1267                .addHeader("Transfer-encoding: chunked")
1268                .setSocketPolicy(DISCONNECT_AT_END));
1269        server.play();
1270
1271        URLConnection connection = server.getUrl("/").openConnection();
1272        try {
1273            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1274            fail();
1275        } catch (IOException e) {
1276        }
1277    }
1278
1279    /**
1280     * This test checks whether connections are gzipped by default. This
1281     * behavior in not required by the API, so a failure of this test does not
1282     * imply a bug in the implementation.
1283     */
1284    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
1285        server.enqueue(new MockResponse()
1286                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
1287                .addHeader("Content-Encoding: gzip"));
1288        server.play();
1289
1290        URLConnection connection = server.getUrl("/").openConnection();
1291        assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1292        assertNull(connection.getContentEncoding());
1293        assertEquals(-1, connection.getContentLength());
1294
1295        RecordedRequest request = server.takeRequest();
1296        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1297    }
1298
1299    public void testClientConfiguredGzipContentEncoding() throws Exception {
1300        byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"));
1301        server.enqueue(new MockResponse()
1302                .setBody(bodyBytes)
1303                .addHeader("Content-Encoding: gzip")
1304                .addHeader("Content-Length: " + bodyBytes.length));
1305        server.play();
1306
1307        URLConnection connection = server.getUrl("/").openConnection();
1308        connection.addRequestProperty("Accept-Encoding", "gzip");
1309        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1310        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
1311        assertEquals(bodyBytes.length, connection.getContentLength());
1312
1313        RecordedRequest request = server.takeRequest();
1314        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1315        assertEquals("gzip", connection.getContentEncoding());
1316    }
1317
1318    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
1319        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
1320    }
1321
1322    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
1323        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
1324    }
1325
1326    public void testClientConfiguredCustomContentEncoding() throws Exception {
1327        server.enqueue(new MockResponse()
1328                .setBody("ABCDE")
1329                .addHeader("Content-Encoding: custom"));
1330        server.play();
1331
1332        URLConnection connection = server.getUrl("/").openConnection();
1333        connection.addRequestProperty("Accept-Encoding", "custom");
1334        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1335
1336        RecordedRequest request = server.takeRequest();
1337        assertContains(request.getHeaders(), "Accept-Encoding: custom");
1338    }
1339
1340    /**
1341     * Test a bug where gzip input streams weren't exhausting the input stream,
1342     * which corrupted the request that followed.
1343     * http://code.google.com/p/android/issues/detail?id=7059
1344     */
1345    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
1346            TransferKind transferKind) throws Exception {
1347        MockResponse responseOne = new MockResponse();
1348        responseOne.addHeader("Content-Encoding: gzip");
1349        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
1350        server.enqueue(responseOne);
1351        MockResponse responseTwo = new MockResponse();
1352        transferKind.setBody(responseTwo, "two (identity)", 5);
1353        server.enqueue(responseTwo);
1354        server.play();
1355
1356        URLConnection connection = server.getUrl("/").openConnection();
1357        connection.addRequestProperty("Accept-Encoding", "gzip");
1358        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1359        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
1360        assertEquals(0, server.takeRequest().getSequenceNumber());
1361
1362        connection = server.getUrl("/").openConnection();
1363        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1364        assertEquals(1, server.takeRequest().getSequenceNumber());
1365    }
1366
1367    /**
1368     * Test that HEAD requests don't have a body regardless of the response
1369     * headers. http://code.google.com/p/android/issues/detail?id=24672
1370     */
1371    public void testHeadAndContentLength() throws Exception {
1372        server.enqueue(new MockResponse()
1373                .clearHeaders()
1374                .addHeader("Content-Length: 100"));
1375        server.enqueue(new MockResponse().setBody("A"));
1376        server.play();
1377
1378        HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/").openConnection();
1379        connection1.setRequestMethod("HEAD");
1380        assertEquals("100", connection1.getHeaderField("Content-Length"));
1381        assertContent("", connection1);
1382
1383        HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/").openConnection();
1384        assertEquals("A", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
1385
1386        assertEquals(0, server.takeRequest().getSequenceNumber());
1387        assertEquals(1, server.takeRequest().getSequenceNumber());
1388    }
1389
1390    /**
1391     * Test that request body chunking works. This test has been relaxed from treating
1392     * the {@link java.net.HttpURLConnection#setChunkedStreamingMode(int)}
1393     * chunk length as being fixed because OkHttp no longer guarantees
1394     * the fixed chunk size. Instead, we check that chunking takes place
1395     * and we force the chunk size with flushes.
1396     */
1397    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
1398        server.enqueue(new MockResponse());
1399        server.play();
1400
1401        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1402        // Later releases of Android ignore the value for chunkLength if it is > 0 and default to
1403        // a fixed chunkLength. During the change-over period while the chunkLength indicates the
1404        // chunk buffer size (inc. header) the chunkLength has to be >= 8. This enables the flush()
1405        // to dictate the size of the chunks.
1406        urlConnection.setChunkedStreamingMode(50 /* chunkLength */);
1407        urlConnection.setDoOutput(true);
1408        OutputStream outputStream = urlConnection.getOutputStream();
1409        String outputString = "ABCDEFGH";
1410        byte[] outputBytes = outputString.getBytes("US-ASCII");
1411        int targetChunkSize = 3;
1412        for (int i = 0; i < outputBytes.length; i += targetChunkSize) {
1413            int count = i + targetChunkSize < outputBytes.length ? 3 : outputBytes.length - i;
1414            outputStream.write(outputBytes, i, count);
1415            outputStream.flush();
1416        }
1417        assertEquals(200, urlConnection.getResponseCode());
1418
1419        RecordedRequest request = server.takeRequest();
1420        assertEquals(outputString, new String(request.getBody(), "US-ASCII"));
1421        assertEquals(Arrays.asList(3, 3, 2), request.getChunkSizes());
1422    }
1423
1424    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
1425        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
1426    }
1427
1428    public void testAuthenticateWithChunkedStreaming() throws Exception {
1429        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
1430    }
1431
1432    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
1433        MockResponse pleaseAuthenticate = new MockResponse()
1434                .setResponseCode(401)
1435                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1436                .setBody("Please authenticate.");
1437        server.enqueue(pleaseAuthenticate);
1438        server.play();
1439
1440        Authenticator.setDefault(new SimpleAuthenticator());
1441        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1442        connection.setDoOutput(true);
1443        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1444        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1445            connection.setFixedLengthStreamingMode(requestBody.length);
1446        } else if (streamingMode == StreamingMode.CHUNKED) {
1447            connection.setChunkedStreamingMode(0);
1448        }
1449        OutputStream outputStream = connection.getOutputStream();
1450        outputStream.write(requestBody);
1451        outputStream.close();
1452        try {
1453            connection.getInputStream();
1454            fail();
1455        } catch (HttpRetryException expected) {
1456        }
1457
1458        // no authorization header for the request...
1459        RecordedRequest request = server.takeRequest();
1460        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1461        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1462    }
1463
1464    public void testSetValidRequestMethod() throws Exception {
1465        server.play();
1466        assertValidRequestMethod("GET");
1467        assertValidRequestMethod("DELETE");
1468        assertValidRequestMethod("HEAD");
1469        assertValidRequestMethod("OPTIONS");
1470        assertValidRequestMethod("POST");
1471        assertValidRequestMethod("PUT");
1472        assertValidRequestMethod("TRACE");
1473    }
1474
1475    private void assertValidRequestMethod(String requestMethod) throws Exception {
1476        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1477        connection.setRequestMethod(requestMethod);
1478        assertEquals(requestMethod, connection.getRequestMethod());
1479    }
1480
1481    public void testSetInvalidRequestMethodLowercase() throws Exception {
1482        server.play();
1483        assertInvalidRequestMethod("get");
1484    }
1485
1486    public void testSetInvalidRequestMethodConnect() throws Exception {
1487        server.play();
1488        assertInvalidRequestMethod("CONNECT");
1489    }
1490
1491    private void assertInvalidRequestMethod(String requestMethod) throws Exception {
1492        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1493        try {
1494            connection.setRequestMethod(requestMethod);
1495            fail();
1496        } catch (ProtocolException expected) {
1497        }
1498    }
1499
1500    public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception {
1501        server.play();
1502        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1503        try {
1504            connection.setFixedLengthStreamingMode(-2);
1505            fail();
1506        } catch (IllegalArgumentException expected) {
1507        }
1508    }
1509
1510    public void testCanSetNegativeChunkedStreamingMode() throws Exception {
1511        server.play();
1512        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1513        connection.setChunkedStreamingMode(-2);
1514    }
1515
1516    public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
1517        server.enqueue(new MockResponse().setBody("A"));
1518        server.play();
1519        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1520        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1521        try {
1522            connection.setFixedLengthStreamingMode(1);
1523            fail();
1524        } catch (IllegalStateException expected) {
1525        }
1526    }
1527
1528    public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception {
1529        server.enqueue(new MockResponse().setBody("A"));
1530        server.play();
1531        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1532        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1533        try {
1534            connection.setChunkedStreamingMode(1);
1535            fail();
1536        } catch (IllegalStateException expected) {
1537        }
1538    }
1539
1540    public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
1541        server.play();
1542        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1543        connection.setChunkedStreamingMode(1);
1544        try {
1545            connection.setFixedLengthStreamingMode(1);
1546            fail();
1547        } catch (IllegalStateException expected) {
1548        }
1549    }
1550
1551    public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
1552        server.play();
1553        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1554        connection.setFixedLengthStreamingMode(1);
1555        try {
1556            connection.setChunkedStreamingMode(1);
1557            fail();
1558        } catch (IllegalStateException expected) {
1559        }
1560    }
1561
1562    public void testSecureFixedLengthStreaming() throws Exception {
1563        testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
1564    }
1565
1566    public void testSecureChunkedStreaming() throws Exception {
1567        testSecureStreamingPost(StreamingMode.CHUNKED);
1568    }
1569
1570    /**
1571     * Users have reported problems using HTTPS with streaming request bodies.
1572     * http://code.google.com/p/android/issues/detail?id=12860
1573     */
1574    private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
1575        TestSSLContext testSSLContext = createDefaultTestSSLContext();
1576        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1577        server.enqueue(new MockResponse().setBody("Success!"));
1578        server.play();
1579
1580        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1581        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1582        connection.setDoOutput(true);
1583        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1584        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1585            connection.setFixedLengthStreamingMode(requestBody.length);
1586        } else if (streamingMode == StreamingMode.CHUNKED) {
1587            connection.setChunkedStreamingMode(0);
1588        }
1589        OutputStream outputStream = connection.getOutputStream();
1590        outputStream.write(requestBody);
1591        outputStream.close();
1592        assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1593
1594        RecordedRequest request = server.takeRequest();
1595        assertEquals("POST / HTTP/1.1", request.getRequestLine());
1596        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1597            assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
1598        } else if (streamingMode == StreamingMode.CHUNKED) {
1599            assertEquals(Arrays.asList(4), request.getChunkSizes());
1600        }
1601        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1602    }
1603
1604    enum StreamingMode {
1605        FIXED_LENGTH, CHUNKED
1606    }
1607
1608    public void testAuthenticateWithPost() throws Exception {
1609        MockResponse pleaseAuthenticate = new MockResponse()
1610                .setResponseCode(401)
1611                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1612                .setBody("Please authenticate.");
1613        // fail auth three times...
1614        server.enqueue(pleaseAuthenticate);
1615        server.enqueue(pleaseAuthenticate);
1616        server.enqueue(pleaseAuthenticate);
1617        // ...then succeed the fourth time
1618        server.enqueue(new MockResponse().setBody("Successful auth!"));
1619        server.play();
1620
1621        Authenticator.setDefault(new SimpleAuthenticator());
1622        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1623        connection.setDoOutput(true);
1624        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1625        OutputStream outputStream = connection.getOutputStream();
1626        outputStream.write(requestBody);
1627        outputStream.close();
1628        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1629
1630        // no authorization header for the first request...
1631        RecordedRequest request = server.takeRequest();
1632        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1633
1634        // ...but the three requests that follow include an authorization header
1635        for (int i = 0; i < 3; i++) {
1636            request = server.takeRequest();
1637            assertEquals("POST / HTTP/1.1", request.getRequestLine());
1638            assertContains(request.getHeaders(), "Authorization: Basic "
1639                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1640            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1641        }
1642    }
1643
1644    public void testAuthenticateWithGet() throws Exception {
1645        MockResponse pleaseAuthenticate = new MockResponse()
1646                .setResponseCode(401)
1647                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1648                .setBody("Please authenticate.");
1649        // fail auth three times...
1650        server.enqueue(pleaseAuthenticate);
1651        server.enqueue(pleaseAuthenticate);
1652        server.enqueue(pleaseAuthenticate);
1653        // ...then succeed the fourth time
1654        server.enqueue(new MockResponse().setBody("Successful auth!"));
1655        server.play();
1656
1657        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1658        Authenticator.setDefault(authenticator);
1659        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1660        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1661        assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType);
1662        assertEquals(server.getPort(), authenticator.requestingPort);
1663        assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite);
1664        assertEquals("protected area", authenticator.requestingPrompt);
1665        assertEquals("http", authenticator.requestingProtocol);
1666        assertEquals("Basic", authenticator.requestingScheme);
1667
1668        // no authorization header for the first request...
1669        RecordedRequest request = server.takeRequest();
1670        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1671
1672        // ...but the three requests that follow requests include an authorization header
1673        for (int i = 0; i < 3; i++) {
1674            request = server.takeRequest();
1675            assertEquals("GET / HTTP/1.1", request.getRequestLine());
1676            assertContains(request.getHeaders(), "Authorization: Basic "
1677                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1678        }
1679    }
1680
1681    // bug 11473660
1682    public void testAuthenticateWithLowerCaseHeadersAndScheme() throws Exception {
1683        MockResponse pleaseAuthenticate = new MockResponse()
1684                .setResponseCode(401)
1685                .addHeader("www-authenticate: basic realm=\"protected area\"")
1686                .setBody("Please authenticate.");
1687        // fail auth three times...
1688        server.enqueue(pleaseAuthenticate);
1689        server.enqueue(pleaseAuthenticate);
1690        server.enqueue(pleaseAuthenticate);
1691        // ...then succeed the fourth time
1692        server.enqueue(new MockResponse().setBody("Successful auth!"));
1693        server.play();
1694
1695        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1696        Authenticator.setDefault(authenticator);
1697        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1698        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1699        assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType);
1700        assertEquals(server.getPort(), authenticator.requestingPort);
1701        assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite);
1702        assertEquals("protected area", authenticator.requestingPrompt);
1703        assertEquals("http", authenticator.requestingProtocol);
1704        assertEquals("basic", authenticator.requestingScheme);
1705    }
1706
1707    // http://code.google.com/p/android/issues/detail?id=19081
1708    public void testAuthenticateWithCommaSeparatedAuthenticationMethods() throws Exception {
1709        server.enqueue(new MockResponse()
1710                .setResponseCode(401)
1711                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\", Basic realm=\"b\", "
1712                        + "Scheme3 realm=\"c\"")
1713                .setBody("Please authenticate."));
1714        server.enqueue(new MockResponse().setBody("Successful auth!"));
1715        server.play();
1716
1717        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1718        authenticator.expectedPrompt = "b";
1719        Authenticator.setDefault(authenticator);
1720        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1721        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1722
1723        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1724        assertContains(server.takeRequest().getHeaders(),
1725                "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1726        assertEquals("Basic", authenticator.requestingScheme);
1727    }
1728
1729    public void testAuthenticateWithMultipleAuthenticationHeaders() throws Exception {
1730        server.enqueue(new MockResponse()
1731                .setResponseCode(401)
1732                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\"")
1733                .addHeader("WWW-Authenticate: Basic realm=\"b\"")
1734                .addHeader("WWW-Authenticate: Scheme3 realm=\"c\"")
1735                .setBody("Please authenticate."));
1736        server.enqueue(new MockResponse().setBody("Successful auth!"));
1737        server.play();
1738
1739        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1740        authenticator.expectedPrompt = "b";
1741        Authenticator.setDefault(authenticator);
1742        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1743        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1744
1745        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1746        assertContains(server.takeRequest().getHeaders(),
1747                "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1748        assertEquals("Basic", authenticator.requestingScheme);
1749    }
1750
1751    public void testRedirectedWithChunkedEncoding() throws Exception {
1752        testRedirected(TransferKind.CHUNKED, true);
1753    }
1754
1755    public void testRedirectedWithContentLengthHeader() throws Exception {
1756        testRedirected(TransferKind.FIXED_LENGTH, true);
1757    }
1758
1759    public void testRedirectedWithNoLengthHeaders() throws Exception {
1760        testRedirected(TransferKind.END_OF_STREAM, false);
1761    }
1762
1763    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1764        MockResponse response = new MockResponse()
1765                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1766                .addHeader("Location: /foo");
1767        transferKind.setBody(response, "This page has moved!", 10);
1768        server.enqueue(response);
1769        server.enqueue(new MockResponse().setBody("This is the new location!"));
1770        server.play();
1771
1772        URLConnection connection = server.getUrl("/").openConnection();
1773        assertEquals("This is the new location!",
1774                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1775
1776        RecordedRequest first = server.takeRequest();
1777        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1778        RecordedRequest retry = server.takeRequest();
1779        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1780        if (reuse) {
1781            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1782        }
1783    }
1784
1785    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1786        TestSSLContext testSSLContext = createDefaultTestSSLContext();
1787        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1788        server.enqueue(new MockResponse()
1789                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1790                .addHeader("Location: /foo")
1791                .setBody("This page has moved!"));
1792        server.enqueue(new MockResponse().setBody("This is the new location!"));
1793        server.play();
1794
1795        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1796        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1797        assertEquals("This is the new location!",
1798                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1799
1800        RecordedRequest first = server.takeRequest();
1801        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1802        RecordedRequest retry = server.takeRequest();
1803        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1804        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1805    }
1806
1807    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1808        TestSSLContext testSSLContext = createDefaultTestSSLContext();
1809        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1810        server.enqueue(new MockResponse()
1811                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1812                .addHeader("Location: http://anyhost/foo")
1813                .setBody("This page has moved!"));
1814        server.play();
1815
1816        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1817        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1818        assertEquals("This page has moved!",
1819                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1820    }
1821
1822    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1823        server.enqueue(new MockResponse()
1824                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1825                .addHeader("Location: https://anyhost/foo")
1826                .setBody("This page has moved!"));
1827        server.play();
1828
1829        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1830        assertEquals("This page has moved!",
1831                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1832    }
1833
1834    public void testRedirectToAnotherOriginServer() throws Exception {
1835        MockWebServer server2 = new MockWebServer();
1836        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1837        server2.play();
1838
1839        server.enqueue(new MockResponse()
1840                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1841                .addHeader("Location: " + server2.getUrl("/").toString())
1842                .setBody("This page has moved!"));
1843        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1844        server.play();
1845
1846        URLConnection connection = server.getUrl("/").openConnection();
1847        assertEquals("This is the 2nd server!",
1848                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1849        assertEquals(server2.getUrl("/"), connection.getURL());
1850
1851        // make sure the first server was careful to recycle the connection
1852        assertEquals("This is the first server again!",
1853                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1854
1855        RecordedRequest first = server.takeRequest();
1856        assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
1857        RecordedRequest second = server2.takeRequest();
1858        assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort());
1859        RecordedRequest third = server.takeRequest();
1860        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1861
1862        server2.shutdown();
1863    }
1864
1865    // http://b/27590872 - assert we do not throw a runtime exception if a server responds with
1866    // a location that cannot be represented directly by URI.
1867    public void testRedirectWithInvalidRedirectUrl() throws Exception {
1868        // The first server hosts a redirect to a second. We need two so that the ProxySelector
1869        // installed is used for the redirect. Otherwise the second request will be handled via the
1870        // existing keep-alive connection.
1871        server.play();
1872
1873        MockWebServer server2 = new MockWebServer();
1874        server2.play();
1875
1876        String targetPath = "/target";
1877        // The "%0" in the suffix is invalid without a second digit.
1878        String invalidSuffix = "?foo=%0&bar=%00";
1879
1880        String redirectPath = server2.getUrl(targetPath).toString();
1881        String invalidRedirectUri = redirectPath + invalidSuffix;
1882
1883        // Redirect to the invalid URI.
1884        server.enqueue(new MockResponse()
1885                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1886                .addHeader("Location: " + invalidRedirectUri));
1887
1888        server2.enqueue(new MockResponse().setBody("Target"));
1889
1890        // Assert the target URI is actually invalid.
1891        try {
1892            new URI(invalidRedirectUri);
1893            fail("Target URL is expected to be invalid");
1894        } catch (URISyntaxException expected) {}
1895
1896        // The ProxySelector requires a URI object, which forces the HttpURLConnectionImpl to create
1897        // a URI object containing a string based on the redirect address, regardless of what it is
1898        // using internally to hold the target address.
1899        ProxySelector originalSelector = ProxySelector.getDefault();
1900        final List<URI> proxySelectorUris = new ArrayList<>();
1901        ProxySelector.setDefault(new ProxySelector() {
1902            @Override
1903            public List<Proxy> select(URI uri) {
1904                if (uri.getScheme().equals("http")) {
1905                    // Ignore socks proxy lookups.
1906                    proxySelectorUris.add(uri);
1907                }
1908                return Collections.singletonList(Proxy.NO_PROXY);
1909            }
1910
1911            @Override
1912            public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
1913                // no-op
1914            }
1915        });
1916
1917        try {
1918            HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1919            assertEquals("Target", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1920
1921            // Inspect the redirect request to see what request was actually made.
1922            RecordedRequest actualRequest = server2.takeRequest();
1923            assertEquals(targetPath + invalidSuffix, actualRequest.getPath());
1924
1925            // The first URI will be the initial request. We want to inspect the redirect.
1926            URI uri = proxySelectorUris.get(1);
1927            // The proxy is selected by Address alone (not the whole target URI).
1928            // In OkHttp, HttpEngine.createAddress() converts to an Address and the
1929            // RouteSelector converts back to address.url().
1930            assertEquals(server2.getUrl("/").toString(), uri.toString());
1931        } finally {
1932            ProxySelector.setDefault(originalSelector);
1933            server2.shutdown();
1934        }
1935    }
1936
1937    public void testInstanceFollowsRedirects() throws Exception {
1938        testInstanceFollowsRedirects("http://www.google.com/");
1939        testInstanceFollowsRedirects("https://www.google.com/");
1940    }
1941
1942    private void testInstanceFollowsRedirects(String spec) throws Exception {
1943        URL url = new URL(spec);
1944        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
1945        urlConnection.setInstanceFollowRedirects(true);
1946        assertTrue(urlConnection.getInstanceFollowRedirects());
1947        urlConnection.setInstanceFollowRedirects(false);
1948        assertFalse(urlConnection.getInstanceFollowRedirects());
1949    }
1950
1951    public void testFollowRedirects() throws Exception {
1952        testFollowRedirects("http://www.google.com/");
1953        testFollowRedirects("https://www.google.com/");
1954    }
1955
1956    private void testFollowRedirects(String spec) throws Exception {
1957        URL url = new URL(spec);
1958        boolean originalValue = HttpURLConnection.getFollowRedirects();
1959        try {
1960            HttpURLConnection.setFollowRedirects(false);
1961            assertFalse(HttpURLConnection.getFollowRedirects());
1962
1963            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1964            assertFalse(connection.getInstanceFollowRedirects());
1965
1966            HttpURLConnection.setFollowRedirects(true);
1967            assertTrue(HttpURLConnection.getFollowRedirects());
1968
1969            HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
1970            assertTrue(connection2.getInstanceFollowRedirects());
1971        } finally {
1972            HttpURLConnection.setFollowRedirects(originalValue);
1973        }
1974    }
1975
1976    public void testResponse300MultipleChoiceWithPost() throws Exception {
1977        // Chrome doesn't follow the redirect, but Firefox and the RI both do
1978        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
1979    }
1980
1981    public void testResponse301MovedPermanentlyWithPost() throws Exception {
1982        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
1983    }
1984
1985    public void testResponse302MovedTemporarilyWithPost() throws Exception {
1986        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
1987    }
1988
1989    public void testResponse303SeeOtherWithPost() throws Exception {
1990        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
1991    }
1992
1993    private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
1994        server.enqueue(new MockResponse()
1995                .setResponseCode(redirectCode)
1996                .addHeader("Location: /page2")
1997                .setBody("This page has moved!"));
1998        server.enqueue(new MockResponse().setBody("Page 2"));
1999        server.play();
2000
2001        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection();
2002        connection.setDoOutput(true);
2003        byte[] requestBody = { 'A', 'B', 'C', 'D' };
2004        OutputStream outputStream = connection.getOutputStream();
2005        outputStream.write(requestBody);
2006        outputStream.close();
2007        assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2008        assertTrue(connection.getDoOutput());
2009
2010        RecordedRequest page1 = server.takeRequest();
2011        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
2012        assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody()));
2013
2014        RecordedRequest page2 = server.takeRequest();
2015        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
2016    }
2017
2018    public void testResponse305UseProxy() throws Exception {
2019        server.play();
2020        server.enqueue(new MockResponse()
2021                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
2022                .addHeader("Location: " + server.getUrl("/"))
2023                .setBody("This page has moved!"));
2024        server.enqueue(new MockResponse().setBody("Proxy Response"));
2025
2026        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection();
2027        // Fails on the RI, which gets "Proxy Response"
2028        assertEquals("This page has moved!",
2029                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2030
2031        RecordedRequest page1 = server.takeRequest();
2032        assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
2033        assertEquals(1, server.getRequestCount());
2034    }
2035
2036    public void testHttpsWithCustomTrustManager() throws Exception {
2037        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
2038        RecordingTrustManager trustManager = new RecordingTrustManager();
2039        SSLContext sc = SSLContext.getInstance("TLS");
2040        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
2041
2042        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
2043        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
2044        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
2045        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
2046        try {
2047            TestSSLContext testSSLContext = createDefaultTestSSLContext();
2048            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2049            server.enqueue(new MockResponse().setBody("ABC"));
2050            server.enqueue(new MockResponse().setBody("DEF"));
2051            server.enqueue(new MockResponse().setBody("GHI"));
2052            server.play();
2053
2054            URL url = server.getUrl("/");
2055            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
2056            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
2057            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
2058
2059            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
2060            assertEquals(Arrays.asList("checkServerTrusted ["
2061                    + "CN=Local Host 3, "
2062                    + "CN=Test Intermediate Certificate Authority 2, "
2063                    + "CN=Test Root Certificate Authority 1"
2064                    + "] ECDHE_RSA"),
2065                    trustManager.calls);
2066        } finally {
2067            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
2068            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
2069        }
2070    }
2071
2072    public void testSetDefaultSSLSocketFactory_null() {
2073        try {
2074            HttpsURLConnection.setDefaultSSLSocketFactory(null);
2075            fail();
2076        } catch (IllegalArgumentException expected) {
2077        }
2078    }
2079
2080    /**
2081     * Test that the timeout period is honored. The connect timeout is applied to each socket
2082     * connection attempt. If a hostname resolves to multiple IPs HttpURLConnection will wait the
2083     * full timeout for each.
2084     */
2085    public void testConnectTimeouts() throws IOException {
2086        // During CTS tests we are limited in what host names we can depend on and unfortunately
2087        // DNS lookups are not pluggable through standard APIs. During manual testing you should be
2088        // able to change this to any name that can be resolved to multiple IPs and it should still
2089        // work.
2090        String hostName = "localhost";
2091        int expectedConnectionAttempts = InetAddress.getAllByName(hostName).length;
2092        int perSocketTimeout = 1000;
2093        final int[] socketCreationCount = new int[1];
2094        final int[] socketConnectTimeouts = new int[expectedConnectionAttempts];
2095
2096        // It is difficult to force socket timeouts reliably so we replace the default SocketFactory
2097        // and have it create Sockets that always time out when connect(SocketAddress, int) is
2098        // called.
2099        SocketFactory originalDefault = SocketFactory.getDefault();
2100        try {
2101            // Override the default SocketFactory so we can intercept socket creation.
2102            SocketFactory.setDefault(new DelegatingSocketFactory(originalDefault) {
2103                @Override
2104                protected Socket configureSocket(Socket socket) throws IOException {
2105                    final int attemptNumber = socketCreationCount[0]++;
2106                    Socket socketWrapper = new DelegatingSocket(socket) {
2107                        @Override
2108                        public void connect(SocketAddress endpoint, int timeout)
2109                                throws IOException {
2110                            socketConnectTimeouts[attemptNumber] = timeout;
2111                            throw new SocketTimeoutException("Simulated timeout after " + timeout);
2112                        }
2113                    };
2114                    return socketWrapper;
2115                }
2116            });
2117
2118            URLConnection urlConnection = new URL("http://" + hostName + "/").openConnection();
2119            urlConnection.setConnectTimeout(perSocketTimeout);
2120            urlConnection.getInputStream();
2121            fail();
2122        } catch (SocketTimeoutException e) {
2123            assertEquals(expectedConnectionAttempts, socketCreationCount[0]);
2124            for (int i = 0; i < expectedConnectionAttempts; i++) {
2125                assertEquals(perSocketTimeout, socketConnectTimeouts[i]);
2126            }
2127        } finally {
2128            SocketFactory.setDefault(originalDefault);
2129        }
2130    }
2131
2132    public void testReadTimeouts() throws IOException {
2133        /*
2134         * This relies on the fact that MockWebServer doesn't close the
2135         * connection after a response has been sent. This causes the client to
2136         * try to read more bytes than are sent, which results in a timeout.
2137         */
2138        MockResponse timeout = new MockResponse()
2139                .setBody("ABC")
2140                .clearHeaders()
2141                .addHeader("Content-Length: 4");
2142        server.enqueue(timeout);
2143        server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
2144        server.play();
2145
2146        URLConnection urlConnection = server.getUrl("/").openConnection();
2147        urlConnection.setReadTimeout(1000);
2148        InputStream in = urlConnection.getInputStream();
2149        assertEquals('A', in.read());
2150        assertEquals('B', in.read());
2151        assertEquals('C', in.read());
2152        try {
2153            in.read(); // if Content-Length was accurate, this would return -1 immediately
2154            fail();
2155        } catch (SocketTimeoutException expected) {
2156        }
2157    }
2158
2159    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
2160        server.enqueue(new MockResponse());
2161        server.play();
2162
2163        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
2164        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
2165        urlConnection.setDoOutput(true);
2166        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
2167        assertEquals(200, urlConnection.getResponseCode());
2168
2169        RecordedRequest request = server.takeRequest();
2170        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
2171    }
2172
2173    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
2174        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
2175        server.enqueue(new MockResponse());
2176        server.play();
2177
2178        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
2179        a.setRequestProperty("Connection", "close");
2180        assertEquals(200, a.getResponseCode());
2181
2182        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
2183        assertEquals(200, b.getResponseCode());
2184
2185        assertEquals(0, server.takeRequest().getSequenceNumber());
2186        assertEquals("When connection: close is used, each request should get its own connection",
2187                0, server.takeRequest().getSequenceNumber());
2188    }
2189
2190    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
2191        server.enqueue(new MockResponse().addHeader("Connection: close"));
2192        server.enqueue(new MockResponse());
2193        server.play();
2194
2195        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
2196        assertEquals(200, a.getResponseCode());
2197
2198        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
2199        assertEquals(200, b.getResponseCode());
2200
2201        assertEquals(0, server.takeRequest().getSequenceNumber());
2202        assertEquals("When connection: close is used, each request should get its own connection",
2203                0, server.takeRequest().getSequenceNumber());
2204    }
2205
2206    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
2207        MockResponse response = new MockResponse()
2208                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
2209                .addHeader("Location: /foo")
2210                .addHeader("Connection: close");
2211        server.enqueue(response);
2212        server.enqueue(new MockResponse().setBody("This is the new location!"));
2213        server.play();
2214
2215        URLConnection connection = server.getUrl("/").openConnection();
2216        assertEquals("This is the new location!",
2217                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2218
2219        assertEquals(0, server.takeRequest().getSequenceNumber());
2220        assertEquals("When connection: close is used, each request should get its own connection",
2221                0, server.takeRequest().getSequenceNumber());
2222    }
2223
2224    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
2225        server.enqueue(new MockResponse()
2226                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
2227                .setBody("This body is not allowed!"));
2228        server.play();
2229
2230        URLConnection connection = server.getUrl("/").openConnection();
2231        assertEquals("This body is not allowed!",
2232                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2233    }
2234
2235    public void testSingleByteReadIsSigned() throws IOException {
2236        server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
2237        server.play();
2238
2239        URLConnection connection = server.getUrl("/").openConnection();
2240        InputStream in = connection.getInputStream();
2241        assertEquals(254, in.read());
2242        assertEquals(255, in.read());
2243        assertEquals(-1, in.read());
2244    }
2245
2246    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
2247        testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
2248    }
2249
2250    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
2251        testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
2252    }
2253
2254    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
2255        testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
2256    }
2257
2258    /**
2259     * We explicitly permit apps to close the upload stream even after it has
2260     * been transmitted.  We also permit flush so that buffered streams can
2261     * do a no-op flush when they are closed. http://b/3038470
2262     */
2263    private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
2264        server.enqueue(new MockResponse().setBody("abc"));
2265        server.play();
2266
2267        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2268        connection.setDoOutput(true);
2269        byte[] upload = "def".getBytes("UTF-8");
2270
2271        if (transferKind == TransferKind.CHUNKED) {
2272            connection.setChunkedStreamingMode(0);
2273        } else if (transferKind == TransferKind.FIXED_LENGTH) {
2274            connection.setFixedLengthStreamingMode(upload.length);
2275        }
2276
2277        OutputStream out = connection.getOutputStream();
2278        out.write(upload);
2279        assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2280
2281        out.flush(); // dubious but permitted
2282        try {
2283            out.write("ghi".getBytes("UTF-8"));
2284            fail();
2285        } catch (IOException expected) {
2286        }
2287    }
2288
2289    public void testGetHeadersThrows() throws IOException {
2290        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
2291        server.play();
2292
2293        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2294        try {
2295            connection.getInputStream();
2296            fail();
2297        } catch (IOException expected) {
2298        }
2299
2300        try {
2301            connection.getInputStream();
2302            fail();
2303        } catch (IOException expected) {
2304        }
2305    }
2306
2307    public void testReadTimeoutsOnRecycledConnections() throws Exception {
2308        server.enqueue(new MockResponse().setBody("ABC"));
2309        server.play();
2310
2311        // The request should work once and then fail
2312        URLConnection connection = server.getUrl("").openConnection();
2313        // Read timeout of a day, sure to cause the test to timeout and fail.
2314        connection.setReadTimeout(24 * 3600 * 1000);
2315        InputStream input = connection.getInputStream();
2316        assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
2317        input.close();
2318        try {
2319            connection = server.getUrl("").openConnection();
2320            // Set the read timeout back to 100ms, this request will time out
2321            // because we've only enqueued one response.
2322            connection.setReadTimeout(100);
2323            connection.getInputStream();
2324            fail();
2325        } catch (IOException expected) {
2326        }
2327    }
2328
2329    /**
2330     * This test goes through the exhaustive set of interesting ASCII characters
2331     * because most of those characters are interesting in some way according to
2332     * RFC 2396 and RFC 2732. http://b/1158780
2333     * After M, Android's HttpURLConnection started canonicalizing hostnames to lower case, IDN
2334     * encoding and being more strict about invalid characters.
2335     */
2336    public void testUrlCharacterMapping() throws Exception {
2337        server.setDispatcher(new Dispatcher() {
2338            @Override public MockResponse dispatch(RecordedRequest request)
2339                throws InterruptedException {
2340                return new MockResponse();
2341            }
2342        });
2343        server.play();
2344
2345        // alphanum
2346        testUrlToUriMapping("abzABZ09", "abzabz09", "abzABZ09", "abzABZ09", "abzABZ09");
2347        testUrlToRequestMapping("abzABZ09", "abzABZ09", "abzABZ09");
2348
2349        // control characters
2350
2351        // On JB-MR2 and below, we would allow a host containing \u0000
2352        // and then generate a request with a Host header that violated RFC2616.
2353        // We now reject such hosts.
2354        //
2355        // The ideal behaviour here is to be "lenient" about the host and rewrite
2356        // it, but attempting to do so introduces a new range of incompatible
2357        // behaviours.
2358        testUrlToUriMapping("\u0000", null, "%00", "%00", "%00"); // RI fails this
2359        testUrlToRequestMapping("\u0000", "%00", "%00");
2360
2361        testUrlToUriMapping("\u0001", null, "%01", "%01", "%01");
2362        testUrlToRequestMapping("\u0001", "%01", "%01");
2363
2364        testUrlToUriMapping("\u001f", null, "%1F", "%1F", "%1F");
2365        testUrlToRequestMapping("\u001f", "%1F", "%1F");
2366
2367        // ascii characters
2368        testUrlToUriMapping("%20", null, "%20", "%20", "%20");
2369        testUrlToRequestMapping("%20", "%20", "%20");
2370        testUrlToUriMapping(" ", null, "%20", "%20", "%20");
2371        testUrlToRequestMapping(" ", "%20", "%20");
2372        testUrlToUriMapping("!", "!", "!", "!", "!");
2373        testUrlToRequestMapping("!", "!", "!");
2374        testUrlToUriMapping("\"", null, "%22", "%22", "%22");
2375        testUrlToRequestMapping("\"", "%22", "%22");
2376        testUrlToUriMapping("#", null, null, null, "%23");
2377        testUrlToRequestMapping("#", null, null);
2378        testUrlToUriMapping("$", "$", "$", "$", "$");
2379        testUrlToRequestMapping("$", "$", "$");
2380        testUrlToUriMapping("&", "&", "&", "&", "&");
2381        testUrlToRequestMapping("&", "&", "&");
2382
2383        // http://b/30405333 - upstream OkHttp encodes single quote (') as %27 in query parameters
2384        // but this breaks iTunes remote apps: iTunes currently does not accept %27 so we have a
2385        // local patch to retain the historic Android behavior of not encoding single quote.
2386        testUrlToUriMapping("'", "'", "'", "'", "'");
2387        testUrlToRequestMapping("'", "'", "'");
2388
2389        testUrlToUriMapping("(", "(", "(", "(", "(");
2390        testUrlToRequestMapping("(", "(", "(");
2391        testUrlToUriMapping(")", ")", ")", ")", ")");
2392        testUrlToRequestMapping(")", ")", ")");
2393        testUrlToUriMapping("*", "*", "*", "*", "*");
2394        testUrlToRequestMapping("*", "*", "*");
2395        testUrlToUriMapping("+", "+", "+", "+", "+");
2396        testUrlToRequestMapping("+", "+", "+");
2397        testUrlToUriMapping(",", ",", ",", ",", ",");
2398        testUrlToRequestMapping(",", ",", ",");
2399        testUrlToUriMapping("-", "-", "-", "-", "-");
2400        testUrlToRequestMapping("-", "-", "-");
2401        testUrlToUriMapping(".", null, ".", ".", ".");
2402        testUrlToRequestMapping(".", ".", ".");
2403        testUrlToUriMapping(".foo", ".foo", ".foo", ".foo", ".foo");
2404        testUrlToRequestMapping(".foo", ".foo", ".foo");
2405        testUrlToUriMapping("/", null, "/", "/", "/");
2406        testUrlToRequestMapping("/", "/", "/");
2407        testUrlToUriMapping(":", null, ":", ":", ":");
2408        testUrlToRequestMapping(":", ":", ":");
2409        testUrlToUriMapping(";", ";", ";", ";", ";");
2410        testUrlToRequestMapping(";", ";", ";");
2411        testUrlToUriMapping("<", null, "%3C", "%3C", "%3C");
2412        testUrlToRequestMapping("<", "%3C", "%3C");
2413        testUrlToUriMapping("=", "=", "=", "=", "=");
2414        testUrlToRequestMapping("=", "=", "=");
2415        testUrlToUriMapping(">", null, "%3E", "%3E", "%3E");
2416        testUrlToRequestMapping(">", "%3E", "%3E");
2417        testUrlToUriMapping("?", null, null, "?", "?");
2418        testUrlToRequestMapping("?", null, "?");
2419        testUrlToUriMapping("@", null, "@", "@", "@");
2420        testUrlToRequestMapping("@", "@", "@");
2421        testUrlToUriMapping("[", null, null, null, "%5B");
2422        testUrlToRequestMapping("[", null, null);
2423        testUrlToUriMapping("\\", null, null, null, "%5C");
2424        testUrlToRequestMapping("\\", null, null);
2425        testUrlToUriMapping("]", null, null, null, "%5D");
2426        testUrlToRequestMapping("]", null, null);
2427        testUrlToUriMapping("^", null, "%5E", null, "%5E");
2428        testUrlToRequestMapping("^", "%5E", null);
2429        testUrlToUriMapping("_", "_", "_", "_", "_");
2430        testUrlToRequestMapping("_", "_", "_");
2431        testUrlToUriMapping("`", null, "%60", null, "%60");
2432        testUrlToRequestMapping("`", "%60", null);
2433        testUrlToUriMapping("{", null, "%7B", null, "%7B");
2434        testUrlToRequestMapping("{", "%7B", null);
2435        testUrlToUriMapping("|", null, "%7C", null, "%7C");
2436        testUrlToRequestMapping("|", "%7C", null);
2437        testUrlToUriMapping("}", null, "%7D", null, "%7D");
2438        testUrlToRequestMapping("}", "%7D", null);
2439        testUrlToUriMapping("~", "~", "~", "~", "~");
2440        testUrlToRequestMapping("~", "~", "~");
2441        testUrlToUriMapping("\u007f", null, "%7F", "%7F", "%7F");
2442        testUrlToRequestMapping("\u007f", "%7F", "%7F");
2443
2444        // beyond ASCII
2445
2446        // 0x80 is the code point for the Euro sign in CP1252 (but not 8859-15 or Unicode).
2447        // Unicode code point 0x80 is a control character and maps to {0xC2, 0x80} in UTF-8.
2448        // 0x80 is outside of the ASCII range and is not supported by IDN in hostnames.
2449        testUrlToUriMapping("\u0080", null, "%C2%80", "%C2%80", "%C2%80");
2450        testUrlToRequestMapping("\u0080", "%C2%80", "%C2%80");
2451
2452        // More complicated transformations for the authorities below.
2453
2454        // 0x20AC is the code point for the Euro sign in Unicode.
2455        // Unicode code point 0x20AC maps to {0xE2, 0x82, 0xAC} in UTF-8
2456        // 0x20AC is not supported by all registrars but there are some legacy domains that
2457        // use it and Android currently supports IDN conversion for it.
2458        testUrlToUriMapping("\u20ac", null /* skip */, "%E2%82%AC", "%E2%82%AC", "%E2%82%AC");
2459        testUrlToUriMappingAuthority("http://host\u20ac.tld/", "http://xn--host-yv7a.tld/");
2460        testUrlToRequestMapping("\u20ac",  "%E2%82%AC", "%E2%82%AC");
2461
2462        // UTF-16 {0xD842, 0xDF9F} -> Unicode 0x20B9F (a Kanji character)
2463        // Unicode code point 0x20B9F maps to {0xF0, 0xA0, 0xAE, 0x9F} in UTF-8
2464        // IDN can deal with this code point.
2465        testUrlToUriMapping("\ud842\udf9f", null /* skip */, "%F0%A0%AE%9F", "%F0%A0%AE%9F",
2466            "%F0%A0%AE%9F");
2467        testUrlToUriMappingAuthority("http://host\uD842\uDF9F.tld/", "http://xn--host-ov06c.tld/");
2468        testUrlToRequestMapping("\ud842\udf9f",  "%F0%A0%AE%9F", "%F0%A0%AE%9F");
2469    }
2470
2471    private void testUrlToUriMappingAuthority(String urlString, String expectedUriString)
2472        throws Exception {
2473        URI authorityUri = backdoorUrlToUri(new URL(urlString));
2474        assertEquals(expectedUriString, authorityUri.toString());
2475    }
2476
2477    /**
2478     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
2479     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
2480     * characters like '{' and '|' by escaping these characters.
2481     */
2482    private URI backdoorUrlToUri(URL url) throws Exception {
2483        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
2484
2485        ResponseCache.setDefault(new ResponseCache() {
2486            @Override public CacheRequest put(URI uri, URLConnection connection)
2487                    throws IOException {
2488                return null;
2489            }
2490            @Override public CacheResponse get(URI uri, String requestMethod,
2491                    Map<String, List<String>> requestHeaders) throws IOException {
2492                uriReference.set(uri);
2493                throw new UnsupportedOperationException();
2494            }
2495        });
2496
2497        try {
2498            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
2499            connection.getResponseCode();
2500        } catch (Exception expected) {
2501        }
2502
2503        return uriReference.get();
2504    }
2505
2506    /*
2507     * Test the request that would be made by making an actual request a MockWebServer and capturing
2508     * the request made.
2509     *
2510     * Any "as" values that are null are not tested.
2511     */
2512    private void testUrlToRequestMapping(
2513            String string, String asFile, String asQuery) throws Exception {
2514        if (asFile != null) {
2515            URL fileUrl = server.getUrl("/file" + string + "/#discarded");
2516            HttpURLConnection urlConnection = (HttpURLConnection) fileUrl.openConnection();
2517            // Bypass the cache.
2518            urlConnection.setUseCaches(false);
2519
2520            assertEquals(200, urlConnection.getResponseCode());
2521            assertEquals("/file" + asFile + "/", server.takeRequest().getPath());
2522        }
2523        if (asQuery != null) {
2524            URL queryUrl = server.getUrl("/file?q" + string + "=x#discarded");
2525            HttpURLConnection urlConnection = (HttpURLConnection) queryUrl.openConnection();
2526            // Bypass the cache.
2527            urlConnection.setUseCaches(false);
2528
2529            assertEquals(200, urlConnection.getResponseCode());
2530            assertEquals("/file?q" + asQuery + "=x", server.takeRequest().getPath());
2531        }
2532    }
2533
2534    /*
2535     * Test the request that would be made by looking at the URI presented to the cache. This
2536     * includes the likely host name that would be used if a request were made. The cache throws an
2537     * exception so no request is actually made.
2538     *
2539     * Any "as" values that are null are not tested.
2540     */
2541    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
2542            String asQuery, String asFragment) throws Exception {
2543        if (asAuthority != null) {
2544            URI authorityUri = backdoorUrlToUri(new URL("http://host" + string + ".tld/"));
2545            assertEquals("http://host" + asAuthority + ".tld/", authorityUri.toString());
2546        }
2547        if (asFile != null) {
2548            URI fileUri = backdoorUrlToUri(new URL("http://host.tld/file" + string + "/"));
2549            assertEquals("http://host.tld/file" + asFile + "/", fileUri.toString());
2550        }
2551        if (asQuery != null) {
2552            URI queryUri = backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x"));
2553            assertEquals("http://host.tld/file?q" + asQuery + "=x", queryUri.toString());
2554        }
2555        assertEquals("http://host.tld/file#" + asFragment + "-x",
2556            backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
2557    }
2558
2559    public void testHostWithNul() throws Exception {
2560        URL url = new URL("http://host\u0000/");
2561        try {
2562            url.openStream();
2563            fail();
2564        } catch (UnknownHostException expected) {}
2565    }
2566
2567    /**
2568     * Don't explode if the cache returns a null body. http://b/3373699
2569     */
2570    public void testResponseCacheReturnsNullOutputStream() throws Exception {
2571        final AtomicBoolean aborted = new AtomicBoolean();
2572        ResponseCache.setDefault(new ResponseCache() {
2573            @Override public CacheResponse get(URI uri, String requestMethod,
2574                    Map<String, List<String>> requestHeaders) throws IOException {
2575                return null;
2576            }
2577            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
2578                return new CacheRequest() {
2579                    @Override public void abort() {
2580                        aborted.set(true);
2581                    }
2582                    @Override public OutputStream getBody() throws IOException {
2583                        return null;
2584                    }
2585                };
2586            }
2587        });
2588
2589        server.enqueue(new MockResponse().setBody("abcdef"));
2590        server.play();
2591
2592        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2593        InputStream in = connection.getInputStream();
2594        assertEquals("abc", readAscii(in, 3));
2595        in.close();
2596        assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
2597    }
2598
2599
2600    /**
2601     * http://code.google.com/p/android/issues/detail?id=14562
2602     */
2603    public void testReadAfterLastByte() throws Exception {
2604        server.enqueue(new MockResponse()
2605                .setBody("ABC")
2606                .clearHeaders()
2607                .addHeader("Connection: close")
2608                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END));
2609        server.play();
2610
2611        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2612        InputStream in = connection.getInputStream();
2613        assertEquals("ABC", readAscii(in, 3));
2614        assertEquals(-1, in.read());
2615        assertEquals(-1, in.read()); // throws IOException in Gingerbread
2616    }
2617
2618    public void testGetContent() throws Exception {
2619        server.enqueue(new MockResponse().setBody("A"));
2620        server.play();
2621        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2622        InputStream in = (InputStream) connection.getContent();
2623        assertEquals("A", readAscii(in, Integer.MAX_VALUE));
2624    }
2625
2626    public void testGetContentOfType() throws Exception {
2627        server.enqueue(new MockResponse().setBody("A"));
2628        server.play();
2629        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2630        try {
2631            connection.getContent(null);
2632            fail();
2633        } catch (NullPointerException expected) {
2634        }
2635        try {
2636            connection.getContent(new Class[] { null });
2637            fail();
2638        } catch (NullPointerException expected) {
2639        }
2640        assertNull(connection.getContent(new Class[] { getClass() }));
2641        connection.disconnect();
2642    }
2643
2644    public void testGetOutputStreamOnGetFails() throws Exception {
2645        server.enqueue(new MockResponse());
2646        server.play();
2647        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2648        try {
2649            connection.getOutputStream();
2650            fail();
2651        } catch (ProtocolException expected) {
2652        }
2653    }
2654
2655    public void testGetOutputAfterGetInputStreamFails() throws Exception {
2656        server.enqueue(new MockResponse());
2657        server.play();
2658        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2659        connection.setDoOutput(true);
2660        try {
2661            connection.getInputStream();
2662            connection.getOutputStream();
2663            fail();
2664        } catch (ProtocolException expected) {
2665        }
2666    }
2667
2668    public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception {
2669        server.enqueue(new MockResponse());
2670        server.play();
2671        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2672        connection.connect();
2673        try {
2674            connection.setDoOutput(true);
2675            fail();
2676        } catch (IllegalStateException expected) {
2677        }
2678        try {
2679            connection.setDoInput(true);
2680            fail();
2681        } catch (IllegalStateException expected) {
2682        }
2683        connection.disconnect();
2684    }
2685
2686    public void testLastModified() throws Exception {
2687        server.enqueue(new MockResponse()
2688                .addHeader("Last-Modified", "Wed, 27 Nov 2013 11:26:00 GMT")
2689                .setBody("Hello"));
2690        server.play();
2691
2692        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2693        connection.connect();
2694
2695        assertEquals(1385551560000L, connection.getLastModified());
2696        assertEquals(1385551560000L, connection.getHeaderFieldDate("Last-Modified", -1));
2697    }
2698
2699    public void testClientSendsContentLength() throws Exception {
2700        server.enqueue(new MockResponse().setBody("A"));
2701        server.play();
2702        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2703        connection.setDoOutput(true);
2704        OutputStream out = connection.getOutputStream();
2705        out.write(new byte[] { 'A', 'B', 'C' });
2706        out.close();
2707        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2708        RecordedRequest request = server.takeRequest();
2709        assertContains(request.getHeaders(), "Content-Length: 3");
2710    }
2711
2712    public void testGetContentLengthConnects() throws Exception {
2713        server.enqueue(new MockResponse().setBody("ABC"));
2714        server.play();
2715        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2716        assertEquals(3, connection.getContentLength());
2717        connection.disconnect();
2718    }
2719
2720    public void testGetContentTypeConnects() throws Exception {
2721        server.enqueue(new MockResponse()
2722                .addHeader("Content-Type: text/plain")
2723                .setBody("ABC"));
2724        server.play();
2725        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2726        assertEquals("text/plain", connection.getContentType());
2727        connection.disconnect();
2728    }
2729
2730    public void testGetContentEncodingConnects() throws Exception {
2731        server.enqueue(new MockResponse()
2732                .addHeader("Content-Encoding: identity")
2733                .setBody("ABC"));
2734        server.play();
2735        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2736        assertEquals("identity", connection.getContentEncoding());
2737        connection.disconnect();
2738    }
2739
2740    // http://b/4361656
2741    public void testUrlContainsQueryButNoPath() throws Exception {
2742        server.enqueue(new MockResponse().setBody("A"));
2743        server.play();
2744        URL url = new URL("http", server.getHostName(), server.getPort(), "?query");
2745        assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE));
2746        RecordedRequest request = server.takeRequest();
2747        assertEquals("GET /?query HTTP/1.1", request.getRequestLine());
2748    }
2749
2750    // http://code.google.com/p/android/issues/detail?id=20442
2751    public void testInputStreamAvailableWithChunkedEncoding() throws Exception {
2752        testInputStreamAvailable(TransferKind.CHUNKED);
2753    }
2754
2755    public void testInputStreamAvailableWithContentLengthHeader() throws Exception {
2756        testInputStreamAvailable(TransferKind.FIXED_LENGTH);
2757    }
2758
2759    public void testInputStreamAvailableWithNoLengthHeaders() throws Exception {
2760        testInputStreamAvailable(TransferKind.END_OF_STREAM);
2761    }
2762
2763    private void testInputStreamAvailable(TransferKind transferKind) throws IOException {
2764        String body = "ABCDEFGH";
2765        MockResponse response = new MockResponse();
2766        transferKind.setBody(response, body, 4);
2767        server.enqueue(response);
2768        server.play();
2769        URLConnection connection = server.getUrl("/").openConnection();
2770        InputStream in = connection.getInputStream();
2771        for (int i = 0; i < body.length(); i++) {
2772            assertTrue(in.available() >= 0);
2773            assertEquals(body.charAt(i), in.read());
2774        }
2775        assertEquals(0, in.available());
2776        assertEquals(-1, in.read());
2777    }
2778
2779    // http://code.google.com/p/android/issues/detail?id=28095
2780    public void testInvalidIpv4Address() throws Exception {
2781        try {
2782            URI uri = new URI("http://1111.111.111.111/index.html");
2783            uri.toURL().openConnection().connect();
2784            fail();
2785        } catch (UnknownHostException expected) {
2786        }
2787    }
2788
2789    public void testConnectIpv6() throws Exception {
2790        server.enqueue(new MockResponse().setBody("testConnectIpv6 body"));
2791        server.play();
2792        URL url = new URL("http://[::1]:" + server.getPort() + "/");
2793        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
2794        assertContent("testConnectIpv6 body", connection);
2795    }
2796
2797    // http://code.google.com/p/android/issues/detail?id=16895
2798    public void testUrlWithSpaceInHost() throws Exception {
2799        URLConnection urlConnection = new URL("http://and roid.com/").openConnection();
2800        try {
2801            urlConnection.getInputStream();
2802            fail();
2803        } catch (UnknownHostException expected) {
2804        }
2805    }
2806
2807    // http://code.google.com/p/android/issues/detail?id=16895
2808    public void testUrlWithSpaceInHostViaHttpProxy() throws Exception {
2809        server.enqueue(new MockResponse());
2810        server.play();
2811        URLConnection urlConnection = new URL("http://and roid.com/")
2812                .openConnection(server.toProxyAddress());
2813
2814        try {
2815            // This test is to check that a NullPointerException is not thrown.
2816            urlConnection.getInputStream();
2817            fail(); // the RI makes a bogus proxy request for "GET http://and roid.com/ HTTP/1.1"
2818        } catch (UnknownHostException expected) {
2819        }
2820    }
2821
2822    /** Checks that if the first TLS handshake fails, no fallback is attempted. */
2823    private void checkNoFallbackOnFailedHandshake(SSLSocketFactory clientSocketFactory,
2824            SSLSocketFactory serverSocketFactory,
2825            String... expectedProtocols)
2826            throws Exception {
2827        server.useHttps(serverSocketFactory, false);
2828        server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
2829        server.enqueue(new MockResponse().setBody("This required fallbacks"));
2830        server.play();
2831
2832        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2833        // Keeps track of the client sockets created so that we can interrogate them.
2834        final boolean disableFallbackScsv = true;
2835        FallbackTestClientSocketFactory fallbackTestClientSocketFactory =
2836                new FallbackTestClientSocketFactory(clientSocketFactory, disableFallbackScsv);
2837        connection.setSSLSocketFactory(fallbackTestClientSocketFactory);
2838        try {
2839            connection.getInputStream().read();
2840            fail();
2841        } catch (SSLHandshakeException expected) {
2842        }
2843        List<SSLSocket> createdSockets = fallbackTestClientSocketFactory.getCreatedSockets();
2844        assertEquals(1, createdSockets.size());
2845        assertSslSocket((TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0),
2846                false /* expectedWasFallbackScsvSet */, expectedProtocols);
2847    }
2848
2849    public void testNoSslFallback_specifiedProtocols() throws Exception {
2850        String[] enabledProtocols = { "TLSv1.2", "TLSv1.1" };
2851        TestSSLContext testSSLContext = createDefaultTestSSLContext();
2852        SSLSocketFactory serverSocketFactory =
2853                new LimitedProtocolsSocketFactory(
2854                        testSSLContext.serverContext.getSocketFactory(),
2855                        enabledProtocols);
2856        SSLSocketFactory clientSocketFactory = new LimitedProtocolsSocketFactory(
2857                testSSLContext.clientContext.getSocketFactory(), enabledProtocols);
2858        checkNoFallbackOnFailedHandshake(clientSocketFactory, serverSocketFactory,
2859                enabledProtocols);
2860    }
2861
2862    public void testNoSslFallback_defaultProtocols() throws Exception {
2863        // Will need to be updated if the enabled protocols in Android's SSLSocketFactory change
2864        String[] expectedEnabledProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1" };
2865
2866        TestSSLContext testSSLContext = createDefaultTestSSLContext();
2867        SSLSocketFactory serverSocketFactory = testSSLContext.serverContext.getSocketFactory();
2868        SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory();
2869        checkNoFallbackOnFailedHandshake(clientSocketFactory, serverSocketFactory,
2870                expectedEnabledProtocols);
2871    }
2872
2873    private static void assertSslSocket(TlsFallbackDisabledScsvSSLSocket socket,
2874            boolean expectedWasFallbackScsvSet, String... expectedEnabledProtocols) {
2875        Set<String> enabledProtocols =
2876                new HashSet<String>(Arrays.asList(socket.getEnabledProtocols()));
2877        Set<String> expectedProtocolsSet = new HashSet<String>(Arrays.asList(expectedEnabledProtocols));
2878        assertEquals(expectedProtocolsSet, enabledProtocols);
2879        assertEquals(expectedWasFallbackScsvSet, socket.wasTlsFallbackScsvSet());
2880    }
2881
2882    public void testInspectSslBeforeConnect() throws Exception {
2883        TestSSLContext testSSLContext = createDefaultTestSSLContext();
2884        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2885        server.enqueue(new MockResponse());
2886        server.play();
2887
2888        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2889        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2890        assertNotNull(connection.getHostnameVerifier());
2891        try {
2892            connection.getLocalCertificates();
2893            fail();
2894        } catch (IllegalStateException expected) {
2895        }
2896        try {
2897            connection.getServerCertificates();
2898            fail();
2899        } catch (IllegalStateException expected) {
2900        }
2901        try {
2902            connection.getCipherSuite();
2903            fail();
2904        } catch (IllegalStateException expected) {
2905        }
2906        try {
2907            connection.getPeerPrincipal();
2908            fail();
2909        } catch (IllegalStateException expected) {
2910        }
2911    }
2912
2913    /**
2914     * Test that we can inspect the SSL session after connect().
2915     * http://code.google.com/p/android/issues/detail?id=24431
2916     */
2917    public void testInspectSslAfterConnect() throws Exception {
2918        TestSSLContext testSSLContext = createDefaultTestSSLContext();
2919        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2920        server.enqueue(new MockResponse());
2921        server.play();
2922
2923        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2924        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2925        connection.connect();
2926        try {
2927            assertNotNull(connection.getHostnameVerifier());
2928            assertNull(connection.getLocalCertificates());
2929            assertNotNull(connection.getServerCertificates());
2930            assertNotNull(connection.getCipherSuite());
2931            assertNotNull(connection.getPeerPrincipal());
2932        } finally {
2933            connection.disconnect();
2934        }
2935    }
2936
2937    /**
2938     * Checks that OkHttp's certificate pinning logic is not used for the common case
2939     * of HttpsUrlConnections.
2940     *
2941     * <p>OkHttp 2.7 introduced logic for Certificate Pinning. We deliberately don't
2942     * expose any API surface that would interact with OkHttp's implementation because
2943     * Android has its own API / implementation for certificate pinning. We can't
2944     * easily test that there is *no* code path that would invoke OkHttp's certificate
2945     * pinning logic, so this test only covers the *common* code path of a
2946     * HttpsURLConnection as a sanity check.
2947     *
2948     * <p>To check whether OkHttp performs certificate pinning under the hood, this
2949     * test disables two {@link Platform} methods. In OkHttp 2.7.5, these two methods
2950     * are exclusively used in relation to certificate pinning. Android only provides
2951     * the minimal implementation of these methods to get OkHttp's tests to pass, so
2952     * they should never be invoked outside of OkHttp's tests.
2953     */
2954    public void testTrustManagerAndTrustRootIndex_unusedForHttpsConnection() throws Exception {
2955        Platform platform = Platform.getAndSetForTest(new PlatformWithoutTrustManager());
2956        try {
2957            testConnectViaHttps();
2958        } finally {
2959            Platform.getAndSetForTest(platform);
2960        }
2961    }
2962
2963    /**
2964     * Similar to {@link #testTrustManagerAndTrustRootIndex_unusedForHttpsConnection()},
2965     * but for the HTTP case. In the HTTP case, no certificate or trust management
2966     * related logic should ever be involved at all, so some pretty basic things must
2967     * be going wrong in order for this test to (unexpectedly) invoke the corresponding
2968     * Platform methods.
2969     */
2970    public void testTrustManagerAndTrustRootIndex_unusedForHttpConnection() throws Exception {
2971        Platform platform = Platform.getAndSetForTest(new PlatformWithoutTrustManager());
2972        try {
2973            server.enqueue(new MockResponse().setBody("response").setResponseCode(200));
2974            server.play();
2975            HttpURLConnection urlConnection =
2976                    (HttpURLConnection) server.getUrl("/").openConnection();
2977            assertEquals(200, urlConnection.getResponseCode());
2978        } finally {
2979            Platform.getAndSetForTest(platform);
2980        }
2981    }
2982
2983    /**
2984     * A {@link Platform} that doesn't support two methods that, in OkHttp 2.7.5,
2985     * are exclusively used to provide custom CertificatePinning.
2986     */
2987    static class PlatformWithoutTrustManager extends Platform {
2988        @Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
2989            throw new AssertionError("Unexpected call");
2990        }
2991        @Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
2992            throw new AssertionError("Unexpected call");
2993        }
2994    }
2995
2996    /**
2997     * Returns a gzipped copy of {@code bytes}.
2998     */
2999    public byte[] gzip(byte[] bytes) throws IOException {
3000        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
3001        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
3002        gzippedOut.write(bytes);
3003        gzippedOut.close();
3004        return bytesOut.toByteArray();
3005    }
3006
3007    /**
3008     * Reads at most {@code limit} characters from {@code in} and asserts that
3009     * content equals {@code expected}.
3010     */
3011    private void assertContent(String expected, URLConnection connection, int limit)
3012            throws IOException {
3013        connection.connect();
3014        assertEquals(expected, readAscii(connection.getInputStream(), limit));
3015        ((HttpURLConnection) connection).disconnect();
3016    }
3017
3018    private void assertContent(String expected, URLConnection connection) throws IOException {
3019        assertContent(expected, connection, Integer.MAX_VALUE);
3020    }
3021
3022    private static void assertHeaderPresent(RecordedRequest request, String headerName) {
3023        assertNotNull(headerName + " missing: " + request.getHeaders(),
3024                request.getHeader(headerName));
3025    }
3026
3027    private void assertContains(List<String> list, String value) {
3028        assertTrue(list.toString(), list.contains(value));
3029    }
3030
3031    private void assertContainsNoneMatching(List<String> list, String pattern) {
3032        for (String header : list) {
3033            if (header.matches(pattern)) {
3034                fail("Header " + header + " matches " + pattern);
3035            }
3036        }
3037    }
3038
3039    private Set<String> newSet(String... elements) {
3040        return new HashSet<String>(Arrays.asList(elements));
3041    }
3042
3043    private TestSSLContext createDefaultTestSSLContext() {
3044        TestSSLContext result = TestSSLContext.create();
3045        testSSLContextsToClose.add(result);
3046        return result;
3047    }
3048
3049    enum TransferKind {
3050        CHUNKED() {
3051            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
3052                    throws IOException {
3053                response.setChunkedBody(content, chunkSize);
3054            }
3055        },
3056        FIXED_LENGTH() {
3057            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
3058                response.setBody(content);
3059            }
3060        },
3061        END_OF_STREAM() {
3062            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
3063                response.setBody(content);
3064                response.setSocketPolicy(DISCONNECT_AT_END);
3065                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
3066                    if (h.next().startsWith("Content-Length:")) {
3067                        h.remove();
3068                        break;
3069                    }
3070                }
3071            }
3072        };
3073
3074        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
3075                throws IOException;
3076
3077        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
3078            setBody(response, content.getBytes("UTF-8"), chunkSize);
3079        }
3080    }
3081
3082    enum ProxyConfig {
3083        NO_PROXY() {
3084            @Override public HttpURLConnection connect(MockWebServer server, URL url)
3085                    throws IOException {
3086                return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
3087            }
3088        },
3089
3090        CREATE_ARG() {
3091            @Override public HttpURLConnection connect(MockWebServer server, URL url)
3092                    throws IOException {
3093                return (HttpURLConnection) url.openConnection(server.toProxyAddress());
3094            }
3095        },
3096
3097        PROXY_SYSTEM_PROPERTY() {
3098            @Override public HttpURLConnection connect(MockWebServer server, URL url)
3099                    throws IOException {
3100                System.setProperty("proxyHost", "localhost");
3101                System.setProperty("proxyPort", Integer.toString(server.getPort()));
3102                return (HttpURLConnection) url.openConnection();
3103            }
3104        },
3105
3106        HTTP_PROXY_SYSTEM_PROPERTY() {
3107            @Override public HttpURLConnection connect(MockWebServer server, URL url)
3108                    throws IOException {
3109                System.setProperty("http.proxyHost", "localhost");
3110                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
3111                return (HttpURLConnection) url.openConnection();
3112            }
3113        },
3114
3115        HTTPS_PROXY_SYSTEM_PROPERTY() {
3116            @Override public HttpURLConnection connect(MockWebServer server, URL url)
3117                    throws IOException {
3118                System.setProperty("https.proxyHost", "localhost");
3119                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
3120                return (HttpURLConnection) url.openConnection();
3121            }
3122        };
3123
3124        public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException;
3125    }
3126
3127    private static class RecordingTrustManager implements X509TrustManager {
3128        private final List<String> calls = new ArrayList<String>();
3129
3130        public X509Certificate[] getAcceptedIssuers() {
3131            calls.add("getAcceptedIssuers");
3132            return new X509Certificate[] {};
3133        }
3134
3135        public void checkClientTrusted(X509Certificate[] chain, String authType)
3136                throws CertificateException {
3137            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
3138        }
3139
3140        public void checkServerTrusted(X509Certificate[] chain, String authType)
3141                throws CertificateException {
3142            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
3143        }
3144
3145        private String certificatesToString(X509Certificate[] certificates) {
3146            List<String> result = new ArrayList<String>();
3147            for (X509Certificate certificate : certificates) {
3148                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
3149            }
3150            return result.toString();
3151        }
3152    }
3153
3154    private static class RecordingHostnameVerifier implements HostnameVerifier {
3155        private final List<String> calls = new ArrayList<String>();
3156
3157        public boolean verify(String hostname, SSLSession session) {
3158            calls.add("verify " + hostname);
3159            return true;
3160        }
3161    }
3162
3163    private static class SimpleAuthenticator extends Authenticator {
3164        /** base64("username:password") */
3165        private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ=";
3166
3167        private String expectedPrompt;
3168        private RequestorType requestorType;
3169        private int requestingPort;
3170        private InetAddress requestingSite;
3171        private String requestingPrompt;
3172        private String requestingProtocol;
3173        private String requestingScheme;
3174
3175        protected PasswordAuthentication getPasswordAuthentication() {
3176            requestorType = getRequestorType();
3177            requestingPort = getRequestingPort();
3178            requestingSite = getRequestingSite();
3179            requestingPrompt = getRequestingPrompt();
3180            requestingProtocol = getRequestingProtocol();
3181            requestingScheme = getRequestingScheme();
3182            return (expectedPrompt == null || expectedPrompt.equals(requestingPrompt))
3183                    ? new PasswordAuthentication("username", "password".toCharArray())
3184                    : null;
3185        }
3186    }
3187
3188    /**
3189     * An SSLSocketFactory that delegates all calls.
3190     */
3191    private static class DelegatingSSLSocketFactory extends SSLSocketFactory {
3192
3193        protected final SSLSocketFactory delegate;
3194
3195        public DelegatingSSLSocketFactory(SSLSocketFactory delegate) {
3196            this.delegate = delegate;
3197        }
3198
3199        @Override
3200        public String[] getDefaultCipherSuites() {
3201            return delegate.getDefaultCipherSuites();
3202        }
3203
3204        @Override
3205        public String[] getSupportedCipherSuites() {
3206            return delegate.getSupportedCipherSuites();
3207        }
3208
3209        @Override
3210        public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose)
3211                throws IOException {
3212            return (SSLSocket) delegate.createSocket(s, host, port, autoClose);
3213        }
3214
3215        @Override
3216        public SSLSocket createSocket() throws IOException {
3217            return (SSLSocket) delegate.createSocket();
3218        }
3219
3220        @Override
3221        public SSLSocket createSocket(String host, int port)
3222                throws IOException, UnknownHostException {
3223            return (SSLSocket) delegate.createSocket(host, port);
3224        }
3225
3226        @Override
3227        public SSLSocket createSocket(String host, int port, InetAddress localHost,
3228                int localPort) throws IOException, UnknownHostException {
3229            return (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
3230        }
3231
3232        @Override
3233        public SSLSocket createSocket(InetAddress host, int port) throws IOException {
3234            return (SSLSocket) delegate.createSocket(host, port);
3235        }
3236
3237        @Override
3238        public SSLSocket createSocket(InetAddress address, int port,
3239                InetAddress localAddress, int localPort) throws IOException {
3240            return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
3241        }
3242
3243    }
3244
3245    /**
3246     * An SSLSocketFactory that delegates calls but limits the enabled protocols for any created
3247     * sockets.
3248     */
3249    private static class LimitedProtocolsSocketFactory extends DelegatingSSLSocketFactory {
3250
3251        private final String[] protocols;
3252
3253        private LimitedProtocolsSocketFactory(SSLSocketFactory delegate, String... protocols) {
3254            super(delegate);
3255            this.protocols = protocols;
3256        }
3257
3258        @Override
3259        public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose)
3260                throws IOException {
3261            SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose);
3262            socket.setEnabledProtocols(protocols);
3263            return socket;
3264        }
3265
3266        @Override
3267        public SSLSocket createSocket() throws IOException {
3268            SSLSocket socket = (SSLSocket) delegate.createSocket();
3269            socket.setEnabledProtocols(protocols);
3270            return socket;
3271        }
3272
3273        @Override
3274        public SSLSocket createSocket(String host, int port)
3275                throws IOException, UnknownHostException {
3276            SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
3277            socket.setEnabledProtocols(protocols);
3278            return socket;
3279        }
3280
3281        @Override
3282        public SSLSocket createSocket(String host, int port, InetAddress localHost,
3283                int localPort) throws IOException, UnknownHostException {
3284            SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
3285            socket.setEnabledProtocols(protocols);
3286            return socket;
3287        }
3288
3289        @Override
3290        public SSLSocket createSocket(InetAddress host, int port) throws IOException {
3291            SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
3292            socket.setEnabledProtocols(protocols);
3293            return socket;
3294        }
3295
3296        @Override
3297        public SSLSocket createSocket(InetAddress address, int port,
3298                InetAddress localAddress, int localPort) throws IOException {
3299            SSLSocket socket =
3300                    (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
3301            socket.setEnabledProtocols(protocols);
3302            return socket;
3303        }
3304    }
3305
3306    /**
3307     * A Socket that forwards all calls to public or protected methods, except for those
3308     * that Socket inherits from Object, to a delegate.
3309     */
3310    private static abstract class DelegatingSocket extends Socket {
3311        private final Socket delegate;
3312
3313        public DelegatingSocket(Socket delegate) {
3314            if (delegate == null) {
3315                throw new NullPointerException();
3316            }
3317            this.delegate = delegate;
3318        }
3319
3320        @Override public void bind(SocketAddress bindpoint) throws IOException { delegate.bind(bindpoint); }
3321        @Override public void close() throws IOException { delegate.close(); }
3322        @Override public void connect(SocketAddress endpoint) throws IOException { delegate.connect(endpoint); }
3323        @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { delegate.connect(endpoint, timeout); }
3324        @Override public SocketChannel getChannel() { return delegate.getChannel(); }
3325        @Override public FileDescriptor getFileDescriptor$() { return delegate.getFileDescriptor$(); }
3326        @Override public InetAddress getInetAddress() { return delegate.getInetAddress(); }
3327        @Override public InputStream getInputStream() throws IOException { return delegate.getInputStream(); }
3328        @Override public boolean getKeepAlive() throws SocketException { return delegate.getKeepAlive(); }
3329        @Override public InetAddress getLocalAddress() { return delegate.getLocalAddress(); }
3330        @Override public int getLocalPort() { return delegate.getLocalPort(); }
3331        @Override public SocketAddress getLocalSocketAddress() { return delegate.getLocalSocketAddress(); }
3332        @Override public boolean getOOBInline() throws SocketException { return delegate.getOOBInline(); }
3333        @Override public OutputStream getOutputStream() throws IOException { return delegate.getOutputStream(); }
3334        @Override public int getPort() { return delegate.getPort(); }
3335        @Override public int getReceiveBufferSize() throws SocketException { return delegate.getReceiveBufferSize(); }
3336        @Override public SocketAddress getRemoteSocketAddress() { return delegate.getRemoteSocketAddress(); }
3337        @Override public boolean getReuseAddress() throws SocketException { return delegate.getReuseAddress(); }
3338        @Override public int getSendBufferSize() throws SocketException { return delegate.getSendBufferSize(); }
3339        @Override public int getSoLinger() throws SocketException { return delegate.getSoLinger(); }
3340        @Override public int getSoTimeout() throws SocketException { return delegate.getSoTimeout(); }
3341        @Override public boolean getTcpNoDelay() throws SocketException { return delegate.getTcpNoDelay(); }
3342        @Override public int getTrafficClass() throws SocketException { return delegate.getTrafficClass(); }
3343        @Override public boolean isBound() { return delegate.isBound(); }
3344        @Override public boolean isClosed() { return delegate.isClosed(); }
3345        @Override public boolean isConnected() { return delegate.isConnected(); }
3346        @Override public boolean isInputShutdown() { return delegate.isInputShutdown(); }
3347        @Override public boolean isOutputShutdown() { return delegate.isOutputShutdown(); }
3348        @Override public void sendUrgentData(int data) throws IOException { delegate.sendUrgentData(data); }
3349        @Override public void setKeepAlive(boolean on) throws SocketException { delegate.setKeepAlive(on); }
3350        @Override public void setOOBInline(boolean on) throws SocketException { delegate.setOOBInline(on); }
3351        @Override public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { delegate.setPerformancePreferences(connectionTime, latency, bandwidth); }
3352        @Override public void setReceiveBufferSize(int size) throws SocketException { delegate.setReceiveBufferSize(size); }
3353        @Override public void setReuseAddress(boolean on) throws SocketException { delegate.setReuseAddress(on); }
3354        @Override public void setSendBufferSize(int size) throws SocketException { delegate.setSendBufferSize(size); }
3355        @Override public void setSoLinger(boolean on, int linger) throws SocketException { delegate.setSoLinger(on, linger); }
3356        @Override public void setSoTimeout(int timeout) throws SocketException { delegate.setSoTimeout(timeout); }
3357        @Override public void setTcpNoDelay(boolean on) throws SocketException { delegate.setTcpNoDelay(on); }
3358        @Override public void setTrafficClass(int tc) throws SocketException { delegate.setTrafficClass(tc); }
3359        @Override public void shutdownInput() throws IOException { delegate.shutdownInput(); }
3360        @Override public void shutdownOutput() throws IOException { delegate.shutdownOutput(); }
3361        @Override public String toString() { return delegate.toString(); }
3362    }
3363
3364    /**
3365     * An {@link javax.net.ssl.SSLSocket} that delegates all calls.
3366     */
3367    private static abstract class DelegatingSSLSocket extends SSLSocket {
3368        protected final SSLSocket delegate;
3369
3370        public DelegatingSSLSocket(SSLSocket delegate) {
3371            this.delegate = delegate;
3372        }
3373
3374        @Override public void shutdownInput() throws IOException {
3375            delegate.shutdownInput();
3376        }
3377
3378        @Override public void shutdownOutput() throws IOException {
3379            delegate.shutdownOutput();
3380        }
3381
3382        @Override public String[] getSupportedCipherSuites() {
3383            return delegate.getSupportedCipherSuites();
3384        }
3385
3386        @Override public String[] getEnabledCipherSuites() {
3387            return delegate.getEnabledCipherSuites();
3388        }
3389
3390        @Override public void setEnabledCipherSuites(String[] suites) {
3391            delegate.setEnabledCipherSuites(suites);
3392        }
3393
3394        @Override public String[] getSupportedProtocols() {
3395            return delegate.getSupportedProtocols();
3396        }
3397
3398        @Override public String[] getEnabledProtocols() {
3399            return delegate.getEnabledProtocols();
3400        }
3401
3402        @Override public void setEnabledProtocols(String[] protocols) {
3403            delegate.setEnabledProtocols(protocols);
3404        }
3405
3406        @Override public SSLSession getSession() {
3407            return delegate.getSession();
3408        }
3409
3410        @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
3411            delegate.addHandshakeCompletedListener(listener);
3412        }
3413
3414        @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
3415            delegate.removeHandshakeCompletedListener(listener);
3416        }
3417
3418        @Override public void startHandshake() throws IOException {
3419            delegate.startHandshake();
3420        }
3421
3422        @Override public void setUseClientMode(boolean mode) {
3423            delegate.setUseClientMode(mode);
3424        }
3425
3426        @Override public boolean getUseClientMode() {
3427            return delegate.getUseClientMode();
3428        }
3429
3430        @Override public void setNeedClientAuth(boolean need) {
3431            delegate.setNeedClientAuth(need);
3432        }
3433
3434        @Override public void setWantClientAuth(boolean want) {
3435            delegate.setWantClientAuth(want);
3436        }
3437
3438        @Override public boolean getNeedClientAuth() {
3439            return delegate.getNeedClientAuth();
3440        }
3441
3442        @Override public boolean getWantClientAuth() {
3443            return delegate.getWantClientAuth();
3444        }
3445
3446        @Override public void setEnableSessionCreation(boolean flag) {
3447            delegate.setEnableSessionCreation(flag);
3448        }
3449
3450        @Override public boolean getEnableSessionCreation() {
3451            return delegate.getEnableSessionCreation();
3452        }
3453
3454        @Override public SSLParameters getSSLParameters() {
3455            return delegate.getSSLParameters();
3456        }
3457
3458        @Override public void setSSLParameters(SSLParameters p) {
3459            delegate.setSSLParameters(p);
3460        }
3461
3462        @Override public void close() throws IOException {
3463            delegate.close();
3464        }
3465
3466        @Override public InetAddress getInetAddress() {
3467            return delegate.getInetAddress();
3468        }
3469
3470        @Override public InputStream getInputStream() throws IOException {
3471            return delegate.getInputStream();
3472        }
3473
3474        @Override public boolean getKeepAlive() throws SocketException {
3475            return delegate.getKeepAlive();
3476        }
3477
3478        @Override public InetAddress getLocalAddress() {
3479            return delegate.getLocalAddress();
3480        }
3481
3482        @Override public int getLocalPort() {
3483            return delegate.getLocalPort();
3484        }
3485
3486        @Override public OutputStream getOutputStream() throws IOException {
3487            return delegate.getOutputStream();
3488        }
3489
3490        @Override public int getPort() {
3491            return delegate.getPort();
3492        }
3493
3494        @Override public int getSoLinger() throws SocketException {
3495            return delegate.getSoLinger();
3496        }
3497
3498        @Override public int getReceiveBufferSize() throws SocketException {
3499            return delegate.getReceiveBufferSize();
3500        }
3501
3502        @Override public int getSendBufferSize() throws SocketException {
3503            return delegate.getSendBufferSize();
3504        }
3505
3506        @Override public int getSoTimeout() throws SocketException {
3507            return delegate.getSoTimeout();
3508        }
3509
3510        @Override public boolean getTcpNoDelay() throws SocketException {
3511            return delegate.getTcpNoDelay();
3512        }
3513
3514        @Override public void setKeepAlive(boolean keepAlive) throws SocketException {
3515            delegate.setKeepAlive(keepAlive);
3516        }
3517
3518        @Override public void setSendBufferSize(int size) throws SocketException {
3519            delegate.setSendBufferSize(size);
3520        }
3521
3522        @Override public void setReceiveBufferSize(int size) throws SocketException {
3523            delegate.setReceiveBufferSize(size);
3524        }
3525
3526        @Override public void setSoLinger(boolean on, int timeout) throws SocketException {
3527            delegate.setSoLinger(on, timeout);
3528        }
3529
3530        @Override public void setSoTimeout(int timeout) throws SocketException {
3531            delegate.setSoTimeout(timeout);
3532        }
3533
3534        @Override public void setTcpNoDelay(boolean on) throws SocketException {
3535            delegate.setTcpNoDelay(on);
3536        }
3537
3538        @Override public String toString() {
3539            return delegate.toString();
3540        }
3541
3542        @Override public SocketAddress getLocalSocketAddress() {
3543            return delegate.getLocalSocketAddress();
3544        }
3545
3546        @Override public SocketAddress getRemoteSocketAddress() {
3547            return delegate.getRemoteSocketAddress();
3548        }
3549
3550        @Override public boolean isBound() {
3551            return delegate.isBound();
3552        }
3553
3554        @Override public boolean isConnected() {
3555            return delegate.isConnected();
3556        }
3557
3558        @Override public boolean isClosed() {
3559            return delegate.isClosed();
3560        }
3561
3562        @Override public void bind(SocketAddress localAddr) throws IOException {
3563            delegate.bind(localAddr);
3564        }
3565
3566        @Override public void connect(SocketAddress remoteAddr) throws IOException {
3567            delegate.connect(remoteAddr);
3568        }
3569
3570        @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
3571            delegate.connect(remoteAddr, timeout);
3572        }
3573
3574        @Override public boolean isInputShutdown() {
3575            return delegate.isInputShutdown();
3576        }
3577
3578        @Override public boolean isOutputShutdown() {
3579            return delegate.isOutputShutdown();
3580        }
3581
3582        @Override public void setReuseAddress(boolean reuse) throws SocketException {
3583            delegate.setReuseAddress(reuse);
3584        }
3585
3586        @Override public boolean getReuseAddress() throws SocketException {
3587            return delegate.getReuseAddress();
3588        }
3589
3590        @Override public void setOOBInline(boolean oobinline) throws SocketException {
3591            delegate.setOOBInline(oobinline);
3592        }
3593
3594        @Override public boolean getOOBInline() throws SocketException {
3595            return delegate.getOOBInline();
3596        }
3597
3598        @Override public void setTrafficClass(int value) throws SocketException {
3599            delegate.setTrafficClass(value);
3600        }
3601
3602        @Override public int getTrafficClass() throws SocketException {
3603            return delegate.getTrafficClass();
3604        }
3605
3606        @Override public void sendUrgentData(int value) throws IOException {
3607            delegate.sendUrgentData(value);
3608        }
3609
3610        @Override public SocketChannel getChannel() {
3611            return delegate.getChannel();
3612        }
3613
3614        @Override public void setPerformancePreferences(int connectionTime, int latency,
3615                int bandwidth) {
3616            delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
3617        }
3618    }
3619
3620    /**
3621     * An SSLSocketFactory that delegates calls. It keeps a record of any sockets created.
3622     * If {@link #disableTlsFallbackScsv} is set to {@code true} then sockets created by the
3623     * delegate are wrapped with ones that will not accept the {@link #TLS_FALLBACK_SCSV} cipher,
3624     * thus bypassing server-side fallback checks on platforms that support it. Unfortunately this
3625     * wrapping will disable any reflection-based calls to SSLSocket from Platform.
3626     */
3627    private static class FallbackTestClientSocketFactory extends DelegatingSSLSocketFactory {
3628        /**
3629         * The cipher suite used during TLS connection fallback to indicate a fallback.
3630         * See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
3631         */
3632        public static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV";
3633
3634        private final boolean disableTlsFallbackScsv;
3635        private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>();
3636
3637        public FallbackTestClientSocketFactory(SSLSocketFactory delegate,
3638                boolean disableTlsFallbackScsv) {
3639            super(delegate);
3640            this.disableTlsFallbackScsv = disableTlsFallbackScsv;
3641        }
3642
3643        @Override public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose)
3644                throws IOException {
3645            SSLSocket socket = super.createSocket(s, host, port, autoClose);
3646            if (disableTlsFallbackScsv) {
3647                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3648            }
3649            createdSockets.add(socket);
3650            return socket;
3651        }
3652
3653        @Override public SSLSocket createSocket() throws IOException {
3654            SSLSocket socket = super.createSocket();
3655            if (disableTlsFallbackScsv) {
3656                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3657            }
3658            createdSockets.add(socket);
3659            return socket;
3660        }
3661
3662        @Override public SSLSocket createSocket(String host,int port) throws IOException {
3663            SSLSocket socket = super.createSocket(host, port);
3664            if (disableTlsFallbackScsv) {
3665                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3666            }
3667            createdSockets.add(socket);
3668            return socket;
3669        }
3670
3671        @Override public SSLSocket createSocket(String host,int port, InetAddress localHost,
3672                int localPort) throws IOException {
3673            SSLSocket socket = super.createSocket(host, port, localHost, localPort);
3674            if (disableTlsFallbackScsv) {
3675                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3676            }
3677            createdSockets.add(socket);
3678            return socket;
3679        }
3680
3681        @Override public SSLSocket createSocket(InetAddress host,int port) throws IOException {
3682            SSLSocket socket = super.createSocket(host, port);
3683            if (disableTlsFallbackScsv) {
3684                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3685            }
3686            createdSockets.add(socket);
3687            return socket;
3688        }
3689
3690        @Override public SSLSocket createSocket(InetAddress address,int port,
3691                InetAddress localAddress, int localPort) throws IOException {
3692            SSLSocket socket = super.createSocket(address, port, localAddress, localPort);
3693            if (disableTlsFallbackScsv) {
3694                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3695            }
3696            createdSockets.add(socket);
3697            return socket;
3698        }
3699
3700        public List<SSLSocket> getCreatedSockets() {
3701            return createdSockets;
3702        }
3703    }
3704
3705    private static class TlsFallbackDisabledScsvSSLSocket extends DelegatingSSLSocket {
3706
3707        private boolean tlsFallbackScsvSet;
3708
3709        public TlsFallbackDisabledScsvSSLSocket(SSLSocket socket) {
3710            super(socket);
3711        }
3712
3713        @Override public void setEnabledCipherSuites(String[] suites) {
3714            List<String> enabledCipherSuites = new ArrayList<String>(suites.length);
3715            for (String suite : suites) {
3716                if (suite.equals(FallbackTestClientSocketFactory.TLS_FALLBACK_SCSV)) {
3717                    // Record that an attempt was made to set TLS_FALLBACK_SCSV, but don't actually
3718                    // set it.
3719                    tlsFallbackScsvSet = true;
3720                } else {
3721                    enabledCipherSuites.add(suite);
3722                }
3723            }
3724            delegate.setEnabledCipherSuites(
3725                    enabledCipherSuites.toArray(new String[enabledCipherSuites.size()]));
3726        }
3727
3728        public boolean wasTlsFallbackScsvSet() {
3729            return tlsFallbackScsvSet;
3730        }
3731    }
3732}
3733