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