URLConnectionTest.java revision 8116f7e97e00d223e7fbe5c950c9a5e3277de124
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.net;
18
19import java.io.BufferedReader;
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.InputStreamReader;
24import java.io.OutputStream;
25import java.net.Authenticator;
26import java.net.CacheRequest;
27import java.net.CacheResponse;
28import java.net.HttpRetryException;
29import java.net.HttpURLConnection;
30import java.net.PasswordAuthentication;
31import java.net.ResponseCache;
32import java.net.SocketTimeoutException;
33import java.net.URI;
34import java.net.URISyntaxException;
35import java.net.URL;
36import java.net.URLConnection;
37import java.security.cert.CertificateException;
38import java.security.cert.X509Certificate;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collections;
42import java.util.HashSet;
43import java.util.Iterator;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.atomic.AtomicReference;
48import java.util.zip.GZIPInputStream;
49import java.util.zip.GZIPOutputStream;
50import javax.net.ssl.HostnameVerifier;
51import javax.net.ssl.HttpsURLConnection;
52import javax.net.ssl.SSLContext;
53import javax.net.ssl.SSLException;
54import javax.net.ssl.SSLSession;
55import javax.net.ssl.SSLSocketFactory;
56import javax.net.ssl.TrustManager;
57import javax.net.ssl.X509TrustManager;
58import libcore.javax.net.ssl.TestSSLContext;
59import tests.http.DefaultResponseCache;
60import tests.http.MockResponse;
61import tests.http.MockWebServer;
62import tests.http.RecordedRequest;
63
64public class URLConnectionTest extends junit.framework.TestCase {
65
66    private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() {
67        protected PasswordAuthentication getPasswordAuthentication() {
68            return new PasswordAuthentication("username", "password".toCharArray());
69        }
70    };
71
72    private MockWebServer server = new MockWebServer();
73
74    @Override protected void tearDown() throws Exception {
75        ResponseCache.setDefault(null);
76        Authenticator.setDefault(null);
77        server.shutdown();
78    }
79
80    public void testRequestHeaders() throws IOException, InterruptedException {
81        server.enqueue(new MockResponse());
82        server.play();
83
84        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
85        urlConnection.addRequestProperty("D", "e");
86        urlConnection.addRequestProperty("D", "f");
87        Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties();
88        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D")));
89        try {
90            requestHeaders.put("G", Arrays.asList("h"));
91            fail("Modified an unmodifiable view.");
92        } catch (UnsupportedOperationException expected) {
93        }
94        try {
95            requestHeaders.get("D").add("i");
96            fail("Modified an unmodifiable view.");
97        } catch (UnsupportedOperationException expected) {
98        }
99        try {
100            urlConnection.setRequestProperty(null, "j");
101            fail();
102        } catch (NullPointerException expected) {
103        }
104        try {
105            urlConnection.addRequestProperty(null, "k");
106            fail();
107        } catch (NullPointerException expected) {
108        }
109        urlConnection.setRequestProperty("NullValue", null); // should fail silently!
110        urlConnection.addRequestProperty("AnotherNullValue", null);  // should fail silently!
111
112        urlConnection.getResponseCode();
113        RecordedRequest request = server.takeRequest();
114        assertContains(request.getHeaders(), "D: e");
115        assertContains(request.getHeaders(), "D: f");
116        assertContainsNoneMatching(request.getHeaders(), "NullValue.*");
117        assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*");
118        assertContainsNoneMatching(request.getHeaders(), "G:.*");
119        assertContainsNoneMatching(request.getHeaders(), "null:.*");
120
121        try {
122            urlConnection.addRequestProperty("N", "o");
123            fail("Set header after connect");
124        } catch (IllegalStateException expected) {
125        }
126        try {
127            urlConnection.setRequestProperty("P", "q");
128            fail("Set header after connect");
129        } catch (IllegalStateException expected) {
130        }
131    }
132
133    public void testResponseHeaders() throws IOException, InterruptedException {
134        server.enqueue(new MockResponse()
135                .setStatus("HTTP/1.0 200 Fantastic")
136                .addHeader("A: b")
137                .addHeader("A: c")
138                .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
139        server.play();
140
141        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
142        assertEquals(200, urlConnection.getResponseCode());
143        assertEquals("Fantastic", urlConnection.getResponseMessage());
144        Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields();
145        assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A")));
146        try {
147            responseHeaders.put("N", Arrays.asList("o"));
148            fail("Modified an unmodifiable view.");
149        } catch (UnsupportedOperationException expected) {
150        }
151        try {
152            responseHeaders.get("A").add("d");
153            fail("Modified an unmodifiable view.");
154        } catch (UnsupportedOperationException expected) {
155        }
156    }
157
158    // Check that if we don't read to the end of a response, the next request on the
159    // recycled connection doesn't get the unread tail of the first request's response.
160    // http://code.google.com/p/android/issues/detail?id=2939
161    public void test_2939() throws Exception {
162        MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8);
163
164        server.enqueue(response);
165        server.enqueue(response);
166        server.play();
167
168        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
169        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
170    }
171
172    // Check that we recognize a few basic mime types by extension.
173    // http://code.google.com/p/android/issues/detail?id=10100
174    public void test_10100() throws Exception {
175        assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg"));
176        assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf"));
177    }
178
179    public void testConnectionsArePooled() throws Exception {
180        MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR");
181
182        server.enqueue(response);
183        server.enqueue(response);
184        server.enqueue(response);
185        server.play();
186
187        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection());
188        assertEquals(0, server.takeRequest().getSequenceNumber());
189        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection());
190        assertEquals(1, server.takeRequest().getSequenceNumber());
191        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection());
192        assertEquals(2, server.takeRequest().getSequenceNumber());
193    }
194
195    public void testChunkedConnectionsArePooled() throws Exception {
196        MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5);
197
198        server.enqueue(response);
199        server.enqueue(response);
200        server.enqueue(response);
201        server.play();
202
203        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection());
204        assertEquals(0, server.takeRequest().getSequenceNumber());
205        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection());
206        assertEquals(1, server.takeRequest().getSequenceNumber());
207        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection());
208        assertEquals(2, server.takeRequest().getSequenceNumber());
209    }
210
211    enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
212
213    public void test_chunkedUpload_byteByByte() throws Exception {
214        doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
215    }
216
217    public void test_chunkedUpload_smallBuffers() throws Exception {
218        doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
219    }
220
221    public void test_chunkedUpload_largeBuffers() throws Exception {
222        doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
223    }
224
225    public void test_fixedLengthUpload_byteByByte() throws Exception {
226        doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
227    }
228
229    public void test_fixedLengthUpload_smallBuffers() throws Exception {
230        doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
231    }
232
233    public void test_fixedLengthUpload_largeBuffers() throws Exception {
234        doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
235    }
236
237    private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception {
238        int n = 512*1024;
239        server.setBodyLimit(0);
240        server.enqueue(new MockResponse());
241        server.play();
242
243        HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection();
244        conn.setDoOutput(true);
245        conn.setRequestMethod("POST");
246        if (uploadKind == TransferKind.CHUNKED) {
247            conn.setChunkedStreamingMode(-1);
248        } else {
249            conn.setFixedLengthStreamingMode(n);
250        }
251        OutputStream out = conn.getOutputStream();
252        if (writeKind == WriteKind.BYTE_BY_BYTE) {
253            for (int i = 0; i < n; ++i) {
254                out.write('x');
255            }
256        } else {
257            byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024];
258            Arrays.fill(buf, (byte) 'x');
259            for (int i = 0; i < n; i += buf.length) {
260                out.write(buf, 0, Math.min(buf.length, n - i));
261            }
262        }
263        out.close();
264        assertEquals(200, conn.getResponseCode());
265        RecordedRequest request = server.takeRequest();
266        assertEquals(n, request.getBodySize());
267        if (uploadKind == TransferKind.CHUNKED) {
268            assertTrue(request.getChunkSizes().size() > 0);
269        } else {
270            assertTrue(request.getChunkSizes().isEmpty());
271        }
272    }
273
274    /**
275     * Test that response caching is consistent with the RI and the spec.
276     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
277     */
278    public void test_responseCaching() throws Exception {
279        // Test each documented HTTP/1.1 code, plus the first unused value in each range.
280        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
281
282        // We can't test 100 because it's not really a response.
283        // assertCached(false, 100);
284        assertCached(false, 101);
285        assertCached(false, 102);
286        assertCached(true,  200);
287        assertCached(false, 201);
288        assertCached(false, 202);
289        assertCached(true,  203);
290        assertCached(false, 204);
291        assertCached(false, 205);
292        assertCached(true,  206);
293        assertCached(false, 207);
294        // (See test_responseCaching_300.)
295        assertCached(true,  301);
296        for (int i = 302; i <= 308; ++i) {
297            assertCached(false, i);
298        }
299        for (int i = 400; i <= 406; ++i) {
300            assertCached(false, i);
301        }
302        // (See test_responseCaching_407.)
303        assertCached(false, 408);
304        assertCached(false, 409);
305        // (See test_responseCaching_410.)
306        for (int i = 411; i <= 418; ++i) {
307            assertCached(false, i);
308        }
309        for (int i = 500; i <= 506; ++i) {
310            assertCached(false, i);
311        }
312    }
313
314    public void test_responseCaching_300() throws Exception {
315        // TODO: fix this for android
316        assertCached(false, 300);
317    }
318
319    public void test_responseCaching_407() throws Exception {
320        // This test will fail on Android because we throw if we're not using a proxy.
321        // This isn't true of the RI, but it seems like useful debugging behavior.
322        assertCached(false, 407);
323    }
324
325    public void test_responseCaching_410() throws Exception {
326        // the HTTP spec permits caching 410s, but the RI doesn't.
327        assertCached(false, 410);
328    }
329
330    private void assertCached(boolean shouldPut, int responseCode) throws Exception {
331        server = new MockWebServer();
332        server.enqueue(new MockResponse()
333                .setResponseCode(responseCode)
334                .setBody("ABCDE")
335                .addHeader("WWW-Authenticate: challenge"));
336        server.play();
337
338        DefaultResponseCache responseCache = new DefaultResponseCache();
339        ResponseCache.setDefault(responseCache);
340        URL url = server.getUrl("/");
341        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
342        assertEquals(responseCode, conn.getResponseCode());
343
344        // exhaust the content stream
345        try {
346            // TODO: remove special case once testUnauthorizedResponseHandling() is fixed
347            if (responseCode != 401) {
348                readAscii(conn.getInputStream(), Integer.MAX_VALUE);
349            }
350        } catch (IOException ignored) {
351        }
352
353        Set<URI> expectedCachedUris = shouldPut
354                ? Collections.singleton(url.toURI())
355                : Collections.<URI>emptySet();
356        assertEquals(Integer.toString(responseCode),
357                expectedCachedUris, responseCache.getContents().keySet());
358        server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
359    }
360
361    public void testConnectViaHttps() throws IOException, InterruptedException {
362        TestSSLContext testSSLContext = TestSSLContext.create();
363
364        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
365        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
366        server.play();
367
368        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
369        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
370
371        assertContent("this response comes via HTTPS", connection);
372
373        RecordedRequest request = server.takeRequest();
374        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
375    }
376
377    public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
378        TestSSLContext testSSLContext = TestSSLContext.create();
379
380        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
381        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
382        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
383        server.play();
384
385        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
386        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
387        assertContent("this response comes via HTTPS", connection);
388
389        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
390        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
391        assertContent("another response via HTTPS", connection);
392
393        assertEquals(0, server.takeRequest().getSequenceNumber());
394        assertEquals(1, server.takeRequest().getSequenceNumber());
395    }
396
397    public void testConnectViaHttpsReusingConnectionsDifferentFactories()
398            throws IOException, InterruptedException {
399        TestSSLContext testSSLContext = TestSSLContext.create();
400
401        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
402        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
403        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
404        server.play();
405
406        // install a custom SSL socket factory so the server can be authorized
407        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
408        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
409        assertContent("this response comes via HTTPS", connection);
410
411        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
412        try {
413            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
414            fail("without an SSL socket factory, the connection should fail");
415        } catch (SSLException expected) {
416        }
417    }
418
419    public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException {
420        TestSSLContext testSSLContext = TestSSLContext.create();
421
422        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
423        server.enqueue(new MockResponse().setDisconnectAtStart(true));
424        server.enqueue(new MockResponse().setBody("this response comes via SSL"));
425        server.play();
426
427        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
428        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
429
430        assertContent("this response comes via SSL", connection);
431
432        RecordedRequest request = server.takeRequest();
433        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
434    }
435
436    public void testConnectViaProxy() throws IOException, InterruptedException {
437        MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
438        server.enqueue(mockResponse);
439        server.play();
440
441        URLConnection connection = new URL("http://android.com/foo").openConnection(
442                server.toProxyAddress());
443        assertContent("this response comes via a proxy", connection);
444
445        RecordedRequest request = server.takeRequest();
446        assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine());
447        assertContains(request.getHeaders(), "Host: android.com");
448    }
449
450    public void testContentDisagreesWithContentLengthHeader() throws IOException {
451        server.enqueue(new MockResponse()
452                .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
453                .clearHeaders()
454                .addHeader("Content-Length: 3"));
455        server.play();
456
457        assertContent("abc", server.getUrl("/").openConnection());
458    }
459
460    public void testContentDisagreesWithChunkedHeader() throws IOException {
461        MockResponse mockResponse = new MockResponse();
462        mockResponse.setChunkedBody("abc", 3);
463        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
464        bytesOut.write(mockResponse.getBody());
465        bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes());
466        mockResponse.setBody(bytesOut.toByteArray());
467        mockResponse.clearHeaders();
468        mockResponse.addHeader("Transfer-encoding: chunked");
469
470        server.enqueue(mockResponse);
471        server.play();
472
473        assertContent("abc", server.getUrl("/").openConnection());
474    }
475
476    public void testConnectViaHttpProxyToHttps() throws IOException, InterruptedException {
477        TestSSLContext testSSLContext = TestSSLContext.create();
478
479        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
480        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
481        server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
482        server.play();
483
484        URL url = new URL("https://android.com/foo");
485        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
486                server.toProxyAddress());
487        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
488        connection.setHostnameVerifier(new HostnameVerifier() {
489            public boolean verify(String hostname, SSLSession session) {
490                return true;
491            }
492        });
493
494        assertContent("this response comes via a secure proxy", connection);
495
496        RecordedRequest connect = server.takeRequest();
497        assertEquals("Connect line failure on proxy",
498                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
499        assertContains(connect.getHeaders(), "Host: android.com");
500
501        RecordedRequest get = server.takeRequest();
502        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
503        assertContains(get.getHeaders(), "Host: android.com");
504    }
505
506    public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
507        testResponseCaching(TransferKind.FIXED_LENGTH);
508    }
509
510    public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
511        testResponseCaching(TransferKind.CHUNKED);
512    }
513
514    public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
515        testResponseCaching(TransferKind.END_OF_STREAM);
516    }
517
518    /**
519     * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
520     * http://code.google.com/p/android/issues/detail?id=8175
521     */
522    private void testResponseCaching(TransferKind transferKind) throws IOException {
523        MockResponse response = new MockResponse();
524        transferKind.setBody(response, "I love puppies but hate spiders", 1);
525        server.enqueue(response);
526        server.play();
527
528        DefaultResponseCache cache = new DefaultResponseCache();
529        ResponseCache.setDefault(cache);
530
531        // Make sure that calling skip() doesn't omit bytes from the cache.
532        URLConnection urlConnection = server.getUrl("/").openConnection();
533        InputStream in = urlConnection.getInputStream();
534        assertEquals("I love ", readAscii(in, "I love ".length()));
535        reliableSkip(in, "puppies but hate ".length());
536        assertEquals("spiders", readAscii(in, "spiders".length()));
537        assertEquals(-1, in.read());
538        in.close();
539        assertEquals(1, cache.getSuccessCount());
540        assertEquals(0, cache.getAbortCount());
541
542        urlConnection = server.getUrl("/").openConnection(); // this response is cached!
543        in = urlConnection.getInputStream();
544        assertEquals("I love puppies but hate spiders",
545                readAscii(in, "I love puppies but hate spiders".length()));
546        assertEquals(-1, in.read());
547        assertEquals(1, cache.getMissCount());
548        assertEquals(1, cache.getHitCount());
549        assertEquals(1, cache.getSuccessCount());
550        assertEquals(0, cache.getAbortCount());
551    }
552
553    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
554        server.enqueue(new MockResponse().setBody("ABC"));
555        server.play();
556
557        final AtomicReference<Map<String, List<String>>> requestHeadersRef
558                = new AtomicReference<Map<String, List<String>>>();
559        ResponseCache.setDefault(new ResponseCache() {
560            @Override public CacheResponse get(URI uri, String requestMethod,
561                    Map<String, List<String>> requestHeaders) throws IOException {
562                requestHeadersRef.set(requestHeaders);
563                return null;
564            }
565            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
566                return null;
567            }
568        });
569
570        URL url = server.getUrl("/");
571        URLConnection urlConnection = url.openConnection();
572        urlConnection.addRequestProperty("A", "android");
573        readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE);
574        assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
575    }
576
577    private void reliableSkip(InputStream in, int length) throws IOException {
578        while (length > 0) {
579            length -= in.skip(length);
580        }
581    }
582
583    /**
584     * Reads {@code count} characters from the stream. If the stream is
585     * exhausted before {@code count} characters can be read, the remaining
586     * characters are returned and the stream is closed.
587     */
588    private String readAscii(InputStream in, int count) throws IOException {
589        StringBuilder result = new StringBuilder();
590        for (int i = 0; i < count; i++) {
591            int value = in.read();
592            if (value == -1) {
593                in.close();
594                break;
595            }
596            result.append((char) value);
597        }
598        return result.toString();
599    }
600
601    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
602        testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
603    }
604
605    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
606        testServerPrematureDisconnect(TransferKind.CHUNKED);
607    }
608
609    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
610        /*
611         * Intentionally empty. This case doesn't make sense because there's no
612         * such thing as a premature disconnect when the disconnect itself
613         * indicates the end of the data stream.
614         */
615    }
616
617    private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
618        MockResponse response = new MockResponse();
619        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
620        server.enqueue(truncateViolently(response, 16));
621        server.enqueue(new MockResponse().setBody("Request #2"));
622        server.play();
623
624        DefaultResponseCache cache = new DefaultResponseCache();
625        ResponseCache.setDefault(cache);
626
627        BufferedReader reader = new BufferedReader(new InputStreamReader(
628                server.getUrl("/").openConnection().getInputStream()));
629        assertEquals("ABCDE", reader.readLine());
630        try {
631            reader.readLine();
632            fail("This implementation silently ignored a truncated HTTP body.");
633        } catch (IOException expected) {
634        }
635
636        assertEquals(1, cache.getAbortCount());
637        assertEquals(0, cache.getSuccessCount());
638        assertContent("Request #2", server.getUrl("/").openConnection());
639        assertEquals(1, cache.getAbortCount());
640        assertEquals(1, cache.getSuccessCount());
641    }
642
643    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
644        testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
645    }
646
647    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
648        testClientPrematureDisconnect(TransferKind.CHUNKED);
649    }
650
651    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
652        testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
653    }
654
655    private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
656        MockResponse response = new MockResponse();
657        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
658        server.enqueue(response);
659        server.enqueue(new MockResponse().setBody("Request #2"));
660        server.play();
661
662        DefaultResponseCache cache = new DefaultResponseCache();
663        ResponseCache.setDefault(cache);
664
665        InputStream in = server.getUrl("/").openConnection().getInputStream();
666        assertEquals("ABCDE", readAscii(in, 5));
667        in.close();
668        try {
669            in.read();
670            fail("Expected an IOException because the stream is closed.");
671        } catch (IOException expected) {
672        }
673
674        assertEquals(1, cache.getAbortCount());
675        assertEquals(0, cache.getSuccessCount());
676        assertContent("Request #2", server.getUrl("/").openConnection());
677        assertEquals(1, cache.getAbortCount());
678        assertEquals(1, cache.getSuccessCount());
679    }
680
681    /**
682     * Shortens the body of {@code response} but not the corresponding headers.
683     * Only useful to test how clients respond to the premature conclusion of
684     * the HTTP body.
685     */
686    private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
687        response.setDisconnectAtEnd(true);
688        List<String> headers = new ArrayList<String>(response.getHeaders());
689        response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
690        response.getHeaders().clear();
691        response.getHeaders().addAll(headers);
692        return response;
693    }
694
695    public void testMarkAndResetWithContentLengthHeader() throws IOException {
696        testMarkAndReset(TransferKind.FIXED_LENGTH);
697    }
698
699    public void testMarkAndResetWithChunkedEncoding() throws IOException {
700        testMarkAndReset(TransferKind.CHUNKED);
701    }
702
703    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
704        testMarkAndReset(TransferKind.END_OF_STREAM);
705    }
706
707    public void testMarkAndReset(TransferKind transferKind) throws IOException {
708        MockResponse response = new MockResponse();
709        transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
710        server.enqueue(response);
711        server.play();
712
713        DefaultResponseCache cache = new DefaultResponseCache();
714        ResponseCache.setDefault(cache);
715
716        InputStream in = server.getUrl("/").openConnection().getInputStream();
717        assertFalse("This implementation claims to support mark().", in.markSupported());
718        in.mark(5);
719        assertEquals("ABCDE", readAscii(in, 5));
720        try {
721            in.reset();
722            fail();
723        } catch (IOException expected) {
724        }
725        assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
726
727        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
728        assertEquals(1, cache.getSuccessCount());
729        assertEquals(1, cache.getHitCount());
730    }
731
732    /**
733     * We've had a bug where we forget the HTTP response when we see response
734     * code 401. This causes a new HTTP request to be issued for every call into
735     * the URLConnection.
736     */
737    public void testUnauthorizedResponseHandling() throws IOException {
738        MockResponse response = new MockResponse()
739                .addHeader("WWW-Authenticate: challenge")
740                .setResponseCode(401) // UNAUTHORIZED
741                .setBody("Unauthorized");
742        server.enqueue(response);
743        server.enqueue(response);
744        server.enqueue(response);
745        server.play();
746
747        URL url = server.getUrl("/");
748        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
749
750        assertEquals(401, conn.getResponseCode());
751        assertEquals(401, conn.getResponseCode());
752        assertEquals(401, conn.getResponseCode());
753        assertEquals(1, server.getRequestCount());
754    }
755
756    public void testNonHexChunkSize() throws IOException {
757        server.enqueue(new MockResponse()
758                .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
759                .clearHeaders()
760                .addHeader("Transfer-encoding: chunked"));
761        server.play();
762
763        URLConnection connection = server.getUrl("/").openConnection();
764        try {
765            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
766            fail();
767        } catch (IOException e) {
768        }
769    }
770
771    public void testMissingChunkBody() throws IOException {
772        server.enqueue(new MockResponse()
773                .setBody("5")
774                .clearHeaders()
775                .addHeader("Transfer-encoding: chunked")
776                .setDisconnectAtEnd(true));
777        server.play();
778
779        URLConnection connection = server.getUrl("/").openConnection();
780        try {
781            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
782            fail();
783        } catch (IOException e) {
784        }
785    }
786
787    /**
788     * This test checks whether connections are gzipped by default. This
789     * behavior in not required by the API, so a failure of this test does not
790     * imply a bug in the implementation.
791     */
792    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
793        server.enqueue(new MockResponse()
794                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
795                .addHeader("Content-Encoding: gzip"));
796        server.play();
797
798        URLConnection connection = server.getUrl("/").openConnection();
799        assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
800        assertNull(connection.getContentEncoding());
801
802        RecordedRequest request = server.takeRequest();
803        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
804    }
805
806    public void testClientConfiguredGzipContentEncoding() throws Exception {
807        server.enqueue(new MockResponse()
808                .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")))
809                .addHeader("Content-Encoding: gzip"));
810        server.play();
811
812        URLConnection connection = server.getUrl("/").openConnection();
813        connection.addRequestProperty("Accept-Encoding", "gzip");
814        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
815        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
816
817        RecordedRequest request = server.takeRequest();
818        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
819    }
820
821    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
822        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
823    }
824
825    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
826        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
827    }
828
829    public void testClientConfiguredCustomContentEncoding() throws Exception {
830        server.enqueue(new MockResponse()
831                .setBody("ABCDE")
832                .addHeader("Content-Encoding: custom"));
833        server.play();
834
835        URLConnection connection = server.getUrl("/").openConnection();
836        connection.addRequestProperty("Accept-Encoding", "custom");
837        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
838
839        RecordedRequest request = server.takeRequest();
840        assertContains(request.getHeaders(), "Accept-Encoding: custom");
841    }
842
843    /**
844     * Test a bug where gzip input streams weren't exhausting the input stream,
845     * which corrupted the request that followed.
846     * http://code.google.com/p/android/issues/detail?id=7059
847     */
848    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
849            TransferKind transferKind) throws Exception {
850        MockResponse responseOne = new MockResponse();
851        responseOne.addHeader("Content-Encoding: gzip");
852        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
853        server.enqueue(responseOne);
854        MockResponse responseTwo = new MockResponse();
855        transferKind.setBody(responseTwo, "two (identity)", 5);
856        server.enqueue(responseTwo);
857        server.play();
858
859        URLConnection connection = server.getUrl("/").openConnection();
860        connection.addRequestProperty("Accept-Encoding", "gzip");
861        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
862        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
863        assertEquals(0, server.takeRequest().getSequenceNumber());
864
865        connection = server.getUrl("/").openConnection();
866        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
867        assertEquals(1, server.takeRequest().getSequenceNumber());
868    }
869
870    /**
871     * Obnoxiously test that the chunk sizes transmitted exactly equal the
872     * requested data+chunk header size. Although setChunkedStreamingMode()
873     * isn't specific about whether the size applies to the data or the
874     * complete chunk, the RI interprets it as a complete chunk.
875     */
876    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
877        server.enqueue(new MockResponse());
878        server.play();
879
880        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
881        urlConnection.setChunkedStreamingMode(8);
882        urlConnection.setDoOutput(true);
883        OutputStream outputStream = urlConnection.getOutputStream();
884        outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII"));
885        assertEquals(200, urlConnection.getResponseCode());
886
887        RecordedRequest request = server.takeRequest();
888        assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII"));
889        assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes());
890    }
891
892    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
893        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
894    }
895
896    public void testAuthenticateWithChunkedStreaming() throws Exception {
897        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
898    }
899
900    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
901        MockResponse pleaseAuthenticate = new MockResponse()
902                .setResponseCode(401)
903                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
904                .setBody("Please authenticate.");
905        server.enqueue(pleaseAuthenticate);
906        server.play();
907
908        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
909        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
910        connection.setDoOutput(true);
911        byte[] requestBody = { 'A', 'B', 'C', 'D' };
912        if (streamingMode == StreamingMode.FIXED_LENGTH) {
913            connection.setFixedLengthStreamingMode(requestBody.length);
914        } else if (streamingMode == StreamingMode.CHUNKED) {
915            connection.setChunkedStreamingMode(0);
916        }
917        OutputStream outputStream = connection.getOutputStream();
918        outputStream.write(requestBody);
919        outputStream.close();
920        try {
921            connection.getInputStream();
922            fail();
923        } catch (HttpRetryException expected) {
924        }
925
926        // no authorization header for the request...
927        RecordedRequest request = server.takeRequest();
928        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
929        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
930    }
931
932    enum StreamingMode {
933        FIXED_LENGTH, CHUNKED
934    }
935
936    public void testAuthenticateWithPost() throws Exception {
937        MockResponse pleaseAuthenticate = new MockResponse()
938                .setResponseCode(401)
939                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
940                .setBody("Please authenticate.");
941        // fail auth three times...
942        server.enqueue(pleaseAuthenticate);
943        server.enqueue(pleaseAuthenticate);
944        server.enqueue(pleaseAuthenticate);
945        // ...then succeed the fourth time
946        server.enqueue(new MockResponse().setBody("Successful auth!"));
947        server.play();
948
949        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
950        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
951        connection.setDoOutput(true);
952        byte[] requestBody = { 'A', 'B', 'C', 'D' };
953        OutputStream outputStream = connection.getOutputStream();
954        outputStream.write(requestBody);
955        outputStream.close();
956        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
957
958        // no authorization header for the first request...
959        RecordedRequest request = server.takeRequest();
960        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
961
962        // ...but the three requests that follow include an authorization header
963        for (int i = 0; i < 3; i++) {
964            request = server.takeRequest();
965            assertEquals("POST / HTTP/1.1", request.getRequestLine());
966            assertContains(request.getHeaders(), "Authorization: Basic "
967                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
968            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
969        }
970    }
971
972    public void testAuthenticateWithGet() throws Exception {
973        MockResponse pleaseAuthenticate = new MockResponse()
974                .setResponseCode(401)
975                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
976                .setBody("Please authenticate.");
977        // fail auth three times...
978        server.enqueue(pleaseAuthenticate);
979        server.enqueue(pleaseAuthenticate);
980        server.enqueue(pleaseAuthenticate);
981        // ...then succeed the fourth time
982        server.enqueue(new MockResponse().setBody("Successful auth!"));
983        server.play();
984
985        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
986        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
987        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
988
989        // no authorization header for the first request...
990        RecordedRequest request = server.takeRequest();
991        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
992
993        // ...but the three requests that follow requests include an authorization header
994        for (int i = 0; i < 3; i++) {
995            request = server.takeRequest();
996            assertEquals("GET / HTTP/1.1", request.getRequestLine());
997            assertContains(request.getHeaders(), "Authorization: Basic "
998                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
999        }
1000    }
1001
1002    public void testRedirectedWithChunkedEncoding() throws Exception {
1003        testRedirected(TransferKind.CHUNKED, true);
1004    }
1005
1006    public void testRedirectedWithContentLengthHeader() throws Exception {
1007        testRedirected(TransferKind.FIXED_LENGTH, true);
1008    }
1009
1010    public void testRedirectedWithNoLengthHeaders() throws Exception {
1011        testRedirected(TransferKind.END_OF_STREAM, false);
1012    }
1013
1014    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1015        MockResponse response = new MockResponse()
1016                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1017                .addHeader("Location: /foo");
1018        transferKind.setBody(response, "This page has moved!", 10);
1019        server.enqueue(response);
1020        server.enqueue(new MockResponse().setBody("This is the new location!"));
1021        server.play();
1022
1023        URLConnection connection = server.getUrl("/").openConnection();
1024        assertEquals("This is the new location!",
1025                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1026
1027        RecordedRequest first = server.takeRequest();
1028        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1029        RecordedRequest retry = server.takeRequest();
1030        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1031        if (reuse) {
1032            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1033        }
1034    }
1035
1036    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1037        TestSSLContext testSSLContext = TestSSLContext.create();
1038        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1039        server.enqueue(new MockResponse()
1040                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1041                .addHeader("Location: /foo")
1042                .setBody("This page has moved!"));
1043        server.enqueue(new MockResponse().setBody("This is the new location!"));
1044        server.play();
1045
1046        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1047        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1048        assertEquals("This is the new location!",
1049                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1050
1051        RecordedRequest first = server.takeRequest();
1052        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1053        RecordedRequest retry = server.takeRequest();
1054        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1055        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1056    }
1057
1058    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1059        TestSSLContext testSSLContext = TestSSLContext.create();
1060        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1061        server.enqueue(new MockResponse()
1062                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1063                .addHeader("Location: http://anyhost/foo")
1064                .setBody("This page has moved!"));
1065        server.play();
1066
1067        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1068        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1069        assertEquals("This page has moved!",
1070                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1071    }
1072
1073    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1074        server.enqueue(new MockResponse()
1075                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1076                .addHeader("Location: https://anyhost/foo")
1077                .setBody("This page has moved!"));
1078        server.play();
1079
1080        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1081        assertEquals("This page has moved!",
1082                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1083    }
1084
1085    public void testRedirectToAnotherOriginServer() throws Exception {
1086        MockWebServer server2 = new MockWebServer();
1087        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1088        server2.play();
1089
1090        server.enqueue(new MockResponse()
1091                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1092                .addHeader("Location: " + server2.getUrl("/").toString())
1093                .setBody("This page has moved!"));
1094        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1095        server.play();
1096
1097        URLConnection connection = server.getUrl("/").openConnection();
1098        assertEquals("This is the 2nd server!",
1099                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1100        assertEquals(server2.getUrl("/"), connection.getURL());
1101
1102        // make sure the first server was careful to recycle the connection
1103        assertEquals("This is the first server again!",
1104                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1105
1106        RecordedRequest first = server.takeRequest();
1107        assertContains(first.getHeaders(), "Host: localhost:" + server.getPort());
1108        RecordedRequest second = server2.takeRequest();
1109        assertContains(second.getHeaders(), "Host: localhost:" + server2.getPort());
1110        RecordedRequest third = server.takeRequest();
1111        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1112
1113        server2.shutdown();
1114    }
1115
1116    public void testHttpsWithCustomTrustManager() throws Exception {
1117        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
1118        RecordingTrustManager trustManager = new RecordingTrustManager();
1119        SSLContext sc = SSLContext.getInstance("TLS");
1120        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
1121
1122        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
1123        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
1124        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
1125        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1126        try {
1127            TestSSLContext testSSLContext = TestSSLContext.create();
1128            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1129            server.enqueue(new MockResponse().setBody("ABC"));
1130            server.enqueue(new MockResponse().setBody("DEF"));
1131            server.enqueue(new MockResponse().setBody("GHI"));
1132            server.play();
1133
1134            URL url = server.getUrl("/");
1135            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
1136            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
1137            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
1138
1139            assertEquals(Arrays.asList("verify localhost"), hostnameVerifier.calls);
1140            assertEquals(Arrays.asList("checkServerTrusted ["
1141                                       + "CN=localhost 1, "
1142                                       + "CN=Test Intermediate Certificate Authority 1, "
1143                                       + "CN=Test Root Certificate Authority 1"
1144                                       + "] RSA"),
1145                    trustManager.calls);
1146        } finally {
1147            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
1148            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
1149        }
1150    }
1151
1152    public void testConnectTimeouts() throws IOException {
1153        // 10.0.0.0 is non-routable and will time out on every network
1154        URLConnection urlConnection = new URL("http://10.0.0.0/").openConnection();
1155        urlConnection.setConnectTimeout(1000);
1156        try {
1157            urlConnection.getInputStream();
1158            fail();
1159        } catch (SocketTimeoutException expected) {
1160        }
1161    }
1162
1163    public void testReadTimeouts() throws IOException {
1164        /*
1165         * This relies on the fact that MockWebServer doesn't close the
1166         * connection after a response has been sent. This causes the client to
1167         * try to read more bytes than are sent, which results in a timeout.
1168         */
1169        MockResponse timeout = new MockResponse()
1170                .setBody("ABC")
1171                .clearHeaders()
1172                .addHeader("Content-Length: 4");
1173        server.enqueue(timeout);
1174        server.play();
1175
1176        URLConnection urlConnection = server.getUrl("/").openConnection();
1177        urlConnection.setReadTimeout(1000);
1178        InputStream in = urlConnection.getInputStream();
1179        assertEquals('A', in.read());
1180        assertEquals('B', in.read());
1181        assertEquals('C', in.read());
1182        try {
1183            in.read(); // if Content-Length was accurate, this would return -1 immediately
1184            fail();
1185        } catch (SocketTimeoutException expected) {
1186        }
1187    }
1188
1189    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
1190        server.enqueue(new MockResponse());
1191        server.play();
1192
1193        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1194        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
1195        urlConnection.setDoOutput(true);
1196        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
1197        assertEquals(200, urlConnection.getResponseCode());
1198
1199        RecordedRequest request = server.takeRequest();
1200        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
1201    }
1202
1203    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
1204        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
1205        server.enqueue(new MockResponse());
1206        server.play();
1207
1208        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1209        a.setRequestProperty("Connection", "close");
1210        assertEquals(200, a.getResponseCode());
1211
1212        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1213        assertEquals(200, b.getResponseCode());
1214
1215        assertEquals(0, server.takeRequest().getSequenceNumber());
1216        assertEquals("When connection: close is used, each request should get its own connection",
1217                0, server.takeRequest().getSequenceNumber());
1218    }
1219
1220    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
1221        server.enqueue(new MockResponse().addHeader("Connection: close"));
1222        server.enqueue(new MockResponse());
1223        server.play();
1224
1225        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1226        assertEquals(200, a.getResponseCode());
1227
1228        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1229        assertEquals(200, b.getResponseCode());
1230
1231        assertEquals(0, server.takeRequest().getSequenceNumber());
1232        assertEquals("When connection: close is used, each request should get its own connection",
1233                0, server.takeRequest().getSequenceNumber());
1234    }
1235
1236    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
1237        MockResponse response = new MockResponse()
1238                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1239                .addHeader("Location: /foo")
1240                .addHeader("Connection: close");
1241        server.enqueue(response);
1242        server.enqueue(new MockResponse().setBody("This is the new location!"));
1243        server.play();
1244
1245        URLConnection connection = server.getUrl("/").openConnection();
1246        assertEquals("This is the new location!",
1247                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1248
1249        assertEquals(0, server.takeRequest().getSequenceNumber());
1250        assertEquals("When connection: close is used, each request should get its own connection",
1251                0, server.takeRequest().getSequenceNumber());
1252    }
1253
1254    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
1255        server.enqueue(new MockResponse()
1256                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
1257                .setBody("This body is not allowed!"));
1258        server.play();
1259
1260        URLConnection connection = server.getUrl("/").openConnection();
1261        assertEquals("This body is not allowed!",
1262                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1263    }
1264
1265    /**
1266     * Encodes the response body using GZIP and adds the corresponding header.
1267     */
1268    public byte[] gzip(byte[] bytes) throws IOException {
1269        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
1270        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
1271        gzippedOut.write(bytes);
1272        gzippedOut.close();
1273        return bytesOut.toByteArray();
1274    }
1275
1276    /**
1277     * Reads at most {@code limit} characters from {@code in} and asserts that
1278     * content equals {@code expected}.
1279     */
1280    private void assertContent(String expected, URLConnection connection, int limit)
1281            throws IOException {
1282        assertEquals(expected, readAscii(connection.getInputStream(), limit));
1283        ((HttpURLConnection) connection).disconnect();
1284    }
1285
1286    private void assertContent(String expected, URLConnection connection) throws IOException {
1287        assertContent(expected, connection, Integer.MAX_VALUE);
1288    }
1289
1290    private void assertContains(List<String> headers, String header) {
1291        assertTrue(headers.toString(), headers.contains(header));
1292    }
1293
1294    private void assertContainsNoneMatching(List<String> headers, String pattern) {
1295        for (String header : headers) {
1296            if (header.matches(pattern)) {
1297                fail("Header " + header + " matches " + pattern);
1298            }
1299        }
1300    }
1301
1302    private Set<String> newSet(String... elements) {
1303        return new HashSet<String>(Arrays.asList(elements));
1304    }
1305
1306    enum TransferKind {
1307        CHUNKED() {
1308            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
1309                    throws IOException {
1310                response.setChunkedBody(content, chunkSize);
1311            }
1312        },
1313        FIXED_LENGTH() {
1314            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
1315                response.setBody(content);
1316            }
1317        },
1318        END_OF_STREAM() {
1319            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
1320                response.setBody(content);
1321                response.setDisconnectAtEnd(true);
1322                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
1323                    if (h.next().startsWith("Content-Length:")) {
1324                        h.remove();
1325                        break;
1326                    }
1327                }
1328            }
1329        };
1330
1331        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
1332                throws IOException;
1333
1334        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
1335            setBody(response, content.getBytes("UTF-8"), chunkSize);
1336        }
1337    }
1338
1339    private static class RecordingTrustManager implements X509TrustManager {
1340        private final List<String> calls = new ArrayList<String>();
1341
1342        public X509Certificate[] getAcceptedIssuers() {
1343            calls.add("getAcceptedIssuers");
1344            return new X509Certificate[] {};
1345        }
1346
1347        public void checkClientTrusted(X509Certificate[] chain, String authType)
1348                throws CertificateException {
1349            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
1350        }
1351
1352        public void checkServerTrusted(X509Certificate[] chain, String authType)
1353                throws CertificateException {
1354            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
1355        }
1356
1357        private String certificatesToString(X509Certificate[] certificates) {
1358            List<String> result = new ArrayList<String>();
1359            for (X509Certificate certificate : certificates) {
1360                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
1361            }
1362            return result.toString();
1363        }
1364    }
1365
1366    private static class RecordingHostnameVerifier implements HostnameVerifier {
1367        private final List<String> calls = new ArrayList<String>();
1368
1369        public boolean verify(String hostname, SSLSession session) {
1370            calls.add("verify " + hostname);
1371            return true;
1372        }
1373    }
1374}
1375