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