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