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