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