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