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