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