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