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