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