URLConnectionTest.java revision d742d7fc7f0593eb8c6e4ac9dd4c0f6a80374e46
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.ByteArrayOutputStream;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.net.Authenticator;
24import java.net.CacheRequest;
25import java.net.CacheResponse;
26import java.net.ConnectException;
27import java.net.HttpRetryException;
28import java.net.HttpURLConnection;
29import java.net.PasswordAuthentication;
30import java.net.ProtocolException;
31import java.net.Proxy;
32import java.net.ResponseCache;
33import java.net.SocketTimeoutException;
34import java.net.URI;
35import java.net.URL;
36import java.net.URLConnection;
37import java.security.cert.CertificateException;
38import java.security.cert.X509Certificate;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collections;
42import java.util.HashSet;
43import java.util.Iterator;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.atomic.AtomicBoolean;
48import java.util.concurrent.atomic.AtomicReference;
49import java.util.zip.GZIPInputStream;
50import java.util.zip.GZIPOutputStream;
51import javax.net.ssl.HostnameVerifier;
52import javax.net.ssl.HttpsURLConnection;
53import javax.net.ssl.SSLContext;
54import javax.net.ssl.SSLException;
55import javax.net.ssl.SSLHandshakeException;
56import javax.net.ssl.SSLSession;
57import javax.net.ssl.SSLSocketFactory;
58import javax.net.ssl.TrustManager;
59import javax.net.ssl.X509TrustManager;
60import libcore.java.security.TestKeyStore;
61import libcore.javax.net.ssl.TestSSLContext;
62import tests.http.MockResponse;
63import tests.http.MockWebServer;
64import tests.http.RecordedRequest;
65import tests.http.SocketPolicy;
66import static tests.http.SocketPolicy.DISCONNECT_AT_END;
67import static tests.http.SocketPolicy.DISCONNECT_AT_START;
68import static tests.http.SocketPolicy.SHUTDOWN_INPUT_AT_END;
69import static tests.http.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
70import tests.net.StuckServer;
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 = server.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    public void testServerClosesSocket() throws Exception {
235        testServerClosesOutput(DISCONNECT_AT_END);
236    }
237
238    public void testServerShutdownInput() throws Exception {
239        testServerClosesOutput(SHUTDOWN_INPUT_AT_END);
240    }
241
242    public void testServerShutdownOutput() throws Exception {
243        testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END);
244    }
245
246    private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception {
247        server.enqueue(new MockResponse()
248                .setBody("This connection won't pool properly")
249                .setSocketPolicy(socketPolicy));
250        server.enqueue(new MockResponse()
251                .setBody("This comes after a busted connection"));
252        server.play();
253
254        assertContent("This connection won't pool properly", server.getUrl("/a").openConnection());
255        assertEquals(0, server.takeRequest().getSequenceNumber());
256        assertContent("This comes after a busted connection", server.getUrl("/b").openConnection());
257        // sequence number 0 means the HTTP socket connection was not reused
258        assertEquals(0, server.takeRequest().getSequenceNumber());
259    }
260
261    enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
262
263    public void test_chunkedUpload_byteByByte() throws Exception {
264        doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
265    }
266
267    public void test_chunkedUpload_smallBuffers() throws Exception {
268        doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
269    }
270
271    public void test_chunkedUpload_largeBuffers() throws Exception {
272        doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
273    }
274
275    public void test_fixedLengthUpload_byteByByte() throws Exception {
276        doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
277    }
278
279    public void test_fixedLengthUpload_smallBuffers() throws Exception {
280        doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
281    }
282
283    public void test_fixedLengthUpload_largeBuffers() throws Exception {
284        doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
285    }
286
287    private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception {
288        int n = 512*1024;
289        server.setBodyLimit(0);
290        server.enqueue(new MockResponse());
291        server.play();
292
293        HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection();
294        conn.setDoOutput(true);
295        conn.setRequestMethod("POST");
296        if (uploadKind == TransferKind.CHUNKED) {
297            conn.setChunkedStreamingMode(-1);
298        } else {
299            conn.setFixedLengthStreamingMode(n);
300        }
301        OutputStream out = conn.getOutputStream();
302        if (writeKind == WriteKind.BYTE_BY_BYTE) {
303            for (int i = 0; i < n; ++i) {
304                out.write('x');
305            }
306        } else {
307            byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024];
308            Arrays.fill(buf, (byte) 'x');
309            for (int i = 0; i < n; i += buf.length) {
310                out.write(buf, 0, Math.min(buf.length, n - i));
311            }
312        }
313        out.close();
314        assertEquals(200, conn.getResponseCode());
315        RecordedRequest request = server.takeRequest();
316        assertEquals(n, request.getBodySize());
317        if (uploadKind == TransferKind.CHUNKED) {
318            assertTrue(request.getChunkSizes().size() > 0);
319        } else {
320            assertTrue(request.getChunkSizes().isEmpty());
321        }
322    }
323
324    public void testGetResponseCodeNoResponseBody() throws Exception {
325        server.enqueue(new MockResponse()
326                .addHeader("abc: def"));
327        server.play();
328
329        URL url = server.getUrl("/");
330        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
331        conn.setDoInput(false);
332        assertEquals("def", conn.getHeaderField("abc"));
333        assertEquals(200, conn.getResponseCode());
334        try {
335            conn.getInputStream();
336            fail();
337        } catch (ProtocolException expected) {
338        }
339    }
340
341    public void testConnectViaHttps() throws IOException, InterruptedException {
342        TestSSLContext testSSLContext = TestSSLContext.create();
343
344        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
345        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
346        server.play();
347
348        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
349        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
350
351        assertContent("this response comes via HTTPS", connection);
352
353        RecordedRequest request = server.takeRequest();
354        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
355    }
356
357    public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
358        TestSSLContext testSSLContext = TestSSLContext.create();
359
360        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
361        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
362        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
363        server.play();
364
365        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
366        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
367        assertContent("this response comes via HTTPS", connection);
368
369        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
370        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
371        assertContent("another response via HTTPS", connection);
372
373        assertEquals(0, server.takeRequest().getSequenceNumber());
374        assertEquals(1, server.takeRequest().getSequenceNumber());
375    }
376
377    public void testConnectViaHttpsReusingConnectionsDifferentFactories()
378            throws IOException, InterruptedException {
379        TestSSLContext testSSLContext = TestSSLContext.create();
380
381        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
382        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
383        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
384        server.play();
385
386        // install a custom SSL socket factory so the server can be authorized
387        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
388        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
389        assertContent("this response comes via HTTPS", connection);
390
391        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
392        try {
393            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
394            fail("without an SSL socket factory, the connection should fail");
395        } catch (SSLException expected) {
396        }
397    }
398
399    public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException {
400        TestSSLContext testSSLContext = TestSSLContext.create();
401
402        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
403        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
404        server.enqueue(new MockResponse().setBody("this response comes via SSL"));
405        server.play();
406
407        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
408        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
409
410        assertContent("this response comes via SSL", connection);
411
412        RecordedRequest request = server.takeRequest();
413        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
414    }
415
416    /**
417     * Verify that we don't retry connections on certificate verification errors.
418     *
419     * http://code.google.com/p/android/issues/detail?id=13178
420     */
421    public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
422        TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(),
423                                                              TestKeyStore.getServer());
424
425        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
426        server.enqueue(new MockResponse()); // unused
427        server.play();
428
429        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
430        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
431        try {
432            connection.getInputStream();
433            fail();
434        } catch (SSLHandshakeException expected) {
435            assertTrue(expected.getCause() instanceof CertificateException);
436        }
437        assertEquals(0, server.getRequestCount());
438    }
439
440    public void testConnectViaProxyUsingProxyArg() throws Exception {
441        testConnectViaProxy(ProxyConfig.CREATE_ARG);
442    }
443
444    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
445        testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
446    }
447
448    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
449        testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
450    }
451
452    private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
453        MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
454        server.enqueue(mockResponse);
455        server.play();
456
457        URL url = new URL("http://android.com/foo");
458        HttpURLConnection connection = proxyConfig.connect(server, url);
459        assertContent("this response comes via a proxy", connection);
460
461        RecordedRequest request = server.takeRequest();
462        assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine());
463        assertContains(request.getHeaders(), "Host: android.com");
464    }
465
466    public void testContentDisagreesWithContentLengthHeader() throws IOException {
467        server.enqueue(new MockResponse()
468                .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
469                .clearHeaders()
470                .addHeader("Content-Length: 3"));
471        server.play();
472
473        assertContent("abc", server.getUrl("/").openConnection());
474    }
475
476    public void testContentDisagreesWithChunkedHeader() throws IOException {
477        MockResponse mockResponse = new MockResponse();
478        mockResponse.setChunkedBody("abc", 3);
479        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
480        bytesOut.write(mockResponse.getBody());
481        bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes());
482        mockResponse.setBody(bytesOut.toByteArray());
483        mockResponse.clearHeaders();
484        mockResponse.addHeader("Transfer-encoding: chunked");
485
486        server.enqueue(mockResponse);
487        server.play();
488
489        assertContent("abc", server.getUrl("/").openConnection());
490    }
491
492    public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
493        testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
494    }
495
496    public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
497        // https should not use http proxy
498        testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
499    }
500
501    private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception {
502        TestSSLContext testSSLContext = TestSSLContext.create();
503
504        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
505        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
506        server.play();
507
508        URL url = server.getUrl("/foo");
509        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
510        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
511
512        assertContent("this response comes via HTTPS", connection);
513
514        RecordedRequest request = server.takeRequest();
515        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
516    }
517
518
519    public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
520        testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
521    }
522
523    /**
524     * We weren't honoring all of the appropriate proxy system properties when
525     * connecting via HTTPS. http://b/3097518
526     */
527    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
528        testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
529    }
530
531    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
532        testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
533    }
534
535    /**
536     * We were verifying the wrong hostname when connecting to an HTTPS site
537     * through a proxy. http://b/3097277
538     */
539    private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
540        TestSSLContext testSSLContext = TestSSLContext.create();
541        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
542
543        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
544        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
545        server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
546        server.play();
547
548        URL url = new URL("https://android.com/foo");
549        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
550        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
551        connection.setHostnameVerifier(hostnameVerifier);
552
553        assertContent("this response comes via a secure proxy", connection);
554
555        RecordedRequest connect = server.takeRequest();
556        assertEquals("Connect line failure on proxy",
557                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
558        assertContains(connect.getHeaders(), "Host: android.com");
559
560        RecordedRequest get = server.takeRequest();
561        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
562        assertContains(get.getHeaders(), "Host: android.com");
563        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
564    }
565
566    /**
567     * Test which headers are sent unencrypted to the HTTP proxy.
568     */
569    public void testProxyConnectIncludesProxyHeadersOnly()
570            throws IOException, InterruptedException {
571        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
572        TestSSLContext testSSLContext = TestSSLContext.create();
573
574        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
575        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
576        server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
577        server.play();
578
579        URL url = new URL("https://android.com/foo");
580        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
581                server.toProxyAddress());
582        connection.addRequestProperty("Private", "Secret");
583        connection.addRequestProperty("Proxy-Authorization", "bar");
584        connection.addRequestProperty("User-Agent", "baz");
585        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
586        connection.setHostnameVerifier(hostnameVerifier);
587        assertContent("encrypted response from the origin server", connection);
588
589        RecordedRequest connect = server.takeRequest();
590        assertContainsNoneMatching(connect.getHeaders(), "Private.*");
591        assertContains(connect.getHeaders(), "Proxy-Authorization: bar");
592        assertContains(connect.getHeaders(), "User-Agent: baz");
593        assertContains(connect.getHeaders(), "Host: android.com");
594        assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive");
595
596        RecordedRequest get = server.takeRequest();
597        assertContains(get.getHeaders(), "Private: Secret");
598        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
599    }
600
601    public void testDisconnectedConnection() throws IOException {
602        server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"));
603        server.play();
604
605        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
606        InputStream in = connection.getInputStream();
607        assertEquals('A', (char) in.read());
608        connection.disconnect();
609        try {
610            in.read();
611            fail("Expected a connection closed exception");
612        } catch (IOException expected) {
613        }
614    }
615
616    public void testDisconnectBeforeConnect() throws IOException {
617        server.enqueue(new MockResponse().setBody("A"));
618        server.play();
619
620        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
621        connection.disconnect();
622
623        assertContent("A", connection);
624        assertEquals(200, connection.getResponseCode());
625    }
626
627    public void testDefaultRequestProperty() throws Exception {
628        URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
629        assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
630    }
631
632    /**
633     * Reads {@code count} characters from the stream. If the stream is
634     * exhausted before {@code count} characters can be read, the remaining
635     * characters are returned and the stream is closed.
636     */
637    private String readAscii(InputStream in, int count) throws IOException {
638        StringBuilder result = new StringBuilder();
639        for (int i = 0; i < count; i++) {
640            int value = in.read();
641            if (value == -1) {
642                in.close();
643                break;
644            }
645            result.append((char) value);
646        }
647        return result.toString();
648    }
649
650    public void testMarkAndResetWithContentLengthHeader() throws IOException {
651        testMarkAndReset(TransferKind.FIXED_LENGTH);
652    }
653
654    public void testMarkAndResetWithChunkedEncoding() throws IOException {
655        testMarkAndReset(TransferKind.CHUNKED);
656    }
657
658    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
659        testMarkAndReset(TransferKind.END_OF_STREAM);
660    }
661
662    private void testMarkAndReset(TransferKind transferKind) throws IOException {
663        MockResponse response = new MockResponse();
664        transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
665        server.enqueue(response);
666        server.enqueue(response);
667        server.play();
668
669        InputStream in = server.getUrl("/").openConnection().getInputStream();
670        assertFalse("This implementation claims to support mark().", in.markSupported());
671        in.mark(5);
672        assertEquals("ABCDE", readAscii(in, 5));
673        try {
674            in.reset();
675            fail();
676        } catch (IOException expected) {
677        }
678        assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
679        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
680    }
681
682    /**
683     * We've had a bug where we forget the HTTP response when we see response
684     * code 401. This causes a new HTTP request to be issued for every call into
685     * the URLConnection.
686     */
687    public void testUnauthorizedResponseHandling() throws IOException {
688        MockResponse response = new MockResponse()
689                .addHeader("WWW-Authenticate: challenge")
690                .setResponseCode(401) // UNAUTHORIZED
691                .setBody("Unauthorized");
692        server.enqueue(response);
693        server.enqueue(response);
694        server.enqueue(response);
695        server.play();
696
697        URL url = server.getUrl("/");
698        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
699
700        assertEquals(401, conn.getResponseCode());
701        assertEquals(401, conn.getResponseCode());
702        assertEquals(401, conn.getResponseCode());
703        assertEquals(1, server.getRequestCount());
704    }
705
706    public void testNonHexChunkSize() throws IOException {
707        server.enqueue(new MockResponse()
708                .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
709                .clearHeaders()
710                .addHeader("Transfer-encoding: chunked"));
711        server.play();
712
713        URLConnection connection = server.getUrl("/").openConnection();
714        try {
715            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
716            fail();
717        } catch (IOException e) {
718        }
719    }
720
721    public void testMissingChunkBody() throws IOException {
722        server.enqueue(new MockResponse()
723                .setBody("5")
724                .clearHeaders()
725                .addHeader("Transfer-encoding: chunked")
726                .setSocketPolicy(DISCONNECT_AT_END));
727        server.play();
728
729        URLConnection connection = server.getUrl("/").openConnection();
730        try {
731            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
732            fail();
733        } catch (IOException e) {
734        }
735    }
736
737    /**
738     * This test checks whether connections are gzipped by default. This
739     * behavior in not required by the API, so a failure of this test does not
740     * imply a bug in the implementation.
741     */
742    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
743        server.enqueue(new MockResponse()
744                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
745                .addHeader("Content-Encoding: gzip"));
746        server.play();
747
748        URLConnection connection = server.getUrl("/").openConnection();
749        assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
750        assertNull(connection.getContentEncoding());
751
752        RecordedRequest request = server.takeRequest();
753        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
754    }
755
756    public void testClientConfiguredGzipContentEncoding() throws Exception {
757        server.enqueue(new MockResponse()
758                .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")))
759                .addHeader("Content-Encoding: gzip"));
760        server.play();
761
762        URLConnection connection = server.getUrl("/").openConnection();
763        connection.addRequestProperty("Accept-Encoding", "gzip");
764        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
765        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
766
767        RecordedRequest request = server.takeRequest();
768        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
769    }
770
771    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
772        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
773    }
774
775    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
776        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
777    }
778
779    public void testClientConfiguredCustomContentEncoding() throws Exception {
780        server.enqueue(new MockResponse()
781                .setBody("ABCDE")
782                .addHeader("Content-Encoding: custom"));
783        server.play();
784
785        URLConnection connection = server.getUrl("/").openConnection();
786        connection.addRequestProperty("Accept-Encoding", "custom");
787        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
788
789        RecordedRequest request = server.takeRequest();
790        assertContains(request.getHeaders(), "Accept-Encoding: custom");
791    }
792
793    /**
794     * Test a bug where gzip input streams weren't exhausting the input stream,
795     * which corrupted the request that followed.
796     * http://code.google.com/p/android/issues/detail?id=7059
797     */
798    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
799            TransferKind transferKind) throws Exception {
800        MockResponse responseOne = new MockResponse();
801        responseOne.addHeader("Content-Encoding: gzip");
802        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
803        server.enqueue(responseOne);
804        MockResponse responseTwo = new MockResponse();
805        transferKind.setBody(responseTwo, "two (identity)", 5);
806        server.enqueue(responseTwo);
807        server.play();
808
809        URLConnection connection = server.getUrl("/").openConnection();
810        connection.addRequestProperty("Accept-Encoding", "gzip");
811        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
812        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
813        assertEquals(0, server.takeRequest().getSequenceNumber());
814
815        connection = server.getUrl("/").openConnection();
816        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
817        assertEquals(1, server.takeRequest().getSequenceNumber());
818    }
819
820    /**
821     * Obnoxiously test that the chunk sizes transmitted exactly equal the
822     * requested data+chunk header size. Although setChunkedStreamingMode()
823     * isn't specific about whether the size applies to the data or the
824     * complete chunk, the RI interprets it as a complete chunk.
825     */
826    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
827        server.enqueue(new MockResponse());
828        server.play();
829
830        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
831        urlConnection.setChunkedStreamingMode(8);
832        urlConnection.setDoOutput(true);
833        OutputStream outputStream = urlConnection.getOutputStream();
834        outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII"));
835        assertEquals(200, urlConnection.getResponseCode());
836
837        RecordedRequest request = server.takeRequest();
838        assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII"));
839        assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes());
840    }
841
842    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
843        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
844    }
845
846    public void testAuthenticateWithChunkedStreaming() throws Exception {
847        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
848    }
849
850    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
851        MockResponse pleaseAuthenticate = new MockResponse()
852                .setResponseCode(401)
853                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
854                .setBody("Please authenticate.");
855        server.enqueue(pleaseAuthenticate);
856        server.play();
857
858        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
859        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
860        connection.setDoOutput(true);
861        byte[] requestBody = { 'A', 'B', 'C', 'D' };
862        if (streamingMode == StreamingMode.FIXED_LENGTH) {
863            connection.setFixedLengthStreamingMode(requestBody.length);
864        } else if (streamingMode == StreamingMode.CHUNKED) {
865            connection.setChunkedStreamingMode(0);
866        }
867        OutputStream outputStream = connection.getOutputStream();
868        outputStream.write(requestBody);
869        outputStream.close();
870        try {
871            connection.getInputStream();
872            fail();
873        } catch (HttpRetryException expected) {
874        }
875
876        // no authorization header for the request...
877        RecordedRequest request = server.takeRequest();
878        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
879        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
880    }
881
882    public void testSecureFixedLengthStreaming() throws Exception {
883        testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
884    }
885
886    public void testSecureChunkedStreaming() throws Exception {
887        testSecureStreamingPost(StreamingMode.CHUNKED);
888    }
889
890    /**
891     * Users have reported problems using HTTPS with streaming request bodies.
892     * http://code.google.com/p/android/issues/detail?id=12860
893     */
894    private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
895        TestSSLContext testSSLContext = TestSSLContext.create();
896        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
897        server.enqueue(new MockResponse().setBody("Success!"));
898        server.play();
899
900        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
901        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
902        connection.setDoOutput(true);
903        byte[] requestBody = { 'A', 'B', 'C', 'D' };
904        if (streamingMode == StreamingMode.FIXED_LENGTH) {
905            connection.setFixedLengthStreamingMode(requestBody.length);
906        } else if (streamingMode == StreamingMode.CHUNKED) {
907            connection.setChunkedStreamingMode(0);
908        }
909        OutputStream outputStream = connection.getOutputStream();
910        outputStream.write(requestBody);
911        outputStream.close();
912        assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
913
914        RecordedRequest request = server.takeRequest();
915        assertEquals("POST / HTTP/1.1", request.getRequestLine());
916        if (streamingMode == StreamingMode.FIXED_LENGTH) {
917            assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
918        } else if (streamingMode == StreamingMode.CHUNKED) {
919            assertEquals(Arrays.asList(4), request.getChunkSizes());
920        }
921        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
922    }
923
924    enum StreamingMode {
925        FIXED_LENGTH, CHUNKED
926    }
927
928    public void testAuthenticateWithPost() throws Exception {
929        MockResponse pleaseAuthenticate = new MockResponse()
930                .setResponseCode(401)
931                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
932                .setBody("Please authenticate.");
933        // fail auth three times...
934        server.enqueue(pleaseAuthenticate);
935        server.enqueue(pleaseAuthenticate);
936        server.enqueue(pleaseAuthenticate);
937        // ...then succeed the fourth time
938        server.enqueue(new MockResponse().setBody("Successful auth!"));
939        server.play();
940
941        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
942        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
943        connection.setDoOutput(true);
944        byte[] requestBody = { 'A', 'B', 'C', 'D' };
945        OutputStream outputStream = connection.getOutputStream();
946        outputStream.write(requestBody);
947        outputStream.close();
948        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
949
950        // no authorization header for the first request...
951        RecordedRequest request = server.takeRequest();
952        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
953
954        // ...but the three requests that follow include an authorization header
955        for (int i = 0; i < 3; i++) {
956            request = server.takeRequest();
957            assertEquals("POST / HTTP/1.1", request.getRequestLine());
958            assertContains(request.getHeaders(), "Authorization: Basic "
959                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
960            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
961        }
962    }
963
964    public void testAuthenticateWithGet() throws Exception {
965        MockResponse pleaseAuthenticate = new MockResponse()
966                .setResponseCode(401)
967                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
968                .setBody("Please authenticate.");
969        // fail auth three times...
970        server.enqueue(pleaseAuthenticate);
971        server.enqueue(pleaseAuthenticate);
972        server.enqueue(pleaseAuthenticate);
973        // ...then succeed the fourth time
974        server.enqueue(new MockResponse().setBody("Successful auth!"));
975        server.play();
976
977        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
978        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
979        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
980
981        // no authorization header for the first request...
982        RecordedRequest request = server.takeRequest();
983        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
984
985        // ...but the three requests that follow requests include an authorization header
986        for (int i = 0; i < 3; i++) {
987            request = server.takeRequest();
988            assertEquals("GET / HTTP/1.1", request.getRequestLine());
989            assertContains(request.getHeaders(), "Authorization: Basic "
990                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
991        }
992    }
993
994    public void testRedirectedWithChunkedEncoding() throws Exception {
995        testRedirected(TransferKind.CHUNKED, true);
996    }
997
998    public void testRedirectedWithContentLengthHeader() throws Exception {
999        testRedirected(TransferKind.FIXED_LENGTH, true);
1000    }
1001
1002    public void testRedirectedWithNoLengthHeaders() throws Exception {
1003        testRedirected(TransferKind.END_OF_STREAM, false);
1004    }
1005
1006    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1007        MockResponse response = new MockResponse()
1008                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1009                .addHeader("Location: /foo");
1010        transferKind.setBody(response, "This page has moved!", 10);
1011        server.enqueue(response);
1012        server.enqueue(new MockResponse().setBody("This is the new location!"));
1013        server.play();
1014
1015        URLConnection connection = server.getUrl("/").openConnection();
1016        assertEquals("This is the new location!",
1017                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1018
1019        RecordedRequest first = server.takeRequest();
1020        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1021        RecordedRequest retry = server.takeRequest();
1022        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1023        if (reuse) {
1024            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1025        }
1026    }
1027
1028    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1029        TestSSLContext testSSLContext = TestSSLContext.create();
1030        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1031        server.enqueue(new MockResponse()
1032                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1033                .addHeader("Location: /foo")
1034                .setBody("This page has moved!"));
1035        server.enqueue(new MockResponse().setBody("This is the new location!"));
1036        server.play();
1037
1038        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1039        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1040        assertEquals("This is the new location!",
1041                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1042
1043        RecordedRequest first = server.takeRequest();
1044        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1045        RecordedRequest retry = server.takeRequest();
1046        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1047        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1048    }
1049
1050    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1051        TestSSLContext testSSLContext = TestSSLContext.create();
1052        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1053        server.enqueue(new MockResponse()
1054                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1055                .addHeader("Location: http://anyhost/foo")
1056                .setBody("This page has moved!"));
1057        server.play();
1058
1059        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1060        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1061        assertEquals("This page has moved!",
1062                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1063    }
1064
1065    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1066        server.enqueue(new MockResponse()
1067                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1068                .addHeader("Location: https://anyhost/foo")
1069                .setBody("This page has moved!"));
1070        server.play();
1071
1072        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1073        assertEquals("This page has moved!",
1074                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1075    }
1076
1077    public void testRedirectToAnotherOriginServer() throws Exception {
1078        MockWebServer server2 = new MockWebServer();
1079        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1080        server2.play();
1081
1082        server.enqueue(new MockResponse()
1083                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1084                .addHeader("Location: " + server2.getUrl("/").toString())
1085                .setBody("This page has moved!"));
1086        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1087        server.play();
1088
1089        URLConnection connection = server.getUrl("/").openConnection();
1090        assertEquals("This is the 2nd server!",
1091                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1092        assertEquals(server2.getUrl("/"), connection.getURL());
1093
1094        // make sure the first server was careful to recycle the connection
1095        assertEquals("This is the first server again!",
1096                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1097
1098        RecordedRequest first = server.takeRequest();
1099        assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
1100        RecordedRequest second = server2.takeRequest();
1101        assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort());
1102        RecordedRequest third = server.takeRequest();
1103        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1104
1105        server2.shutdown();
1106    }
1107
1108    public void testResponse300MultipleChoiceWithPost() throws Exception {
1109        // Chrome doesn't follow the redirect, but Firefox and the RI both do
1110        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
1111    }
1112
1113    public void testResponse301MovedPermanentlyWithPost() throws Exception {
1114        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
1115    }
1116
1117    public void testResponse302MovedTemporarilyWithPost() throws Exception {
1118        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
1119    }
1120
1121    public void testResponse303SeeOtherWithPost() throws Exception {
1122        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
1123    }
1124
1125    private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
1126        server.enqueue(new MockResponse()
1127                .setResponseCode(redirectCode)
1128                .addHeader("Location: /page2")
1129                .setBody("This page has moved!"));
1130        server.enqueue(new MockResponse().setBody("Page 2"));
1131        server.play();
1132
1133        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection();
1134        connection.setDoOutput(true);
1135        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1136        OutputStream outputStream = connection.getOutputStream();
1137        outputStream.write(requestBody);
1138        outputStream.close();
1139        assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1140        assertTrue(connection.getDoOutput());
1141
1142        RecordedRequest page1 = server.takeRequest();
1143        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
1144        assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody()));
1145
1146        RecordedRequest page2 = server.takeRequest();
1147        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
1148    }
1149
1150    public void testResponse305UseProxy() throws Exception {
1151        server.play();
1152        server.enqueue(new MockResponse()
1153                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
1154                .addHeader("Location: " + server.getUrl("/"))
1155                .setBody("This page has moved!"));
1156        server.enqueue(new MockResponse().setBody("Proxy Response"));
1157
1158        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection();
1159        // Fails on the RI, which gets "Proxy Response"
1160        assertEquals("This page has moved!",
1161                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1162
1163        RecordedRequest page1 = server.takeRequest();
1164        assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
1165        assertEquals(1, server.getRequestCount());
1166    }
1167
1168    public void testHttpsWithCustomTrustManager() throws Exception {
1169        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
1170        RecordingTrustManager trustManager = new RecordingTrustManager();
1171        SSLContext sc = SSLContext.getInstance("TLS");
1172        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
1173
1174        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
1175        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
1176        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
1177        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1178        try {
1179            TestSSLContext testSSLContext = TestSSLContext.create();
1180            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1181            server.enqueue(new MockResponse().setBody("ABC"));
1182            server.enqueue(new MockResponse().setBody("DEF"));
1183            server.enqueue(new MockResponse().setBody("GHI"));
1184            server.play();
1185
1186            URL url = server.getUrl("/");
1187            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
1188            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
1189            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
1190
1191            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
1192            assertEquals(Arrays.asList("checkServerTrusted ["
1193                    + "CN=" + hostName + " 1, "
1194                    + "CN=Test Intermediate Certificate Authority 1, "
1195                    + "CN=Test Root Certificate Authority 1"
1196                    + "] RSA"),
1197                    trustManager.calls);
1198        } finally {
1199            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
1200            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
1201        }
1202    }
1203
1204    public void testConnectTimeouts() throws IOException {
1205        StuckServer ss = new StuckServer();
1206        int serverPort = ss.getLocalPort();
1207        URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection();
1208        int timeout = 1000;
1209        urlConnection.setConnectTimeout(timeout);
1210        long start = System.currentTimeMillis();
1211        try {
1212            urlConnection.getInputStream();
1213            fail();
1214        } catch (SocketTimeoutException expected) {
1215            long actual = System.currentTimeMillis() - start;
1216            assertTrue(Math.abs(timeout - actual) < 500);
1217        } finally {
1218            ss.close();
1219        }
1220    }
1221
1222    public void testReadTimeouts() throws IOException {
1223        /*
1224         * This relies on the fact that MockWebServer doesn't close the
1225         * connection after a response has been sent. This causes the client to
1226         * try to read more bytes than are sent, which results in a timeout.
1227         */
1228        MockResponse timeout = new MockResponse()
1229                .setBody("ABC")
1230                .clearHeaders()
1231                .addHeader("Content-Length: 4");
1232        server.enqueue(timeout);
1233        server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
1234        server.play();
1235
1236        URLConnection urlConnection = server.getUrl("/").openConnection();
1237        urlConnection.setReadTimeout(1000);
1238        InputStream in = urlConnection.getInputStream();
1239        assertEquals('A', in.read());
1240        assertEquals('B', in.read());
1241        assertEquals('C', in.read());
1242        try {
1243            in.read(); // if Content-Length was accurate, this would return -1 immediately
1244            fail();
1245        } catch (SocketTimeoutException expected) {
1246        }
1247    }
1248
1249    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
1250        server.enqueue(new MockResponse());
1251        server.play();
1252
1253        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1254        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
1255        urlConnection.setDoOutput(true);
1256        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
1257        assertEquals(200, urlConnection.getResponseCode());
1258
1259        RecordedRequest request = server.takeRequest();
1260        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
1261    }
1262
1263    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
1264        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
1265        server.enqueue(new MockResponse());
1266        server.play();
1267
1268        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1269        a.setRequestProperty("Connection", "close");
1270        assertEquals(200, a.getResponseCode());
1271
1272        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1273        assertEquals(200, b.getResponseCode());
1274
1275        assertEquals(0, server.takeRequest().getSequenceNumber());
1276        assertEquals("When connection: close is used, each request should get its own connection",
1277                0, server.takeRequest().getSequenceNumber());
1278    }
1279
1280    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
1281        server.enqueue(new MockResponse().addHeader("Connection: close"));
1282        server.enqueue(new MockResponse());
1283        server.play();
1284
1285        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1286        assertEquals(200, a.getResponseCode());
1287
1288        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1289        assertEquals(200, b.getResponseCode());
1290
1291        assertEquals(0, server.takeRequest().getSequenceNumber());
1292        assertEquals("When connection: close is used, each request should get its own connection",
1293                0, server.takeRequest().getSequenceNumber());
1294    }
1295
1296    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
1297        MockResponse response = new MockResponse()
1298                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1299                .addHeader("Location: /foo")
1300                .addHeader("Connection: close");
1301        server.enqueue(response);
1302        server.enqueue(new MockResponse().setBody("This is the new location!"));
1303        server.play();
1304
1305        URLConnection connection = server.getUrl("/").openConnection();
1306        assertEquals("This is the new location!",
1307                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1308
1309        assertEquals(0, server.takeRequest().getSequenceNumber());
1310        assertEquals("When connection: close is used, each request should get its own connection",
1311                0, server.takeRequest().getSequenceNumber());
1312    }
1313
1314    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
1315        server.enqueue(new MockResponse()
1316                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
1317                .setBody("This body is not allowed!"));
1318        server.play();
1319
1320        URLConnection connection = server.getUrl("/").openConnection();
1321        assertEquals("This body is not allowed!",
1322                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1323    }
1324
1325    public void testSingleByteReadIsSigned() throws IOException {
1326        server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
1327        server.play();
1328
1329        URLConnection connection = server.getUrl("/").openConnection();
1330        InputStream in = connection.getInputStream();
1331        assertEquals(254, in.read());
1332        assertEquals(255, in.read());
1333        assertEquals(-1, in.read());
1334    }
1335
1336    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
1337        testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
1338    }
1339
1340    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
1341        testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
1342    }
1343
1344    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
1345        testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
1346    }
1347
1348    /**
1349     * We explicitly permit apps to close the upload stream even after it has
1350     * been transmitted.  We also permit flush so that buffered streams can
1351     * do a no-op flush when they are closed. http://b/3038470
1352     */
1353    private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
1354        server.enqueue(new MockResponse().setBody("abc"));
1355        server.play();
1356
1357        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1358        connection.setDoOutput(true);
1359        byte[] upload = "def".getBytes("UTF-8");
1360
1361        if (transferKind == TransferKind.CHUNKED) {
1362            connection.setChunkedStreamingMode(0);
1363        } else if (transferKind == TransferKind.FIXED_LENGTH) {
1364            connection.setFixedLengthStreamingMode(upload.length);
1365        }
1366
1367        OutputStream out = connection.getOutputStream();
1368        out.write(upload);
1369        assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1370
1371        out.flush(); // dubious but permitted
1372        try {
1373            out.write("ghi".getBytes("UTF-8"));
1374            fail();
1375        } catch (IOException expected) {
1376        }
1377    }
1378
1379    public void testGetHeadersThrows() throws IOException {
1380        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
1381        server.play();
1382
1383        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1384        try {
1385            connection.getInputStream();
1386            fail();
1387        } catch (IOException expected) {
1388        }
1389
1390        try {
1391            connection.getInputStream();
1392            fail();
1393        } catch (IOException expected) {
1394        }
1395    }
1396
1397    public void testGetKeepAlive() throws Exception {
1398        MockWebServer server = new MockWebServer();
1399        server.enqueue(new MockResponse().setBody("ABC"));
1400        server.play();
1401
1402        // The request should work once and then fail
1403        URLConnection connection = server.getUrl("").openConnection();
1404        InputStream input = connection.getInputStream();
1405        assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
1406        input.close();
1407        try {
1408            server.getUrl("").openConnection().getInputStream();
1409            fail();
1410        } catch (ConnectException expected) {
1411        }
1412    }
1413
1414    /**
1415     * This test goes through the exhaustive set of interesting ASCII characters
1416     * because most of those characters are interesting in some way according to
1417     * RFC 2396 and RFC 2732. http://b/1158780
1418     */
1419    public void testLenientUrlToUri() throws Exception {
1420        // alphanum
1421        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
1422
1423        // control characters
1424        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
1425        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
1426
1427        // ascii characters
1428        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1429        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1430        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
1431        testUrlToUriMapping("!", "!", "!", "!", "!");
1432        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
1433        testUrlToUriMapping("#", null, null, null, "%23");
1434        testUrlToUriMapping("$", "$", "$", "$", "$");
1435        testUrlToUriMapping("&", "&", "&", "&", "&");
1436        testUrlToUriMapping("'", "'", "'", "'", "'");
1437        testUrlToUriMapping("(", "(", "(", "(", "(");
1438        testUrlToUriMapping(")", ")", ")", ")", ")");
1439        testUrlToUriMapping("*", "*", "*", "*", "*");
1440        testUrlToUriMapping("+", "+", "+", "+", "+");
1441        testUrlToUriMapping(",", ",", ",", ",", ",");
1442        testUrlToUriMapping("-", "-", "-", "-", "-");
1443        testUrlToUriMapping(".", ".", ".", ".", ".");
1444        testUrlToUriMapping("/", null, "/", "/", "/");
1445        testUrlToUriMapping(":", null, ":", ":", ":");
1446        testUrlToUriMapping(";", ";", ";", ";", ";");
1447        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
1448        testUrlToUriMapping("=", "=", "=", "=", "=");
1449        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
1450        testUrlToUriMapping("?", null, null, "?", "?");
1451        testUrlToUriMapping("@", "@", "@", "@", "@");
1452        testUrlToUriMapping("[", null, "%5B", null, "%5B");
1453        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
1454        testUrlToUriMapping("]", null, "%5D", null, "%5D");
1455        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
1456        testUrlToUriMapping("_", "_", "_", "_", "_");
1457        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
1458        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
1459        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
1460        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
1461        testUrlToUriMapping("~", "~", "~", "~", "~");
1462        testUrlToUriMapping("~", "~", "~", "~", "~");
1463        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
1464
1465        // beyond ascii
1466        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
1467        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
1468        testUrlToUriMapping("\ud842\udf9f",
1469                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
1470    }
1471
1472    public void testLenientUrlToUriNul() throws Exception {
1473        testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this
1474    }
1475
1476    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
1477            String asQuery, String asFragment) throws Exception {
1478        if (asAuthority != null) {
1479            assertEquals("http://host" + asAuthority + ".tld/",
1480                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
1481        }
1482        if (asFile != null) {
1483            assertEquals("http://host.tld/file" + asFile + "/",
1484                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
1485        }
1486        if (asQuery != null) {
1487            assertEquals("http://host.tld/file?q" + asQuery + "=x",
1488                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
1489        }
1490        assertEquals("http://host.tld/file#" + asFragment + "-x",
1491                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
1492    }
1493
1494    /**
1495     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
1496     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
1497     * characters like '{' and '|' by escaping these characters.
1498     */
1499    private URI backdoorUrlToUri(URL url) throws Exception {
1500        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
1501
1502        ResponseCache.setDefault(new ResponseCache() {
1503            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1504                return null;
1505            }
1506            @Override public CacheResponse get(URI uri, String requestMethod,
1507                    Map<String, List<String>> requestHeaders) throws IOException {
1508                uriReference.set(uri);
1509                throw new UnsupportedOperationException();
1510            }
1511        });
1512
1513        try {
1514            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1515            connection.getResponseCode();
1516        } catch (Exception expected) {
1517        }
1518
1519        return uriReference.get();
1520    }
1521
1522    /**
1523     * Don't explode if the cache returns a null body. http://b/3373699
1524     */
1525    public void testResponseCacheReturnsNullOutputStream() throws Exception {
1526        final AtomicBoolean aborted = new AtomicBoolean();
1527        ResponseCache.setDefault(new ResponseCache() {
1528            @Override public CacheResponse get(URI uri, String requestMethod,
1529                    Map<String, List<String>> requestHeaders) throws IOException {
1530                return null;
1531            }
1532            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1533                return new CacheRequest() {
1534                    @Override public void abort() {
1535                        aborted.set(true);
1536                    }
1537                    @Override public OutputStream getBody() throws IOException {
1538                        return null;
1539                    }
1540                };
1541            }
1542        });
1543
1544        server.enqueue(new MockResponse().setBody("abcdef"));
1545        server.play();
1546
1547        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1548        InputStream in = connection.getInputStream();
1549        assertEquals("abc", readAscii(in, 3));
1550        in.close();
1551        assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
1552    }
1553
1554    /**
1555     * http://code.google.com/p/android/issues/detail?id=14562
1556     */
1557    public void testReadAfterLastByte() throws Exception {
1558        server.enqueue(new MockResponse()
1559                .setBody("ABC")
1560                .clearHeaders()
1561                .addHeader("Connection: close")
1562                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END));
1563        server.play();
1564
1565        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1566        InputStream in = connection.getInputStream();
1567        assertEquals("ABC", readAscii(in, 3));
1568        assertEquals(-1, in.read());
1569        assertEquals(-1, in.read()); // throws IOException in Gingerbread
1570    }
1571
1572    /**
1573     * Returns a gzipped copy of {@code bytes}.
1574     */
1575    public byte[] gzip(byte[] bytes) throws IOException {
1576        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
1577        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
1578        gzippedOut.write(bytes);
1579        gzippedOut.close();
1580        return bytesOut.toByteArray();
1581    }
1582
1583    /**
1584     * Reads at most {@code limit} characters from {@code in} and asserts that
1585     * content equals {@code expected}.
1586     */
1587    private void assertContent(String expected, URLConnection connection, int limit)
1588            throws IOException {
1589        connection.connect();
1590        assertEquals(expected, readAscii(connection.getInputStream(), limit));
1591        ((HttpURLConnection) connection).disconnect();
1592    }
1593
1594    private void assertContent(String expected, URLConnection connection) throws IOException {
1595        assertContent(expected, connection, Integer.MAX_VALUE);
1596    }
1597
1598    private void assertContains(List<String> headers, String header) {
1599        assertTrue(headers.toString(), headers.contains(header));
1600    }
1601
1602    private void assertContainsNoneMatching(List<String> headers, String pattern) {
1603        for (String header : headers) {
1604            if (header.matches(pattern)) {
1605                fail("Header " + header + " matches " + pattern);
1606            }
1607        }
1608    }
1609
1610    private Set<String> newSet(String... elements) {
1611        return new HashSet<String>(Arrays.asList(elements));
1612    }
1613
1614    enum TransferKind {
1615        CHUNKED() {
1616            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
1617                    throws IOException {
1618                response.setChunkedBody(content, chunkSize);
1619            }
1620        },
1621        FIXED_LENGTH() {
1622            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
1623                response.setBody(content);
1624            }
1625        },
1626        END_OF_STREAM() {
1627            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
1628                response.setBody(content);
1629                response.setSocketPolicy(DISCONNECT_AT_END);
1630                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
1631                    if (h.next().startsWith("Content-Length:")) {
1632                        h.remove();
1633                        break;
1634                    }
1635                }
1636            }
1637        };
1638
1639        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
1640                throws IOException;
1641
1642        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
1643            setBody(response, content.getBytes("UTF-8"), chunkSize);
1644        }
1645    }
1646
1647    enum ProxyConfig {
1648        NO_PROXY() {
1649            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1650                    throws IOException {
1651                return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
1652            }
1653        },
1654
1655        CREATE_ARG() {
1656            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1657                    throws IOException {
1658                return (HttpURLConnection) url.openConnection(server.toProxyAddress());
1659            }
1660        },
1661
1662        PROXY_SYSTEM_PROPERTY() {
1663            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1664                    throws IOException {
1665                System.setProperty("proxyHost", "localhost");
1666                System.setProperty("proxyPort", Integer.toString(server.getPort()));
1667                return (HttpURLConnection) url.openConnection();
1668            }
1669        },
1670
1671        HTTP_PROXY_SYSTEM_PROPERTY() {
1672            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1673                    throws IOException {
1674                System.setProperty("http.proxyHost", "localhost");
1675                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
1676                return (HttpURLConnection) url.openConnection();
1677            }
1678        },
1679
1680        HTTPS_PROXY_SYSTEM_PROPERTY() {
1681            @Override public HttpURLConnection connect(MockWebServer server, URL url)
1682                    throws IOException {
1683                System.setProperty("https.proxyHost", "localhost");
1684                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
1685                return (HttpURLConnection) url.openConnection();
1686            }
1687        };
1688
1689        public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException;
1690    }
1691
1692    private static class RecordingTrustManager implements X509TrustManager {
1693        private final List<String> calls = new ArrayList<String>();
1694
1695        public X509Certificate[] getAcceptedIssuers() {
1696            calls.add("getAcceptedIssuers");
1697            return new X509Certificate[] {};
1698        }
1699
1700        public void checkClientTrusted(X509Certificate[] chain, String authType)
1701                throws CertificateException {
1702            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
1703        }
1704
1705        public void checkServerTrusted(X509Certificate[] chain, String authType)
1706                throws CertificateException {
1707            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
1708        }
1709
1710        private String certificatesToString(X509Certificate[] certificates) {
1711            List<String> result = new ArrayList<String>();
1712            for (X509Certificate certificate : certificates) {
1713                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
1714            }
1715            return result.toString();
1716        }
1717    }
1718
1719    private static class RecordingHostnameVerifier implements HostnameVerifier {
1720        private final List<String> calls = new ArrayList<String>();
1721
1722        public boolean verify(String hostname, SSLSession session) {
1723            calls.add("verify " + hostname);
1724            return true;
1725        }
1726    }
1727}
1728