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