URLConnectionTest.java revision 0c2fd828abec671333b8b88281825fd27a783723
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 java.io.BufferedReader;
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.InputStreamReader;
24import java.io.OutputStream;
25import java.net.Authenticator;
26import java.net.CacheRequest;
27import java.net.CacheResponse;
28import java.net.ConnectException;
29import java.net.HttpRetryException;
30import java.net.HttpURLConnection;
31import java.net.PasswordAuthentication;
32import java.net.ProtocolException;
33import java.net.Proxy;
34import java.net.ResponseCache;
35import java.net.SecureCacheResponse;
36import java.net.SocketTimeoutException;
37import java.net.URI;
38import java.net.URISyntaxException;
39import java.net.URL;
40import java.net.URLConnection;
41import java.security.Principal;
42import java.security.cert.Certificate;
43import java.security.cert.CertificateException;
44import java.security.cert.X509Certificate;
45import java.util.ArrayList;
46import java.util.Arrays;
47import java.util.Collections;
48import java.util.HashSet;
49import java.util.Iterator;
50import java.util.List;
51import java.util.Map;
52import java.util.Set;
53import java.util.concurrent.atomic.AtomicInteger;
54import java.util.concurrent.atomic.AtomicBoolean;
55import java.util.concurrent.atomic.AtomicReference;
56import java.util.zip.GZIPInputStream;
57import java.util.zip.GZIPOutputStream;
58import javax.net.ssl.HostnameVerifier;
59import javax.net.ssl.HttpsURLConnection;
60import javax.net.ssl.SSLContext;
61import javax.net.ssl.SSLException;
62import javax.net.ssl.SSLHandshakeException;
63import javax.net.ssl.SSLSession;
64import javax.net.ssl.SSLSocketFactory;
65import javax.net.ssl.TrustManager;
66import javax.net.ssl.X509TrustManager;
67import libcore.java.security.TestKeyStore;
68import libcore.javax.net.ssl.TestSSLContext;
69import tests.http.DefaultResponseCache;
70import tests.http.MockResponse;
71import tests.http.MockWebServer;
72import tests.http.RecordedRequest;
73import tests.http.SocketPolicy;
74import static tests.http.SocketPolicy.DISCONNECT_AT_END;
75import static tests.http.SocketPolicy.DISCONNECT_AT_START;
76import static tests.http.SocketPolicy.SHUTDOWN_INPUT_AT_END;
77import static tests.http.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
78import tests.net.StuckServer;
79
80public class URLConnectionTest extends junit.framework.TestCase {
81
82    private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() {
83        protected PasswordAuthentication getPasswordAuthentication() {
84            return new PasswordAuthentication("username", "password".toCharArray());
85        }
86    };
87
88    private MockWebServer server = new MockWebServer();
89    private String hostName;
90
91    @Override protected void setUp() throws Exception {
92        super.setUp();
93        hostName = server.getHostName();
94    }
95
96    @Override protected void tearDown() throws Exception {
97        ResponseCache.setDefault(null);
98        Authenticator.setDefault(null);
99        System.clearProperty("proxyHost");
100        System.clearProperty("proxyPort");
101        System.clearProperty("http.proxyHost");
102        System.clearProperty("http.proxyPort");
103        System.clearProperty("https.proxyHost");
104        System.clearProperty("https.proxyPort");
105        server.shutdown();
106        super.tearDown();
107    }
108
109    public void testRequestHeaders() throws IOException, InterruptedException {
110        server.enqueue(new MockResponse());
111        server.play();
112
113        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
114        urlConnection.addRequestProperty("D", "e");
115        urlConnection.addRequestProperty("D", "f");
116        Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties();
117        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D")));
118        try {
119            requestHeaders.put("G", Arrays.asList("h"));
120            fail("Modified an unmodifiable view.");
121        } catch (UnsupportedOperationException expected) {
122        }
123        try {
124            requestHeaders.get("D").add("i");
125            fail("Modified an unmodifiable view.");
126        } catch (UnsupportedOperationException expected) {
127        }
128        try {
129            urlConnection.setRequestProperty(null, "j");
130            fail();
131        } catch (NullPointerException expected) {
132        }
133        try {
134            urlConnection.addRequestProperty(null, "k");
135            fail();
136        } catch (NullPointerException expected) {
137        }
138        urlConnection.setRequestProperty("NullValue", null); // should fail silently!
139        urlConnection.addRequestProperty("AnotherNullValue", null);  // should fail silently!
140
141        urlConnection.getResponseCode();
142        RecordedRequest request = server.takeRequest();
143        assertContains(request.getHeaders(), "D: e");
144        assertContains(request.getHeaders(), "D: f");
145        assertContainsNoneMatching(request.getHeaders(), "NullValue.*");
146        assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*");
147        assertContainsNoneMatching(request.getHeaders(), "G:.*");
148        assertContainsNoneMatching(request.getHeaders(), "null:.*");
149
150        try {
151            urlConnection.addRequestProperty("N", "o");
152            fail("Set header after connect");
153        } catch (IllegalStateException expected) {
154        }
155        try {
156            urlConnection.setRequestProperty("P", "q");
157            fail("Set header after connect");
158        } catch (IllegalStateException expected) {
159        }
160    }
161
162    public void testResponseHeaders() throws IOException, InterruptedException {
163        server.enqueue(new MockResponse()
164                .setStatus("HTTP/1.0 200 Fantastic")
165                .addHeader("A: b")
166                .addHeader("A: c")
167                .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
168        server.play();
169
170        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
171        assertEquals(200, urlConnection.getResponseCode());
172        assertEquals("Fantastic", urlConnection.getResponseMessage());
173        assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null));
174        Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields();
175        assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null));
176        assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A")));
177        try {
178            responseHeaders.put("N", Arrays.asList("o"));
179            fail("Modified an unmodifiable view.");
180        } catch (UnsupportedOperationException expected) {
181        }
182        try {
183            responseHeaders.get("A").add("d");
184            fail("Modified an unmodifiable view.");
185        } catch (UnsupportedOperationException expected) {
186        }
187    }
188
189    // Check that if we don't read to the end of a response, the next request on the
190    // recycled connection doesn't get the unread tail of the first request's response.
191    // http://code.google.com/p/android/issues/detail?id=2939
192    public void test_2939() throws Exception {
193        MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8);
194
195        server.enqueue(response);
196        server.enqueue(response);
197        server.play();
198
199        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
200        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
201    }
202
203    // Check that we recognize a few basic mime types by extension.
204    // http://code.google.com/p/android/issues/detail?id=10100
205    public void test_10100() throws Exception {
206        assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg"));
207        assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf"));
208    }
209
210    public void testConnectionsArePooled() throws Exception {
211        MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR");
212
213        server.enqueue(response);
214        server.enqueue(response);
215        server.enqueue(response);
216        server.play();
217
218        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection());
219        assertEquals(0, server.takeRequest().getSequenceNumber());
220        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection());
221        assertEquals(1, server.takeRequest().getSequenceNumber());
222        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection());
223        assertEquals(2, server.takeRequest().getSequenceNumber());
224    }
225
226    public void testChunkedConnectionsArePooled() throws Exception {
227        MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5);
228
229        server.enqueue(response);
230        server.enqueue(response);
231        server.enqueue(response);
232        server.play();
233
234        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection());
235        assertEquals(0, server.takeRequest().getSequenceNumber());
236        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection());
237        assertEquals(1, server.takeRequest().getSequenceNumber());
238        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection());
239        assertEquals(2, server.takeRequest().getSequenceNumber());
240    }
241
242    public void testServerClosesSocket() throws Exception {
243        testServerClosesOutput(DISCONNECT_AT_END);
244    }
245
246    public void testServerShutdownInput() throws Exception {
247        testServerClosesOutput(SHUTDOWN_INPUT_AT_END);
248    }
249
250    public void testServerShutdownOutput() throws Exception {
251        testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END);
252    }
253
254    private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception {
255        server.enqueue(new MockResponse()
256                .setBody("This connection won't pool properly")
257                .setSocketPolicy(socketPolicy));
258        server.enqueue(new MockResponse()
259                .setBody("This comes after a busted connection"));
260        server.play();
261
262        assertContent("This connection won't pool properly", server.getUrl("/a").openConnection());
263        assertEquals(0, server.takeRequest().getSequenceNumber());
264        assertContent("This comes after a busted connection", server.getUrl("/b").openConnection());
265        // sequence number 0 means the HTTP socket connection was not reused
266        assertEquals(0, server.takeRequest().getSequenceNumber());
267    }
268
269    enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
270
271    public void test_chunkedUpload_byteByByte() throws Exception {
272        doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
273    }
274
275    public void test_chunkedUpload_smallBuffers() throws Exception {
276        doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
277    }
278
279    public void test_chunkedUpload_largeBuffers() throws Exception {
280        doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
281    }
282
283    public void test_fixedLengthUpload_byteByByte() throws Exception {
284        doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
285    }
286
287    public void test_fixedLengthUpload_smallBuffers() throws Exception {
288        doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
289    }
290
291    public void test_fixedLengthUpload_largeBuffers() throws Exception {
292        doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
293    }
294
295    private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception {
296        int n = 512*1024;
297        server.setBodyLimit(0);
298        server.enqueue(new MockResponse());
299        server.play();
300
301        HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection();
302        conn.setDoOutput(true);
303        conn.setRequestMethod("POST");
304        if (uploadKind == TransferKind.CHUNKED) {
305            conn.setChunkedStreamingMode(-1);
306        } else {
307            conn.setFixedLengthStreamingMode(n);
308        }
309        OutputStream out = conn.getOutputStream();
310        if (writeKind == WriteKind.BYTE_BY_BYTE) {
311            for (int i = 0; i < n; ++i) {
312                out.write('x');
313            }
314        } else {
315            byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024];
316            Arrays.fill(buf, (byte) 'x');
317            for (int i = 0; i < n; i += buf.length) {
318                out.write(buf, 0, Math.min(buf.length, n - i));
319            }
320        }
321        out.close();
322        assertEquals(200, conn.getResponseCode());
323        RecordedRequest request = server.takeRequest();
324        assertEquals(n, request.getBodySize());
325        if (uploadKind == TransferKind.CHUNKED) {
326            assertTrue(request.getChunkSizes().size() > 0);
327        } else {
328            assertTrue(request.getChunkSizes().isEmpty());
329        }
330    }
331
332    /**
333     * Test that response caching is consistent with the RI and the spec.
334     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
335     */
336    public void test_responseCaching() throws Exception {
337        // Test each documented HTTP/1.1 code, plus the first unused value in each range.
338        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
339
340        // We can't test 100 because it's not really a response.
341        // assertCached(false, 100);
342        assertCached(false, 101);
343        assertCached(false, 102);
344        assertCached(true,  200);
345        assertCached(false, 201);
346        assertCached(false, 202);
347        assertCached(true,  203);
348        assertCached(false, 204);
349        assertCached(false, 205);
350        assertCached(true,  206);
351        assertCached(false, 207);
352        // (See test_responseCaching_300.)
353        assertCached(true,  301);
354        for (int i = 302; i <= 308; ++i) {
355            assertCached(false, i);
356        }
357        for (int i = 400; i <= 406; ++i) {
358            assertCached(false, i);
359        }
360        // (See test_responseCaching_407.)
361        assertCached(false, 408);
362        assertCached(false, 409);
363        // (See test_responseCaching_410.)
364        for (int i = 411; i <= 418; ++i) {
365            assertCached(false, i);
366        }
367        for (int i = 500; i <= 506; ++i) {
368            assertCached(false, i);
369        }
370    }
371
372    public void test_responseCaching_300() throws Exception {
373        // TODO: fix this for android
374        assertCached(false, 300);
375    }
376
377    /**
378     * Response code 407 should only come from proxy servers. Android's client
379     * throws if it is sent by an origin server.
380     */
381    public void testOriginServerSends407() throws Exception {
382        server.enqueue(new MockResponse().setResponseCode(407));
383        server.play();
384
385        URL url = server.getUrl("/");
386        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
387        try {
388            conn.getResponseCode();
389            fail();
390        } catch (IOException expected) {
391        }
392    }
393
394    public void test_responseCaching_410() throws Exception {
395        // the HTTP spec permits caching 410s, but the RI doesn't.
396        assertCached(false, 410);
397    }
398
399    private void assertCached(boolean shouldPut, int responseCode) throws Exception {
400        server = new MockWebServer();
401        server.enqueue(new MockResponse()
402                .setResponseCode(responseCode)
403                .setBody("ABCDE")
404                .addHeader("WWW-Authenticate: challenge"));
405        server.play();
406
407        DefaultResponseCache responseCache = new DefaultResponseCache();
408        ResponseCache.setDefault(responseCache);
409        URL url = server.getUrl("/");
410        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
411        assertEquals(responseCode, conn.getResponseCode());
412
413        // exhaust the content stream
414        try {
415            // TODO: remove special case once testUnauthorizedResponseHandling() is fixed
416            if (responseCode != 401) {
417                readAscii(conn.getInputStream(), Integer.MAX_VALUE);
418            }
419        } catch (IOException expected) {
420        }
421
422        Set<URI> expectedCachedUris = shouldPut
423                ? Collections.singleton(url.toURI())
424                : Collections.<URI>emptySet();
425        assertEquals(Integer.toString(responseCode),
426                expectedCachedUris, responseCache.getContents().keySet());
427        server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
428    }
429
430    /**
431     * Test that we can interrogate the response when the cache is being
432     * populated. http://code.google.com/p/android/issues/detail?id=7787
433     */
434    public void testResponseCacheCallbackApis() throws Exception {
435        final String body = "ABCDE";
436        final AtomicInteger cacheCount = new AtomicInteger();
437
438        server.enqueue(new MockResponse()
439                .setStatus("HTTP/1.1 200 Fantastic")
440                .addHeader("fgh: ijk")
441                .setBody(body));
442        server.play();
443
444        ResponseCache.setDefault(new ResponseCache() {
445            @Override public CacheResponse get(URI uri, String requestMethod,
446                    Map<String, List<String>> requestHeaders) throws IOException {
447                return null;
448            }
449            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
450                HttpURLConnection httpConnection = (HttpURLConnection) conn;
451                assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
452                assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"),
453                        httpConnection.getHeaderFields().get(null));
454                assertEquals(200, httpConnection.getResponseCode());
455                assertEquals("Fantastic", httpConnection.getResponseMessage());
456                assertEquals(body.length(), httpConnection.getContentLength());
457                assertEquals("ijk", httpConnection.getHeaderField("fgh"));
458                try {
459                    httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
460                    fail();
461                } catch (IOException expected) {
462                }
463                cacheCount.incrementAndGet();
464                return null;
465            }
466        });
467
468        URL url = server.getUrl("/");
469        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
470        assertEquals(body, readAscii(connection.getInputStream(), Integer.MAX_VALUE));
471        assertEquals(1, cacheCount.get());
472    }
473
474    public void testGetResponseCodeNoResponseBody() throws Exception {
475        server.enqueue(new MockResponse()
476                .addHeader("abc: def"));
477        server.play();
478
479        URL url = server.getUrl("/");
480        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
481        conn.setDoInput(false);
482        assertEquals("def", conn.getHeaderField("abc"));
483        assertEquals(200, conn.getResponseCode());
484        try {
485            conn.getInputStream();
486            fail();
487        } catch (ProtocolException expected) {
488        }
489    }
490
491    public void testConnectViaHttps() throws IOException, InterruptedException {
492        TestSSLContext testSSLContext = TestSSLContext.create();
493
494        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
495        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
496        server.play();
497
498        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
499        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
500
501        assertContent("this response comes via HTTPS", connection);
502
503        RecordedRequest request = server.takeRequest();
504        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
505    }
506
507    public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
508        TestSSLContext testSSLContext = TestSSLContext.create();
509
510        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
511        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
512        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
513        server.play();
514
515        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
516        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
517        assertContent("this response comes via HTTPS", connection);
518
519        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
520        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
521        assertContent("another response via HTTPS", connection);
522
523        assertEquals(0, server.takeRequest().getSequenceNumber());
524        assertEquals(1, server.takeRequest().getSequenceNumber());
525    }
526
527    public void testConnectViaHttpsReusingConnectionsDifferentFactories()
528            throws IOException, InterruptedException {
529        TestSSLContext testSSLContext = TestSSLContext.create();
530
531        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
532        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
533        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
534        server.play();
535
536        // install a custom SSL socket factory so the server can be authorized
537        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
538        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
539        assertContent("this response comes via HTTPS", connection);
540
541        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
542        try {
543            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
544            fail("without an SSL socket factory, the connection should fail");
545        } catch (SSLException expected) {
546        }
547    }
548
549    public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException {
550        TestSSLContext testSSLContext = TestSSLContext.create();
551
552        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
553        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
554        server.enqueue(new MockResponse().setBody("this response comes via SSL"));
555        server.play();
556
557        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
558        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
559
560        assertContent("this response comes via SSL", connection);
561
562        RecordedRequest request = server.takeRequest();
563        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
564    }
565
566    /**
567     * Verify that we don't retry connections on certificate verification errors.
568     *
569     * http://code.google.com/p/android/issues/detail?id=13178
570     */
571    public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
572        TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(),
573                                                              TestKeyStore.getServer());
574
575        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
576        server.enqueue(new MockResponse()); // unused
577        server.play();
578
579        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
580        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
581        try {
582            connection.getInputStream();
583            fail();
584        } catch (SSLHandshakeException expected) {
585            assertTrue(expected.getCause() instanceof CertificateException);
586        }
587        assertEquals(0, server.getRequestCount());
588    }
589
590    public void testConnectViaProxyUsingProxyArg() throws Exception {
591        testConnectViaProxy(ProxyConfig.CREATE_ARG);
592    }
593
594    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
595        testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
596    }
597
598    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
599        testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
600    }
601
602    private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
603        MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
604        server.enqueue(mockResponse);
605        server.play();
606
607        URL url = new URL("http://android.com/foo");
608        HttpURLConnection connection = proxyConfig.connect(server, url);
609        assertContent("this response comes via a proxy", connection);
610
611        RecordedRequest request = server.takeRequest();
612        assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine());
613        assertContains(request.getHeaders(), "Host: android.com");
614    }
615
616    public void testContentDisagreesWithContentLengthHeader() throws IOException {
617        server.enqueue(new MockResponse()
618                .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
619                .clearHeaders()
620                .addHeader("Content-Length: 3"));
621        server.play();
622
623        assertContent("abc", server.getUrl("/").openConnection());
624    }
625
626    public void testContentDisagreesWithChunkedHeader() throws IOException {
627        MockResponse mockResponse = new MockResponse();
628        mockResponse.setChunkedBody("abc", 3);
629        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
630        bytesOut.write(mockResponse.getBody());
631        bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes());
632        mockResponse.setBody(bytesOut.toByteArray());
633        mockResponse.clearHeaders();
634        mockResponse.addHeader("Transfer-encoding: chunked");
635
636        server.enqueue(mockResponse);
637        server.play();
638
639        assertContent("abc", server.getUrl("/").openConnection());
640    }
641
642    public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
643        testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
644    }
645
646    public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
647        // https should not use http proxy
648        testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
649    }
650
651    private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception {
652        TestSSLContext testSSLContext = TestSSLContext.create();
653
654        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
655        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
656        server.play();
657
658        URL url = server.getUrl("/foo");
659        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
660        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
661
662        assertContent("this response comes via HTTPS", connection);
663
664        RecordedRequest request = server.takeRequest();
665        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
666    }
667
668
669    public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
670        testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
671    }
672
673    /**
674     * We weren't honoring all of the appropriate proxy system properties when
675     * connecting via HTTPS. http://b/3097518
676     */
677    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
678        testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
679    }
680
681    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
682        testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
683    }
684
685    /**
686     * We were verifying the wrong hostname when connecting to an HTTPS site
687     * through a proxy. http://b/3097277
688     */
689    private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
690        TestSSLContext testSSLContext = TestSSLContext.create();
691        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
692
693        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
694        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
695        server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
696        server.play();
697
698        URL url = new URL("https://android.com/foo");
699        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
700        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
701        connection.setHostnameVerifier(hostnameVerifier);
702
703        assertContent("this response comes via a secure proxy", connection);
704
705        RecordedRequest connect = server.takeRequest();
706        assertEquals("Connect line failure on proxy",
707                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
708        assertContains(connect.getHeaders(), "Host: android.com");
709
710        RecordedRequest get = server.takeRequest();
711        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
712        assertContains(get.getHeaders(), "Host: android.com");
713        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
714    }
715
716    /**
717     * Test which headers are sent unencrypted to the HTTP proxy.
718     */
719    public void testProxyConnectIncludesProxyHeadersOnly()
720            throws IOException, InterruptedException {
721        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
722        TestSSLContext testSSLContext = TestSSLContext.create();
723
724        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
725        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
726        server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
727        server.play();
728
729        URL url = new URL("https://android.com/foo");
730        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
731                server.toProxyAddress());
732        connection.addRequestProperty("Private", "Secret");
733        connection.addRequestProperty("Proxy-Authorization", "bar");
734        connection.addRequestProperty("User-Agent", "baz");
735        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
736        connection.setHostnameVerifier(hostnameVerifier);
737        assertContent("encrypted response from the origin server", connection);
738
739        RecordedRequest connect = server.takeRequest();
740        assertContainsNoneMatching(connect.getHeaders(), "Private.*");
741        assertContains(connect.getHeaders(), "Proxy-Authorization: bar");
742        assertContains(connect.getHeaders(), "User-Agent: baz");
743        assertContains(connect.getHeaders(), "Host: android.com");
744        assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive");
745
746        RecordedRequest get = server.takeRequest();
747        assertContains(get.getHeaders(), "Private: Secret");
748        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
749    }
750
751    public void testDisconnectedConnection() throws IOException {
752        server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"));
753        server.play();
754
755        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
756        InputStream in = connection.getInputStream();
757        assertEquals('A', (char) in.read());
758        connection.disconnect();
759        try {
760            in.read();
761            fail("Expected a connection closed exception");
762        } catch (IOException expected) {
763        }
764    }
765
766    public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
767        testResponseCaching(TransferKind.FIXED_LENGTH);
768    }
769
770    public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
771        testResponseCaching(TransferKind.CHUNKED);
772    }
773
774    public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
775        testResponseCaching(TransferKind.END_OF_STREAM);
776    }
777
778    /**
779     * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
780     * http://code.google.com/p/android/issues/detail?id=8175
781     */
782    private void testResponseCaching(TransferKind transferKind) throws IOException {
783        MockResponse response = new MockResponse()
784                .setStatus("HTTP/1.1 200 Fantastic");
785        transferKind.setBody(response, "I love puppies but hate spiders", 1);
786        server.enqueue(response);
787        server.play();
788
789        DefaultResponseCache cache = new DefaultResponseCache();
790        ResponseCache.setDefault(cache);
791
792        // Make sure that calling skip() doesn't omit bytes from the cache.
793        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
794        InputStream in = urlConnection.getInputStream();
795        assertEquals("I love ", readAscii(in, "I love ".length()));
796        reliableSkip(in, "puppies but hate ".length());
797        assertEquals("spiders", readAscii(in, "spiders".length()));
798        assertEquals(-1, in.read());
799        in.close();
800        assertEquals(1, cache.getSuccessCount());
801        assertEquals(0, cache.getAbortCount());
802
803        urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
804        in = urlConnection.getInputStream();
805        assertEquals("I love puppies but hate spiders",
806                readAscii(in, "I love puppies but hate spiders".length()));
807        assertEquals(200, urlConnection.getResponseCode());
808        assertEquals("Fantastic", urlConnection.getResponseMessage());
809
810        assertEquals(-1, in.read());
811        assertEquals(1, cache.getMissCount());
812        assertEquals(1, cache.getHitCount());
813        assertEquals(1, cache.getSuccessCount());
814        assertEquals(0, cache.getAbortCount());
815    }
816
817    public void testSecureResponseCaching() throws IOException {
818        TestSSLContext testSSLContext = TestSSLContext.create();
819        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
820        server.enqueue(new MockResponse().setBody("ABC"));
821        server.play();
822
823        DefaultResponseCache cache = new DefaultResponseCache();
824        ResponseCache.setDefault(cache);
825
826        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
827        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
828        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
829
830        // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
831        String suite = connection.getCipherSuite();
832        List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
833        List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
834        Principal peerPrincipal = connection.getPeerPrincipal();
835        Principal localPrincipal = connection.getLocalPrincipal();
836
837        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
838        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
839        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
840
841        assertEquals(1, cache.getMissCount());
842        assertEquals(1, cache.getHitCount());
843
844        assertEquals(suite, connection.getCipherSuite());
845        assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
846        assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
847        assertEquals(peerPrincipal, connection.getPeerPrincipal());
848        assertEquals(localPrincipal, connection.getLocalPrincipal());
849    }
850
851    public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException {
852        TestSSLContext testSSLContext = TestSSLContext.create();
853        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
854        server.enqueue(new MockResponse().setBody("ABC"));
855        server.enqueue(new MockResponse().setBody("DEF"));
856        server.play();
857
858        ResponseCache insecureResponseCache = new InsecureResponseCache();
859        ResponseCache.setDefault(insecureResponseCache);
860
861        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
862        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
863        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
864
865        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
866        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
867        assertEquals("DEF", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
868    }
869
870    public void testResponseCachingAndRedirects() throws IOException {
871        server.enqueue(new MockResponse()
872                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
873                .addHeader("Location: /foo"));
874        server.enqueue(new MockResponse().setBody("ABC"));
875        server.enqueue(new MockResponse().setBody("DEF"));
876        server.play();
877
878        DefaultResponseCache cache = new DefaultResponseCache();
879        ResponseCache.setDefault(cache);
880
881        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
882        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
883
884        connection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
885        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
886
887        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
888        assertEquals(2, cache.getHitCount());
889    }
890
891    public void testSecureResponseCachingAndRedirects() throws IOException {
892        TestSSLContext testSSLContext = TestSSLContext.create();
893        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
894        server.enqueue(new MockResponse()
895                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
896                .addHeader("Location: /foo"));
897        server.enqueue(new MockResponse().setBody("ABC"));
898        server.enqueue(new MockResponse().setBody("DEF"));
899        server.play();
900
901        DefaultResponseCache cache = new DefaultResponseCache();
902        ResponseCache.setDefault(cache);
903
904        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
905        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
906        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
907
908        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
909        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
910        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
911
912        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
913        assertEquals(2, cache.getHitCount());
914    }
915
916    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
917        server.enqueue(new MockResponse().setBody("ABC"));
918        server.play();
919
920        final AtomicReference<Map<String, List<String>>> requestHeadersRef
921                = new AtomicReference<Map<String, List<String>>>();
922        ResponseCache.setDefault(new ResponseCache() {
923            @Override public CacheResponse get(URI uri, String requestMethod,
924                    Map<String, List<String>> requestHeaders) throws IOException {
925                requestHeadersRef.set(requestHeaders);
926                return null;
927            }
928            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
929                return null;
930            }
931        });
932
933        URL url = server.getUrl("/");
934        URLConnection urlConnection = url.openConnection();
935        urlConnection.addRequestProperty("A", "android");
936        readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE);
937        assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
938    }
939
940    private void reliableSkip(InputStream in, int length) throws IOException {
941        while (length > 0) {
942            length -= in.skip(length);
943        }
944    }
945
946    /**
947     * Reads {@code count} characters from the stream. If the stream is
948     * exhausted before {@code count} characters can be read, the remaining
949     * characters are returned and the stream is closed.
950     */
951    private String readAscii(InputStream in, int count) throws IOException {
952        StringBuilder result = new StringBuilder();
953        for (int i = 0; i < count; i++) {
954            int value = in.read();
955            if (value == -1) {
956                in.close();
957                break;
958            }
959            result.append((char) value);
960        }
961        return result.toString();
962    }
963
964    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
965        testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
966    }
967
968    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
969        testServerPrematureDisconnect(TransferKind.CHUNKED);
970    }
971
972    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
973        /*
974         * Intentionally empty. This case doesn't make sense because there's no
975         * such thing as a premature disconnect when the disconnect itself
976         * indicates the end of the data stream.
977         */
978    }
979
980    private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
981        MockResponse response = new MockResponse();
982        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
983        server.enqueue(truncateViolently(response, 16));
984        server.enqueue(new MockResponse().setBody("Request #2"));
985        server.play();
986
987        DefaultResponseCache cache = new DefaultResponseCache();
988        ResponseCache.setDefault(cache);
989
990        BufferedReader reader = new BufferedReader(new InputStreamReader(
991                server.getUrl("/").openConnection().getInputStream()));
992        assertEquals("ABCDE", reader.readLine());
993        try {
994            reader.readLine();
995            fail("This implementation silently ignored a truncated HTTP body.");
996        } catch (IOException expected) {
997        }
998
999        assertEquals(1, cache.getAbortCount());
1000        assertEquals(0, cache.getSuccessCount());
1001        assertContent("Request #2", server.getUrl("/").openConnection());
1002        assertEquals(1, cache.getAbortCount());
1003        assertEquals(1, cache.getSuccessCount());
1004    }
1005
1006    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
1007        testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
1008    }
1009
1010    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
1011        testClientPrematureDisconnect(TransferKind.CHUNKED);
1012    }
1013
1014    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
1015        testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
1016    }
1017
1018    private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
1019        MockResponse response = new MockResponse();
1020        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
1021        server.enqueue(response);
1022        server.enqueue(new MockResponse().setBody("Request #2"));
1023        server.play();
1024
1025        DefaultResponseCache cache = new DefaultResponseCache();
1026        ResponseCache.setDefault(cache);
1027
1028        InputStream in = server.getUrl("/").openConnection().getInputStream();
1029        assertEquals("ABCDE", readAscii(in, 5));
1030        in.close();
1031        try {
1032            in.read();
1033            fail("Expected an IOException because the stream is closed.");
1034        } catch (IOException expected) {
1035        }
1036
1037        assertEquals(1, cache.getAbortCount());
1038        assertEquals(0, cache.getSuccessCount());
1039        assertContent("Request #2", server.getUrl("/").openConnection());
1040        assertEquals(1, cache.getAbortCount());
1041        assertEquals(1, cache.getSuccessCount());
1042    }
1043
1044    /**
1045     * Shortens the body of {@code response} but not the corresponding headers.
1046     * Only useful to test how clients respond to the premature conclusion of
1047     * the HTTP body.
1048     */
1049    private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
1050        response.setSocketPolicy(DISCONNECT_AT_END);
1051        List<String> headers = new ArrayList<String>(response.getHeaders());
1052        response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
1053        response.getHeaders().clear();
1054        response.getHeaders().addAll(headers);
1055        return response;
1056    }
1057
1058    public void testMarkAndResetWithContentLengthHeader() throws IOException {
1059        testMarkAndReset(TransferKind.FIXED_LENGTH);
1060    }
1061
1062    public void testMarkAndResetWithChunkedEncoding() throws IOException {
1063        testMarkAndReset(TransferKind.CHUNKED);
1064    }
1065
1066    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
1067        testMarkAndReset(TransferKind.END_OF_STREAM);
1068    }
1069
1070    private void testMarkAndReset(TransferKind transferKind) throws IOException {
1071        MockResponse response = new MockResponse();
1072        transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
1073        server.enqueue(response);
1074        server.play();
1075
1076        DefaultResponseCache cache = new DefaultResponseCache();
1077        ResponseCache.setDefault(cache);
1078
1079        InputStream in = server.getUrl("/").openConnection().getInputStream();
1080        assertFalse("This implementation claims to support mark().", in.markSupported());
1081        in.mark(5);
1082        assertEquals("ABCDE", readAscii(in, 5));
1083        try {
1084            in.reset();
1085            fail();
1086        } catch (IOException expected) {
1087        }
1088        assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
1089
1090        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
1091        assertEquals(1, cache.getSuccessCount());
1092        assertEquals(1, cache.getHitCount());
1093    }
1094
1095    /**
1096     * We've had a bug where we forget the HTTP response when we see response
1097     * code 401. This causes a new HTTP request to be issued for every call into
1098     * the URLConnection.
1099     */
1100    public void testUnauthorizedResponseHandling() throws IOException {
1101        MockResponse response = new MockResponse()
1102                .addHeader("WWW-Authenticate: challenge")
1103                .setResponseCode(401) // UNAUTHORIZED
1104                .setBody("Unauthorized");
1105        server.enqueue(response);
1106        server.enqueue(response);
1107        server.enqueue(response);
1108        server.play();
1109
1110        URL url = server.getUrl("/");
1111        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1112
1113        assertEquals(401, conn.getResponseCode());
1114        assertEquals(401, conn.getResponseCode());
1115        assertEquals(401, conn.getResponseCode());
1116        assertEquals(1, server.getRequestCount());
1117    }
1118
1119    public void testNonHexChunkSize() throws IOException {
1120        server.enqueue(new MockResponse()
1121                .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
1122                .clearHeaders()
1123                .addHeader("Transfer-encoding: chunked"));
1124        server.play();
1125
1126        URLConnection connection = server.getUrl("/").openConnection();
1127        try {
1128            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1129            fail();
1130        } catch (IOException e) {
1131        }
1132    }
1133
1134    public void testMissingChunkBody() throws IOException {
1135        server.enqueue(new MockResponse()
1136                .setBody("5")
1137                .clearHeaders()
1138                .addHeader("Transfer-encoding: chunked")
1139                .setSocketPolicy(DISCONNECT_AT_END));
1140        server.play();
1141
1142        URLConnection connection = server.getUrl("/").openConnection();
1143        try {
1144            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1145            fail();
1146        } catch (IOException e) {
1147        }
1148    }
1149
1150    /**
1151     * This test checks whether connections are gzipped by default. This
1152     * behavior in not required by the API, so a failure of this test does not
1153     * imply a bug in the implementation.
1154     */
1155    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
1156        server.enqueue(new MockResponse()
1157                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
1158                .addHeader("Content-Encoding: gzip"));
1159        server.play();
1160
1161        URLConnection connection = server.getUrl("/").openConnection();
1162        assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1163        assertNull(connection.getContentEncoding());
1164
1165        RecordedRequest request = server.takeRequest();
1166        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1167    }
1168
1169    public void testClientConfiguredGzipContentEncoding() throws Exception {
1170        server.enqueue(new MockResponse()
1171                .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")))
1172                .addHeader("Content-Encoding: gzip"));
1173        server.play();
1174
1175        URLConnection connection = server.getUrl("/").openConnection();
1176        connection.addRequestProperty("Accept-Encoding", "gzip");
1177        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1178        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
1179
1180        RecordedRequest request = server.takeRequest();
1181        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1182    }
1183
1184    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
1185        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
1186    }
1187
1188    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
1189        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
1190    }
1191
1192    public void testClientConfiguredCustomContentEncoding() throws Exception {
1193        server.enqueue(new MockResponse()
1194                .setBody("ABCDE")
1195                .addHeader("Content-Encoding: custom"));
1196        server.play();
1197
1198        URLConnection connection = server.getUrl("/").openConnection();
1199        connection.addRequestProperty("Accept-Encoding", "custom");
1200        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1201
1202        RecordedRequest request = server.takeRequest();
1203        assertContains(request.getHeaders(), "Accept-Encoding: custom");
1204    }
1205
1206    /**
1207     * Test a bug where gzip input streams weren't exhausting the input stream,
1208     * which corrupted the request that followed.
1209     * http://code.google.com/p/android/issues/detail?id=7059
1210     */
1211    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
1212            TransferKind transferKind) throws Exception {
1213        MockResponse responseOne = new MockResponse();
1214        responseOne.addHeader("Content-Encoding: gzip");
1215        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
1216        server.enqueue(responseOne);
1217        MockResponse responseTwo = new MockResponse();
1218        transferKind.setBody(responseTwo, "two (identity)", 5);
1219        server.enqueue(responseTwo);
1220        server.play();
1221
1222        URLConnection connection = server.getUrl("/").openConnection();
1223        connection.addRequestProperty("Accept-Encoding", "gzip");
1224        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1225        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
1226        assertEquals(0, server.takeRequest().getSequenceNumber());
1227
1228        connection = server.getUrl("/").openConnection();
1229        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1230        assertEquals(1, server.takeRequest().getSequenceNumber());
1231    }
1232
1233    /**
1234     * Obnoxiously test that the chunk sizes transmitted exactly equal the
1235     * requested data+chunk header size. Although setChunkedStreamingMode()
1236     * isn't specific about whether the size applies to the data or the
1237     * complete chunk, the RI interprets it as a complete chunk.
1238     */
1239    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
1240        server.enqueue(new MockResponse());
1241        server.play();
1242
1243        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1244        urlConnection.setChunkedStreamingMode(8);
1245        urlConnection.setDoOutput(true);
1246        OutputStream outputStream = urlConnection.getOutputStream();
1247        outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII"));
1248        assertEquals(200, urlConnection.getResponseCode());
1249
1250        RecordedRequest request = server.takeRequest();
1251        assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII"));
1252        assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes());
1253    }
1254
1255    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
1256        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
1257    }
1258
1259    public void testAuthenticateWithChunkedStreaming() throws Exception {
1260        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
1261    }
1262
1263    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
1264        MockResponse pleaseAuthenticate = new MockResponse()
1265                .setResponseCode(401)
1266                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1267                .setBody("Please authenticate.");
1268        server.enqueue(pleaseAuthenticate);
1269        server.play();
1270
1271        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
1272        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1273        connection.setDoOutput(true);
1274        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1275        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1276            connection.setFixedLengthStreamingMode(requestBody.length);
1277        } else if (streamingMode == StreamingMode.CHUNKED) {
1278            connection.setChunkedStreamingMode(0);
1279        }
1280        OutputStream outputStream = connection.getOutputStream();
1281        outputStream.write(requestBody);
1282        outputStream.close();
1283        try {
1284            connection.getInputStream();
1285            fail();
1286        } catch (HttpRetryException expected) {
1287        }
1288
1289        // no authorization header for the request...
1290        RecordedRequest request = server.takeRequest();
1291        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1292        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1293    }
1294
1295    public void testSecureFixedLengthStreaming() throws Exception {
1296        testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
1297    }
1298
1299    public void testSecureChunkedStreaming() throws Exception {
1300        testSecureStreamingPost(StreamingMode.CHUNKED);
1301    }
1302
1303    /**
1304     * Users have reported problems using HTTPS with streaming request bodies.
1305     * http://code.google.com/p/android/issues/detail?id=12860
1306     */
1307    private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
1308        TestSSLContext testSSLContext = TestSSLContext.create();
1309        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1310        server.enqueue(new MockResponse().setBody("Success!"));
1311        server.play();
1312
1313        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1314        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1315        connection.setDoOutput(true);
1316        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1317        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1318            connection.setFixedLengthStreamingMode(requestBody.length);
1319        } else if (streamingMode == StreamingMode.CHUNKED) {
1320            connection.setChunkedStreamingMode(0);
1321        }
1322        OutputStream outputStream = connection.getOutputStream();
1323        outputStream.write(requestBody);
1324        outputStream.close();
1325        assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1326
1327        RecordedRequest request = server.takeRequest();
1328        assertEquals("POST / HTTP/1.1", request.getRequestLine());
1329        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1330            assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
1331        } else if (streamingMode == StreamingMode.CHUNKED) {
1332            assertEquals(Arrays.asList(4), request.getChunkSizes());
1333        }
1334        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1335    }
1336
1337    enum StreamingMode {
1338        FIXED_LENGTH, CHUNKED
1339    }
1340
1341    public void testAuthenticateWithPost() throws Exception {
1342        MockResponse pleaseAuthenticate = new MockResponse()
1343                .setResponseCode(401)
1344                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1345                .setBody("Please authenticate.");
1346        // fail auth three times...
1347        server.enqueue(pleaseAuthenticate);
1348        server.enqueue(pleaseAuthenticate);
1349        server.enqueue(pleaseAuthenticate);
1350        // ...then succeed the fourth time
1351        server.enqueue(new MockResponse().setBody("Successful auth!"));
1352        server.play();
1353
1354        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
1355        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1356        connection.setDoOutput(true);
1357        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1358        OutputStream outputStream = connection.getOutputStream();
1359        outputStream.write(requestBody);
1360        outputStream.close();
1361        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1362
1363        // no authorization header for the first request...
1364        RecordedRequest request = server.takeRequest();
1365        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1366
1367        // ...but the three requests that follow include an authorization header
1368        for (int i = 0; i < 3; i++) {
1369            request = server.takeRequest();
1370            assertEquals("POST / HTTP/1.1", request.getRequestLine());
1371            assertContains(request.getHeaders(), "Authorization: Basic "
1372                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
1373            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1374        }
1375    }
1376
1377    public void testAuthenticateWithGet() throws Exception {
1378        MockResponse pleaseAuthenticate = new MockResponse()
1379                .setResponseCode(401)
1380                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1381                .setBody("Please authenticate.");
1382        // fail auth three times...
1383        server.enqueue(pleaseAuthenticate);
1384        server.enqueue(pleaseAuthenticate);
1385        server.enqueue(pleaseAuthenticate);
1386        // ...then succeed the fourth time
1387        server.enqueue(new MockResponse().setBody("Successful auth!"));
1388        server.play();
1389
1390        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
1391        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1392        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1393
1394        // no authorization header for the first request...
1395        RecordedRequest request = server.takeRequest();
1396        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1397
1398        // ...but the three requests that follow requests include an authorization header
1399        for (int i = 0; i < 3; i++) {
1400            request = server.takeRequest();
1401            assertEquals("GET / HTTP/1.1", request.getRequestLine());
1402            assertContains(request.getHeaders(), "Authorization: Basic "
1403                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
1404        }
1405    }
1406
1407    public void testRedirectedWithChunkedEncoding() throws Exception {
1408        testRedirected(TransferKind.CHUNKED, true);
1409    }
1410
1411    public void testRedirectedWithContentLengthHeader() throws Exception {
1412        testRedirected(TransferKind.FIXED_LENGTH, true);
1413    }
1414
1415    public void testRedirectedWithNoLengthHeaders() throws Exception {
1416        testRedirected(TransferKind.END_OF_STREAM, false);
1417    }
1418
1419    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1420        MockResponse response = new MockResponse()
1421                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1422                .addHeader("Location: /foo");
1423        transferKind.setBody(response, "This page has moved!", 10);
1424        server.enqueue(response);
1425        server.enqueue(new MockResponse().setBody("This is the new location!"));
1426        server.play();
1427
1428        URLConnection connection = server.getUrl("/").openConnection();
1429        assertEquals("This is the new location!",
1430                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1431
1432        RecordedRequest first = server.takeRequest();
1433        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1434        RecordedRequest retry = server.takeRequest();
1435        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1436        if (reuse) {
1437            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1438        }
1439    }
1440
1441    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1442        TestSSLContext testSSLContext = TestSSLContext.create();
1443        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1444        server.enqueue(new MockResponse()
1445                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1446                .addHeader("Location: /foo")
1447                .setBody("This page has moved!"));
1448        server.enqueue(new MockResponse().setBody("This is the new location!"));
1449        server.play();
1450
1451        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1452        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1453        assertEquals("This is the new location!",
1454                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1455
1456        RecordedRequest first = server.takeRequest();
1457        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1458        RecordedRequest retry = server.takeRequest();
1459        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1460        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1461    }
1462
1463    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1464        TestSSLContext testSSLContext = TestSSLContext.create();
1465        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1466        server.enqueue(new MockResponse()
1467                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1468                .addHeader("Location: http://anyhost/foo")
1469                .setBody("This page has moved!"));
1470        server.play();
1471
1472        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1473        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1474        assertEquals("This page has moved!",
1475                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1476    }
1477
1478    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1479        server.enqueue(new MockResponse()
1480                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1481                .addHeader("Location: https://anyhost/foo")
1482                .setBody("This page has moved!"));
1483        server.play();
1484
1485        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1486        assertEquals("This page has moved!",
1487                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1488    }
1489
1490    public void testRedirectToAnotherOriginServer() throws Exception {
1491        MockWebServer server2 = new MockWebServer();
1492        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1493        server2.play();
1494
1495        server.enqueue(new MockResponse()
1496                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1497                .addHeader("Location: " + server2.getUrl("/").toString())
1498                .setBody("This page has moved!"));
1499        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1500        server.play();
1501
1502        URLConnection connection = server.getUrl("/").openConnection();
1503        assertEquals("This is the 2nd server!",
1504                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1505        assertEquals(server2.getUrl("/"), connection.getURL());
1506
1507        // make sure the first server was careful to recycle the connection
1508        assertEquals("This is the first server again!",
1509                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1510
1511        RecordedRequest first = server.takeRequest();
1512        assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
1513        RecordedRequest second = server2.takeRequest();
1514        assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort());
1515        RecordedRequest third = server.takeRequest();
1516        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1517
1518        server2.shutdown();
1519    }
1520
1521    public void testHttpsWithCustomTrustManager() throws Exception {
1522        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
1523        RecordingTrustManager trustManager = new RecordingTrustManager();
1524        SSLContext sc = SSLContext.getInstance("TLS");
1525        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
1526
1527        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
1528        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
1529        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
1530        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1531        try {
1532            TestSSLContext testSSLContext = TestSSLContext.create();
1533            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1534            server.enqueue(new MockResponse().setBody("ABC"));
1535            server.enqueue(new MockResponse().setBody("DEF"));
1536            server.enqueue(new MockResponse().setBody("GHI"));
1537            server.play();
1538
1539            URL url = server.getUrl("/");
1540            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
1541            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
1542            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
1543
1544            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
1545            assertEquals(Arrays.asList("checkServerTrusted ["
1546                    + "CN=" + hostName + " 1, "
1547                    + "CN=Test Intermediate Certificate Authority 1, "
1548                    + "CN=Test Root Certificate Authority 1"
1549                    + "] RSA"),
1550                    trustManager.calls);
1551        } finally {
1552            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
1553            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
1554        }
1555    }
1556
1557    public void testConnectTimeouts() throws IOException {
1558        StuckServer ss = new StuckServer();
1559        int serverPort = ss.getLocalPort();
1560        URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection();
1561        int timeout = 1000;
1562        urlConnection.setConnectTimeout(timeout);
1563        long start = System.currentTimeMillis();
1564        try {
1565            urlConnection.getInputStream();
1566            fail();
1567        } catch (SocketTimeoutException expected) {
1568            long actual = System.currentTimeMillis() - start;
1569            assertTrue(Math.abs(timeout - actual) < 500);
1570        } finally {
1571            ss.close();
1572        }
1573    }
1574
1575    public void testReadTimeouts() throws IOException {
1576        /*
1577         * This relies on the fact that MockWebServer doesn't close the
1578         * connection after a response has been sent. This causes the client to
1579         * try to read more bytes than are sent, which results in a timeout.
1580         */
1581        MockResponse timeout = new MockResponse()
1582                .setBody("ABC")
1583                .clearHeaders()
1584                .addHeader("Content-Length: 4");
1585        server.enqueue(timeout);
1586        server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
1587        server.play();
1588
1589        URLConnection urlConnection = server.getUrl("/").openConnection();
1590        urlConnection.setReadTimeout(1000);
1591        InputStream in = urlConnection.getInputStream();
1592        assertEquals('A', in.read());
1593        assertEquals('B', in.read());
1594        assertEquals('C', in.read());
1595        try {
1596            in.read(); // if Content-Length was accurate, this would return -1 immediately
1597            fail();
1598        } catch (SocketTimeoutException expected) {
1599        }
1600    }
1601
1602    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
1603        server.enqueue(new MockResponse());
1604        server.play();
1605
1606        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1607        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
1608        urlConnection.setDoOutput(true);
1609        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
1610        assertEquals(200, urlConnection.getResponseCode());
1611
1612        RecordedRequest request = server.takeRequest();
1613        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
1614    }
1615
1616    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
1617        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
1618        server.enqueue(new MockResponse());
1619        server.play();
1620
1621        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1622        a.setRequestProperty("Connection", "close");
1623        assertEquals(200, a.getResponseCode());
1624
1625        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1626        assertEquals(200, b.getResponseCode());
1627
1628        assertEquals(0, server.takeRequest().getSequenceNumber());
1629        assertEquals("When connection: close is used, each request should get its own connection",
1630                0, server.takeRequest().getSequenceNumber());
1631    }
1632
1633    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
1634        server.enqueue(new MockResponse().addHeader("Connection: close"));
1635        server.enqueue(new MockResponse());
1636        server.play();
1637
1638        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1639        assertEquals(200, a.getResponseCode());
1640
1641        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1642        assertEquals(200, b.getResponseCode());
1643
1644        assertEquals(0, server.takeRequest().getSequenceNumber());
1645        assertEquals("When connection: close is used, each request should get its own connection",
1646                0, server.takeRequest().getSequenceNumber());
1647    }
1648
1649    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
1650        MockResponse response = new MockResponse()
1651                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1652                .addHeader("Location: /foo")
1653                .addHeader("Connection: close");
1654        server.enqueue(response);
1655        server.enqueue(new MockResponse().setBody("This is the new location!"));
1656        server.play();
1657
1658        URLConnection connection = server.getUrl("/").openConnection();
1659        assertEquals("This is the new location!",
1660                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1661
1662        assertEquals(0, server.takeRequest().getSequenceNumber());
1663        assertEquals("When connection: close is used, each request should get its own connection",
1664                0, server.takeRequest().getSequenceNumber());
1665    }
1666
1667    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
1668        server.enqueue(new MockResponse()
1669                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
1670                .setBody("This body is not allowed!"));
1671        server.play();
1672
1673        URLConnection connection = server.getUrl("/").openConnection();
1674        assertEquals("This body is not allowed!",
1675                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1676    }
1677
1678    public void testSingleByteReadIsSigned() throws IOException {
1679        server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
1680        server.play();
1681
1682        URLConnection connection = server.getUrl("/").openConnection();
1683        InputStream in = connection.getInputStream();
1684        assertEquals(254, in.read());
1685        assertEquals(255, in.read());
1686        assertEquals(-1, in.read());
1687    }
1688
1689    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
1690        testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
1691    }
1692
1693    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
1694        testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
1695    }
1696
1697    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
1698        testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
1699    }
1700
1701    /**
1702     * We explicitly permit apps to close the upload stream even after it has
1703     * been transmitted.  We also permit flush so that buffered streams can
1704     * do a no-op flush when they are closed. http://b/3038470
1705     */
1706    private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
1707        server.enqueue(new MockResponse().setBody("abc"));
1708        server.play();
1709
1710        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1711        connection.setDoOutput(true);
1712        byte[] upload = "def".getBytes("UTF-8");
1713
1714        if (transferKind == TransferKind.CHUNKED) {
1715            connection.setChunkedStreamingMode(0);
1716        } else if (transferKind == TransferKind.FIXED_LENGTH) {
1717            connection.setFixedLengthStreamingMode(upload.length);
1718        }
1719
1720        OutputStream out = connection.getOutputStream();
1721        out.write(upload);
1722        assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1723
1724        out.flush(); // dubious but permitted
1725        try {
1726            out.write("ghi".getBytes("UTF-8"));
1727            fail();
1728        } catch (IOException expected) {
1729        }
1730    }
1731
1732    public void testGetHeadersThrows() throws IOException {
1733        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
1734        server.play();
1735
1736        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1737        try {
1738            connection.getInputStream();
1739            fail();
1740        } catch (IOException expected) {
1741        }
1742
1743        try {
1744            connection.getInputStream();
1745            fail();
1746        } catch (IOException expected) {
1747        }
1748    }
1749
1750    public void testGetKeepAlive() throws Exception {
1751        MockWebServer server = new MockWebServer();
1752        server.enqueue(new MockResponse().setBody("ABC"));
1753        server.play();
1754
1755        // The request should work once and then fail
1756        URLConnection connection = server.getUrl("").openConnection();
1757        InputStream input = connection.getInputStream();
1758        assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
1759        input.close();
1760        try {
1761            server.getUrl("").openConnection().getInputStream();
1762            fail();
1763        } catch (ConnectException expected) {
1764        }
1765    }
1766
1767    /**
1768     * This test goes through the exhaustive set of interesting ASCII characters
1769     * because most of those characters are interesting in some way according to
1770     * RFC 2396 and RFC 2732. http://b/1158780
1771     */
1772    public void testLenientUrlToUri() throws Exception {
1773        // alphanum
1774        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
1775
1776        // control characters
1777        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
1778        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
1779
1780        // ascii characters
1781        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1782        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1783        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
1784        testUrlToUriMapping("!", "!", "!", "!", "!");
1785        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
1786        testUrlToUriMapping("#", null, null, null, "%23");
1787        testUrlToUriMapping("$", "$", "$", "$", "$");
1788        testUrlToUriMapping("&", "&", "&", "&", "&");
1789        testUrlToUriMapping("'", "'", "'", "'", "'");
1790        testUrlToUriMapping("(", "(", "(", "(", "(");
1791        testUrlToUriMapping(")", ")", ")", ")", ")");
1792        testUrlToUriMapping("*", "*", "*", "*", "*");
1793        testUrlToUriMapping("+", "+", "+", "+", "+");
1794        testUrlToUriMapping(",", ",", ",", ",", ",");
1795        testUrlToUriMapping("-", "-", "-", "-", "-");
1796        testUrlToUriMapping(".", ".", ".", ".", ".");
1797        testUrlToUriMapping("/", null, "/", "/", "/");
1798        testUrlToUriMapping(":", null, ":", ":", ":");
1799        testUrlToUriMapping(";", ";", ";", ";", ";");
1800        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
1801        testUrlToUriMapping("=", "=", "=", "=", "=");
1802        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
1803        testUrlToUriMapping("?", null, null, "?", "?");
1804        testUrlToUriMapping("@", "@", "@", "@", "@");
1805        testUrlToUriMapping("[", null, "%5B", null, "%5B");
1806        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
1807        testUrlToUriMapping("]", null, "%5D", null, "%5D");
1808        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
1809        testUrlToUriMapping("_", "_", "_", "_", "_");
1810        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
1811        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
1812        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
1813        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
1814        testUrlToUriMapping("~", "~", "~", "~", "~");
1815        testUrlToUriMapping("~", "~", "~", "~", "~");
1816        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
1817
1818        // beyond ascii
1819        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
1820        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
1821        testUrlToUriMapping("\ud842\udf9f",
1822                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
1823    }
1824
1825    public void testLenientUrlToUriNul() throws Exception {
1826        testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this
1827    }
1828
1829    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
1830            String asQuery, String asFragment) throws Exception {
1831        if (asAuthority != null) {
1832            assertEquals("http://host" + asAuthority + ".tld/",
1833                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
1834        }
1835        if (asFile != null) {
1836            assertEquals("http://host.tld/file" + asFile + "/",
1837                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
1838        }
1839        if (asQuery != null) {
1840            assertEquals("http://host.tld/file?q" + asQuery + "=x",
1841                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
1842        }
1843        assertEquals("http://host.tld/file#" + asFragment + "-x",
1844                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
1845    }
1846
1847    /**
1848     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
1849     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
1850     * characters like '{' and '|' by escaping these characters.
1851     */
1852    private URI backdoorUrlToUri(URL url) throws Exception {
1853        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
1854
1855        ResponseCache.setDefault(new ResponseCache() {
1856            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1857                return null;
1858            }
1859            @Override public CacheResponse get(URI uri, String requestMethod,
1860                    Map<String, List<String>> requestHeaders) throws IOException {
1861                uriReference.set(uri);
1862                throw new UnsupportedOperationException();
1863            }
1864        });
1865
1866        try {
1867            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1868            connection.getResponseCode();
1869        } catch (Exception expected) {
1870        }
1871
1872        return uriReference.get();
1873    }
1874
1875    /**
1876     * Don't explode if the cache returns a null body. http://b/3373699
1877     */
1878    public void testResponseCacheReturnsNullOutputStream() throws Exception {
1879        final AtomicBoolean aborted = new AtomicBoolean();
1880        ResponseCache.setDefault(new ResponseCache() {
1881            @Override public CacheResponse get(URI uri, String requestMethod,
1882                    Map<String, List<String>> requestHeaders) throws IOException {
1883                return null;
1884            }
1885            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1886                return new CacheRequest() {
1887                    @Override public void abort() {
1888                        aborted.set(true);
1889                    }
1890                    @Override public OutputStream getBody() throws IOException {
1891                        return null;
1892                    }
1893                };
1894            }
1895        });
1896
1897        server.enqueue(new MockResponse().setBody("abcdef"));
1898        server.play();
1899
1900        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1901        InputStream in = connection.getInputStream();
1902        assertEquals("abc", readAscii(in, 3));
1903        in.close();
1904        assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
1905    }
1906
1907    /**
1908     * Encodes the response body using GZIP and adds the corresponding header.
1909     */
1910    public byte[] gzip(byte[] bytes) throws IOException {
1911        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
1912        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
1913        gzippedOut.write(bytes);
1914        gzippedOut.close();
1915        return bytesOut.toByteArray();
1916    }
1917
1918    private <T> List<T> toListOrNull(T[] arrayOrNull) {
1919        return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
1920    }
1921
1922    /**
1923     * Reads at most {@code limit} characters from {@code in} and asserts that
1924     * content equals {@code expected}.
1925     */
1926    private void assertContent(String expected, URLConnection connection, int limit)
1927            throws IOException {
1928        connection.connect();
1929        assertEquals(expected, readAscii(connection.getInputStream(), limit));
1930        ((HttpURLConnection) connection).disconnect();
1931    }
1932
1933    private void assertContent(String expected, URLConnection connection) throws IOException {
1934        assertContent(expected, connection, Integer.MAX_VALUE);
1935    }
1936
1937    private void assertContains(List<String> headers, String header) {
1938        assertTrue(headers.toString(), headers.contains(header));
1939    }
1940
1941    private void assertContainsNoneMatching(List<String> headers, String pattern) {
1942        for (String header : headers) {
1943            if (header.matches(pattern)) {
1944                fail("Header " + header + " matches " + pattern);
1945            }
1946        }
1947    }
1948
1949    private Set<String> newSet(String... elements) {
1950        return new HashSet<String>(Arrays.asList(elements));
1951    }
1952
1953    enum TransferKind {
1954        CHUNKED() {
1955            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
1956                    throws IOException {
1957                response.setChunkedBody(content, chunkSize);
1958            }
1959        },
1960        FIXED_LENGTH() {
1961            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
1962                response.setBody(content);
1963            }
1964        },
1965        END_OF_STREAM() {
1966            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
1967                response.setBody(content);
1968                response.setSocketPolicy(DISCONNECT_AT_END);
1969                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
1970                    if (h.next().startsWith("Content-Length:")) {
1971                        h.remove();
1972                        break;
1973                    }
1974                }
1975            }
1976        };
1977
1978        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
1979                throws IOException;
1980
1981        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
1982            setBody(response, content.getBytes("UTF-8"), chunkSize);
1983        }
1984    }
1985
1986    enum ProxyConfig {
1987        NO_PROXY() {
1988            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1989                    throws IOException {
1990                return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
1991            }
1992        },
1993
1994        CREATE_ARG() {
1995            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1996                    throws IOException {
1997                return (HttpURLConnection) url.openConnection(server.toProxyAddress());
1998            }
1999        },
2000
2001        PROXY_SYSTEM_PROPERTY() {
2002            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2003                    throws IOException {
2004                System.setProperty("proxyHost", "localhost");
2005                System.setProperty("proxyPort", Integer.toString(server.getPort()));
2006                return (HttpURLConnection) url.openConnection();
2007            }
2008        },
2009
2010        HTTP_PROXY_SYSTEM_PROPERTY() {
2011            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2012                    throws IOException {
2013                System.setProperty("http.proxyHost", "localhost");
2014                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
2015                return (HttpURLConnection) url.openConnection();
2016            }
2017        },
2018
2019        HTTPS_PROXY_SYSTEM_PROPERTY() {
2020            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2021                    throws IOException {
2022                System.setProperty("https.proxyHost", "localhost");
2023                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
2024                return (HttpURLConnection) url.openConnection();
2025            }
2026        };
2027
2028        public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException;
2029    }
2030
2031    private static class RecordingTrustManager implements X509TrustManager {
2032        private final List<String> calls = new ArrayList<String>();
2033
2034        public X509Certificate[] getAcceptedIssuers() {
2035            calls.add("getAcceptedIssuers");
2036            return new X509Certificate[] {};
2037        }
2038
2039        public void checkClientTrusted(X509Certificate[] chain, String authType)
2040                throws CertificateException {
2041            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
2042        }
2043
2044        public void checkServerTrusted(X509Certificate[] chain, String authType)
2045                throws CertificateException {
2046            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
2047        }
2048
2049        private String certificatesToString(X509Certificate[] certificates) {
2050            List<String> result = new ArrayList<String>();
2051            for (X509Certificate certificate : certificates) {
2052                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
2053            }
2054            return result.toString();
2055        }
2056    }
2057
2058    private static class RecordingHostnameVerifier implements HostnameVerifier {
2059        private final List<String> calls = new ArrayList<String>();
2060
2061        public boolean verify(String hostname, SSLSession session) {
2062            calls.add("verify " + hostname);
2063            return true;
2064        }
2065    }
2066
2067    private static class InsecureResponseCache extends ResponseCache {
2068        private final DefaultResponseCache delegate = new DefaultResponseCache();
2069
2070        @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
2071            return delegate.put(uri, connection);
2072        }
2073
2074        @Override public CacheResponse get(URI uri, String requestMethod,
2075                Map<String, List<String>> requestHeaders) throws IOException {
2076            final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders);
2077            if (response instanceof SecureCacheResponse) {
2078                return new CacheResponse() {
2079                    @Override public InputStream getBody() throws IOException {
2080                        return response.getBody();
2081                    }
2082                    @Override public Map<String, List<String>> getHeaders() throws IOException {
2083                        return response.getHeaders();
2084                    }
2085                };
2086            }
2087            return response;
2088        }
2089    }
2090}
2091