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