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