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