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