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