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