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