URLConnectionTest.java revision 8a1e42f2cfcb3cab8cfba7a572d68c534598d627
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        RecordedRequest request = server.takeRequest();
535        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
536    }
537
538    /**
539     * Verify that we don't retry connections on certificate verification errors.
540     *
541     * http://code.google.com/p/android/issues/detail?id=13178
542     */
543    public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
544        TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(),
545                                                              TestKeyStore.getServer());
546
547        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
548        server.enqueue(new MockResponse()); // unused
549        server.play();
550
551        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
552        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
553        try {
554            connection.getInputStream();
555            fail();
556        } catch (SSLHandshakeException expected) {
557            assertTrue(expected.getCause() instanceof CertificateException);
558        }
559        assertEquals(0, server.getRequestCount());
560    }
561
562    public void testConnectViaProxyUsingProxyArg() throws Exception {
563        testConnectViaProxy(ProxyConfig.CREATE_ARG);
564    }
565
566    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
567        testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
568    }
569
570    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
571        testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
572    }
573
574    private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
575        MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
576        server.enqueue(mockResponse);
577        server.play();
578
579        URL url = new URL("http://android.com/foo");
580        HttpURLConnection connection = proxyConfig.connect(server, url);
581        assertContent("this response comes via a proxy", connection);
582
583        RecordedRequest request = server.takeRequest();
584        assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine());
585        assertContains(request.getHeaders(), "Host: android.com");
586    }
587
588    public void testContentDisagreesWithContentLengthHeader() throws IOException {
589        server.enqueue(new MockResponse()
590                .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
591                .clearHeaders()
592                .addHeader("Content-Length: 3"));
593        server.play();
594
595        assertContent("abc", server.getUrl("/").openConnection());
596    }
597
598    public void testContentDisagreesWithChunkedHeader() throws IOException {
599        MockResponse mockResponse = new MockResponse();
600        mockResponse.setChunkedBody("abc", 3);
601        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
602        bytesOut.write(mockResponse.getBody());
603        bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes());
604        mockResponse.setBody(bytesOut.toByteArray());
605        mockResponse.clearHeaders();
606        mockResponse.addHeader("Transfer-encoding: chunked");
607
608        server.enqueue(mockResponse);
609        server.play();
610
611        assertContent("abc", server.getUrl("/").openConnection());
612    }
613
614    public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
615        testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
616    }
617
618    public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
619        // https should not use http proxy
620        testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
621    }
622
623    private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception {
624        TestSSLContext testSSLContext = TestSSLContext.create();
625
626        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
627        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
628        server.play();
629
630        URL url = server.getUrl("/foo");
631        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
632        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
633
634        assertContent("this response comes via HTTPS", connection);
635
636        RecordedRequest request = server.takeRequest();
637        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
638    }
639
640
641    public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
642        testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
643    }
644
645    /**
646     * We weren't honoring all of the appropriate proxy system properties when
647     * connecting via HTTPS. http://b/3097518
648     */
649    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
650        testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
651    }
652
653    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
654        testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
655    }
656
657    /**
658     * We were verifying the wrong hostname when connecting to an HTTPS site
659     * through a proxy. http://b/3097277
660     */
661    private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
662        TestSSLContext testSSLContext = TestSSLContext.create();
663        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
664
665        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
666        server.enqueue(new MockResponse()
667                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
668                .clearHeaders());
669        server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
670        server.play();
671
672        URL url = new URL("https://android.com/foo");
673        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
674        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
675        connection.setHostnameVerifier(hostnameVerifier);
676
677        assertContent("this response comes via a secure proxy", connection);
678
679        RecordedRequest connect = server.takeRequest();
680        assertEquals("Connect line failure on proxy",
681                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
682        assertContains(connect.getHeaders(), "Host: android.com");
683
684        RecordedRequest get = server.takeRequest();
685        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
686        assertContains(get.getHeaders(), "Host: android.com");
687        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
688    }
689
690
691    /**
692     * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912
693     */
694    public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception {
695        ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY;
696
697        TestSSLContext testSSLContext = TestSSLContext.create();
698
699        initResponseCache();
700
701        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
702        server.enqueue(new MockResponse()
703                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
704                .clearHeaders()
705                .setBody("bogus proxy connect response content")); // Key to reproducing b/6754912
706        server.play();
707
708        URL url = new URL("https://android.com/foo");
709        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
710        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
711
712        try {
713            connection.connect();
714            fail();
715        } catch (IOException expected) {
716            // Thrown when the connect causes SSLSocket.startHandshake() to throw
717            // when it sees the "bogus proxy connect response content"
718            // instead of a ServerHello handshake message.
719        }
720
721        RecordedRequest connect = server.takeRequest();
722        assertEquals("Connect line failure on proxy",
723                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
724        assertContains(connect.getHeaders(), "Host: android.com");
725    }
726
727    private void initResponseCache() throws IOException {
728        String tmp = System.getProperty("java.io.tmpdir");
729        File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
730        cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
731        ResponseCache.setDefault(cache);
732    }
733
734    /**
735     * Test which headers are sent unencrypted to the HTTP proxy.
736     */
737    public void testProxyConnectIncludesProxyHeadersOnly()
738            throws IOException, InterruptedException {
739        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
740        TestSSLContext testSSLContext = TestSSLContext.create();
741
742        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
743        server.enqueue(new MockResponse()
744                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
745                .clearHeaders());
746        server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
747        server.play();
748
749        URL url = new URL("https://android.com/foo");
750        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
751                server.toProxyAddress());
752        connection.addRequestProperty("Private", "Secret");
753        connection.addRequestProperty("Proxy-Authorization", "bar");
754        connection.addRequestProperty("User-Agent", "baz");
755        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
756        connection.setHostnameVerifier(hostnameVerifier);
757        assertContent("encrypted response from the origin server", connection);
758
759        RecordedRequest connect = server.takeRequest();
760        assertContainsNoneMatching(connect.getHeaders(), "Private.*");
761        assertContains(connect.getHeaders(), "Proxy-Authorization: bar");
762        assertContains(connect.getHeaders(), "User-Agent: baz");
763        assertContains(connect.getHeaders(), "Host: android.com");
764        assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive");
765
766        RecordedRequest get = server.takeRequest();
767        assertContains(get.getHeaders(), "Private: Secret");
768        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
769    }
770
771    public void testProxyAuthenticateOnConnect() throws Exception {
772        Authenticator.setDefault(new SimpleAuthenticator());
773        TestSSLContext testSSLContext = TestSSLContext.create();
774        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
775        server.enqueue(new MockResponse()
776                .setResponseCode(407)
777                .addHeader("Proxy-Authenticate: Basic realm=\"localhost\""));
778        server.enqueue(new MockResponse()
779                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
780                .clearHeaders());
781        server.enqueue(new MockResponse().setBody("A"));
782        server.play();
783
784        URL url = new URL("https://android.com/foo");
785        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
786                server.toProxyAddress());
787        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
788        connection.setHostnameVerifier(new RecordingHostnameVerifier());
789        assertContent("A", connection);
790
791        RecordedRequest connect1 = server.takeRequest();
792        assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine());
793        assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*");
794
795        RecordedRequest connect2 = server.takeRequest();
796        assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine());
797        assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic "
798                + SimpleAuthenticator.BASE_64_CREDENTIALS);
799
800        RecordedRequest get = server.takeRequest();
801        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
802        assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*");
803    }
804
805    // Don't disconnect after building a tunnel with CONNECT
806    // http://code.google.com/p/android/issues/detail?id=37221
807    public void testProxyWithConnectionClose() throws IOException {
808        TestSSLContext testSSLContext = TestSSLContext.create();
809        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
810        server.enqueue(new MockResponse()
811                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
812                .clearHeaders());
813        server.enqueue(new MockResponse().setBody("this response comes via a proxy"));
814        server.play();
815
816        URL url = new URL("https://android.com/foo");
817        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
818                server.toProxyAddress());
819        connection.setRequestProperty("Connection", "close");
820        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
821        connection.setHostnameVerifier(new RecordingHostnameVerifier());
822
823        assertContent("this response comes via a proxy", connection);
824    }
825
826    public void testDisconnectedConnection() throws IOException {
827        server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"));
828        server.play();
829
830        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
831        InputStream in = connection.getInputStream();
832        assertEquals('A', (char) in.read());
833        connection.disconnect();
834        try {
835            in.read();
836            fail("Expected a connection closed exception");
837        } catch (IOException expected) {
838        }
839    }
840
841    public void testDisconnectBeforeConnect() throws IOException {
842        server.enqueue(new MockResponse().setBody("A"));
843        server.play();
844
845        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
846        connection.disconnect();
847
848        assertContent("A", connection);
849        assertEquals(200, connection.getResponseCode());
850    }
851
852    public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException {
853        CloseGuardGuard guard = new CloseGuardGuard();
854        try {
855            server.enqueue(new MockResponse()
856                           .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
857                           .addHeader("Content-Encoding: gzip"));
858            server.play();
859
860            HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
861            assertEquals(200, connection.getResponseCode());
862            connection.disconnect();
863            connection = null;
864            assertFalse(guard.wasCloseGuardCalled());
865        } finally {
866            guard.close();
867        }
868    }
869
870    public static class CloseGuardGuard implements Closeable, CloseGuard.Reporter  {
871        private final CloseGuard.Reporter oldReporter = CloseGuard.getReporter();
872
873        private AtomicBoolean closeGuardCalled = new AtomicBoolean();
874
875        public CloseGuardGuard() {
876            CloseGuard.setReporter(this);
877        }
878
879        @Override public void report(String message, Throwable allocationSite) {
880            oldReporter.report(message, allocationSite);
881            closeGuardCalled.set(true);
882        }
883
884        public boolean wasCloseGuardCalled() {
885            FinalizationTester.induceFinalization();
886            close();
887            return closeGuardCalled.get();
888        }
889
890        @Override public void close() {
891            CloseGuard.setReporter(oldReporter);
892        }
893
894    }
895
896    public void testDefaultRequestProperty() throws Exception {
897        URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
898        assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
899    }
900
901    /**
902     * Reads {@code count} characters from the stream. If the stream is
903     * exhausted before {@code count} characters can be read, the remaining
904     * characters are returned and the stream is closed.
905     */
906    private String readAscii(InputStream in, int count) throws IOException {
907        StringBuilder result = new StringBuilder();
908        for (int i = 0; i < count; i++) {
909            int value = in.read();
910            if (value == -1) {
911                in.close();
912                break;
913            }
914            result.append((char) value);
915        }
916        return result.toString();
917    }
918
919    public void testMarkAndResetWithContentLengthHeader() throws IOException {
920        testMarkAndReset(TransferKind.FIXED_LENGTH);
921    }
922
923    public void testMarkAndResetWithChunkedEncoding() throws IOException {
924        testMarkAndReset(TransferKind.CHUNKED);
925    }
926
927    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
928        testMarkAndReset(TransferKind.END_OF_STREAM);
929    }
930
931    private void testMarkAndReset(TransferKind transferKind) throws IOException {
932        MockResponse response = new MockResponse();
933        transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
934        server.enqueue(response);
935        server.enqueue(response);
936        server.play();
937
938        InputStream in = server.getUrl("/").openConnection().getInputStream();
939        assertFalse("This implementation claims to support mark().", in.markSupported());
940        in.mark(5);
941        assertEquals("ABCDE", readAscii(in, 5));
942        try {
943            in.reset();
944            fail();
945        } catch (IOException expected) {
946        }
947        assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
948        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
949    }
950
951    /**
952     * We've had a bug where we forget the HTTP response when we see response
953     * code 401. This causes a new HTTP request to be issued for every call into
954     * the URLConnection.
955     */
956    public void testUnauthorizedResponseHandling() throws IOException {
957        MockResponse response = new MockResponse()
958                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
959                .setResponseCode(401) // UNAUTHORIZED
960                .setBody("Unauthorized");
961        server.enqueue(response);
962        server.enqueue(response);
963        server.enqueue(response);
964        server.play();
965
966        URL url = server.getUrl("/");
967        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
968
969        assertEquals(401, conn.getResponseCode());
970        assertEquals(401, conn.getResponseCode());
971        assertEquals(401, conn.getResponseCode());
972        assertEquals(1, server.getRequestCount());
973    }
974
975    public void testNonHexChunkSize() throws IOException {
976        server.enqueue(new MockResponse()
977                .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
978                .clearHeaders()
979                .addHeader("Transfer-encoding: chunked"));
980        server.play();
981
982        URLConnection connection = server.getUrl("/").openConnection();
983        try {
984            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
985            fail();
986        } catch (IOException e) {
987        }
988    }
989
990    public void testMissingChunkBody() throws IOException {
991        server.enqueue(new MockResponse()
992                .setBody("5")
993                .clearHeaders()
994                .addHeader("Transfer-encoding: chunked")
995                .setSocketPolicy(DISCONNECT_AT_END));
996        server.play();
997
998        URLConnection connection = server.getUrl("/").openConnection();
999        try {
1000            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1001            fail();
1002        } catch (IOException e) {
1003        }
1004    }
1005
1006    /**
1007     * This test checks whether connections are gzipped by default. This
1008     * behavior in not required by the API, so a failure of this test does not
1009     * imply a bug in the implementation.
1010     */
1011    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
1012        server.enqueue(new MockResponse()
1013                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
1014                .addHeader("Content-Encoding: gzip"));
1015        server.play();
1016
1017        URLConnection connection = server.getUrl("/").openConnection();
1018        assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1019        assertNull(connection.getContentEncoding());
1020        assertEquals(-1, connection.getContentLength());
1021
1022        RecordedRequest request = server.takeRequest();
1023        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1024    }
1025
1026    public void testClientConfiguredGzipContentEncoding() throws Exception {
1027        byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"));
1028        server.enqueue(new MockResponse()
1029                .setBody(bodyBytes)
1030                .addHeader("Content-Encoding: gzip")
1031                .addHeader("Content-Length: " + bodyBytes.length));
1032        server.play();
1033
1034        URLConnection connection = server.getUrl("/").openConnection();
1035        connection.addRequestProperty("Accept-Encoding", "gzip");
1036        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1037        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
1038        assertEquals(bodyBytes.length, connection.getContentLength());
1039
1040        RecordedRequest request = server.takeRequest();
1041        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1042    }
1043
1044    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
1045        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
1046    }
1047
1048    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
1049        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
1050    }
1051
1052    public void testClientConfiguredCustomContentEncoding() throws Exception {
1053        server.enqueue(new MockResponse()
1054                .setBody("ABCDE")
1055                .addHeader("Content-Encoding: custom"));
1056        server.play();
1057
1058        URLConnection connection = server.getUrl("/").openConnection();
1059        connection.addRequestProperty("Accept-Encoding", "custom");
1060        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1061
1062        RecordedRequest request = server.takeRequest();
1063        assertContains(request.getHeaders(), "Accept-Encoding: custom");
1064    }
1065
1066    /**
1067     * Test a bug where gzip input streams weren't exhausting the input stream,
1068     * which corrupted the request that followed.
1069     * http://code.google.com/p/android/issues/detail?id=7059
1070     */
1071    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
1072            TransferKind transferKind) throws Exception {
1073        MockResponse responseOne = new MockResponse();
1074        responseOne.addHeader("Content-Encoding: gzip");
1075        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
1076        server.enqueue(responseOne);
1077        MockResponse responseTwo = new MockResponse();
1078        transferKind.setBody(responseTwo, "two (identity)", 5);
1079        server.enqueue(responseTwo);
1080        server.play();
1081
1082        URLConnection connection = server.getUrl("/").openConnection();
1083        connection.addRequestProperty("Accept-Encoding", "gzip");
1084        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1085        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
1086        assertEquals(0, server.takeRequest().getSequenceNumber());
1087
1088        connection = server.getUrl("/").openConnection();
1089        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1090        assertEquals(1, server.takeRequest().getSequenceNumber());
1091    }
1092
1093    /**
1094     * Test that HEAD requests don't have a body regardless of the response
1095     * headers. http://code.google.com/p/android/issues/detail?id=24672
1096     */
1097    public void testHeadAndContentLength() throws Exception {
1098        server.enqueue(new MockResponse()
1099                .clearHeaders()
1100                .addHeader("Content-Length: 100"));
1101        server.enqueue(new MockResponse().setBody("A"));
1102        server.play();
1103
1104        HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/").openConnection();
1105        connection1.setRequestMethod("HEAD");
1106        assertEquals("100", connection1.getHeaderField("Content-Length"));
1107        assertContent("", connection1);
1108
1109        HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/").openConnection();
1110        assertEquals("A", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
1111
1112        assertEquals(0, server.takeRequest().getSequenceNumber());
1113        assertEquals(1, server.takeRequest().getSequenceNumber());
1114    }
1115
1116    /**
1117     * Obnoxiously test that the chunk sizes transmitted exactly equal the
1118     * requested data+chunk header size. Although setChunkedStreamingMode()
1119     * isn't specific about whether the size applies to the data or the
1120     * complete chunk, the RI interprets it as a complete chunk.
1121     */
1122    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
1123        server.enqueue(new MockResponse());
1124        server.play();
1125
1126        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1127        urlConnection.setChunkedStreamingMode(8);
1128        urlConnection.setDoOutput(true);
1129        OutputStream outputStream = urlConnection.getOutputStream();
1130        outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII"));
1131        assertEquals(200, urlConnection.getResponseCode());
1132
1133        RecordedRequest request = server.takeRequest();
1134        assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII"));
1135        assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes());
1136    }
1137
1138    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
1139        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
1140    }
1141
1142    public void testAuthenticateWithChunkedStreaming() throws Exception {
1143        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
1144    }
1145
1146    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
1147        MockResponse pleaseAuthenticate = new MockResponse()
1148                .setResponseCode(401)
1149                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1150                .setBody("Please authenticate.");
1151        server.enqueue(pleaseAuthenticate);
1152        server.play();
1153
1154        Authenticator.setDefault(new SimpleAuthenticator());
1155        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1156        connection.setDoOutput(true);
1157        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1158        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1159            connection.setFixedLengthStreamingMode(requestBody.length);
1160        } else if (streamingMode == StreamingMode.CHUNKED) {
1161            connection.setChunkedStreamingMode(0);
1162        }
1163        OutputStream outputStream = connection.getOutputStream();
1164        outputStream.write(requestBody);
1165        outputStream.close();
1166        try {
1167            connection.getInputStream();
1168            fail();
1169        } catch (HttpRetryException expected) {
1170        }
1171
1172        // no authorization header for the request...
1173        RecordedRequest request = server.takeRequest();
1174        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1175        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1176    }
1177
1178    public void testSetValidRequestMethod() throws Exception {
1179        server.play();
1180        assertValidRequestMethod("GET");
1181        assertValidRequestMethod("DELETE");
1182        assertValidRequestMethod("HEAD");
1183        assertValidRequestMethod("OPTIONS");
1184        assertValidRequestMethod("POST");
1185        assertValidRequestMethod("PUT");
1186        assertValidRequestMethod("TRACE");
1187    }
1188
1189    private void assertValidRequestMethod(String requestMethod) throws Exception {
1190        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1191        connection.setRequestMethod(requestMethod);
1192        assertEquals(requestMethod, connection.getRequestMethod());
1193    }
1194
1195    public void testSetInvalidRequestMethodLowercase() throws Exception {
1196        server.play();
1197        assertInvalidRequestMethod("get");
1198    }
1199
1200    public void testSetInvalidRequestMethodConnect() throws Exception {
1201        server.play();
1202        assertInvalidRequestMethod("CONNECT");
1203    }
1204
1205    private void assertInvalidRequestMethod(String requestMethod) throws Exception {
1206        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1207        try {
1208            connection.setRequestMethod(requestMethod);
1209            fail();
1210        } catch (ProtocolException expected) {
1211        }
1212    }
1213
1214    public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception {
1215        server.play();
1216        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1217        try {
1218            connection.setFixedLengthStreamingMode(-2);
1219            fail();
1220        } catch (IllegalArgumentException expected) {
1221        }
1222    }
1223
1224    public void testCanSetNegativeChunkedStreamingMode() throws Exception {
1225        server.play();
1226        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1227        connection.setChunkedStreamingMode(-2);
1228    }
1229
1230    public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
1231        server.enqueue(new MockResponse().setBody("A"));
1232        server.play();
1233        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1234        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1235        try {
1236            connection.setFixedLengthStreamingMode(1);
1237            fail();
1238        } catch (IllegalStateException expected) {
1239        }
1240    }
1241
1242    public void testCannotSetChunkedStreamingModeAfterConnect() 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.setChunkedStreamingMode(1);
1249            fail();
1250        } catch (IllegalStateException expected) {
1251        }
1252    }
1253
1254    public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
1255        server.play();
1256        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1257        connection.setChunkedStreamingMode(1);
1258        try {
1259            connection.setFixedLengthStreamingMode(1);
1260            fail();
1261        } catch (IllegalStateException expected) {
1262        }
1263    }
1264
1265    public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
1266        server.play();
1267        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1268        connection.setFixedLengthStreamingMode(1);
1269        try {
1270            connection.setChunkedStreamingMode(1);
1271            fail();
1272        } catch (IllegalStateException expected) {
1273        }
1274    }
1275
1276    public void testSecureFixedLengthStreaming() throws Exception {
1277        testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
1278    }
1279
1280    public void testSecureChunkedStreaming() throws Exception {
1281        testSecureStreamingPost(StreamingMode.CHUNKED);
1282    }
1283
1284    /**
1285     * Users have reported problems using HTTPS with streaming request bodies.
1286     * http://code.google.com/p/android/issues/detail?id=12860
1287     */
1288    private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
1289        TestSSLContext testSSLContext = TestSSLContext.create();
1290        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1291        server.enqueue(new MockResponse().setBody("Success!"));
1292        server.play();
1293
1294        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1295        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1296        connection.setDoOutput(true);
1297        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1298        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1299            connection.setFixedLengthStreamingMode(requestBody.length);
1300        } else if (streamingMode == StreamingMode.CHUNKED) {
1301            connection.setChunkedStreamingMode(0);
1302        }
1303        OutputStream outputStream = connection.getOutputStream();
1304        outputStream.write(requestBody);
1305        outputStream.close();
1306        assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1307
1308        RecordedRequest request = server.takeRequest();
1309        assertEquals("POST / HTTP/1.1", request.getRequestLine());
1310        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1311            assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
1312        } else if (streamingMode == StreamingMode.CHUNKED) {
1313            assertEquals(Arrays.asList(4), request.getChunkSizes());
1314        }
1315        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1316    }
1317
1318    enum StreamingMode {
1319        FIXED_LENGTH, CHUNKED
1320    }
1321
1322    public void testAuthenticateWithPost() throws Exception {
1323        MockResponse pleaseAuthenticate = new MockResponse()
1324                .setResponseCode(401)
1325                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1326                .setBody("Please authenticate.");
1327        // fail auth three times...
1328        server.enqueue(pleaseAuthenticate);
1329        server.enqueue(pleaseAuthenticate);
1330        server.enqueue(pleaseAuthenticate);
1331        // ...then succeed the fourth time
1332        server.enqueue(new MockResponse().setBody("Successful auth!"));
1333        server.play();
1334
1335        Authenticator.setDefault(new SimpleAuthenticator());
1336        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1337        connection.setDoOutput(true);
1338        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1339        OutputStream outputStream = connection.getOutputStream();
1340        outputStream.write(requestBody);
1341        outputStream.close();
1342        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1343
1344        // no authorization header for the first request...
1345        RecordedRequest request = server.takeRequest();
1346        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1347
1348        // ...but the three requests that follow include an authorization header
1349        for (int i = 0; i < 3; i++) {
1350            request = server.takeRequest();
1351            assertEquals("POST / HTTP/1.1", request.getRequestLine());
1352            assertContains(request.getHeaders(), "Authorization: Basic "
1353                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1354            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1355        }
1356    }
1357
1358    public void testAuthenticateWithGet() throws Exception {
1359        MockResponse pleaseAuthenticate = new MockResponse()
1360                .setResponseCode(401)
1361                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1362                .setBody("Please authenticate.");
1363        // fail auth three times...
1364        server.enqueue(pleaseAuthenticate);
1365        server.enqueue(pleaseAuthenticate);
1366        server.enqueue(pleaseAuthenticate);
1367        // ...then succeed the fourth time
1368        server.enqueue(new MockResponse().setBody("Successful auth!"));
1369        server.play();
1370
1371        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1372        Authenticator.setDefault(authenticator);
1373        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1374        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1375        assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType);
1376        assertEquals(server.getPort(), authenticator.requestingPort);
1377        assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite);
1378        assertEquals("protected area", authenticator.requestingPrompt);
1379        assertEquals("http", authenticator.requestingProtocol);
1380        assertEquals("Basic", authenticator.requestingScheme);
1381
1382        // no authorization header for the first request...
1383        RecordedRequest request = server.takeRequest();
1384        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1385
1386        // ...but the three requests that follow requests include an authorization header
1387        for (int i = 0; i < 3; i++) {
1388            request = server.takeRequest();
1389            assertEquals("GET / HTTP/1.1", request.getRequestLine());
1390            assertContains(request.getHeaders(), "Authorization: Basic "
1391                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1392        }
1393    }
1394
1395    // http://code.google.com/p/android/issues/detail?id=19081
1396    public void testAuthenticateWithCommaSeparatedAuthenticationMethods() throws Exception {
1397        server.enqueue(new MockResponse()
1398                .setResponseCode(401)
1399                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\", Scheme2 realm=\"b\", "
1400                        + "Scheme3 realm=\"c\"")
1401                .setBody("Please authenticate."));
1402        server.enqueue(new MockResponse().setBody("Successful auth!"));
1403        server.play();
1404
1405        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1406        authenticator.expectedPrompt = "b";
1407        Authenticator.setDefault(authenticator);
1408        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1409        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1410
1411        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1412        assertContains(server.takeRequest().getHeaders(),
1413                "Authorization: Scheme2 " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1414        assertEquals("Scheme2", authenticator.requestingScheme);
1415    }
1416
1417    public void testAuthenticateWithMultipleAuthenticationHeaders() throws Exception {
1418        server.enqueue(new MockResponse()
1419                .setResponseCode(401)
1420                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\"")
1421                .addHeader("WWW-Authenticate: Scheme2 realm=\"b\"")
1422                .addHeader("WWW-Authenticate: Scheme3 realm=\"c\"")
1423                .setBody("Please authenticate."));
1424        server.enqueue(new MockResponse().setBody("Successful auth!"));
1425        server.play();
1426
1427        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1428        authenticator.expectedPrompt = "b";
1429        Authenticator.setDefault(authenticator);
1430        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1431        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1432
1433        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1434        assertContains(server.takeRequest().getHeaders(),
1435                "Authorization: Scheme2 " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1436        assertEquals("Scheme2", authenticator.requestingScheme);
1437    }
1438
1439    public void testRedirectedWithChunkedEncoding() throws Exception {
1440        testRedirected(TransferKind.CHUNKED, true);
1441    }
1442
1443    public void testRedirectedWithContentLengthHeader() throws Exception {
1444        testRedirected(TransferKind.FIXED_LENGTH, true);
1445    }
1446
1447    public void testRedirectedWithNoLengthHeaders() throws Exception {
1448        testRedirected(TransferKind.END_OF_STREAM, false);
1449    }
1450
1451    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1452        MockResponse response = new MockResponse()
1453                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1454                .addHeader("Location: /foo");
1455        transferKind.setBody(response, "This page has moved!", 10);
1456        server.enqueue(response);
1457        server.enqueue(new MockResponse().setBody("This is the new location!"));
1458        server.play();
1459
1460        URLConnection connection = server.getUrl("/").openConnection();
1461        assertEquals("This is the new location!",
1462                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1463
1464        RecordedRequest first = server.takeRequest();
1465        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1466        RecordedRequest retry = server.takeRequest();
1467        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1468        if (reuse) {
1469            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1470        }
1471    }
1472
1473    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1474        TestSSLContext testSSLContext = TestSSLContext.create();
1475        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1476        server.enqueue(new MockResponse()
1477                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1478                .addHeader("Location: /foo")
1479                .setBody("This page has moved!"));
1480        server.enqueue(new MockResponse().setBody("This is the new location!"));
1481        server.play();
1482
1483        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1484        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1485        assertEquals("This is the new location!",
1486                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1487
1488        RecordedRequest first = server.takeRequest();
1489        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1490        RecordedRequest retry = server.takeRequest();
1491        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1492        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1493    }
1494
1495    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1496        TestSSLContext testSSLContext = TestSSLContext.create();
1497        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1498        server.enqueue(new MockResponse()
1499                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1500                .addHeader("Location: http://anyhost/foo")
1501                .setBody("This page has moved!"));
1502        server.play();
1503
1504        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1505        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1506        assertEquals("This page has moved!",
1507                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1508    }
1509
1510    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1511        server.enqueue(new MockResponse()
1512                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1513                .addHeader("Location: https://anyhost/foo")
1514                .setBody("This page has moved!"));
1515        server.play();
1516
1517        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1518        assertEquals("This page has moved!",
1519                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1520    }
1521
1522    public void testRedirectToAnotherOriginServer() throws Exception {
1523        MockWebServer server2 = new MockWebServer();
1524        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1525        server2.play();
1526
1527        server.enqueue(new MockResponse()
1528                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1529                .addHeader("Location: " + server2.getUrl("/").toString())
1530                .setBody("This page has moved!"));
1531        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1532        server.play();
1533
1534        URLConnection connection = server.getUrl("/").openConnection();
1535        assertEquals("This is the 2nd server!",
1536                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1537        assertEquals(server2.getUrl("/"), connection.getURL());
1538
1539        // make sure the first server was careful to recycle the connection
1540        assertEquals("This is the first server again!",
1541                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1542
1543        RecordedRequest first = server.takeRequest();
1544        assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
1545        RecordedRequest second = server2.takeRequest();
1546        assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort());
1547        RecordedRequest third = server.takeRequest();
1548        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1549
1550        server2.shutdown();
1551    }
1552
1553    public void testResponse300MultipleChoiceWithPost() throws Exception {
1554        // Chrome doesn't follow the redirect, but Firefox and the RI both do
1555        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
1556    }
1557
1558    public void testResponse301MovedPermanentlyWithPost() throws Exception {
1559        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
1560    }
1561
1562    public void testResponse302MovedTemporarilyWithPost() throws Exception {
1563        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
1564    }
1565
1566    public void testResponse303SeeOtherWithPost() throws Exception {
1567        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
1568    }
1569
1570    private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
1571        server.enqueue(new MockResponse()
1572                .setResponseCode(redirectCode)
1573                .addHeader("Location: /page2")
1574                .setBody("This page has moved!"));
1575        server.enqueue(new MockResponse().setBody("Page 2"));
1576        server.play();
1577
1578        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection();
1579        connection.setDoOutput(true);
1580        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1581        OutputStream outputStream = connection.getOutputStream();
1582        outputStream.write(requestBody);
1583        outputStream.close();
1584        assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1585        assertTrue(connection.getDoOutput());
1586
1587        RecordedRequest page1 = server.takeRequest();
1588        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
1589        assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody()));
1590
1591        RecordedRequest page2 = server.takeRequest();
1592        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
1593    }
1594
1595    public void testResponse305UseProxy() throws Exception {
1596        server.play();
1597        server.enqueue(new MockResponse()
1598                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
1599                .addHeader("Location: " + server.getUrl("/"))
1600                .setBody("This page has moved!"));
1601        server.enqueue(new MockResponse().setBody("Proxy Response"));
1602
1603        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection();
1604        // Fails on the RI, which gets "Proxy Response"
1605        assertEquals("This page has moved!",
1606                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1607
1608        RecordedRequest page1 = server.takeRequest();
1609        assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
1610        assertEquals(1, server.getRequestCount());
1611    }
1612
1613    public void testHttpsWithCustomTrustManager() throws Exception {
1614        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
1615        RecordingTrustManager trustManager = new RecordingTrustManager();
1616        SSLContext sc = SSLContext.getInstance("TLS");
1617        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
1618
1619        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
1620        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
1621        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
1622        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1623        try {
1624            TestSSLContext testSSLContext = TestSSLContext.create();
1625            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1626            server.enqueue(new MockResponse().setBody("ABC"));
1627            server.enqueue(new MockResponse().setBody("DEF"));
1628            server.enqueue(new MockResponse().setBody("GHI"));
1629            server.play();
1630
1631            URL url = server.getUrl("/");
1632            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
1633            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
1634            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
1635
1636            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
1637            assertEquals(Arrays.asList("checkServerTrusted ["
1638                    + "CN=" + hostName + " 1, "
1639                    + "CN=Test Intermediate Certificate Authority 1, "
1640                    + "CN=Test Root Certificate Authority 1"
1641                    + "] RSA"),
1642                    trustManager.calls);
1643        } finally {
1644            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
1645            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
1646        }
1647    }
1648
1649    /**
1650     * Test that the timeout period is honored. The timeout may be doubled!
1651     * HttpURLConnection will wait the full timeout for each of the server's IP
1652     * addresses. This is typically one IPv4 address and one IPv6 address.
1653     */
1654    public void testConnectTimeouts() throws IOException {
1655        StuckServer ss = new StuckServer(true);
1656        int serverPort = ss.getLocalPort();
1657        String hostName = ss.getLocalSocketAddress().getAddress().getHostAddress();
1658        URLConnection urlConnection = new URL("http://" + hostName + ":" + serverPort + "/")
1659                .openConnection();
1660
1661        int timeout = 1000;
1662        urlConnection.setConnectTimeout(timeout);
1663        long start = System.currentTimeMillis();
1664        try {
1665            urlConnection.getInputStream();
1666            fail();
1667        } catch (SocketTimeoutException expected) {
1668            long elapsed = System.currentTimeMillis() - start;
1669            int attempts = InetAddress.getAllByName("localhost").length; // one per IP address
1670            assertTrue("timeout=" +timeout + ", elapsed=" + elapsed + ", attempts=" + attempts,
1671                    Math.abs((attempts * timeout) - elapsed) < 500);
1672        } finally {
1673            ss.close();
1674        }
1675    }
1676
1677    public void testReadTimeouts() throws IOException {
1678        /*
1679         * This relies on the fact that MockWebServer doesn't close the
1680         * connection after a response has been sent. This causes the client to
1681         * try to read more bytes than are sent, which results in a timeout.
1682         */
1683        MockResponse timeout = new MockResponse()
1684                .setBody("ABC")
1685                .clearHeaders()
1686                .addHeader("Content-Length: 4");
1687        server.enqueue(timeout);
1688        server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
1689        server.play();
1690
1691        URLConnection urlConnection = server.getUrl("/").openConnection();
1692        urlConnection.setReadTimeout(1000);
1693        InputStream in = urlConnection.getInputStream();
1694        assertEquals('A', in.read());
1695        assertEquals('B', in.read());
1696        assertEquals('C', in.read());
1697        try {
1698            in.read(); // if Content-Length was accurate, this would return -1 immediately
1699            fail();
1700        } catch (SocketTimeoutException expected) {
1701        }
1702    }
1703
1704    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
1705        server.enqueue(new MockResponse());
1706        server.play();
1707
1708        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1709        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
1710        urlConnection.setDoOutput(true);
1711        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
1712        assertEquals(200, urlConnection.getResponseCode());
1713
1714        RecordedRequest request = server.takeRequest();
1715        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
1716    }
1717
1718    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
1719        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
1720        server.enqueue(new MockResponse());
1721        server.play();
1722
1723        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1724        a.setRequestProperty("Connection", "close");
1725        assertEquals(200, a.getResponseCode());
1726
1727        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1728        assertEquals(200, b.getResponseCode());
1729
1730        assertEquals(0, server.takeRequest().getSequenceNumber());
1731        assertEquals("When connection: close is used, each request should get its own connection",
1732                0, server.takeRequest().getSequenceNumber());
1733    }
1734
1735    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
1736        server.enqueue(new MockResponse().addHeader("Connection: close"));
1737        server.enqueue(new MockResponse());
1738        server.play();
1739
1740        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1741        assertEquals(200, a.getResponseCode());
1742
1743        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1744        assertEquals(200, b.getResponseCode());
1745
1746        assertEquals(0, server.takeRequest().getSequenceNumber());
1747        assertEquals("When connection: close is used, each request should get its own connection",
1748                0, server.takeRequest().getSequenceNumber());
1749    }
1750
1751    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
1752        MockResponse response = new MockResponse()
1753                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1754                .addHeader("Location: /foo")
1755                .addHeader("Connection: close");
1756        server.enqueue(response);
1757        server.enqueue(new MockResponse().setBody("This is the new location!"));
1758        server.play();
1759
1760        URLConnection connection = server.getUrl("/").openConnection();
1761        assertEquals("This is the new location!",
1762                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1763
1764        assertEquals(0, server.takeRequest().getSequenceNumber());
1765        assertEquals("When connection: close is used, each request should get its own connection",
1766                0, server.takeRequest().getSequenceNumber());
1767    }
1768
1769    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
1770        server.enqueue(new MockResponse()
1771                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
1772                .setBody("This body is not allowed!"));
1773        server.play();
1774
1775        URLConnection connection = server.getUrl("/").openConnection();
1776        assertEquals("This body is not allowed!",
1777                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1778    }
1779
1780    public void testSingleByteReadIsSigned() throws IOException {
1781        server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
1782        server.play();
1783
1784        URLConnection connection = server.getUrl("/").openConnection();
1785        InputStream in = connection.getInputStream();
1786        assertEquals(254, in.read());
1787        assertEquals(255, in.read());
1788        assertEquals(-1, in.read());
1789    }
1790
1791    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
1792        testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
1793    }
1794
1795    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
1796        testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
1797    }
1798
1799    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
1800        testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
1801    }
1802
1803    /**
1804     * We explicitly permit apps to close the upload stream even after it has
1805     * been transmitted.  We also permit flush so that buffered streams can
1806     * do a no-op flush when they are closed. http://b/3038470
1807     */
1808    private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
1809        server.enqueue(new MockResponse().setBody("abc"));
1810        server.play();
1811
1812        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1813        connection.setDoOutput(true);
1814        byte[] upload = "def".getBytes("UTF-8");
1815
1816        if (transferKind == TransferKind.CHUNKED) {
1817            connection.setChunkedStreamingMode(0);
1818        } else if (transferKind == TransferKind.FIXED_LENGTH) {
1819            connection.setFixedLengthStreamingMode(upload.length);
1820        }
1821
1822        OutputStream out = connection.getOutputStream();
1823        out.write(upload);
1824        assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1825
1826        out.flush(); // dubious but permitted
1827        try {
1828            out.write("ghi".getBytes("UTF-8"));
1829            fail();
1830        } catch (IOException expected) {
1831        }
1832    }
1833
1834    public void testGetHeadersThrows() throws IOException {
1835        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
1836        server.play();
1837
1838        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1839        try {
1840            connection.getInputStream();
1841            fail();
1842        } catch (IOException expected) {
1843        }
1844
1845        try {
1846            connection.getInputStream();
1847            fail();
1848        } catch (IOException expected) {
1849        }
1850    }
1851
1852    public void testGetKeepAlive() throws Exception {
1853        server.enqueue(new MockResponse().setBody("ABC"));
1854        server.play();
1855
1856        // The request should work once and then fail
1857        URLConnection connection = server.getUrl("").openConnection();
1858        InputStream input = connection.getInputStream();
1859        assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
1860        input.close();
1861        try {
1862            server.getUrl("").openConnection().getInputStream();
1863            fail();
1864        } catch (ConnectException expected) {
1865        }
1866    }
1867
1868    /**
1869     * This test goes through the exhaustive set of interesting ASCII characters
1870     * because most of those characters are interesting in some way according to
1871     * RFC 2396 and RFC 2732. http://b/1158780
1872     */
1873    public void testLenientUrlToUri() throws Exception {
1874        // alphanum
1875        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
1876
1877        // control characters
1878        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
1879        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
1880
1881        // ascii characters
1882        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1883        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1884        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
1885        testUrlToUriMapping("!", "!", "!", "!", "!");
1886        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
1887        testUrlToUriMapping("#", null, null, null, "%23");
1888        testUrlToUriMapping("$", "$", "$", "$", "$");
1889        testUrlToUriMapping("&", "&", "&", "&", "&");
1890        testUrlToUriMapping("'", "'", "'", "'", "'");
1891        testUrlToUriMapping("(", "(", "(", "(", "(");
1892        testUrlToUriMapping(")", ")", ")", ")", ")");
1893        testUrlToUriMapping("*", "*", "*", "*", "*");
1894        testUrlToUriMapping("+", "+", "+", "+", "+");
1895        testUrlToUriMapping(",", ",", ",", ",", ",");
1896        testUrlToUriMapping("-", "-", "-", "-", "-");
1897        testUrlToUriMapping(".", ".", ".", ".", ".");
1898        testUrlToUriMapping("/", null, "/", "/", "/");
1899        testUrlToUriMapping(":", null, ":", ":", ":");
1900        testUrlToUriMapping(";", ";", ";", ";", ";");
1901        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
1902        testUrlToUriMapping("=", "=", "=", "=", "=");
1903        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
1904        testUrlToUriMapping("?", null, null, "?", "?");
1905        testUrlToUriMapping("@", "@", "@", "@", "@");
1906        testUrlToUriMapping("[", null, "%5B", null, "%5B");
1907        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
1908        testUrlToUriMapping("]", null, "%5D", null, "%5D");
1909        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
1910        testUrlToUriMapping("_", "_", "_", "_", "_");
1911        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
1912        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
1913        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
1914        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
1915        testUrlToUriMapping("~", "~", "~", "~", "~");
1916        testUrlToUriMapping("~", "~", "~", "~", "~");
1917        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
1918
1919        // beyond ascii
1920        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
1921        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
1922        testUrlToUriMapping("\ud842\udf9f",
1923                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
1924    }
1925
1926    public void testLenientUrlToUriNul() throws Exception {
1927        // On JB-MR2 and below, we would allow a host containing \u0000
1928        // and then generate a request with a Host header that violated RFC2616.
1929        // We now reject such hosts.
1930        //
1931        // The ideal behaviour here is to be "lenient" about the host and rewrite
1932        // it, but attempting to do so introduces a new range of incompatible
1933        // behaviours.
1934        testUrlToUriMapping("\u0000", null, "%00", "%00", "%00"); // RI fails this
1935    }
1936
1937    public void testHostWithNul() throws Exception {
1938        URL url = new URL("http://host\u0000/");
1939        try {
1940            url.openStream();
1941            fail();
1942        } catch (IllegalArgumentException expected) {}
1943    }
1944
1945    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
1946            String asQuery, String asFragment) throws Exception {
1947        if (asAuthority != null) {
1948            assertEquals("http://host" + asAuthority + ".tld/",
1949                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
1950        }
1951        if (asFile != null) {
1952            assertEquals("http://host.tld/file" + asFile + "/",
1953                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
1954        }
1955        if (asQuery != null) {
1956            assertEquals("http://host.tld/file?q" + asQuery + "=x",
1957                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
1958        }
1959        assertEquals("http://host.tld/file#" + asFragment + "-x",
1960                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
1961    }
1962
1963    /**
1964     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
1965     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
1966     * characters like '{' and '|' by escaping these characters.
1967     */
1968    private URI backdoorUrlToUri(URL url) throws Exception {
1969        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
1970
1971        ResponseCache.setDefault(new ResponseCache() {
1972            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
1973                return null;
1974            }
1975            @Override public CacheResponse get(URI uri, String requestMethod,
1976                    Map<String, List<String>> requestHeaders) throws IOException {
1977                uriReference.set(uri);
1978                throw new UnsupportedOperationException();
1979            }
1980        });
1981
1982        try {
1983            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1984            connection.getResponseCode();
1985        } catch (Exception expected) {
1986        }
1987
1988        return uriReference.get();
1989    }
1990
1991    /**
1992     * Don't explode if the cache returns a null body. http://b/3373699
1993     */
1994    public void testResponseCacheReturnsNullOutputStream() throws Exception {
1995        final AtomicBoolean aborted = new AtomicBoolean();
1996        ResponseCache.setDefault(new ResponseCache() {
1997            @Override public CacheResponse get(URI uri, String requestMethod,
1998                    Map<String, List<String>> requestHeaders) throws IOException {
1999                return null;
2000            }
2001            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
2002                return new CacheRequest() {
2003                    @Override public void abort() {
2004                        aborted.set(true);
2005                    }
2006                    @Override public OutputStream getBody() throws IOException {
2007                        return null;
2008                    }
2009                };
2010            }
2011        });
2012
2013        server.enqueue(new MockResponse().setBody("abcdef"));
2014        server.play();
2015
2016        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2017        InputStream in = connection.getInputStream();
2018        assertEquals("abc", readAscii(in, 3));
2019        in.close();
2020        assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
2021    }
2022
2023
2024    /**
2025     * http://code.google.com/p/android/issues/detail?id=14562
2026     */
2027    public void testReadAfterLastByte() throws Exception {
2028        server.enqueue(new MockResponse()
2029                .setBody("ABC")
2030                .clearHeaders()
2031                .addHeader("Connection: close")
2032                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END));
2033        server.play();
2034
2035        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2036        InputStream in = connection.getInputStream();
2037        assertEquals("ABC", readAscii(in, 3));
2038        assertEquals(-1, in.read());
2039        assertEquals(-1, in.read()); // throws IOException in Gingerbread
2040    }
2041
2042    public void testGetContent() throws Exception {
2043        server.enqueue(new MockResponse().setBody("A"));
2044        server.play();
2045        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2046        InputStream in = (InputStream) connection.getContent();
2047        assertEquals("A", readAscii(in, Integer.MAX_VALUE));
2048    }
2049
2050    public void testGetContentOfType() throws Exception {
2051        server.enqueue(new MockResponse().setBody("A"));
2052        server.play();
2053        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2054        try {
2055            connection.getContent(null);
2056            fail();
2057        } catch (NullPointerException expected) {
2058        }
2059        try {
2060            connection.getContent(new Class[] { null });
2061            fail();
2062        } catch (NullPointerException expected) {
2063        }
2064        assertNull(connection.getContent(new Class[] { getClass() }));
2065        connection.disconnect();
2066    }
2067
2068    public void testGetOutputStreamOnGetFails() throws Exception {
2069        server.enqueue(new MockResponse());
2070        server.play();
2071        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2072        try {
2073            connection.getOutputStream();
2074            fail();
2075        } catch (ProtocolException expected) {
2076        }
2077    }
2078
2079    public void testGetOutputAfterGetInputStreamFails() throws Exception {
2080        server.enqueue(new MockResponse());
2081        server.play();
2082        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2083        connection.setDoOutput(true);
2084        try {
2085            connection.getInputStream();
2086            connection.getOutputStream();
2087            fail();
2088        } catch (ProtocolException expected) {
2089        }
2090    }
2091
2092    public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception {
2093        server.enqueue(new MockResponse());
2094        server.play();
2095        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2096        connection.connect();
2097        try {
2098            connection.setDoOutput(true);
2099            fail();
2100        } catch (IllegalStateException expected) {
2101        }
2102        try {
2103            connection.setDoInput(true);
2104            fail();
2105        } catch (IllegalStateException expected) {
2106        }
2107        connection.disconnect();
2108    }
2109
2110    public void testClientSendsContentLength() throws Exception {
2111        server.enqueue(new MockResponse().setBody("A"));
2112        server.play();
2113        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2114        connection.setDoOutput(true);
2115        OutputStream out = connection.getOutputStream();
2116        out.write(new byte[] { 'A', 'B', 'C' });
2117        out.close();
2118        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2119        RecordedRequest request = server.takeRequest();
2120        assertContains(request.getHeaders(), "Content-Length: 3");
2121    }
2122
2123    public void testGetContentLengthConnects() throws Exception {
2124        server.enqueue(new MockResponse().setBody("ABC"));
2125        server.play();
2126        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2127        assertEquals(3, connection.getContentLength());
2128        connection.disconnect();
2129    }
2130
2131    public void testGetContentTypeConnects() throws Exception {
2132        server.enqueue(new MockResponse()
2133                .addHeader("Content-Type: text/plain")
2134                .setBody("ABC"));
2135        server.play();
2136        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2137        assertEquals("text/plain", connection.getContentType());
2138        connection.disconnect();
2139    }
2140
2141    public void testGetContentEncodingConnects() throws Exception {
2142        server.enqueue(new MockResponse()
2143                .addHeader("Content-Encoding: identity")
2144                .setBody("ABC"));
2145        server.play();
2146        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2147        assertEquals("identity", connection.getContentEncoding());
2148        connection.disconnect();
2149    }
2150
2151    // http://b/4361656
2152    public void testUrlContainsQueryButNoPath() throws Exception {
2153        server.enqueue(new MockResponse().setBody("A"));
2154        server.play();
2155        URL url = new URL("http", server.getHostName(), server.getPort(), "?query");
2156        assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE));
2157        RecordedRequest request = server.takeRequest();
2158        assertEquals("GET /?query HTTP/1.1", request.getRequestLine());
2159    }
2160
2161    // http://code.google.com/p/android/issues/detail?id=20442
2162    public void testInputStreamAvailableWithChunkedEncoding() throws Exception {
2163        testInputStreamAvailable(TransferKind.CHUNKED);
2164    }
2165
2166    public void testInputStreamAvailableWithContentLengthHeader() throws Exception {
2167        testInputStreamAvailable(TransferKind.FIXED_LENGTH);
2168    }
2169
2170    public void testInputStreamAvailableWithNoLengthHeaders() throws Exception {
2171        testInputStreamAvailable(TransferKind.END_OF_STREAM);
2172    }
2173
2174    private void testInputStreamAvailable(TransferKind transferKind) throws IOException {
2175        String body = "ABCDEFGH";
2176        MockResponse response = new MockResponse();
2177        transferKind.setBody(response, body, 4);
2178        server.enqueue(response);
2179        server.play();
2180        URLConnection connection = server.getUrl("/").openConnection();
2181        InputStream in = connection.getInputStream();
2182        for (int i = 0; i < body.length(); i++) {
2183            assertTrue(in.available() >= 0);
2184            assertEquals(body.charAt(i), in.read());
2185        }
2186        assertEquals(0, in.available());
2187        assertEquals(-1, in.read());
2188    }
2189
2190    // http://code.google.com/p/android/issues/detail?id=28095
2191    public void testInvalidIpv4Address() throws Exception {
2192        try {
2193            URI uri = new URI("http://1111.111.111.111/index.html");
2194            uri.toURL().openConnection().connect();
2195            fail();
2196        } catch (UnknownHostException expected) {
2197        }
2198    }
2199
2200    // http://code.google.com/p/android/issues/detail?id=16895
2201    public void testUrlWithSpaceInHost() throws Exception {
2202        URLConnection urlConnection = new URL("http://and roid.com/").openConnection();
2203        try {
2204            urlConnection.getInputStream();
2205            fail();
2206        } catch (UnknownHostException expected) {
2207        }
2208    }
2209
2210    public void testUrlWithSpaceInHostViaHttpProxy() throws Exception {
2211        server.enqueue(new MockResponse());
2212        server.play();
2213        URLConnection urlConnection = new URL("http://and roid.com/")
2214                .openConnection(server.toProxyAddress());
2215        try {
2216            urlConnection.getInputStream();
2217            fail(); // the RI makes a bogus proxy request for "GET http://and roid.com/ HTTP/1.1"
2218        } catch (UnknownHostException expected) {
2219        }
2220    }
2221
2222    public void testSslFallback() throws Exception {
2223        TestSSLContext testSSLContext = TestSSLContext.create();
2224        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2225        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
2226        server.enqueue(new MockResponse().setBody("This required a 2nd handshake"));
2227        server.play();
2228
2229        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2230        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2231        assertEquals("This required a 2nd handshake",
2232                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2233
2234        RecordedRequest first = server.takeRequest();
2235        assertEquals(0, first.getSequenceNumber());
2236        RecordedRequest retry = server.takeRequest();
2237        assertEquals(0, retry.getSequenceNumber());
2238        assertEquals("SSLv3", retry.getSslProtocol());
2239    }
2240
2241    public void testInspectSslBeforeConnect() throws Exception {
2242        TestSSLContext testSSLContext = TestSSLContext.create();
2243        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2244        server.enqueue(new MockResponse());
2245        server.play();
2246
2247        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2248        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2249        assertNotNull(connection.getHostnameVerifier());
2250        try {
2251            connection.getLocalCertificates();
2252            fail();
2253        } catch (IllegalStateException expected) {
2254        }
2255        try {
2256            connection.getServerCertificates();
2257            fail();
2258        } catch (IllegalStateException expected) {
2259        }
2260        try {
2261            connection.getCipherSuite();
2262            fail();
2263        } catch (IllegalStateException expected) {
2264        }
2265        try {
2266            connection.getPeerPrincipal();
2267            fail();
2268        } catch (IllegalStateException expected) {
2269        }
2270    }
2271
2272    /**
2273     * Test that we can inspect the SSL session after connect().
2274     * http://code.google.com/p/android/issues/detail?id=24431
2275     */
2276    public void testInspectSslAfterConnect() throws Exception {
2277        TestSSLContext testSSLContext = TestSSLContext.create();
2278        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2279        server.enqueue(new MockResponse());
2280        server.play();
2281
2282        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2283        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2284        connection.connect();
2285        assertNotNull(connection.getHostnameVerifier());
2286        assertNull(connection.getLocalCertificates());
2287        assertNotNull(connection.getServerCertificates());
2288        assertNotNull(connection.getCipherSuite());
2289        assertNotNull(connection.getPeerPrincipal());
2290    }
2291
2292    /**
2293     * Returns a gzipped copy of {@code bytes}.
2294     */
2295    public byte[] gzip(byte[] bytes) throws IOException {
2296        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
2297        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
2298        gzippedOut.write(bytes);
2299        gzippedOut.close();
2300        return bytesOut.toByteArray();
2301    }
2302
2303    /**
2304     * Reads at most {@code limit} characters from {@code in} and asserts that
2305     * content equals {@code expected}.
2306     */
2307    private void assertContent(String expected, URLConnection connection, int limit)
2308            throws IOException {
2309        connection.connect();
2310        assertEquals(expected, readAscii(connection.getInputStream(), limit));
2311        ((HttpURLConnection) connection).disconnect();
2312    }
2313
2314    private void assertContent(String expected, URLConnection connection) throws IOException {
2315        assertContent(expected, connection, Integer.MAX_VALUE);
2316    }
2317
2318    private void assertContains(List<String> headers, String header) {
2319        assertTrue(headers.toString(), headers.contains(header));
2320    }
2321
2322    private void assertContainsNoneMatching(List<String> headers, String pattern) {
2323        for (String header : headers) {
2324            if (header.matches(pattern)) {
2325                fail("Header " + header + " matches " + pattern);
2326            }
2327        }
2328    }
2329
2330    private Set<String> newSet(String... elements) {
2331        return new HashSet<String>(Arrays.asList(elements));
2332    }
2333
2334    enum TransferKind {
2335        CHUNKED() {
2336            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
2337                    throws IOException {
2338                response.setChunkedBody(content, chunkSize);
2339            }
2340        },
2341        FIXED_LENGTH() {
2342            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
2343                response.setBody(content);
2344            }
2345        },
2346        END_OF_STREAM() {
2347            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
2348                response.setBody(content);
2349                response.setSocketPolicy(DISCONNECT_AT_END);
2350                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
2351                    if (h.next().startsWith("Content-Length:")) {
2352                        h.remove();
2353                        break;
2354                    }
2355                }
2356            }
2357        };
2358
2359        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
2360                throws IOException;
2361
2362        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
2363            setBody(response, content.getBytes("UTF-8"), chunkSize);
2364        }
2365    }
2366
2367    enum ProxyConfig {
2368        NO_PROXY() {
2369            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2370                    throws IOException {
2371                return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
2372            }
2373        },
2374
2375        CREATE_ARG() {
2376            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2377                    throws IOException {
2378                return (HttpURLConnection) url.openConnection(server.toProxyAddress());
2379            }
2380        },
2381
2382        PROXY_SYSTEM_PROPERTY() {
2383            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2384                    throws IOException {
2385                System.setProperty("proxyHost", "localhost");
2386                System.setProperty("proxyPort", Integer.toString(server.getPort()));
2387                return (HttpURLConnection) url.openConnection();
2388            }
2389        },
2390
2391        HTTP_PROXY_SYSTEM_PROPERTY() {
2392            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2393                    throws IOException {
2394                System.setProperty("http.proxyHost", "localhost");
2395                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
2396                return (HttpURLConnection) url.openConnection();
2397            }
2398        },
2399
2400        HTTPS_PROXY_SYSTEM_PROPERTY() {
2401            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2402                    throws IOException {
2403                System.setProperty("https.proxyHost", "localhost");
2404                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
2405                return (HttpURLConnection) url.openConnection();
2406            }
2407        };
2408
2409        public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException;
2410    }
2411
2412    private static class RecordingTrustManager implements X509TrustManager {
2413        private final List<String> calls = new ArrayList<String>();
2414
2415        public X509Certificate[] getAcceptedIssuers() {
2416            calls.add("getAcceptedIssuers");
2417            return new X509Certificate[] {};
2418        }
2419
2420        public void checkClientTrusted(X509Certificate[] chain, String authType)
2421                throws CertificateException {
2422            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
2423        }
2424
2425        public void checkServerTrusted(X509Certificate[] chain, String authType)
2426                throws CertificateException {
2427            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
2428        }
2429
2430        private String certificatesToString(X509Certificate[] certificates) {
2431            List<String> result = new ArrayList<String>();
2432            for (X509Certificate certificate : certificates) {
2433                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
2434            }
2435            return result.toString();
2436        }
2437    }
2438
2439    private static class RecordingHostnameVerifier implements HostnameVerifier {
2440        private final List<String> calls = new ArrayList<String>();
2441
2442        public boolean verify(String hostname, SSLSession session) {
2443            calls.add("verify " + hostname);
2444            return true;
2445        }
2446    }
2447
2448    private static class SimpleAuthenticator extends Authenticator {
2449        /** base64("username:password") */
2450        private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ=";
2451
2452        private String expectedPrompt;
2453        private RequestorType requestorType;
2454        private int requestingPort;
2455        private InetAddress requestingSite;
2456        private String requestingPrompt;
2457        private String requestingProtocol;
2458        private String requestingScheme;
2459
2460        protected PasswordAuthentication getPasswordAuthentication() {
2461            requestorType = getRequestorType();
2462            requestingPort = getRequestingPort();
2463            requestingSite = getRequestingSite();
2464            requestingPrompt = getRequestingPrompt();
2465            requestingProtocol = getRequestingProtocol();
2466            requestingScheme = getRequestingScheme();
2467            return (expectedPrompt == null || expectedPrompt.equals(requestingPrompt))
2468                    ? new PasswordAuthentication("username", "password".toCharArray())
2469                    : null;
2470        }
2471    }
2472}
2473