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