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