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