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