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