URLConnectionTest.java revision 6d41a7cc3cb4cc684c8bece69ddc55954812ad6e
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        assertEquals(-1, connection.getContentLength());
993
994        RecordedRequest request = server.takeRequest();
995        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
996    }
997
998    public void testClientConfiguredGzipContentEncoding() throws Exception {
999        byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"));
1000        server.enqueue(new MockResponse()
1001                .setBody(bodyBytes)
1002                .addHeader("Content-Encoding: gzip")
1003                .addHeader("Content-Length: " + bodyBytes.length));
1004        server.play();
1005
1006        URLConnection connection = server.getUrl("/").openConnection();
1007        connection.addRequestProperty("Accept-Encoding", "gzip");
1008        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1009        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
1010        assertEquals(bodyBytes.length, connection.getContentLength());
1011
1012        RecordedRequest request = server.takeRequest();
1013        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1014    }
1015
1016    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
1017        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
1018    }
1019
1020    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
1021        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
1022    }
1023
1024    public void testClientConfiguredCustomContentEncoding() throws Exception {
1025        server.enqueue(new MockResponse()
1026                .setBody("ABCDE")
1027                .addHeader("Content-Encoding: custom"));
1028        server.play();
1029
1030        URLConnection connection = server.getUrl("/").openConnection();
1031        connection.addRequestProperty("Accept-Encoding", "custom");
1032        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1033
1034        RecordedRequest request = server.takeRequest();
1035        assertContains(request.getHeaders(), "Accept-Encoding: custom");
1036    }
1037
1038    /**
1039     * Test a bug where gzip input streams weren't exhausting the input stream,
1040     * which corrupted the request that followed.
1041     * http://code.google.com/p/android/issues/detail?id=7059
1042     */
1043    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
1044            TransferKind transferKind) throws Exception {
1045        MockResponse responseOne = new MockResponse();
1046        responseOne.addHeader("Content-Encoding: gzip");
1047        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
1048        server.enqueue(responseOne);
1049        MockResponse responseTwo = new MockResponse();
1050        transferKind.setBody(responseTwo, "two (identity)", 5);
1051        server.enqueue(responseTwo);
1052        server.play();
1053
1054        URLConnection connection = server.getUrl("/").openConnection();
1055        connection.addRequestProperty("Accept-Encoding", "gzip");
1056        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1057        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
1058        assertEquals(0, server.takeRequest().getSequenceNumber());
1059
1060        connection = server.getUrl("/").openConnection();
1061        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1062        assertEquals(1, server.takeRequest().getSequenceNumber());
1063    }
1064
1065    /**
1066     * Test that HEAD requests don't have a body regardless of the response
1067     * headers. http://code.google.com/p/android/issues/detail?id=24672
1068     */
1069    public void testHeadAndContentLength() throws Exception {
1070        server.enqueue(new MockResponse()
1071                .clearHeaders()
1072                .addHeader("Content-Length: 100"));
1073        server.enqueue(new MockResponse().setBody("A"));
1074        server.play();
1075
1076        HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/").openConnection();
1077        connection1.setRequestMethod("HEAD");
1078        assertEquals("100", connection1.getHeaderField("Content-Length"));
1079        assertContent("", connection1);
1080
1081        HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/").openConnection();
1082        assertEquals("A", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
1083
1084        assertEquals(0, server.takeRequest().getSequenceNumber());
1085        assertEquals(1, server.takeRequest().getSequenceNumber());
1086    }
1087
1088    /**
1089     * Obnoxiously test that the chunk sizes transmitted exactly equal the
1090     * requested data+chunk header size. Although setChunkedStreamingMode()
1091     * isn't specific about whether the size applies to the data or the
1092     * complete chunk, the RI interprets it as a complete chunk.
1093     */
1094    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
1095        server.enqueue(new MockResponse());
1096        server.play();
1097
1098        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1099        urlConnection.setChunkedStreamingMode(8);
1100        urlConnection.setDoOutput(true);
1101        OutputStream outputStream = urlConnection.getOutputStream();
1102        outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII"));
1103        assertEquals(200, urlConnection.getResponseCode());
1104
1105        RecordedRequest request = server.takeRequest();
1106        assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII"));
1107        assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes());
1108    }
1109
1110    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
1111        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
1112    }
1113
1114    public void testAuthenticateWithChunkedStreaming() throws Exception {
1115        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
1116    }
1117
1118    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
1119        MockResponse pleaseAuthenticate = new MockResponse()
1120                .setResponseCode(401)
1121                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1122                .setBody("Please authenticate.");
1123        server.enqueue(pleaseAuthenticate);
1124        server.play();
1125
1126        Authenticator.setDefault(new SimpleAuthenticator());
1127        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1128        connection.setDoOutput(true);
1129        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1130        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1131            connection.setFixedLengthStreamingMode(requestBody.length);
1132        } else if (streamingMode == StreamingMode.CHUNKED) {
1133            connection.setChunkedStreamingMode(0);
1134        }
1135        OutputStream outputStream = connection.getOutputStream();
1136        outputStream.write(requestBody);
1137        outputStream.close();
1138        try {
1139            connection.getInputStream();
1140            fail();
1141        } catch (HttpRetryException expected) {
1142        }
1143
1144        // no authorization header for the request...
1145        RecordedRequest request = server.takeRequest();
1146        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1147        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1148    }
1149
1150    public void testSetValidRequestMethod() throws Exception {
1151        server.play();
1152        assertValidRequestMethod("GET");
1153        assertValidRequestMethod("DELETE");
1154        assertValidRequestMethod("HEAD");
1155        assertValidRequestMethod("OPTIONS");
1156        assertValidRequestMethod("POST");
1157        assertValidRequestMethod("PUT");
1158        assertValidRequestMethod("TRACE");
1159    }
1160
1161    private void assertValidRequestMethod(String requestMethod) throws Exception {
1162        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1163        connection.setRequestMethod(requestMethod);
1164        assertEquals(requestMethod, connection.getRequestMethod());
1165    }
1166
1167    public void testSetInvalidRequestMethodLowercase() throws Exception {
1168        server.play();
1169        assertInvalidRequestMethod("get");
1170    }
1171
1172    public void testSetInvalidRequestMethodConnect() throws Exception {
1173        server.play();
1174        assertInvalidRequestMethod("CONNECT");
1175    }
1176
1177    private void assertInvalidRequestMethod(String requestMethod) throws Exception {
1178        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1179        try {
1180            connection.setRequestMethod(requestMethod);
1181            fail();
1182        } catch (ProtocolException expected) {
1183        }
1184    }
1185
1186    public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception {
1187        server.play();
1188        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1189        try {
1190            connection.setFixedLengthStreamingMode(-2);
1191            fail();
1192        } catch (IllegalArgumentException expected) {
1193        }
1194    }
1195
1196    public void testCanSetNegativeChunkedStreamingMode() throws Exception {
1197        server.play();
1198        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1199        connection.setChunkedStreamingMode(-2);
1200    }
1201
1202    public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
1203        server.enqueue(new MockResponse().setBody("A"));
1204        server.play();
1205        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1206        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1207        try {
1208            connection.setFixedLengthStreamingMode(1);
1209            fail();
1210        } catch (IllegalStateException expected) {
1211        }
1212    }
1213
1214    public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception {
1215        server.enqueue(new MockResponse().setBody("A"));
1216        server.play();
1217        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1218        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1219        try {
1220            connection.setChunkedStreamingMode(1);
1221            fail();
1222        } catch (IllegalStateException expected) {
1223        }
1224    }
1225
1226    public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
1227        server.play();
1228        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1229        connection.setChunkedStreamingMode(1);
1230        try {
1231            connection.setFixedLengthStreamingMode(1);
1232            fail();
1233        } catch (IllegalStateException expected) {
1234        }
1235    }
1236
1237    public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
1238        server.play();
1239        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1240        connection.setFixedLengthStreamingMode(1);
1241        try {
1242            connection.setChunkedStreamingMode(1);
1243            fail();
1244        } catch (IllegalStateException expected) {
1245        }
1246    }
1247
1248    public void testSecureFixedLengthStreaming() throws Exception {
1249        testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
1250    }
1251
1252    public void testSecureChunkedStreaming() throws Exception {
1253        testSecureStreamingPost(StreamingMode.CHUNKED);
1254    }
1255
1256    /**
1257     * Users have reported problems using HTTPS with streaming request bodies.
1258     * http://code.google.com/p/android/issues/detail?id=12860
1259     */
1260    private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
1261        TestSSLContext testSSLContext = TestSSLContext.create();
1262        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1263        server.enqueue(new MockResponse().setBody("Success!"));
1264        server.play();
1265
1266        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1267        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1268        connection.setDoOutput(true);
1269        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1270        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1271            connection.setFixedLengthStreamingMode(requestBody.length);
1272        } else if (streamingMode == StreamingMode.CHUNKED) {
1273            connection.setChunkedStreamingMode(0);
1274        }
1275        OutputStream outputStream = connection.getOutputStream();
1276        outputStream.write(requestBody);
1277        outputStream.close();
1278        assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1279
1280        RecordedRequest request = server.takeRequest();
1281        assertEquals("POST / HTTP/1.1", request.getRequestLine());
1282        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1283            assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
1284        } else if (streamingMode == StreamingMode.CHUNKED) {
1285            assertEquals(Arrays.asList(4), request.getChunkSizes());
1286        }
1287        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1288    }
1289
1290    enum StreamingMode {
1291        FIXED_LENGTH, CHUNKED
1292    }
1293
1294    public void testAuthenticateWithPost() throws Exception {
1295        MockResponse pleaseAuthenticate = new MockResponse()
1296                .setResponseCode(401)
1297                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1298                .setBody("Please authenticate.");
1299        // fail auth three times...
1300        server.enqueue(pleaseAuthenticate);
1301        server.enqueue(pleaseAuthenticate);
1302        server.enqueue(pleaseAuthenticate);
1303        // ...then succeed the fourth time
1304        server.enqueue(new MockResponse().setBody("Successful auth!"));
1305        server.play();
1306
1307        Authenticator.setDefault(new SimpleAuthenticator());
1308        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1309        connection.setDoOutput(true);
1310        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1311        OutputStream outputStream = connection.getOutputStream();
1312        outputStream.write(requestBody);
1313        outputStream.close();
1314        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1315
1316        // no authorization header for the first request...
1317        RecordedRequest request = server.takeRequest();
1318        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1319
1320        // ...but the three requests that follow include an authorization header
1321        for (int i = 0; i < 3; i++) {
1322            request = server.takeRequest();
1323            assertEquals("POST / HTTP/1.1", request.getRequestLine());
1324            assertContains(request.getHeaders(), "Authorization: Basic "
1325                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1326            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1327        }
1328    }
1329
1330    public void testAuthenticateWithGet() throws Exception {
1331        MockResponse pleaseAuthenticate = new MockResponse()
1332                .setResponseCode(401)
1333                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1334                .setBody("Please authenticate.");
1335        // fail auth three times...
1336        server.enqueue(pleaseAuthenticate);
1337        server.enqueue(pleaseAuthenticate);
1338        server.enqueue(pleaseAuthenticate);
1339        // ...then succeed the fourth time
1340        server.enqueue(new MockResponse().setBody("Successful auth!"));
1341        server.play();
1342
1343        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1344        Authenticator.setDefault(authenticator);
1345        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1346        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1347        assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType);
1348        assertEquals(server.getPort(), authenticator.requestingPort);
1349        assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite);
1350        assertEquals("protected area", authenticator.requestingPrompt);
1351        assertEquals("http", authenticator.requestingProtocol);
1352        assertEquals("Basic", authenticator.requestingScheme);
1353
1354        // no authorization header for the first request...
1355        RecordedRequest request = server.takeRequest();
1356        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1357
1358        // ...but the three requests that follow requests include an authorization header
1359        for (int i = 0; i < 3; i++) {
1360            request = server.takeRequest();
1361            assertEquals("GET / HTTP/1.1", request.getRequestLine());
1362            assertContains(request.getHeaders(), "Authorization: Basic "
1363                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1364        }
1365    }
1366
1367    // http://code.google.com/p/android/issues/detail?id=19081
1368    public void testAuthenticateWithCommaSeparatedAuthenticationMethods() throws Exception {
1369        server.enqueue(new MockResponse()
1370                .setResponseCode(401)
1371                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\", Scheme2 realm=\"b\", "
1372                        + "Scheme3 realm=\"c\"")
1373                .setBody("Please authenticate."));
1374        server.enqueue(new MockResponse().setBody("Successful auth!"));
1375        server.play();
1376
1377        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1378        authenticator.expectedPrompt = "b";
1379        Authenticator.setDefault(authenticator);
1380        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1381        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1382
1383        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1384        assertContains(server.takeRequest().getHeaders(),
1385                "Authorization: Scheme2 " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1386        assertEquals("Scheme2", authenticator.requestingScheme);
1387    }
1388
1389    public void testAuthenticateWithMultipleAuthenticationHeaders() throws Exception {
1390        server.enqueue(new MockResponse()
1391                .setResponseCode(401)
1392                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\"")
1393                .addHeader("WWW-Authenticate: Scheme2 realm=\"b\"")
1394                .addHeader("WWW-Authenticate: Scheme3 realm=\"c\"")
1395                .setBody("Please authenticate."));
1396        server.enqueue(new MockResponse().setBody("Successful auth!"));
1397        server.play();
1398
1399        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1400        authenticator.expectedPrompt = "b";
1401        Authenticator.setDefault(authenticator);
1402        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1403        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1404
1405        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1406        assertContains(server.takeRequest().getHeaders(),
1407                "Authorization: Scheme2 " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1408        assertEquals("Scheme2", authenticator.requestingScheme);
1409    }
1410
1411    public void testRedirectedWithChunkedEncoding() throws Exception {
1412        testRedirected(TransferKind.CHUNKED, true);
1413    }
1414
1415    public void testRedirectedWithContentLengthHeader() throws Exception {
1416        testRedirected(TransferKind.FIXED_LENGTH, true);
1417    }
1418
1419    public void testRedirectedWithNoLengthHeaders() throws Exception {
1420        testRedirected(TransferKind.END_OF_STREAM, false);
1421    }
1422
1423    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1424        MockResponse response = new MockResponse()
1425                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1426                .addHeader("Location: /foo");
1427        transferKind.setBody(response, "This page has moved!", 10);
1428        server.enqueue(response);
1429        server.enqueue(new MockResponse().setBody("This is the new location!"));
1430        server.play();
1431
1432        URLConnection connection = server.getUrl("/").openConnection();
1433        assertEquals("This is the new location!",
1434                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1435
1436        RecordedRequest first = server.takeRequest();
1437        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1438        RecordedRequest retry = server.takeRequest();
1439        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1440        if (reuse) {
1441            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1442        }
1443    }
1444
1445    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1446        TestSSLContext testSSLContext = TestSSLContext.create();
1447        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1448        server.enqueue(new MockResponse()
1449                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1450                .addHeader("Location: /foo")
1451                .setBody("This page has moved!"));
1452        server.enqueue(new MockResponse().setBody("This is the new location!"));
1453        server.play();
1454
1455        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1456        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1457        assertEquals("This is the new location!",
1458                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1459
1460        RecordedRequest first = server.takeRequest();
1461        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1462        RecordedRequest retry = server.takeRequest();
1463        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1464        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1465    }
1466
1467    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1468        TestSSLContext testSSLContext = TestSSLContext.create();
1469        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1470        server.enqueue(new MockResponse()
1471                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1472                .addHeader("Location: http://anyhost/foo")
1473                .setBody("This page has moved!"));
1474        server.play();
1475
1476        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1477        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1478        assertEquals("This page has moved!",
1479                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1480    }
1481
1482    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1483        server.enqueue(new MockResponse()
1484                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1485                .addHeader("Location: https://anyhost/foo")
1486                .setBody("This page has moved!"));
1487        server.play();
1488
1489        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1490        assertEquals("This page has moved!",
1491                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1492    }
1493
1494    public void testRedirectToAnotherOriginServer() throws Exception {
1495        MockWebServer server2 = new MockWebServer();
1496        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1497        server2.play();
1498
1499        server.enqueue(new MockResponse()
1500                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1501                .addHeader("Location: " + server2.getUrl("/").toString())
1502                .setBody("This page has moved!"));
1503        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1504        server.play();
1505
1506        URLConnection connection = server.getUrl("/").openConnection();
1507        assertEquals("This is the 2nd server!",
1508                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1509        assertEquals(server2.getUrl("/"), connection.getURL());
1510
1511        // make sure the first server was careful to recycle the connection
1512        assertEquals("This is the first server again!",
1513                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1514
1515        RecordedRequest first = server.takeRequest();
1516        assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
1517        RecordedRequest second = server2.takeRequest();
1518        assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort());
1519        RecordedRequest third = server.takeRequest();
1520        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1521
1522        server2.shutdown();
1523    }
1524
1525    public void testResponse300MultipleChoiceWithPost() throws Exception {
1526        // Chrome doesn't follow the redirect, but Firefox and the RI both do
1527        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
1528    }
1529
1530    public void testResponse301MovedPermanentlyWithPost() throws Exception {
1531        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
1532    }
1533
1534    public void testResponse302MovedTemporarilyWithPost() throws Exception {
1535        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
1536    }
1537
1538    public void testResponse303SeeOtherWithPost() throws Exception {
1539        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
1540    }
1541
1542    private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
1543        server.enqueue(new MockResponse()
1544                .setResponseCode(redirectCode)
1545                .addHeader("Location: /page2")
1546                .setBody("This page has moved!"));
1547        server.enqueue(new MockResponse().setBody("Page 2"));
1548        server.play();
1549
1550        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection();
1551        connection.setDoOutput(true);
1552        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1553        OutputStream outputStream = connection.getOutputStream();
1554        outputStream.write(requestBody);
1555        outputStream.close();
1556        assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1557        assertTrue(connection.getDoOutput());
1558
1559        RecordedRequest page1 = server.takeRequest();
1560        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
1561        assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody()));
1562
1563        RecordedRequest page2 = server.takeRequest();
1564        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
1565    }
1566
1567    public void testResponse305UseProxy() throws Exception {
1568        server.play();
1569        server.enqueue(new MockResponse()
1570                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
1571                .addHeader("Location: " + server.getUrl("/"))
1572                .setBody("This page has moved!"));
1573        server.enqueue(new MockResponse().setBody("Proxy Response"));
1574
1575        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection();
1576        // Fails on the RI, which gets "Proxy Response"
1577        assertEquals("This page has moved!",
1578                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1579
1580        RecordedRequest page1 = server.takeRequest();
1581        assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
1582        assertEquals(1, server.getRequestCount());
1583    }
1584
1585    public void testHttpsWithCustomTrustManager() throws Exception {
1586        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
1587        RecordingTrustManager trustManager = new RecordingTrustManager();
1588        SSLContext sc = SSLContext.getInstance("TLS");
1589        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
1590
1591        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
1592        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
1593        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
1594        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1595        try {
1596            TestSSLContext testSSLContext = TestSSLContext.create();
1597            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1598            server.enqueue(new MockResponse().setBody("ABC"));
1599            server.enqueue(new MockResponse().setBody("DEF"));
1600            server.enqueue(new MockResponse().setBody("GHI"));
1601            server.play();
1602
1603            URL url = server.getUrl("/");
1604            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
1605            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
1606            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
1607
1608            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
1609            assertEquals(Arrays.asList("checkServerTrusted ["
1610                    + "CN=" + hostName + " 1, "
1611                    + "CN=Test Intermediate Certificate Authority 1, "
1612                    + "CN=Test Root Certificate Authority 1"
1613                    + "] RSA"),
1614                    trustManager.calls);
1615        } finally {
1616            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
1617            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
1618        }
1619    }
1620
1621    /**
1622     * Test that the timeout period is honored. The timeout may be doubled!
1623     * HttpURLConnection will wait the full timeout for each of the server's IP
1624     * addresses. This is typically one IPv4 address and one IPv6 address.
1625     */
1626    public void testConnectTimeouts() throws IOException {
1627        StuckServer ss = new StuckServer();
1628        int serverPort = ss.getLocalPort();
1629        URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection();
1630        int timeout = 1000;
1631        urlConnection.setConnectTimeout(timeout);
1632        long start = System.currentTimeMillis();
1633        try {
1634            urlConnection.getInputStream();
1635            fail();
1636        } catch (SocketTimeoutException expected) {
1637            long elapsed = System.currentTimeMillis() - start;
1638            int attempts = InetAddress.getAllByName("localhost").length; // one per IP address
1639            assertTrue("timeout=" +timeout + ", elapsed=" + elapsed + ", attempts=" + attempts,
1640                    Math.abs((attempts * timeout) - elapsed) < 500);
1641        } finally {
1642            ss.close();
1643        }
1644    }
1645
1646    public void testReadTimeouts() throws IOException {
1647        /*
1648         * This relies on the fact that MockWebServer doesn't close the
1649         * connection after a response has been sent. This causes the client to
1650         * try to read more bytes than are sent, which results in a timeout.
1651         */
1652        MockResponse timeout = new MockResponse()
1653                .setBody("ABC")
1654                .clearHeaders()
1655                .addHeader("Content-Length: 4");
1656        server.enqueue(timeout);
1657        server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
1658        server.play();
1659
1660        URLConnection urlConnection = server.getUrl("/").openConnection();
1661        urlConnection.setReadTimeout(1000);
1662        InputStream in = urlConnection.getInputStream();
1663        assertEquals('A', in.read());
1664        assertEquals('B', in.read());
1665        assertEquals('C', in.read());
1666        try {
1667            in.read(); // if Content-Length was accurate, this would return -1 immediately
1668            fail();
1669        } catch (SocketTimeoutException expected) {
1670        }
1671    }
1672
1673    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
1674        server.enqueue(new MockResponse());
1675        server.play();
1676
1677        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1678        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
1679        urlConnection.setDoOutput(true);
1680        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
1681        assertEquals(200, urlConnection.getResponseCode());
1682
1683        RecordedRequest request = server.takeRequest();
1684        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
1685    }
1686
1687    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
1688        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
1689        server.enqueue(new MockResponse());
1690        server.play();
1691
1692        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1693        a.setRequestProperty("Connection", "close");
1694        assertEquals(200, a.getResponseCode());
1695
1696        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1697        assertEquals(200, b.getResponseCode());
1698
1699        assertEquals(0, server.takeRequest().getSequenceNumber());
1700        assertEquals("When connection: close is used, each request should get its own connection",
1701                0, server.takeRequest().getSequenceNumber());
1702    }
1703
1704    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
1705        server.enqueue(new MockResponse().addHeader("Connection: close"));
1706        server.enqueue(new MockResponse());
1707        server.play();
1708
1709        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1710        assertEquals(200, a.getResponseCode());
1711
1712        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1713        assertEquals(200, b.getResponseCode());
1714
1715        assertEquals(0, server.takeRequest().getSequenceNumber());
1716        assertEquals("When connection: close is used, each request should get its own connection",
1717                0, server.takeRequest().getSequenceNumber());
1718    }
1719
1720    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
1721        MockResponse response = new MockResponse()
1722                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1723                .addHeader("Location: /foo")
1724                .addHeader("Connection: close");
1725        server.enqueue(response);
1726        server.enqueue(new MockResponse().setBody("This is the new location!"));
1727        server.play();
1728
1729        URLConnection connection = server.getUrl("/").openConnection();
1730        assertEquals("This is the new location!",
1731                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1732
1733        assertEquals(0, server.takeRequest().getSequenceNumber());
1734        assertEquals("When connection: close is used, each request should get its own connection",
1735                0, server.takeRequest().getSequenceNumber());
1736    }
1737
1738    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
1739        server.enqueue(new MockResponse()
1740                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
1741                .setBody("This body is not allowed!"));
1742        server.play();
1743
1744        URLConnection connection = server.getUrl("/").openConnection();
1745        assertEquals("This body is not allowed!",
1746                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1747    }
1748
1749    public void testSingleByteReadIsSigned() throws IOException {
1750        server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
1751        server.play();
1752
1753        URLConnection connection = server.getUrl("/").openConnection();
1754        InputStream in = connection.getInputStream();
1755        assertEquals(254, in.read());
1756        assertEquals(255, in.read());
1757        assertEquals(-1, in.read());
1758    }
1759
1760    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
1761        testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
1762    }
1763
1764    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
1765        testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
1766    }
1767
1768    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
1769        testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
1770    }
1771
1772    /**
1773     * We explicitly permit apps to close the upload stream even after it has
1774     * been transmitted.  We also permit flush so that buffered streams can
1775     * do a no-op flush when they are closed. http://b/3038470
1776     */
1777    private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
1778        server.enqueue(new MockResponse().setBody("abc"));
1779        server.play();
1780
1781        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1782        connection.setDoOutput(true);
1783        byte[] upload = "def".getBytes("UTF-8");
1784
1785        if (transferKind == TransferKind.CHUNKED) {
1786            connection.setChunkedStreamingMode(0);
1787        } else if (transferKind == TransferKind.FIXED_LENGTH) {
1788            connection.setFixedLengthStreamingMode(upload.length);
1789        }
1790
1791        OutputStream out = connection.getOutputStream();
1792        out.write(upload);
1793        assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1794
1795        out.flush(); // dubious but permitted
1796        try {
1797            out.write("ghi".getBytes("UTF-8"));
1798            fail();
1799        } catch (IOException expected) {
1800        }
1801    }
1802
1803    public void testGetHeadersThrows() throws IOException {
1804        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
1805        server.play();
1806
1807        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1808        try {
1809            connection.getInputStream();
1810            fail();
1811        } catch (IOException expected) {
1812        }
1813
1814        try {
1815            connection.getInputStream();
1816            fail();
1817        } catch (IOException expected) {
1818        }
1819    }
1820
1821    public void testGetKeepAlive() throws Exception {
1822        MockWebServer server = new MockWebServer();
1823        server.enqueue(new MockResponse().setBody("ABC"));
1824        server.play();
1825
1826        // The request should work once and then fail
1827        URLConnection connection = server.getUrl("").openConnection();
1828        InputStream input = connection.getInputStream();
1829        assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
1830        input.close();
1831        try {
1832            server.getUrl("").openConnection().getInputStream();
1833            fail();
1834        } catch (ConnectException expected) {
1835        }
1836    }
1837
1838    /**
1839     * This test goes through the exhaustive set of interesting ASCII characters
1840     * because most of those characters are interesting in some way according to
1841     * RFC 2396 and RFC 2732. http://b/1158780
1842     */
1843    public void testLenientUrlToUri() throws Exception {
1844        // alphanum
1845        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
1846
1847        // control characters
1848        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
1849        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
1850
1851        // ascii characters
1852        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1853        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1854        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
1855        testUrlToUriMapping("!", "!", "!", "!", "!");
1856        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
1857        testUrlToUriMapping("#", null, null, null, "%23");
1858        testUrlToUriMapping("$", "$", "$", "$", "$");
1859        testUrlToUriMapping("&", "&", "&", "&", "&");
1860        testUrlToUriMapping("'", "'", "'", "'", "'");
1861        testUrlToUriMapping("(", "(", "(", "(", "(");
1862        testUrlToUriMapping(")", ")", ")", ")", ")");
1863        testUrlToUriMapping("*", "*", "*", "*", "*");
1864        testUrlToUriMapping("+", "+", "+", "+", "+");
1865        testUrlToUriMapping(",", ",", ",", ",", ",");
1866        testUrlToUriMapping("-", "-", "-", "-", "-");
1867        testUrlToUriMapping(".", ".", ".", ".", ".");
1868        testUrlToUriMapping("/", null, "/", "/", "/");
1869        testUrlToUriMapping(":", null, ":", ":", ":");
1870        testUrlToUriMapping(";", ";", ";", ";", ";");
1871        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
1872        testUrlToUriMapping("=", "=", "=", "=", "=");
1873        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
1874        testUrlToUriMapping("?", null, null, "?", "?");
1875        testUrlToUriMapping("@", "@", "@", "@", "@");
1876        testUrlToUriMapping("[", null, "%5B", null, "%5B");
1877        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
1878        testUrlToUriMapping("]", null, "%5D", null, "%5D");
1879        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
1880        testUrlToUriMapping("_", "_", "_", "_", "_");
1881        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
1882        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
1883        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
1884        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
1885        testUrlToUriMapping("~", "~", "~", "~", "~");
1886        testUrlToUriMapping("~", "~", "~", "~", "~");
1887        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
1888
1889        // beyond ascii
1890        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
1891        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
1892        testUrlToUriMapping("\ud842\udf9f",
1893                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
1894    }
1895
1896    public void testLenientUrlToUriNul() throws Exception {
1897        testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this
1898    }
1899
1900    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
1901            String asQuery, String asFragment) throws Exception {
1902        if (asAuthority != null) {
1903            assertEquals("http://host" + asAuthority + ".tld/",
1904                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
1905        }
1906        if (asFile != null) {
1907            assertEquals("http://host.tld/file" + asFile + "/",
1908                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
1909        }
1910        if (asQuery != null) {
1911            assertEquals("http://host.tld/file?q" + asQuery + "=x",
1912                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
1913        }
1914        assertEquals("http://host.tld/file#" + asFragment + "-x",
1915                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
1916    }
1917
1918    /**
1919     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
1920     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
1921     * characters like '{' and '|' by escaping these characters.
1922     */
1923    private URI backdoorUrlToUri(URL url) throws Exception {
1924        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
1925
1926        ResponseCache.setDefault(new ResponseCache() {
1927            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1928                return null;
1929            }
1930            @Override public CacheResponse get(URI uri, String requestMethod,
1931                    Map<String, List<String>> requestHeaders) throws IOException {
1932                uriReference.set(uri);
1933                throw new UnsupportedOperationException();
1934            }
1935        });
1936
1937        try {
1938            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1939            connection.getResponseCode();
1940        } catch (Exception expected) {
1941        }
1942
1943        return uriReference.get();
1944    }
1945
1946    /**
1947     * Don't explode if the cache returns a null body. http://b/3373699
1948     */
1949    public void testResponseCacheReturnsNullOutputStream() throws Exception {
1950        final AtomicBoolean aborted = new AtomicBoolean();
1951        ResponseCache.setDefault(new ResponseCache() {
1952            @Override public CacheResponse get(URI uri, String requestMethod,
1953                    Map<String, List<String>> requestHeaders) throws IOException {
1954                return null;
1955            }
1956            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1957                return new CacheRequest() {
1958                    @Override public void abort() {
1959                        aborted.set(true);
1960                    }
1961                    @Override public OutputStream getBody() throws IOException {
1962                        return null;
1963                    }
1964                };
1965            }
1966        });
1967
1968        server.enqueue(new MockResponse().setBody("abcdef"));
1969        server.play();
1970
1971        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1972        InputStream in = connection.getInputStream();
1973        assertEquals("abc", readAscii(in, 3));
1974        in.close();
1975        assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
1976    }
1977
1978
1979    /**
1980     * http://code.google.com/p/android/issues/detail?id=14562
1981     */
1982    public void testReadAfterLastByte() throws Exception {
1983        server.enqueue(new MockResponse()
1984                .setBody("ABC")
1985                .clearHeaders()
1986                .addHeader("Connection: close")
1987                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END));
1988        server.play();
1989
1990        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1991        InputStream in = connection.getInputStream();
1992        assertEquals("ABC", readAscii(in, 3));
1993        assertEquals(-1, in.read());
1994        assertEquals(-1, in.read()); // throws IOException in Gingerbread
1995    }
1996
1997    public void testGetContent() throws Exception {
1998        server.enqueue(new MockResponse().setBody("A"));
1999        server.play();
2000        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2001        InputStream in = (InputStream) connection.getContent();
2002        assertEquals("A", readAscii(in, Integer.MAX_VALUE));
2003    }
2004
2005    public void testGetContentOfType() throws Exception {
2006        server.enqueue(new MockResponse().setBody("A"));
2007        server.play();
2008        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2009        try {
2010            connection.getContent(null);
2011            fail();
2012        } catch (NullPointerException expected) {
2013        }
2014        try {
2015            connection.getContent(new Class[] { null });
2016            fail();
2017        } catch (NullPointerException expected) {
2018        }
2019        assertNull(connection.getContent(new Class[] { getClass() }));
2020        connection.disconnect();
2021    }
2022
2023    public void testGetOutputStreamOnGetFails() throws Exception {
2024        server.enqueue(new MockResponse());
2025        server.play();
2026        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2027        try {
2028            connection.getOutputStream();
2029            fail();
2030        } catch (ProtocolException expected) {
2031        }
2032    }
2033
2034    public void testGetOutputAfterGetInputStreamFails() throws Exception {
2035        server.enqueue(new MockResponse());
2036        server.play();
2037        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2038        connection.setDoOutput(true);
2039        try {
2040            connection.getInputStream();
2041            connection.getOutputStream();
2042            fail();
2043        } catch (ProtocolException expected) {
2044        }
2045    }
2046
2047    public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception {
2048        server.enqueue(new MockResponse());
2049        server.play();
2050        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2051        connection.connect();
2052        try {
2053            connection.setDoOutput(true);
2054            fail();
2055        } catch (IllegalStateException expected) {
2056        }
2057        try {
2058            connection.setDoInput(true);
2059            fail();
2060        } catch (IllegalStateException expected) {
2061        }
2062        connection.disconnect();
2063    }
2064
2065    public void testClientSendsContentLength() throws Exception {
2066        server.enqueue(new MockResponse().setBody("A"));
2067        server.play();
2068        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2069        connection.setDoOutput(true);
2070        OutputStream out = connection.getOutputStream();
2071        out.write(new byte[] { 'A', 'B', 'C' });
2072        out.close();
2073        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2074        RecordedRequest request = server.takeRequest();
2075        assertContains(request.getHeaders(), "Content-Length: 3");
2076    }
2077
2078    public void testGetContentLengthConnects() throws Exception {
2079        server.enqueue(new MockResponse().setBody("ABC"));
2080        server.play();
2081        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2082        assertEquals(3, connection.getContentLength());
2083        connection.disconnect();
2084    }
2085
2086    public void testGetContentTypeConnects() throws Exception {
2087        server.enqueue(new MockResponse()
2088                .addHeader("Content-Type: text/plain")
2089                .setBody("ABC"));
2090        server.play();
2091        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2092        assertEquals("text/plain", connection.getContentType());
2093        connection.disconnect();
2094    }
2095
2096    public void testGetContentEncodingConnects() throws Exception {
2097        server.enqueue(new MockResponse()
2098                .addHeader("Content-Encoding: identity")
2099                .setBody("ABC"));
2100        server.play();
2101        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2102        assertEquals("identity", connection.getContentEncoding());
2103        connection.disconnect();
2104    }
2105
2106    // http://b/4361656
2107    public void testUrlContainsQueryButNoPath() throws Exception {
2108        server.enqueue(new MockResponse().setBody("A"));
2109        server.play();
2110        URL url = new URL("http", server.getHostName(), server.getPort(), "?query");
2111        assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE));
2112        RecordedRequest request = server.takeRequest();
2113        assertEquals("GET /?query HTTP/1.1", request.getRequestLine());
2114    }
2115
2116    // http://code.google.com/p/android/issues/detail?id=20442
2117    public void testInputStreamAvailableWithChunkedEncoding() throws Exception {
2118        testInputStreamAvailable(TransferKind.CHUNKED);
2119    }
2120
2121    public void testInputStreamAvailableWithContentLengthHeader() throws Exception {
2122        testInputStreamAvailable(TransferKind.FIXED_LENGTH);
2123    }
2124
2125    public void testInputStreamAvailableWithNoLengthHeaders() throws Exception {
2126        testInputStreamAvailable(TransferKind.END_OF_STREAM);
2127    }
2128
2129    private void testInputStreamAvailable(TransferKind transferKind) throws IOException {
2130        String body = "ABCDEFGH";
2131        MockResponse response = new MockResponse();
2132        transferKind.setBody(response, body, 4);
2133        server.enqueue(response);
2134        server.play();
2135        URLConnection connection = server.getUrl("/").openConnection();
2136        InputStream in = connection.getInputStream();
2137        for (int i = 0; i < body.length(); i++) {
2138            assertTrue(in.available() >= 0);
2139            assertEquals(body.charAt(i), in.read());
2140        }
2141        assertEquals(0, in.available());
2142        assertEquals(-1, in.read());
2143    }
2144
2145    // http://code.google.com/p/android/issues/detail?id=28095
2146    public void testInvalidIpv4Address() throws Exception {
2147        try {
2148            URI uri = new URI("http://1111.111.111.111/index.html");
2149            uri.toURL().openConnection().connect();
2150            fail();
2151        } catch (UnknownHostException expected) {
2152        }
2153    }
2154
2155    // http://code.google.com/p/android/issues/detail?id=16895
2156    public void testUrlWithSpaceInHost() throws Exception {
2157        URLConnection urlConnection = new URL("http://and roid.com/").openConnection();
2158        try {
2159            urlConnection.getInputStream();
2160            fail();
2161        } catch (UnknownHostException expected) {
2162        }
2163    }
2164
2165    public void testUrlWithSpaceInHostViaHttpProxy() throws Exception {
2166        server.enqueue(new MockResponse());
2167        server.play();
2168        URLConnection urlConnection = new URL("http://and roid.com/")
2169                .openConnection(server.toProxyAddress());
2170        try {
2171            urlConnection.getInputStream();
2172            fail(); // the RI makes a bogus proxy request for "GET http://and roid.com/ HTTP/1.1"
2173        } catch (UnknownHostException expected) {
2174        }
2175    }
2176
2177    public void testSslFallback() throws Exception {
2178        TestSSLContext testSSLContext = TestSSLContext.create();
2179        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2180        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
2181        server.enqueue(new MockResponse().setBody("This required a 2nd handshake"));
2182        server.play();
2183
2184        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2185        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2186        assertEquals("This required a 2nd handshake",
2187                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2188
2189        RecordedRequest first = server.takeRequest();
2190        assertEquals(0, first.getSequenceNumber());
2191        RecordedRequest retry = server.takeRequest();
2192        assertEquals(0, retry.getSequenceNumber());
2193        assertEquals("SSLv3", retry.getSslProtocol());
2194    }
2195
2196    public void testInspectSslBeforeConnect() throws Exception {
2197        TestSSLContext testSSLContext = TestSSLContext.create();
2198        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2199        server.enqueue(new MockResponse());
2200        server.play();
2201
2202        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2203        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2204        assertNotNull(connection.getHostnameVerifier());
2205        try {
2206            connection.getLocalCertificates();
2207            fail();
2208        } catch (IllegalStateException expected) {
2209        }
2210        try {
2211            connection.getServerCertificates();
2212            fail();
2213        } catch (IllegalStateException expected) {
2214        }
2215        try {
2216            connection.getCipherSuite();
2217            fail();
2218        } catch (IllegalStateException expected) {
2219        }
2220        try {
2221            connection.getPeerPrincipal();
2222            fail();
2223        } catch (IllegalStateException expected) {
2224        }
2225    }
2226
2227    /**
2228     * Test that we can inspect the SSL session after connect().
2229     * http://code.google.com/p/android/issues/detail?id=24431
2230     */
2231    public void testInspectSslAfterConnect() throws Exception {
2232        TestSSLContext testSSLContext = TestSSLContext.create();
2233        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2234        server.enqueue(new MockResponse());
2235        server.play();
2236
2237        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2238        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2239        connection.connect();
2240        assertNotNull(connection.getHostnameVerifier());
2241        assertNull(connection.getLocalCertificates());
2242        assertNotNull(connection.getServerCertificates());
2243        assertNotNull(connection.getCipherSuite());
2244        assertNotNull(connection.getPeerPrincipal());
2245    }
2246
2247    /**
2248     * Returns a gzipped copy of {@code bytes}.
2249     */
2250    public byte[] gzip(byte[] bytes) throws IOException {
2251        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
2252        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
2253        gzippedOut.write(bytes);
2254        gzippedOut.close();
2255        return bytesOut.toByteArray();
2256    }
2257
2258    /**
2259     * Reads at most {@code limit} characters from {@code in} and asserts that
2260     * content equals {@code expected}.
2261     */
2262    private void assertContent(String expected, URLConnection connection, int limit)
2263            throws IOException {
2264        connection.connect();
2265        assertEquals(expected, readAscii(connection.getInputStream(), limit));
2266        ((HttpURLConnection) connection).disconnect();
2267    }
2268
2269    private void assertContent(String expected, URLConnection connection) throws IOException {
2270        assertContent(expected, connection, Integer.MAX_VALUE);
2271    }
2272
2273    private void assertContains(List<String> headers, String header) {
2274        assertTrue(headers.toString(), headers.contains(header));
2275    }
2276
2277    private void assertContainsNoneMatching(List<String> headers, String pattern) {
2278        for (String header : headers) {
2279            if (header.matches(pattern)) {
2280                fail("Header " + header + " matches " + pattern);
2281            }
2282        }
2283    }
2284
2285    private Set<String> newSet(String... elements) {
2286        return new HashSet<String>(Arrays.asList(elements));
2287    }
2288
2289    enum TransferKind {
2290        CHUNKED() {
2291            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
2292                    throws IOException {
2293                response.setChunkedBody(content, chunkSize);
2294            }
2295        },
2296        FIXED_LENGTH() {
2297            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
2298                response.setBody(content);
2299            }
2300        },
2301        END_OF_STREAM() {
2302            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
2303                response.setBody(content);
2304                response.setSocketPolicy(DISCONNECT_AT_END);
2305                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
2306                    if (h.next().startsWith("Content-Length:")) {
2307                        h.remove();
2308                        break;
2309                    }
2310                }
2311            }
2312        };
2313
2314        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
2315                throws IOException;
2316
2317        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
2318            setBody(response, content.getBytes("UTF-8"), chunkSize);
2319        }
2320    }
2321
2322    enum ProxyConfig {
2323        NO_PROXY() {
2324            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2325                    throws IOException {
2326                return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
2327            }
2328        },
2329
2330        CREATE_ARG() {
2331            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2332                    throws IOException {
2333                return (HttpURLConnection) url.openConnection(server.toProxyAddress());
2334            }
2335        },
2336
2337        PROXY_SYSTEM_PROPERTY() {
2338            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2339                    throws IOException {
2340                System.setProperty("proxyHost", "localhost");
2341                System.setProperty("proxyPort", Integer.toString(server.getPort()));
2342                return (HttpURLConnection) url.openConnection();
2343            }
2344        },
2345
2346        HTTP_PROXY_SYSTEM_PROPERTY() {
2347            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2348                    throws IOException {
2349                System.setProperty("http.proxyHost", "localhost");
2350                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
2351                return (HttpURLConnection) url.openConnection();
2352            }
2353        },
2354
2355        HTTPS_PROXY_SYSTEM_PROPERTY() {
2356            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2357                    throws IOException {
2358                System.setProperty("https.proxyHost", "localhost");
2359                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
2360                return (HttpURLConnection) url.openConnection();
2361            }
2362        };
2363
2364        public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException;
2365    }
2366
2367    private static class RecordingTrustManager implements X509TrustManager {
2368        private final List<String> calls = new ArrayList<String>();
2369
2370        public X509Certificate[] getAcceptedIssuers() {
2371            calls.add("getAcceptedIssuers");
2372            return new X509Certificate[] {};
2373        }
2374
2375        public void checkClientTrusted(X509Certificate[] chain, String authType)
2376                throws CertificateException {
2377            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
2378        }
2379
2380        public void checkServerTrusted(X509Certificate[] chain, String authType)
2381                throws CertificateException {
2382            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
2383        }
2384
2385        private String certificatesToString(X509Certificate[] certificates) {
2386            List<String> result = new ArrayList<String>();
2387            for (X509Certificate certificate : certificates) {
2388                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
2389            }
2390            return result.toString();
2391        }
2392    }
2393
2394    private static class RecordingHostnameVerifier implements HostnameVerifier {
2395        private final List<String> calls = new ArrayList<String>();
2396
2397        public boolean verify(String hostname, SSLSession session) {
2398            calls.add("verify " + hostname);
2399            return true;
2400        }
2401    }
2402
2403    private static class SimpleAuthenticator extends Authenticator {
2404        /** base64("username:password") */
2405        private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ=";
2406
2407        private String expectedPrompt;
2408        private RequestorType requestorType;
2409        private int requestingPort;
2410        private InetAddress requestingSite;
2411        private String requestingPrompt;
2412        private String requestingProtocol;
2413        private String requestingScheme;
2414
2415        protected PasswordAuthentication getPasswordAuthentication() {
2416            requestorType = getRequestorType();
2417            requestingPort = getRequestingPort();
2418            requestingSite = getRequestingSite();
2419            requestingPrompt = getRequestingPrompt();
2420            requestingProtocol = getRequestingProtocol();
2421            requestingScheme = getRequestingScheme();
2422            return (expectedPrompt == null || expectedPrompt.equals(requestingPrompt))
2423                    ? new PasswordAuthentication("username", "password".toCharArray())
2424                    : null;
2425        }
2426    }
2427}
2428