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