1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.net;
18
19import com.android.okhttp.AndroidShimResponseCache;
20
21import com.google.mockwebserver.MockResponse;
22import com.google.mockwebserver.MockWebServer;
23import com.google.mockwebserver.RecordedRequest;
24import com.google.mockwebserver.SocketPolicy;
25import java.io.ByteArrayOutputStream;
26import java.io.File;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
30import java.net.Authenticator;
31import java.net.CacheRequest;
32import java.net.CacheResponse;
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.Socket;
41import java.net.SocketAddress;
42import java.net.SocketException;
43import java.net.SocketTimeoutException;
44import java.net.URI;
45import java.net.URL;
46import java.net.URLConnection;
47import java.net.UnknownHostException;
48import java.nio.channels.SocketChannel;
49import java.security.cert.CertificateException;
50import java.security.cert.X509Certificate;
51import java.util.ArrayList;
52import java.util.Arrays;
53import java.util.Collections;
54import java.util.HashSet;
55import java.util.Iterator;
56import java.util.List;
57import java.util.Map;
58import java.util.Set;
59import java.util.UUID;
60import java.util.concurrent.TimeUnit;
61import java.util.concurrent.atomic.AtomicBoolean;
62import java.util.concurrent.atomic.AtomicReference;
63import java.util.zip.GZIPInputStream;
64import java.util.zip.GZIPOutputStream;
65import javax.net.ssl.HandshakeCompletedListener;
66import javax.net.ssl.HostnameVerifier;
67import javax.net.ssl.HttpsURLConnection;
68import javax.net.ssl.SSLContext;
69import javax.net.ssl.SSLException;
70import javax.net.ssl.SSLHandshakeException;
71import javax.net.ssl.SSLParameters;
72import javax.net.ssl.SSLSession;
73import javax.net.ssl.SSLSocket;
74import javax.net.ssl.SSLSocketFactory;
75import javax.net.ssl.TrustManager;
76import javax.net.ssl.X509TrustManager;
77import libcore.java.security.TestKeyStore;
78import libcore.java.util.AbstractResourceLeakageDetectorTestCase;
79import libcore.javax.net.ssl.TestSSLContext;
80import tests.net.StuckServer;
81
82import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
83import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
84import static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
85import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
86import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
87
88public final class URLConnectionTest extends AbstractResourceLeakageDetectorTestCase {
89
90    private MockWebServer server;
91    private AndroidShimResponseCache cache;
92    private String hostName;
93
94    @Override protected void setUp() throws Exception {
95        super.setUp();
96        server = new MockWebServer();
97        hostName = server.getHostName();
98    }
99
100    @Override protected void tearDown() throws Exception {
101        ResponseCache.setDefault(null);
102        Authenticator.setDefault(null);
103        System.clearProperty("proxyHost");
104        System.clearProperty("proxyPort");
105        System.clearProperty("http.proxyHost");
106        System.clearProperty("http.proxyPort");
107        System.clearProperty("https.proxyHost");
108        System.clearProperty("https.proxyPort");
109        server.shutdown();
110        server = null;
111        if (cache != null) {
112            cache.delete();
113            cache = null;
114        }
115        super.tearDown();
116    }
117
118    public void testRequestHeaders() throws IOException, InterruptedException {
119        server.enqueue(new MockResponse());
120        server.play();
121
122        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
123        urlConnection.addRequestProperty("D", "e");
124        urlConnection.addRequestProperty("D", "f");
125        assertEquals("f", urlConnection.getRequestProperty("D"));
126        assertEquals("f", urlConnection.getRequestProperty("d"));
127        Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties();
128        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D")));
129        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d")));
130        try {
131            requestHeaders.put("G", Arrays.asList("h"));
132            fail("Modified an unmodifiable view.");
133        } catch (UnsupportedOperationException expected) {
134        }
135        try {
136            requestHeaders.get("D").add("i");
137            fail("Modified an unmodifiable view.");
138        } catch (UnsupportedOperationException expected) {
139        }
140        try {
141            urlConnection.setRequestProperty(null, "j");
142            fail();
143        } catch (NullPointerException expected) {
144        }
145        try {
146            urlConnection.addRequestProperty(null, "k");
147            fail();
148        } catch (NullPointerException expected) {
149        }
150        urlConnection.setRequestProperty("NullValue", null); // should fail silently!
151        assertNull(urlConnection.getRequestProperty("NullValue"));
152        urlConnection.addRequestProperty("AnotherNullValue", null);  // should fail silently!
153        assertNull(urlConnection.getRequestProperty("AnotherNullValue"));
154
155        urlConnection.getResponseCode();
156        RecordedRequest request = server.takeRequest();
157        assertContains(request.getHeaders(), "D: e");
158        assertContains(request.getHeaders(), "D: f");
159        assertContainsNoneMatching(request.getHeaders(), "NullValue.*");
160        assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*");
161        assertContainsNoneMatching(request.getHeaders(), "G:.*");
162        assertContainsNoneMatching(request.getHeaders(), "null:.*");
163
164        try {
165            urlConnection.addRequestProperty("N", "o");
166            fail("Set header after connect");
167        } catch (IllegalStateException expected) {
168        }
169        try {
170            urlConnection.setRequestProperty("P", "q");
171            fail("Set header after connect");
172        } catch (IllegalStateException expected) {
173        }
174        try {
175            urlConnection.getRequestProperties();
176            fail();
177        } catch (IllegalStateException expected) {
178        }
179    }
180
181    public void testGetRequestPropertyReturnsLastValue() throws Exception {
182        server.play();
183        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
184        urlConnection.addRequestProperty("A", "value1");
185        urlConnection.addRequestProperty("A", "value2");
186        assertEquals("value2", urlConnection.getRequestProperty("A"));
187    }
188
189    public void testResponseHeaders() throws IOException, InterruptedException {
190        server.enqueue(new MockResponse()
191                .setStatus("HTTP/1.0 200 Fantastic")
192                .addHeader("A: c")
193                .addHeader("B: d")
194                .addHeader("A: e")
195                .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
196        server.play();
197
198        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
199        assertEquals(200, urlConnection.getResponseCode());
200        assertEquals("Fantastic", urlConnection.getResponseMessage());
201        assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null));
202        Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields();
203        assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null));
204        assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A")));
205        assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a")));
206        try {
207            responseHeaders.put("N", Arrays.asList("o"));
208            fail("Modified an unmodifiable view.");
209        } catch (UnsupportedOperationException expected) {
210        }
211        try {
212            responseHeaders.get("A").add("f");
213            fail("Modified an unmodifiable view.");
214        } catch (UnsupportedOperationException expected) {
215        }
216        assertEquals("A", urlConnection.getHeaderFieldKey(0));
217        assertEquals("c", urlConnection.getHeaderField(0));
218        assertEquals("B", urlConnection.getHeaderFieldKey(1));
219        assertEquals("d", urlConnection.getHeaderField(1));
220        assertEquals("A", urlConnection.getHeaderFieldKey(2));
221        assertEquals("e", urlConnection.getHeaderField(2));
222    }
223
224    public void testGetErrorStreamOnSuccessfulRequest() throws Exception {
225        server.enqueue(new MockResponse().setBody("A"));
226        server.play();
227        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
228        assertNull(connection.getErrorStream());
229    }
230
231    public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception {
232        server.enqueue(new MockResponse().setResponseCode(404).setBody("A"));
233        server.play();
234        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
235        assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE));
236    }
237
238    // Check that if we don't read to the end of a response, the next request on the
239    // recycled connection doesn't get the unread tail of the first request's response.
240    // http://code.google.com/p/android/issues/detail?id=2939
241    public void test_2939() throws Exception {
242        MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8);
243
244        server.enqueue(response);
245        server.enqueue(response);
246        server.play();
247
248        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
249        assertContent("ABCDE", server.getUrl("/").openConnection(), 5);
250    }
251
252    // Check that we recognize a few basic mime types by extension.
253    // http://code.google.com/p/android/issues/detail?id=10100
254    public void test_10100() throws Exception {
255        assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg"));
256        assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf"));
257    }
258
259    public void testConnectionsArePooled() throws Exception {
260        MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR");
261
262        server.enqueue(response);
263        server.enqueue(response);
264        server.enqueue(response);
265        server.play();
266
267        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection());
268        assertEquals(0, server.takeRequest().getSequenceNumber());
269        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection());
270        assertEquals(1, server.takeRequest().getSequenceNumber());
271        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection());
272        assertEquals(2, server.takeRequest().getSequenceNumber());
273    }
274
275    public void testChunkedConnectionsArePooled() throws Exception {
276        MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5);
277
278        server.enqueue(response);
279        server.enqueue(response);
280        server.enqueue(response);
281        server.play();
282
283        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection());
284        assertEquals(0, server.takeRequest().getSequenceNumber());
285        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection());
286        assertEquals(1, server.takeRequest().getSequenceNumber());
287        assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection());
288        assertEquals(2, server.takeRequest().getSequenceNumber());
289    }
290
291    /**
292     * Test that connections are added to the pool as soon as the response has
293     * been consumed.
294     */
295    public void testConnectionsArePooledWithoutExplicitDisconnect() throws Exception {
296        server.enqueue(new MockResponse().setBody("ABC"));
297        server.enqueue(new MockResponse().setBody("DEF"));
298        server.play();
299
300        URLConnection connection1 = server.getUrl("/").openConnection();
301        assertEquals("ABC", readAscii(connection1.getInputStream(), Integer.MAX_VALUE));
302        assertEquals(0, server.takeRequest().getSequenceNumber());
303        URLConnection connection2 = server.getUrl("/").openConnection();
304        assertEquals("DEF", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
305        assertEquals(1, server.takeRequest().getSequenceNumber());
306    }
307
308    public void testServerClosesSocket() throws Exception {
309        testServerClosesSocket(DISCONNECT_AT_END);
310    }
311
312    public void testServerShutdownInput() throws Exception {
313        testServerClosesSocket(SHUTDOWN_INPUT_AT_END);
314    }
315
316    private void testServerClosesSocket(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().setBody("This comes after a busted connection"));
321        server.play();
322
323        assertContent("This connection won't pool properly", server.getUrl("/a").openConnection());
324        assertEquals(0, server.takeRequest().getSequenceNumber());
325        assertContent("This comes after a busted connection", server.getUrl("/b").openConnection());
326        // sequence number 0 means the HTTP socket connection was not reused
327        assertEquals(0, server.takeRequest().getSequenceNumber());
328    }
329
330    public void testServerShutdownOutput() throws Exception {
331        // This test causes MockWebServer to log a "connection failed" stack trace
332
333        // Setting the server workerThreads to 1 ensures the responses are generated in the order
334        // the requests are accepted by the server. Without this the second and third requests made
335        // by the client (the request for "/b" and the retry for "/b" when the bad socket is
336        // detected) can be handled by the server out of order leading to test failure.
337        server.setWorkerThreads(1);
338        server.enqueue(new MockResponse()
339                .setBody("Output shutdown after this response")
340                .setSocketPolicy(SHUTDOWN_OUTPUT_AT_END));
341        server.enqueue(new MockResponse().setBody("This response will fail to write"));
342        server.enqueue(new MockResponse().setBody("This comes after a busted connection"));
343        server.play();
344
345        assertContent("Output shutdown after this response", server.getUrl("/a").openConnection());
346        assertEquals(0, server.takeRequest().getSequenceNumber());
347        assertContent("This comes after a busted connection", server.getUrl("/b").openConnection());
348        assertEquals(1, server.takeRequest().getSequenceNumber());
349        assertEquals(0, server.takeRequest().getSequenceNumber());
350    }
351
352    enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
353
354    public void test_chunkedUpload_byteByByte() throws Exception {
355        doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
356    }
357
358    public void test_chunkedUpload_smallBuffers() throws Exception {
359        doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
360    }
361
362    public void test_chunkedUpload_largeBuffers() throws Exception {
363        doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
364    }
365
366    public void test_fixedLengthUpload_byteByByte() throws Exception {
367        doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
368    }
369
370    public void test_fixedLengthUpload_smallBuffers() throws Exception {
371        doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
372    }
373
374    public void test_fixedLengthUpload_largeBuffers() throws Exception {
375        doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
376    }
377
378    private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception {
379        int n = 512*1024;
380        server.setBodyLimit(0);
381        server.enqueue(new MockResponse());
382        server.play();
383
384        HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection();
385        conn.setDoOutput(true);
386        conn.setRequestMethod("POST");
387        if (uploadKind == TransferKind.CHUNKED) {
388            conn.setChunkedStreamingMode(-1);
389        } else {
390            conn.setFixedLengthStreamingMode(n);
391        }
392        OutputStream out = conn.getOutputStream();
393        if (writeKind == WriteKind.BYTE_BY_BYTE) {
394            for (int i = 0; i < n; ++i) {
395                out.write('x');
396            }
397        } else {
398            byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024];
399            Arrays.fill(buf, (byte) 'x');
400            for (int i = 0; i < n; i += buf.length) {
401                out.write(buf, 0, Math.min(buf.length, n - i));
402            }
403        }
404        out.close();
405        assertEquals(200, conn.getResponseCode());
406        RecordedRequest request = server.takeRequest();
407        assertEquals(n, request.getBodySize());
408        if (uploadKind == TransferKind.CHUNKED) {
409            assertTrue(request.getChunkSizes().size() > 0);
410        } else {
411            assertTrue(request.getChunkSizes().isEmpty());
412        }
413    }
414
415    public void testGetResponseCodeNoResponseBody() throws Exception {
416        server.enqueue(new MockResponse()
417                .addHeader("abc: def"));
418        server.play();
419
420        URL url = server.getUrl("/");
421        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
422        conn.setDoInput(false);
423        assertEquals("def", conn.getHeaderField("abc"));
424        assertEquals(200, conn.getResponseCode());
425        try {
426            conn.getInputStream();
427            fail();
428        } catch (ProtocolException expected) {
429        }
430    }
431
432    public void testConnectViaHttps() throws IOException, InterruptedException {
433        TestSSLContext testSSLContext = TestSSLContext.create();
434
435        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
436        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
437        server.play();
438
439        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
440        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
441
442        assertContent("this response comes via HTTPS", connection);
443
444        RecordedRequest request = server.takeRequest();
445        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
446        assertEquals("TLSv1.2", request.getSslProtocol());
447    }
448
449    public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
450        TestSSLContext testSSLContext = TestSSLContext.create();
451        SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory();
452
453        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
454        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
455        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
456        server.play();
457
458        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
459        connection.setSSLSocketFactory(clientSocketFactory);
460        assertContent("this response comes via HTTPS", connection);
461
462        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
463        connection.setSSLSocketFactory(clientSocketFactory);
464        assertContent("another response via HTTPS", connection);
465
466        assertEquals(0, server.takeRequest().getSequenceNumber());
467        assertEquals(1, server.takeRequest().getSequenceNumber());
468    }
469
470    public void testConnectViaHttpsReusingConnectionsDifferentFactories()
471            throws IOException, InterruptedException {
472        TestSSLContext testSSLContext = TestSSLContext.create();
473
474        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
475        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
476        server.enqueue(new MockResponse().setBody("another response via HTTPS"));
477        server.play();
478
479        // install a custom SSL socket factory so the server can be authorized
480        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
481        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
482        assertContent("this response comes via HTTPS", connection);
483
484        connection = (HttpsURLConnection) server.getUrl("/").openConnection();
485        try {
486            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
487            fail("without an SSL socket factory, the connection should fail");
488        } catch (SSLException expected) {
489        }
490    }
491
492    /**
493     * Verify that we don't retry connections on certificate verification errors.
494     *
495     * http://code.google.com/p/android/issues/detail?id=13178
496     */
497    public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
498        TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(),
499                                                              TestKeyStore.getServer());
500
501        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
502        server.enqueue(new MockResponse()); // unused
503        server.play();
504
505        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
506        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
507        try {
508            connection.getInputStream();
509            fail();
510        } catch (SSLHandshakeException expected) {
511            assertTrue(expected.getCause() instanceof CertificateException);
512        }
513        assertEquals(0, server.getRequestCount());
514    }
515
516    public void testConnectViaProxyUsingProxyArg() throws Exception {
517        testConnectViaProxy(ProxyConfig.CREATE_ARG);
518    }
519
520    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
521        testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
522    }
523
524    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
525        testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
526    }
527
528    private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
529        MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
530        server.enqueue(mockResponse);
531        server.play();
532
533        URL url = new URL("http://android.com/foo");
534        HttpURLConnection connection = proxyConfig.connect(server, url);
535        assertContent("this response comes via a proxy", connection);
536
537        RecordedRequest request = server.takeRequest();
538        assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine());
539        assertContains(request.getHeaders(), "Host: android.com");
540    }
541
542    public void testContentDisagreesWithContentLengthHeader() throws IOException {
543        server.enqueue(new MockResponse()
544                .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
545                .clearHeaders()
546                .addHeader("Content-Length: 3"));
547        server.play();
548
549        assertContent("abc", server.getUrl("/").openConnection());
550    }
551
552    public void testContentDisagreesWithChunkedHeader() throws IOException {
553        MockResponse mockResponse = new MockResponse();
554        mockResponse.setChunkedBody("abc", 3);
555        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
556        bytesOut.write(mockResponse.getBody());
557        bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes());
558        mockResponse.setBody(bytesOut.toByteArray());
559        mockResponse.clearHeaders();
560        mockResponse.addHeader("Transfer-encoding: chunked");
561
562        server.enqueue(mockResponse);
563        server.play();
564
565        assertContent("abc", server.getUrl("/").openConnection());
566    }
567
568    public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
569        testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
570    }
571
572    public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
573        // https should not use http proxy
574        testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
575    }
576
577    private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception {
578        TestSSLContext testSSLContext = TestSSLContext.create();
579
580        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
581        server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
582        server.play();
583
584        URL url = server.getUrl("/foo");
585        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
586        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
587
588        assertContent("this response comes via HTTPS", connection);
589
590        RecordedRequest request = server.takeRequest();
591        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
592    }
593
594
595    public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
596        testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
597    }
598
599    /**
600     * We weren't honoring all of the appropriate proxy system properties when
601     * connecting via HTTPS. http://b/3097518
602     */
603    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
604        testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
605    }
606
607    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
608        testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
609    }
610
611    /**
612     * We were verifying the wrong hostname when connecting to an HTTPS site
613     * through a proxy. http://b/3097277
614     */
615    private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
616        TestSSLContext testSSLContext = TestSSLContext.create();
617        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
618
619        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
620        server.enqueue(new MockResponse()
621                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
622                .clearHeaders());
623        server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
624        server.play();
625
626        URL url = new URL("https://android.com/foo");
627        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
628        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
629        connection.setHostnameVerifier(hostnameVerifier);
630
631        assertContent("this response comes via a secure proxy", connection);
632
633        RecordedRequest connect = server.takeRequest();
634        assertEquals("Connect line failure on proxy",
635                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
636        assertContains(connect.getHeaders(), "Host: android.com");
637
638        RecordedRequest get = server.takeRequest();
639        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
640        assertContains(get.getHeaders(), "Host: android.com");
641        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
642    }
643
644
645    /**
646     * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912
647     */
648    public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception {
649        TestSSLContext testSSLContext = TestSSLContext.create();
650
651        initResponseCache();
652
653        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
654
655        // The inclusion of a body in the response to the CONNECT is key to reproducing b/6754912.
656        MockResponse badProxyResponse = new MockResponse()
657                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
658                .clearHeaders()
659                .setBody("bogus proxy connect response content");
660
661        server.enqueue(badProxyResponse);
662        server.enqueue(new MockResponse().setBody("response"));
663
664        server.play();
665
666        URL url = new URL("https://android.com/foo");
667        ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY;
668        HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url);
669        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
670        connection.setHostnameVerifier(new RecordingHostnameVerifier());
671        assertContent("response", connection);
672
673        RecordedRequest connect = server.takeRequest();
674        assertEquals("CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
675        assertContains(connect.getHeaders(), "Host: android.com");
676    }
677
678    private void initResponseCache() throws IOException {
679        String tmp = System.getProperty("java.io.tmpdir");
680        File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
681        cache = AndroidShimResponseCache.create(cacheDir, Integer.MAX_VALUE);
682        ResponseCache.setDefault(cache);
683    }
684
685    /**
686     * Test Etag headers are returned correctly when a client-side cache is not installed.
687     * https://code.google.com/p/android/issues/detail?id=108949
688     */
689    public void testEtagHeaders_uncached() throws Exception {
690        final String etagValue1 = "686897696a7c876b7e";
691        final String body1 = "Response with etag 1";
692        final String etagValue2 = "686897696a7c876b7f";
693        final String body2 = "Response with etag 2";
694
695        server.enqueue(
696            new MockResponse()
697                .setBody(body1)
698                .setHeader("Content-Type", "text/plain")
699                .setHeader("Etag", etagValue1));
700        server.enqueue(
701            new MockResponse()
702                .setBody(body2)
703                .setHeader("Content-Type", "text/plain")
704                .setHeader("Etag", etagValue2));
705        server.play();
706
707        URL url = server.getUrl("/");
708        HttpURLConnection connection1 = (HttpURLConnection) url.openConnection();
709        assertEquals(etagValue1, connection1.getHeaderField("Etag"));
710        assertContent(body1, connection1);
711        connection1.disconnect();
712
713        // Discard the server-side record of the request made.
714        server.takeRequest();
715
716        HttpURLConnection connection2 = (HttpURLConnection) url.openConnection();
717        assertEquals(etagValue2, connection2.getHeaderField("Etag"));
718        assertContent(body2, connection2);
719        connection2.disconnect();
720
721        // Check the client did not cache.
722        RecordedRequest request = server.takeRequest();
723        assertNull(request.getHeader("If-None-Match"));
724    }
725
726    /**
727     * Test Etag headers are returned correctly when a client-side cache is installed and the server
728     * data is unchanged.
729     * https://code.google.com/p/android/issues/detail?id=108949
730     */
731    public void testEtagHeaders_cachedWithServerHit() throws Exception {
732        final String etagValue = "686897696a7c876b7e";
733        final String body = "Response with etag";
734
735        server.enqueue(
736            new MockResponse()
737                .setBody(body)
738                .setResponseCode(HttpURLConnection.HTTP_OK)
739                .setHeader("Content-Type", "text/plain")
740                .setHeader("Etag", etagValue));
741
742        server.enqueue(
743            new MockResponse()
744                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
745        server.play();
746
747        initResponseCache();
748
749        URL url = server.getUrl("/");
750        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
751        assertEquals(etagValue, connection.getHeaderField("Etag"));
752        assertContent(body, connection);
753        connection.disconnect();
754
755        // Discard the server-side record of the request made.
756        server.takeRequest();
757
758        // Confirm the cached body is returned along with the original etag header.
759        HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection();
760        assertEquals(etagValue, cachedConnection.getHeaderField("Etag"));
761        assertContent(body, cachedConnection);
762        cachedConnection.disconnect();
763
764        // Check the client formatted the request correctly.
765        RecordedRequest request = server.takeRequest();
766        assertEquals(etagValue, request.getHeader("If-None-Match"));
767    }
768
769    /**
770     * Test Etag headers are returned correctly when a client-side cache is installed and the server
771     * data has changed.
772     * https://code.google.com/p/android/issues/detail?id=108949
773     */
774    public void testEtagHeaders_cachedWithServerMiss() throws Exception {
775        final String etagValue1 = "686897696a7c876b7e";
776        final String body1 = "Response with etag 1";
777        final String etagValue2 = "686897696a7c876b7f";
778        final String body2 = "Response with etag 2";
779
780        server.enqueue(
781            new MockResponse()
782                .setBody(body1)
783                .setResponseCode(HttpURLConnection.HTTP_OK)
784                .setHeader("Content-Type", "text/plain")
785                .setHeader("Etag", etagValue1));
786
787        server.enqueue(
788            new MockResponse()
789                .setBody(body2)
790                .setResponseCode(HttpURLConnection.HTTP_OK)
791                .setHeader("Content-Type", "text/plain")
792                .setHeader("Etag", etagValue2));
793
794        server.play();
795
796        initResponseCache();
797
798        URL url = server.getUrl("/");
799        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
800        assertEquals(etagValue1, connection.getHeaderField("Etag"));
801        assertContent(body1, connection);
802        connection.disconnect();
803
804        // Discard the server-side record of the request made.
805        server.takeRequest();
806
807        // Confirm the new body is returned along with the new etag header.
808        HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection();
809        assertEquals(etagValue2, cachedConnection.getHeaderField("Etag"));
810        assertContent(body2, cachedConnection);
811        cachedConnection.disconnect();
812
813        // Check the client formatted the request correctly.
814        RecordedRequest request = server.takeRequest();
815        assertEquals(etagValue1, request.getHeader("If-None-Match"));
816    }
817
818    /**
819     * Test which headers are sent unencrypted to the HTTP proxy.
820     */
821    public void testProxyConnectIncludesProxyHeadersOnly()
822            throws IOException, InterruptedException {
823        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
824        TestSSLContext testSSLContext = TestSSLContext.create();
825
826        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
827        server.enqueue(new MockResponse()
828                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
829                .clearHeaders());
830        server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
831        server.play();
832
833        URL url = new URL("https://android.com/foo");
834        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
835                server.toProxyAddress());
836        connection.addRequestProperty("Private", "Secret");
837        connection.addRequestProperty("Proxy-Authorization", "bar");
838        connection.addRequestProperty("User-Agent", "baz");
839        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
840        connection.setHostnameVerifier(hostnameVerifier);
841        assertContent("encrypted response from the origin server", connection);
842
843        RecordedRequest connect = server.takeRequest();
844        assertContainsNoneMatching(connect.getHeaders(), "Private.*");
845        assertContains(connect.getHeaders(), "Proxy-Authorization: bar");
846        assertContains(connect.getHeaders(), "User-Agent: baz");
847        assertContains(connect.getHeaders(), "Host: android.com");
848        assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive");
849
850        RecordedRequest get = server.takeRequest();
851        assertContains(get.getHeaders(), "Private: Secret");
852        assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
853    }
854
855    public void testProxyAuthenticateOnConnect() throws Exception {
856        Authenticator.setDefault(new SimpleAuthenticator());
857        TestSSLContext testSSLContext = TestSSLContext.create();
858        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
859        server.enqueue(new MockResponse()
860                .setResponseCode(407)
861                .addHeader("Proxy-Authenticate: Basic realm=\"localhost\""));
862        server.enqueue(new MockResponse()
863                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
864                .clearHeaders());
865        server.enqueue(new MockResponse().setBody("A"));
866        server.play();
867
868        URL url = new URL("https://android.com/foo");
869        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
870                server.toProxyAddress());
871        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
872        connection.setHostnameVerifier(new RecordingHostnameVerifier());
873        assertContent("A", connection);
874
875        RecordedRequest connect1 = server.takeRequest();
876        assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine());
877        assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*");
878
879        RecordedRequest connect2 = server.takeRequest();
880        assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine());
881        assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic "
882                + SimpleAuthenticator.BASE_64_CREDENTIALS);
883
884        RecordedRequest get = server.takeRequest();
885        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
886        assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*");
887    }
888
889    // Don't disconnect after building a tunnel with CONNECT
890    // http://code.google.com/p/android/issues/detail?id=37221
891    public void testProxyWithConnectionClose() throws IOException {
892        TestSSLContext testSSLContext = TestSSLContext.create();
893        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
894        server.enqueue(new MockResponse()
895                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
896                .clearHeaders());
897        server.enqueue(new MockResponse().setBody("this response comes via a proxy"));
898        server.play();
899
900        URL url = new URL("https://android.com/foo");
901        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
902                server.toProxyAddress());
903        connection.setRequestProperty("Connection", "close");
904        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
905        connection.setHostnameVerifier(new RecordingHostnameVerifier());
906
907        assertContent("this response comes via a proxy", connection);
908    }
909
910    public void testDisconnectedConnection() throws IOException {
911        server.enqueue(new MockResponse()
912                .throttleBody(2, 100, TimeUnit.MILLISECONDS)
913                .setBody("ABCD"));
914        server.play();
915
916        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
917        InputStream in = connection.getInputStream();
918        assertEquals('A', (char) in.read());
919        connection.disconnect();
920        try {
921            // Reading 'B' may succeed if it's buffered.
922            in.read();
923            // But 'C' shouldn't be buffered (the response is throttled) and this should fail.
924            in.read();
925            fail("Expected a connection closed exception");
926        } catch (IOException expected) {
927        }
928    }
929
930    public void testDisconnectBeforeConnect() throws IOException {
931        server.enqueue(new MockResponse().setBody("A"));
932        server.play();
933
934        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
935        connection.disconnect();
936
937        assertContent("A", connection);
938        assertEquals(200, connection.getResponseCode());
939    }
940
941    public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException {
942        server.enqueue(new MockResponse()
943                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
944                .addHeader("Content-Encoding: gzip"));
945        server.play();
946
947        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
948        try {
949            assertEquals(200, connection.getResponseCode());
950        } finally {
951            connection.disconnect();
952        }
953    }
954
955    public void testDefaultRequestProperty() throws Exception {
956        URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
957        assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
958    }
959
960    /**
961     * Reads {@code count} characters from the stream. If the stream is
962     * exhausted before {@code count} characters can be read, the remaining
963     * characters are returned and the stream is closed.
964     */
965    private String readAscii(InputStream in, int count) throws IOException {
966        StringBuilder result = new StringBuilder();
967        for (int i = 0; i < count; i++) {
968            int value = in.read();
969            if (value == -1) {
970                in.close();
971                break;
972            }
973            result.append((char) value);
974        }
975        return result.toString();
976    }
977
978    public void testMarkAndResetWithContentLengthHeader() throws IOException {
979        testMarkAndReset(TransferKind.FIXED_LENGTH);
980    }
981
982    public void testMarkAndResetWithChunkedEncoding() throws IOException {
983        testMarkAndReset(TransferKind.CHUNKED);
984    }
985
986    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
987        testMarkAndReset(TransferKind.END_OF_STREAM);
988    }
989
990    private void testMarkAndReset(TransferKind transferKind) throws IOException {
991        MockResponse response = new MockResponse();
992        transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
993        server.enqueue(response);
994        server.enqueue(response);
995        server.play();
996
997        InputStream in = server.getUrl("/").openConnection().getInputStream();
998        assertFalse("This implementation claims to support mark().", in.markSupported());
999        in.mark(5);
1000        assertEquals("ABCDE", readAscii(in, 5));
1001        try {
1002            in.reset();
1003            fail();
1004        } catch (IOException expected) {
1005        }
1006        assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
1007        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
1008    }
1009
1010    /**
1011     * We've had a bug where we forget the HTTP response when we see response
1012     * code 401. This causes a new HTTP request to be issued for every call into
1013     * the URLConnection.
1014     */
1015    public void testUnauthorizedResponseHandling() throws IOException {
1016        MockResponse response = new MockResponse()
1017                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1018                .setResponseCode(401) // UNAUTHORIZED
1019                .setBody("Unauthorized");
1020        server.enqueue(response);
1021        server.enqueue(response);
1022        server.enqueue(response);
1023        server.play();
1024
1025        URL url = server.getUrl("/");
1026        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1027
1028        assertEquals(401, conn.getResponseCode());
1029        assertEquals(401, conn.getResponseCode());
1030        assertEquals(401, conn.getResponseCode());
1031        assertEquals(1, server.getRequestCount());
1032    }
1033
1034    public void testNonHexChunkSize() throws IOException {
1035        server.enqueue(new MockResponse()
1036                .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
1037                .clearHeaders()
1038                .addHeader("Transfer-encoding: chunked"));
1039        server.play();
1040
1041        URLConnection connection = server.getUrl("/").openConnection();
1042        try {
1043            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1044            fail();
1045        } catch (IOException e) {
1046        }
1047    }
1048
1049    public void testMissingChunkBody() throws IOException {
1050        server.enqueue(new MockResponse()
1051                .setBody("5")
1052                .clearHeaders()
1053                .addHeader("Transfer-encoding: chunked")
1054                .setSocketPolicy(DISCONNECT_AT_END));
1055        server.play();
1056
1057        URLConnection connection = server.getUrl("/").openConnection();
1058        try {
1059            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
1060            fail();
1061        } catch (IOException e) {
1062        }
1063    }
1064
1065    /**
1066     * This test checks whether connections are gzipped by default. This
1067     * behavior in not required by the API, so a failure of this test does not
1068     * imply a bug in the implementation.
1069     */
1070    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
1071        server.enqueue(new MockResponse()
1072                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
1073                .addHeader("Content-Encoding: gzip"));
1074        server.play();
1075
1076        URLConnection connection = server.getUrl("/").openConnection();
1077        assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1078        assertNull(connection.getContentEncoding());
1079        assertEquals(-1, connection.getContentLength());
1080
1081        RecordedRequest request = server.takeRequest();
1082        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1083    }
1084
1085    public void testClientConfiguredGzipContentEncoding() throws Exception {
1086        byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"));
1087        server.enqueue(new MockResponse()
1088                .setBody(bodyBytes)
1089                .addHeader("Content-Encoding: gzip")
1090                .addHeader("Content-Length: " + bodyBytes.length));
1091        server.play();
1092
1093        URLConnection connection = server.getUrl("/").openConnection();
1094        connection.addRequestProperty("Accept-Encoding", "gzip");
1095        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1096        assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
1097        assertEquals(bodyBytes.length, connection.getContentLength());
1098
1099        RecordedRequest request = server.takeRequest();
1100        assertContains(request.getHeaders(), "Accept-Encoding: gzip");
1101        assertEquals("gzip", connection.getContentEncoding());
1102    }
1103
1104    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
1105        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH);
1106    }
1107
1108    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
1109        testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED);
1110    }
1111
1112    public void testClientConfiguredCustomContentEncoding() throws Exception {
1113        server.enqueue(new MockResponse()
1114                .setBody("ABCDE")
1115                .addHeader("Content-Encoding: custom"));
1116        server.play();
1117
1118        URLConnection connection = server.getUrl("/").openConnection();
1119        connection.addRequestProperty("Accept-Encoding", "custom");
1120        assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1121
1122        RecordedRequest request = server.takeRequest();
1123        assertContains(request.getHeaders(), "Accept-Encoding: custom");
1124    }
1125
1126    /**
1127     * Test a bug where gzip input streams weren't exhausting the input stream,
1128     * which corrupted the request that followed.
1129     * http://code.google.com/p/android/issues/detail?id=7059
1130     */
1131    private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
1132            TransferKind transferKind) throws Exception {
1133        MockResponse responseOne = new MockResponse();
1134        responseOne.addHeader("Content-Encoding: gzip");
1135        transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5);
1136        server.enqueue(responseOne);
1137        MockResponse responseTwo = new MockResponse();
1138        transferKind.setBody(responseTwo, "two (identity)", 5);
1139        server.enqueue(responseTwo);
1140        server.play();
1141
1142        URLConnection connection = server.getUrl("/").openConnection();
1143        connection.addRequestProperty("Accept-Encoding", "gzip");
1144        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
1145        assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
1146        assertEquals(0, server.takeRequest().getSequenceNumber());
1147
1148        connection = server.getUrl("/").openConnection();
1149        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1150        assertEquals(1, server.takeRequest().getSequenceNumber());
1151    }
1152
1153    /**
1154     * Test that HEAD requests don't have a body regardless of the response
1155     * headers. http://code.google.com/p/android/issues/detail?id=24672
1156     */
1157    public void testHeadAndContentLength() throws Exception {
1158        server.enqueue(new MockResponse()
1159                .clearHeaders()
1160                .addHeader("Content-Length: 100"));
1161        server.enqueue(new MockResponse().setBody("A"));
1162        server.play();
1163
1164        HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/").openConnection();
1165        connection1.setRequestMethod("HEAD");
1166        assertEquals("100", connection1.getHeaderField("Content-Length"));
1167        assertContent("", connection1);
1168
1169        HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/").openConnection();
1170        assertEquals("A", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
1171
1172        assertEquals(0, server.takeRequest().getSequenceNumber());
1173        assertEquals(1, server.takeRequest().getSequenceNumber());
1174    }
1175
1176    /**
1177     * Test that request body chunking works. This test has been relaxed from treating
1178     * the {@link java.net.HttpURLConnection#setChunkedStreamingMode(int)}
1179     * chunk length as being fixed because OkHttp no longer guarantees
1180     * the fixed chunk size. Instead, we check that chunking takes place
1181     * and we force the chunk size with flushes.
1182     */
1183    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
1184        server.enqueue(new MockResponse());
1185        server.play();
1186
1187        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1188        // Later releases of Android ignore the value for chunkLength if it is > 0 and default to
1189        // a fixed chunkLength. During the change-over period while the chunkLength indicates the
1190        // chunk buffer size (inc. header) the chunkLength has to be >= 8. This enables the flush()
1191        // to dictate the size of the chunks.
1192        urlConnection.setChunkedStreamingMode(50 /* chunkLength */);
1193        urlConnection.setDoOutput(true);
1194        OutputStream outputStream = urlConnection.getOutputStream();
1195        String outputString = "ABCDEFGH";
1196        byte[] outputBytes = outputString.getBytes("US-ASCII");
1197        int targetChunkSize = 3;
1198        for (int i = 0; i < outputBytes.length; i += targetChunkSize) {
1199            int count = i + targetChunkSize < outputBytes.length ? 3 : outputBytes.length - i;
1200            outputStream.write(outputBytes, i, count);
1201            outputStream.flush();
1202        }
1203        assertEquals(200, urlConnection.getResponseCode());
1204
1205        RecordedRequest request = server.takeRequest();
1206        assertEquals(outputString, new String(request.getBody(), "US-ASCII"));
1207        assertEquals(Arrays.asList(3, 3, 2), request.getChunkSizes());
1208    }
1209
1210    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
1211        testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
1212    }
1213
1214    public void testAuthenticateWithChunkedStreaming() throws Exception {
1215        testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
1216    }
1217
1218    private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
1219        MockResponse pleaseAuthenticate = new MockResponse()
1220                .setResponseCode(401)
1221                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1222                .setBody("Please authenticate.");
1223        server.enqueue(pleaseAuthenticate);
1224        server.play();
1225
1226        Authenticator.setDefault(new SimpleAuthenticator());
1227        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1228        connection.setDoOutput(true);
1229        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1230        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1231            connection.setFixedLengthStreamingMode(requestBody.length);
1232        } else if (streamingMode == StreamingMode.CHUNKED) {
1233            connection.setChunkedStreamingMode(0);
1234        }
1235        OutputStream outputStream = connection.getOutputStream();
1236        outputStream.write(requestBody);
1237        outputStream.close();
1238        try {
1239            connection.getInputStream();
1240            fail();
1241        } catch (HttpRetryException expected) {
1242        }
1243
1244        // no authorization header for the request...
1245        RecordedRequest request = server.takeRequest();
1246        assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*");
1247        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1248    }
1249
1250    public void testSetValidRequestMethod() throws Exception {
1251        server.play();
1252        assertValidRequestMethod("GET");
1253        assertValidRequestMethod("DELETE");
1254        assertValidRequestMethod("HEAD");
1255        assertValidRequestMethod("OPTIONS");
1256        assertValidRequestMethod("POST");
1257        assertValidRequestMethod("PUT");
1258        assertValidRequestMethod("TRACE");
1259    }
1260
1261    private void assertValidRequestMethod(String requestMethod) throws Exception {
1262        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1263        connection.setRequestMethod(requestMethod);
1264        assertEquals(requestMethod, connection.getRequestMethod());
1265    }
1266
1267    public void testSetInvalidRequestMethodLowercase() throws Exception {
1268        server.play();
1269        assertInvalidRequestMethod("get");
1270    }
1271
1272    public void testSetInvalidRequestMethodConnect() throws Exception {
1273        server.play();
1274        assertInvalidRequestMethod("CONNECT");
1275    }
1276
1277    private void assertInvalidRequestMethod(String requestMethod) throws Exception {
1278        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1279        try {
1280            connection.setRequestMethod(requestMethod);
1281            fail();
1282        } catch (ProtocolException expected) {
1283        }
1284    }
1285
1286    public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception {
1287        server.play();
1288        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1289        try {
1290            connection.setFixedLengthStreamingMode(-2);
1291            fail();
1292        } catch (IllegalArgumentException expected) {
1293        }
1294    }
1295
1296    public void testCanSetNegativeChunkedStreamingMode() throws Exception {
1297        server.play();
1298        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1299        connection.setChunkedStreamingMode(-2);
1300    }
1301
1302    public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
1303        server.enqueue(new MockResponse().setBody("A"));
1304        server.play();
1305        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1306        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1307        try {
1308            connection.setFixedLengthStreamingMode(1);
1309            fail();
1310        } catch (IllegalStateException expected) {
1311        }
1312    }
1313
1314    public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception {
1315        server.enqueue(new MockResponse().setBody("A"));
1316        server.play();
1317        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1318        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1319        try {
1320            connection.setChunkedStreamingMode(1);
1321            fail();
1322        } catch (IllegalStateException expected) {
1323        }
1324    }
1325
1326    public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
1327        server.play();
1328        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1329        connection.setChunkedStreamingMode(1);
1330        try {
1331            connection.setFixedLengthStreamingMode(1);
1332            fail();
1333        } catch (IllegalStateException expected) {
1334        }
1335    }
1336
1337    public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
1338        server.play();
1339        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1340        connection.setFixedLengthStreamingMode(1);
1341        try {
1342            connection.setChunkedStreamingMode(1);
1343            fail();
1344        } catch (IllegalStateException expected) {
1345        }
1346    }
1347
1348    public void testSecureFixedLengthStreaming() throws Exception {
1349        testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
1350    }
1351
1352    public void testSecureChunkedStreaming() throws Exception {
1353        testSecureStreamingPost(StreamingMode.CHUNKED);
1354    }
1355
1356    /**
1357     * Users have reported problems using HTTPS with streaming request bodies.
1358     * http://code.google.com/p/android/issues/detail?id=12860
1359     */
1360    private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
1361        TestSSLContext testSSLContext = TestSSLContext.create();
1362        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1363        server.enqueue(new MockResponse().setBody("Success!"));
1364        server.play();
1365
1366        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1367        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1368        connection.setDoOutput(true);
1369        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1370        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1371            connection.setFixedLengthStreamingMode(requestBody.length);
1372        } else if (streamingMode == StreamingMode.CHUNKED) {
1373            connection.setChunkedStreamingMode(0);
1374        }
1375        OutputStream outputStream = connection.getOutputStream();
1376        outputStream.write(requestBody);
1377        outputStream.close();
1378        assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1379
1380        RecordedRequest request = server.takeRequest();
1381        assertEquals("POST / HTTP/1.1", request.getRequestLine());
1382        if (streamingMode == StreamingMode.FIXED_LENGTH) {
1383            assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
1384        } else if (streamingMode == StreamingMode.CHUNKED) {
1385            assertEquals(Arrays.asList(4), request.getChunkSizes());
1386        }
1387        assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1388    }
1389
1390    enum StreamingMode {
1391        FIXED_LENGTH, CHUNKED
1392    }
1393
1394    public void testAuthenticateWithPost() throws Exception {
1395        MockResponse pleaseAuthenticate = new MockResponse()
1396                .setResponseCode(401)
1397                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1398                .setBody("Please authenticate.");
1399        // fail auth three times...
1400        server.enqueue(pleaseAuthenticate);
1401        server.enqueue(pleaseAuthenticate);
1402        server.enqueue(pleaseAuthenticate);
1403        // ...then succeed the fourth time
1404        server.enqueue(new MockResponse().setBody("Successful auth!"));
1405        server.play();
1406
1407        Authenticator.setDefault(new SimpleAuthenticator());
1408        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1409        connection.setDoOutput(true);
1410        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1411        OutputStream outputStream = connection.getOutputStream();
1412        outputStream.write(requestBody);
1413        outputStream.close();
1414        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1415
1416        // no authorization header for the first request...
1417        RecordedRequest request = server.takeRequest();
1418        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1419
1420        // ...but the three requests that follow include an authorization header
1421        for (int i = 0; i < 3; i++) {
1422            request = server.takeRequest();
1423            assertEquals("POST / HTTP/1.1", request.getRequestLine());
1424            assertContains(request.getHeaders(), "Authorization: Basic "
1425                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1426            assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
1427        }
1428    }
1429
1430    public void testAuthenticateWithGet() throws Exception {
1431        MockResponse pleaseAuthenticate = new MockResponse()
1432                .setResponseCode(401)
1433                .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1434                .setBody("Please authenticate.");
1435        // fail auth three times...
1436        server.enqueue(pleaseAuthenticate);
1437        server.enqueue(pleaseAuthenticate);
1438        server.enqueue(pleaseAuthenticate);
1439        // ...then succeed the fourth time
1440        server.enqueue(new MockResponse().setBody("Successful auth!"));
1441        server.play();
1442
1443        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1444        Authenticator.setDefault(authenticator);
1445        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1446        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1447        assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType);
1448        assertEquals(server.getPort(), authenticator.requestingPort);
1449        assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite);
1450        assertEquals("protected area", authenticator.requestingPrompt);
1451        assertEquals("http", authenticator.requestingProtocol);
1452        assertEquals("Basic", authenticator.requestingScheme);
1453
1454        // no authorization header for the first request...
1455        RecordedRequest request = server.takeRequest();
1456        assertContainsNoneMatching(request.getHeaders(), "Authorization: .*");
1457
1458        // ...but the three requests that follow requests include an authorization header
1459        for (int i = 0; i < 3; i++) {
1460            request = server.takeRequest();
1461            assertEquals("GET / HTTP/1.1", request.getRequestLine());
1462            assertContains(request.getHeaders(), "Authorization: Basic "
1463                    + SimpleAuthenticator.BASE_64_CREDENTIALS);
1464        }
1465    }
1466
1467    // bug 11473660
1468    public void testAuthenticateWithLowerCaseHeadersAndScheme() throws Exception {
1469        MockResponse pleaseAuthenticate = new MockResponse()
1470                .setResponseCode(401)
1471                .addHeader("www-authenticate: basic realm=\"protected area\"")
1472                .setBody("Please authenticate.");
1473        // fail auth three times...
1474        server.enqueue(pleaseAuthenticate);
1475        server.enqueue(pleaseAuthenticate);
1476        server.enqueue(pleaseAuthenticate);
1477        // ...then succeed the fourth time
1478        server.enqueue(new MockResponse().setBody("Successful auth!"));
1479        server.play();
1480
1481        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1482        Authenticator.setDefault(authenticator);
1483        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1484        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1485        assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType);
1486        assertEquals(server.getPort(), authenticator.requestingPort);
1487        assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite);
1488        assertEquals("protected area", authenticator.requestingPrompt);
1489        assertEquals("http", authenticator.requestingProtocol);
1490        assertEquals("basic", authenticator.requestingScheme);
1491    }
1492
1493    // http://code.google.com/p/android/issues/detail?id=19081
1494    public void testAuthenticateWithCommaSeparatedAuthenticationMethods() throws Exception {
1495        server.enqueue(new MockResponse()
1496                .setResponseCode(401)
1497                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\", Basic realm=\"b\", "
1498                        + "Scheme3 realm=\"c\"")
1499                .setBody("Please authenticate."));
1500        server.enqueue(new MockResponse().setBody("Successful auth!"));
1501        server.play();
1502
1503        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1504        authenticator.expectedPrompt = "b";
1505        Authenticator.setDefault(authenticator);
1506        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1507        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1508
1509        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1510        assertContains(server.takeRequest().getHeaders(),
1511                "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1512        assertEquals("Basic", authenticator.requestingScheme);
1513    }
1514
1515    public void testAuthenticateWithMultipleAuthenticationHeaders() throws Exception {
1516        server.enqueue(new MockResponse()
1517                .setResponseCode(401)
1518                .addHeader("WWW-Authenticate: Scheme1 realm=\"a\"")
1519                .addHeader("WWW-Authenticate: Basic realm=\"b\"")
1520                .addHeader("WWW-Authenticate: Scheme3 realm=\"c\"")
1521                .setBody("Please authenticate."));
1522        server.enqueue(new MockResponse().setBody("Successful auth!"));
1523        server.play();
1524
1525        SimpleAuthenticator authenticator = new SimpleAuthenticator();
1526        authenticator.expectedPrompt = "b";
1527        Authenticator.setDefault(authenticator);
1528        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1529        assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1530
1531        assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
1532        assertContains(server.takeRequest().getHeaders(),
1533                "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS);
1534        assertEquals("Basic", authenticator.requestingScheme);
1535    }
1536
1537    public void testRedirectedWithChunkedEncoding() throws Exception {
1538        testRedirected(TransferKind.CHUNKED, true);
1539    }
1540
1541    public void testRedirectedWithContentLengthHeader() throws Exception {
1542        testRedirected(TransferKind.FIXED_LENGTH, true);
1543    }
1544
1545    public void testRedirectedWithNoLengthHeaders() throws Exception {
1546        testRedirected(TransferKind.END_OF_STREAM, false);
1547    }
1548
1549    private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
1550        MockResponse response = new MockResponse()
1551                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1552                .addHeader("Location: /foo");
1553        transferKind.setBody(response, "This page has moved!", 10);
1554        server.enqueue(response);
1555        server.enqueue(new MockResponse().setBody("This is the new location!"));
1556        server.play();
1557
1558        URLConnection connection = server.getUrl("/").openConnection();
1559        assertEquals("This is the new location!",
1560                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1561
1562        RecordedRequest first = server.takeRequest();
1563        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1564        RecordedRequest retry = server.takeRequest();
1565        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1566        if (reuse) {
1567            assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1568        }
1569    }
1570
1571    public void testRedirectedOnHttps() throws IOException, InterruptedException {
1572        TestSSLContext testSSLContext = TestSSLContext.create();
1573        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1574        server.enqueue(new MockResponse()
1575                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1576                .addHeader("Location: /foo")
1577                .setBody("This page has moved!"));
1578        server.enqueue(new MockResponse().setBody("This is the new location!"));
1579        server.play();
1580
1581        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1582        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1583        assertEquals("This is the new location!",
1584                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1585
1586        RecordedRequest first = server.takeRequest();
1587        assertEquals("GET / HTTP/1.1", first.getRequestLine());
1588        RecordedRequest retry = server.takeRequest();
1589        assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
1590        assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
1591    }
1592
1593    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
1594        TestSSLContext testSSLContext = TestSSLContext.create();
1595        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1596        server.enqueue(new MockResponse()
1597                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1598                .addHeader("Location: http://anyhost/foo")
1599                .setBody("This page has moved!"));
1600        server.play();
1601
1602        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
1603        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
1604        assertEquals("This page has moved!",
1605                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1606    }
1607
1608    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
1609        server.enqueue(new MockResponse()
1610                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1611                .addHeader("Location: https://anyhost/foo")
1612                .setBody("This page has moved!"));
1613        server.play();
1614
1615        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1616        assertEquals("This page has moved!",
1617                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1618    }
1619
1620    public void testRedirectToAnotherOriginServer() throws Exception {
1621        MockWebServer server2 = new MockWebServer();
1622        server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
1623        server2.play();
1624
1625        server.enqueue(new MockResponse()
1626                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1627                .addHeader("Location: " + server2.getUrl("/").toString())
1628                .setBody("This page has moved!"));
1629        server.enqueue(new MockResponse().setBody("This is the first server again!"));
1630        server.play();
1631
1632        URLConnection connection = server.getUrl("/").openConnection();
1633        assertEquals("This is the 2nd server!",
1634                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1635        assertEquals(server2.getUrl("/"), connection.getURL());
1636
1637        // make sure the first server was careful to recycle the connection
1638        assertEquals("This is the first server again!",
1639                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
1640
1641        RecordedRequest first = server.takeRequest();
1642        assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
1643        RecordedRequest second = server2.takeRequest();
1644        assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort());
1645        RecordedRequest third = server.takeRequest();
1646        assertEquals("Expected connection reuse", 1, third.getSequenceNumber());
1647
1648        server2.shutdown();
1649    }
1650
1651    public void testResponse300MultipleChoiceWithPost() throws Exception {
1652        // Chrome doesn't follow the redirect, but Firefox and the RI both do
1653        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
1654    }
1655
1656    public void testResponse301MovedPermanentlyWithPost() throws Exception {
1657        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
1658    }
1659
1660    public void testResponse302MovedTemporarilyWithPost() throws Exception {
1661        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
1662    }
1663
1664    public void testResponse303SeeOtherWithPost() throws Exception {
1665        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
1666    }
1667
1668    private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
1669        server.enqueue(new MockResponse()
1670                .setResponseCode(redirectCode)
1671                .addHeader("Location: /page2")
1672                .setBody("This page has moved!"));
1673        server.enqueue(new MockResponse().setBody("Page 2"));
1674        server.play();
1675
1676        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection();
1677        connection.setDoOutput(true);
1678        byte[] requestBody = { 'A', 'B', 'C', 'D' };
1679        OutputStream outputStream = connection.getOutputStream();
1680        outputStream.write(requestBody);
1681        outputStream.close();
1682        assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1683        assertTrue(connection.getDoOutput());
1684
1685        RecordedRequest page1 = server.takeRequest();
1686        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
1687        assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody()));
1688
1689        RecordedRequest page2 = server.takeRequest();
1690        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
1691    }
1692
1693    public void testResponse305UseProxy() throws Exception {
1694        server.play();
1695        server.enqueue(new MockResponse()
1696                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
1697                .addHeader("Location: " + server.getUrl("/"))
1698                .setBody("This page has moved!"));
1699        server.enqueue(new MockResponse().setBody("Proxy Response"));
1700
1701        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection();
1702        // Fails on the RI, which gets "Proxy Response"
1703        assertEquals("This page has moved!",
1704                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1705
1706        RecordedRequest page1 = server.takeRequest();
1707        assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
1708        assertEquals(1, server.getRequestCount());
1709    }
1710
1711    public void testHttpsWithCustomTrustManager() throws Exception {
1712        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
1713        RecordingTrustManager trustManager = new RecordingTrustManager();
1714        SSLContext sc = SSLContext.getInstance("TLS");
1715        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
1716
1717        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
1718        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
1719        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
1720        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1721        try {
1722            TestSSLContext testSSLContext = TestSSLContext.create();
1723            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
1724            server.enqueue(new MockResponse().setBody("ABC"));
1725            server.enqueue(new MockResponse().setBody("DEF"));
1726            server.enqueue(new MockResponse().setBody("GHI"));
1727            server.play();
1728
1729            URL url = server.getUrl("/");
1730            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
1731            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
1732            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
1733
1734            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
1735            assertEquals(Arrays.asList("checkServerTrusted ["
1736                    + "CN=" + hostName + " 1, "
1737                    + "CN=Test Intermediate Certificate Authority 1, "
1738                    + "CN=Test Root Certificate Authority 1"
1739                    + "] ECDHE_RSA"),
1740                    trustManager.calls);
1741        } finally {
1742            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
1743            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
1744        }
1745    }
1746
1747    /**
1748     * Test that the timeout period is honored. The timeout may be doubled!
1749     * HttpURLConnection will wait the full timeout for each of the server's IP
1750     * addresses. This is typically one IPv4 address and one IPv6 address.
1751     */
1752    public void testConnectTimeouts() throws IOException {
1753        StuckServer ss = new StuckServer(true);
1754        int serverPort = ss.getLocalPort();
1755        String hostName = ss.getLocalSocketAddress().getAddress().getHostAddress();
1756        URLConnection urlConnection = new URL("http://" + hostName + ":" + serverPort + "/")
1757                .openConnection();
1758
1759        int timeout = 1000;
1760        urlConnection.setConnectTimeout(timeout);
1761        long start = System.currentTimeMillis();
1762        try {
1763            urlConnection.getInputStream();
1764            fail();
1765        } catch (SocketTimeoutException expected) {
1766            long elapsed = System.currentTimeMillis() - start;
1767            int attempts = InetAddress.getAllByName("localhost").length; // one per IP address
1768            assertTrue("timeout=" +timeout + ", elapsed=" + elapsed + ", attempts=" + attempts,
1769                    Math.abs((attempts * timeout) - elapsed) < 500);
1770        } finally {
1771            ss.close();
1772        }
1773    }
1774
1775    public void testReadTimeouts() throws IOException {
1776        /*
1777         * This relies on the fact that MockWebServer doesn't close the
1778         * connection after a response has been sent. This causes the client to
1779         * try to read more bytes than are sent, which results in a timeout.
1780         */
1781        MockResponse timeout = new MockResponse()
1782                .setBody("ABC")
1783                .clearHeaders()
1784                .addHeader("Content-Length: 4");
1785        server.enqueue(timeout);
1786        server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
1787        server.play();
1788
1789        URLConnection urlConnection = server.getUrl("/").openConnection();
1790        urlConnection.setReadTimeout(1000);
1791        InputStream in = urlConnection.getInputStream();
1792        assertEquals('A', in.read());
1793        assertEquals('B', in.read());
1794        assertEquals('C', in.read());
1795        try {
1796            in.read(); // if Content-Length was accurate, this would return -1 immediately
1797            fail();
1798        } catch (SocketTimeoutException expected) {
1799        }
1800    }
1801
1802    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
1803        server.enqueue(new MockResponse());
1804        server.play();
1805
1806        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
1807        urlConnection.setRequestProperty("Transfer-encoding", "chunked");
1808        urlConnection.setDoOutput(true);
1809        urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
1810        assertEquals(200, urlConnection.getResponseCode());
1811
1812        RecordedRequest request = server.takeRequest();
1813        assertEquals("ABC", new String(request.getBody(), "UTF-8"));
1814    }
1815
1816    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
1817        server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
1818        server.enqueue(new MockResponse());
1819        server.play();
1820
1821        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1822        a.setRequestProperty("Connection", "close");
1823        assertEquals(200, a.getResponseCode());
1824
1825        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1826        assertEquals(200, b.getResponseCode());
1827
1828        assertEquals(0, server.takeRequest().getSequenceNumber());
1829        assertEquals("When connection: close is used, each request should get its own connection",
1830                0, server.takeRequest().getSequenceNumber());
1831    }
1832
1833    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
1834        server.enqueue(new MockResponse().addHeader("Connection: close"));
1835        server.enqueue(new MockResponse());
1836        server.play();
1837
1838        HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection();
1839        assertEquals(200, a.getResponseCode());
1840
1841        HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection();
1842        assertEquals(200, b.getResponseCode());
1843
1844        assertEquals(0, server.takeRequest().getSequenceNumber());
1845        assertEquals("When connection: close is used, each request should get its own connection",
1846                0, server.takeRequest().getSequenceNumber());
1847    }
1848
1849    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
1850        MockResponse response = new MockResponse()
1851                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1852                .addHeader("Location: /foo")
1853                .addHeader("Connection: close");
1854        server.enqueue(response);
1855        server.enqueue(new MockResponse().setBody("This is the new location!"));
1856        server.play();
1857
1858        URLConnection connection = server.getUrl("/").openConnection();
1859        assertEquals("This is the new location!",
1860                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1861
1862        assertEquals(0, server.takeRequest().getSequenceNumber());
1863        assertEquals("When connection: close is used, each request should get its own connection",
1864                0, server.takeRequest().getSequenceNumber());
1865    }
1866
1867    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
1868        server.enqueue(new MockResponse()
1869                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
1870                .setBody("This body is not allowed!"));
1871        server.play();
1872
1873        URLConnection connection = server.getUrl("/").openConnection();
1874        assertEquals("This body is not allowed!",
1875                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1876    }
1877
1878    public void testSingleByteReadIsSigned() throws IOException {
1879        server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
1880        server.play();
1881
1882        URLConnection connection = server.getUrl("/").openConnection();
1883        InputStream in = connection.getInputStream();
1884        assertEquals(254, in.read());
1885        assertEquals(255, in.read());
1886        assertEquals(-1, in.read());
1887    }
1888
1889    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
1890        testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
1891    }
1892
1893    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
1894        testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
1895    }
1896
1897    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
1898        testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
1899    }
1900
1901    /**
1902     * We explicitly permit apps to close the upload stream even after it has
1903     * been transmitted.  We also permit flush so that buffered streams can
1904     * do a no-op flush when they are closed. http://b/3038470
1905     */
1906    private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
1907        server.enqueue(new MockResponse().setBody("abc"));
1908        server.play();
1909
1910        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1911        connection.setDoOutput(true);
1912        byte[] upload = "def".getBytes("UTF-8");
1913
1914        if (transferKind == TransferKind.CHUNKED) {
1915            connection.setChunkedStreamingMode(0);
1916        } else if (transferKind == TransferKind.FIXED_LENGTH) {
1917            connection.setFixedLengthStreamingMode(upload.length);
1918        }
1919
1920        OutputStream out = connection.getOutputStream();
1921        out.write(upload);
1922        assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1923
1924        out.flush(); // dubious but permitted
1925        try {
1926            out.write("ghi".getBytes("UTF-8"));
1927            fail();
1928        } catch (IOException expected) {
1929        }
1930    }
1931
1932    public void testGetHeadersThrows() throws IOException {
1933        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
1934        server.play();
1935
1936        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
1937        try {
1938            connection.getInputStream();
1939            fail();
1940        } catch (IOException expected) {
1941        }
1942
1943        try {
1944            connection.getInputStream();
1945            fail();
1946        } catch (IOException expected) {
1947        }
1948    }
1949
1950    public void testReadTimeoutsOnRecycledConnections() throws Exception {
1951        server.enqueue(new MockResponse().setBody("ABC"));
1952        server.play();
1953
1954        // The request should work once and then fail
1955        URLConnection connection = server.getUrl("").openConnection();
1956        // Read timeout of a day, sure to cause the test to timeout and fail.
1957        connection.setReadTimeout(24 * 3600 * 1000);
1958        InputStream input = connection.getInputStream();
1959        assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
1960        input.close();
1961        try {
1962            connection = server.getUrl("").openConnection();
1963            // Set the read timeout back to 100ms, this request will time out
1964            // because we've only enqueued one response.
1965            connection.setReadTimeout(100);
1966            connection.getInputStream();
1967            fail();
1968        } catch (IOException expected) {
1969        }
1970    }
1971
1972    /**
1973     * This test goes through the exhaustive set of interesting ASCII characters
1974     * because most of those characters are interesting in some way according to
1975     * RFC 2396 and RFC 2732. http://b/1158780
1976     */
1977    public void testLenientUrlToUri() throws Exception {
1978        // alphanum
1979        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
1980
1981        // control characters
1982        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
1983        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
1984
1985        // ascii characters
1986        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1987        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
1988        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
1989        testUrlToUriMapping("!", "!", "!", "!", "!");
1990        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
1991        testUrlToUriMapping("#", null, null, null, "%23");
1992        testUrlToUriMapping("$", "$", "$", "$", "$");
1993        testUrlToUriMapping("&", "&", "&", "&", "&");
1994        testUrlToUriMapping("'", "'", "'", "'", "'");
1995        testUrlToUriMapping("(", "(", "(", "(", "(");
1996        testUrlToUriMapping(")", ")", ")", ")", ")");
1997        testUrlToUriMapping("*", "*", "*", "*", "*");
1998        testUrlToUriMapping("+", "+", "+", "+", "+");
1999        testUrlToUriMapping(",", ",", ",", ",", ",");
2000        testUrlToUriMapping("-", "-", "-", "-", "-");
2001        testUrlToUriMapping(".", ".", ".", ".", ".");
2002        testUrlToUriMapping("/", null, "/", "/", "/");
2003        testUrlToUriMapping(":", null, ":", ":", ":");
2004        testUrlToUriMapping(";", ";", ";", ";", ";");
2005        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
2006        testUrlToUriMapping("=", "=", "=", "=", "=");
2007        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
2008        testUrlToUriMapping("?", null, null, "?", "?");
2009        testUrlToUriMapping("@", "@", "@", "@", "@");
2010        testUrlToUriMapping("[", null, "%5B", null, "%5B");
2011        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
2012        testUrlToUriMapping("]", null, "%5D", null, "%5D");
2013        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
2014        testUrlToUriMapping("_", "_", "_", "_", "_");
2015        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
2016        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
2017        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
2018        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
2019        testUrlToUriMapping("~", "~", "~", "~", "~");
2020        testUrlToUriMapping("~", "~", "~", "~", "~");
2021        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
2022
2023        // beyond ascii
2024        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
2025        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
2026        testUrlToUriMapping("\ud842\udf9f",
2027                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
2028    }
2029
2030    public void testLenientUrlToUriNul() throws Exception {
2031        // On JB-MR2 and below, we would allow a host containing \u0000
2032        // and then generate a request with a Host header that violated RFC2616.
2033        // We now reject such hosts.
2034        //
2035        // The ideal behaviour here is to be "lenient" about the host and rewrite
2036        // it, but attempting to do so introduces a new range of incompatible
2037        // behaviours.
2038        testUrlToUriMapping("\u0000", null, "%00", "%00", "%00"); // RI fails this
2039    }
2040
2041    public void testHostWithNul() throws Exception {
2042        URL url = new URL("http://host\u0000/");
2043        try {
2044            url.openStream();
2045            fail();
2046        } catch (IllegalArgumentException expected) {}
2047    }
2048
2049    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
2050            String asQuery, String asFragment) throws Exception {
2051        if (asAuthority != null) {
2052            assertEquals("http://host" + asAuthority + ".tld/",
2053                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
2054        }
2055        if (asFile != null) {
2056            assertEquals("http://host.tld/file" + asFile + "/",
2057                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
2058        }
2059        if (asQuery != null) {
2060            assertEquals("http://host.tld/file?q" + asQuery + "=x",
2061                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
2062        }
2063        assertEquals("http://host.tld/file#" + asFragment + "-x",
2064                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
2065    }
2066
2067    /**
2068     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
2069     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
2070     * characters like '{' and '|' by escaping these characters.
2071     */
2072    private URI backdoorUrlToUri(URL url) throws Exception {
2073        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
2074
2075        ResponseCache.setDefault(new ResponseCache() {
2076            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
2077                return null;
2078            }
2079            @Override public CacheResponse get(URI uri, String requestMethod,
2080                    Map<String, List<String>> requestHeaders) throws IOException {
2081                uriReference.set(uri);
2082                throw new UnsupportedOperationException();
2083            }
2084        });
2085
2086        try {
2087            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
2088            connection.getResponseCode();
2089        } catch (Exception expected) {
2090        }
2091
2092        return uriReference.get();
2093    }
2094
2095    /**
2096     * Don't explode if the cache returns a null body. http://b/3373699
2097     */
2098    public void testResponseCacheReturnsNullOutputStream() throws Exception {
2099        final AtomicBoolean aborted = new AtomicBoolean();
2100        ResponseCache.setDefault(new ResponseCache() {
2101            @Override public CacheResponse get(URI uri, String requestMethod,
2102                    Map<String, List<String>> requestHeaders) throws IOException {
2103                return null;
2104            }
2105            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
2106                return new CacheRequest() {
2107                    @Override public void abort() {
2108                        aborted.set(true);
2109                    }
2110                    @Override public OutputStream getBody() throws IOException {
2111                        return null;
2112                    }
2113                };
2114            }
2115        });
2116
2117        server.enqueue(new MockResponse().setBody("abcdef"));
2118        server.play();
2119
2120        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2121        InputStream in = connection.getInputStream();
2122        assertEquals("abc", readAscii(in, 3));
2123        in.close();
2124        assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
2125    }
2126
2127
2128    /**
2129     * http://code.google.com/p/android/issues/detail?id=14562
2130     */
2131    public void testReadAfterLastByte() throws Exception {
2132        server.enqueue(new MockResponse()
2133                .setBody("ABC")
2134                .clearHeaders()
2135                .addHeader("Connection: close")
2136                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END));
2137        server.play();
2138
2139        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2140        InputStream in = connection.getInputStream();
2141        assertEquals("ABC", readAscii(in, 3));
2142        assertEquals(-1, in.read());
2143        assertEquals(-1, in.read()); // throws IOException in Gingerbread
2144    }
2145
2146    public void testGetContent() throws Exception {
2147        server.enqueue(new MockResponse().setBody("A"));
2148        server.play();
2149        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2150        InputStream in = (InputStream) connection.getContent();
2151        assertEquals("A", readAscii(in, Integer.MAX_VALUE));
2152    }
2153
2154    public void testGetContentOfType() throws Exception {
2155        server.enqueue(new MockResponse().setBody("A"));
2156        server.play();
2157        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2158        try {
2159            connection.getContent(null);
2160            fail();
2161        } catch (NullPointerException expected) {
2162        }
2163        try {
2164            connection.getContent(new Class[] { null });
2165            fail();
2166        } catch (NullPointerException expected) {
2167        }
2168        assertNull(connection.getContent(new Class[] { getClass() }));
2169        connection.disconnect();
2170    }
2171
2172    public void testGetOutputStreamOnGetFails() throws Exception {
2173        server.enqueue(new MockResponse());
2174        server.play();
2175        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2176        try {
2177            connection.getOutputStream();
2178            fail();
2179        } catch (ProtocolException expected) {
2180        }
2181    }
2182
2183    public void testGetOutputAfterGetInputStreamFails() throws Exception {
2184        server.enqueue(new MockResponse());
2185        server.play();
2186        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2187        connection.setDoOutput(true);
2188        try {
2189            connection.getInputStream();
2190            connection.getOutputStream();
2191            fail();
2192        } catch (ProtocolException expected) {
2193        }
2194    }
2195
2196    public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception {
2197        server.enqueue(new MockResponse());
2198        server.play();
2199        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2200        connection.connect();
2201        try {
2202            connection.setDoOutput(true);
2203            fail();
2204        } catch (IllegalStateException expected) {
2205        }
2206        try {
2207            connection.setDoInput(true);
2208            fail();
2209        } catch (IllegalStateException expected) {
2210        }
2211        connection.disconnect();
2212    }
2213
2214    public void testLastModified() throws Exception {
2215        server.enqueue(new MockResponse()
2216                .addHeader("Last-Modified", "Wed, 27 Nov 2013 11:26:00 GMT")
2217                .setBody("Hello"));
2218        server.play();
2219
2220        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2221        connection.connect();
2222
2223        assertEquals(1385551560000L, connection.getLastModified());
2224        assertEquals(1385551560000L, connection.getHeaderFieldDate("Last-Modified", -1));
2225    }
2226
2227    public void testClientSendsContentLength() throws Exception {
2228        server.enqueue(new MockResponse().setBody("A"));
2229        server.play();
2230        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2231        connection.setDoOutput(true);
2232        OutputStream out = connection.getOutputStream();
2233        out.write(new byte[] { 'A', 'B', 'C' });
2234        out.close();
2235        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2236        RecordedRequest request = server.takeRequest();
2237        assertContains(request.getHeaders(), "Content-Length: 3");
2238    }
2239
2240    public void testGetContentLengthConnects() throws Exception {
2241        server.enqueue(new MockResponse().setBody("ABC"));
2242        server.play();
2243        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2244        assertEquals(3, connection.getContentLength());
2245        connection.disconnect();
2246    }
2247
2248    public void testGetContentTypeConnects() throws Exception {
2249        server.enqueue(new MockResponse()
2250                .addHeader("Content-Type: text/plain")
2251                .setBody("ABC"));
2252        server.play();
2253        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2254        assertEquals("text/plain", connection.getContentType());
2255        connection.disconnect();
2256    }
2257
2258    public void testGetContentEncodingConnects() throws Exception {
2259        server.enqueue(new MockResponse()
2260                .addHeader("Content-Encoding: identity")
2261                .setBody("ABC"));
2262        server.play();
2263        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
2264        assertEquals("identity", connection.getContentEncoding());
2265        connection.disconnect();
2266    }
2267
2268    // http://b/4361656
2269    public void testUrlContainsQueryButNoPath() throws Exception {
2270        server.enqueue(new MockResponse().setBody("A"));
2271        server.play();
2272        URL url = new URL("http", server.getHostName(), server.getPort(), "?query");
2273        assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE));
2274        RecordedRequest request = server.takeRequest();
2275        assertEquals("GET /?query HTTP/1.1", request.getRequestLine());
2276    }
2277
2278    // http://code.google.com/p/android/issues/detail?id=20442
2279    public void testInputStreamAvailableWithChunkedEncoding() throws Exception {
2280        testInputStreamAvailable(TransferKind.CHUNKED);
2281    }
2282
2283    public void testInputStreamAvailableWithContentLengthHeader() throws Exception {
2284        testInputStreamAvailable(TransferKind.FIXED_LENGTH);
2285    }
2286
2287    public void testInputStreamAvailableWithNoLengthHeaders() throws Exception {
2288        testInputStreamAvailable(TransferKind.END_OF_STREAM);
2289    }
2290
2291    private void testInputStreamAvailable(TransferKind transferKind) throws IOException {
2292        String body = "ABCDEFGH";
2293        MockResponse response = new MockResponse();
2294        transferKind.setBody(response, body, 4);
2295        server.enqueue(response);
2296        server.play();
2297        URLConnection connection = server.getUrl("/").openConnection();
2298        InputStream in = connection.getInputStream();
2299        for (int i = 0; i < body.length(); i++) {
2300            assertTrue(in.available() >= 0);
2301            assertEquals(body.charAt(i), in.read());
2302        }
2303        assertEquals(0, in.available());
2304        assertEquals(-1, in.read());
2305    }
2306
2307    // http://code.google.com/p/android/issues/detail?id=28095
2308    public void testInvalidIpv4Address() throws Exception {
2309        try {
2310            URI uri = new URI("http://1111.111.111.111/index.html");
2311            uri.toURL().openConnection().connect();
2312            fail();
2313        } catch (UnknownHostException expected) {
2314        }
2315    }
2316
2317    // http://code.google.com/p/android/issues/detail?id=16895
2318    public void testUrlWithSpaceInHost() throws Exception {
2319        URLConnection urlConnection = new URL("http://and roid.com/").openConnection();
2320        try {
2321            urlConnection.getInputStream();
2322            fail();
2323        } catch (UnknownHostException expected) {
2324        }
2325    }
2326
2327    // http://code.google.com/p/android/issues/detail?id=16895
2328    public void testUrlWithSpaceInHostViaHttpProxy() throws Exception {
2329        server.enqueue(new MockResponse());
2330        server.play();
2331        URLConnection urlConnection = new URL("http://and roid.com/")
2332                .openConnection(server.toProxyAddress());
2333
2334        // This test is to check that a NullPointerException is not thrown.
2335        urlConnection.getInputStream();
2336    }
2337
2338    public void testSslFallback_allSupportedProtocols() throws Exception {
2339        TestSSLContext testSSLContext = TestSSLContext.create();
2340
2341        String[] allSupportedProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3" };
2342        SSLSocketFactory serverSocketFactory =
2343                new LimitedProtocolsSocketFactory(
2344                        testSSLContext.serverContext.getSocketFactory(),
2345                        allSupportedProtocols);
2346        server.useHttps(serverSocketFactory, false);
2347        server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
2348        server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
2349        server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
2350        server.enqueue(new MockResponse().setBody("This required fallbacks"));
2351        server.play();
2352
2353        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2354        // Keeps track of the client sockets created so that we can interrogate them.
2355        final boolean disableFallbackScsv = true;
2356        FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory(
2357                new LimitedProtocolsSocketFactory(
2358                        testSSLContext.clientContext.getSocketFactory(), allSupportedProtocols),
2359                disableFallbackScsv);
2360        connection.setSSLSocketFactory(clientSocketFactory);
2361        assertEquals("This required fallbacks",
2362                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2363
2364        // Confirm the server accepted a single connection.
2365        RecordedRequest retry = server.takeRequest();
2366        assertEquals(0, retry.getSequenceNumber());
2367        assertEquals("SSLv3", retry.getSslProtocol());
2368
2369        // Confirm the client fallback looks ok.
2370        List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets();
2371        assertEquals(4, createdSockets.size());
2372        TlsFallbackDisabledScsvSSLSocket clientSocket1 =
2373                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0);
2374        assertSslSocket(clientSocket1,
2375                false /* expectedWasFallbackScsvSet */, "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3");
2376
2377        TlsFallbackDisabledScsvSSLSocket clientSocket2 =
2378                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(1);
2379        assertSslSocket(clientSocket2,
2380                true /* expectedWasFallbackScsvSet */, "TLSv1.1", "TLSv1", "SSLv3");
2381
2382        TlsFallbackDisabledScsvSSLSocket clientSocket3 =
2383                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(2);
2384        assertSslSocket(clientSocket3, true /* expectedWasFallbackScsvSet */, "TLSv1", "SSLv3");
2385
2386        TlsFallbackDisabledScsvSSLSocket clientSocket4 =
2387                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(3);
2388        assertSslSocket(clientSocket4, true /* expectedWasFallbackScsvSet */, "SSLv3");
2389    }
2390
2391    public void testSslFallback_defaultProtocols() throws Exception {
2392        TestSSLContext testSSLContext = TestSSLContext.create();
2393
2394        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2395        server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
2396        server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
2397        server.enqueue(new MockResponse().setBody("This required fallbacks"));
2398        server.play();
2399
2400        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2401        // Keeps track of the client sockets created so that we can interrogate them.
2402        final boolean disableFallbackScsv = true;
2403        FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory(
2404                testSSLContext.clientContext.getSocketFactory(),
2405                disableFallbackScsv);
2406        connection.setSSLSocketFactory(clientSocketFactory);
2407        assertEquals("This required fallbacks",
2408                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
2409
2410        // Confirm the server accepted a single connection.
2411        RecordedRequest retry = server.takeRequest();
2412        assertEquals(0, retry.getSequenceNumber());
2413        assertEquals("TLSv1", retry.getSslProtocol());
2414
2415        // Confirm the client fallback looks ok.
2416        List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets();
2417        assertEquals(3, createdSockets.size());
2418        TlsFallbackDisabledScsvSSLSocket clientSocket1 =
2419                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0);
2420        assertSslSocket(clientSocket1,
2421                false /* expectedWasFallbackScsvSet */, "TLSv1.2", "TLSv1.1", "TLSv1");
2422
2423        TlsFallbackDisabledScsvSSLSocket clientSocket2 =
2424                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(1);
2425        assertSslSocket(clientSocket2, true /* expectedWasFallbackScsvSet */, "TLSv1.1", "TLSv1");
2426
2427        TlsFallbackDisabledScsvSSLSocket clientSocket3 =
2428                (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(2);
2429        assertSslSocket(clientSocket3, true /* expectedWasFallbackScsvSet */, "TLSv1");
2430    }
2431
2432    private static void assertSslSocket(TlsFallbackDisabledScsvSSLSocket socket,
2433            boolean expectedWasFallbackScsvSet, String... expectedEnabledProtocols) {
2434        Set<String> enabledProtocols =
2435                new HashSet<String>(Arrays.asList(socket.getEnabledProtocols()));
2436        Set<String> expectedProtocolsSet = new HashSet<String>(Arrays.asList(expectedEnabledProtocols));
2437        assertEquals(expectedProtocolsSet, enabledProtocols);
2438        assertEquals(expectedWasFallbackScsvSet, socket.wasTlsFallbackScsvSet());
2439    }
2440
2441    public void testInspectSslBeforeConnect() throws Exception {
2442        TestSSLContext testSSLContext = TestSSLContext.create();
2443        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2444        server.enqueue(new MockResponse());
2445        server.play();
2446
2447        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2448        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2449        assertNotNull(connection.getHostnameVerifier());
2450        try {
2451            connection.getLocalCertificates();
2452            fail();
2453        } catch (IllegalStateException expected) {
2454        }
2455        try {
2456            connection.getServerCertificates();
2457            fail();
2458        } catch (IllegalStateException expected) {
2459        }
2460        try {
2461            connection.getCipherSuite();
2462            fail();
2463        } catch (IllegalStateException expected) {
2464        }
2465        try {
2466            connection.getPeerPrincipal();
2467            fail();
2468        } catch (IllegalStateException expected) {
2469        }
2470    }
2471
2472    /**
2473     * Test that we can inspect the SSL session after connect().
2474     * http://code.google.com/p/android/issues/detail?id=24431
2475     */
2476    public void testInspectSslAfterConnect() throws Exception {
2477        TestSSLContext testSSLContext = TestSSLContext.create();
2478        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2479        server.enqueue(new MockResponse());
2480        server.play();
2481
2482        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2483        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2484        connection.connect();
2485        try {
2486            assertNotNull(connection.getHostnameVerifier());
2487            assertNull(connection.getLocalCertificates());
2488            assertNotNull(connection.getServerCertificates());
2489            assertNotNull(connection.getCipherSuite());
2490            assertNotNull(connection.getPeerPrincipal());
2491        } finally {
2492            connection.disconnect();
2493        }
2494    }
2495
2496    /**
2497     * Returns a gzipped copy of {@code bytes}.
2498     */
2499    public byte[] gzip(byte[] bytes) throws IOException {
2500        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
2501        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
2502        gzippedOut.write(bytes);
2503        gzippedOut.close();
2504        return bytesOut.toByteArray();
2505    }
2506
2507    /**
2508     * Reads at most {@code limit} characters from {@code in} and asserts that
2509     * content equals {@code expected}.
2510     */
2511    private void assertContent(String expected, URLConnection connection, int limit)
2512            throws IOException {
2513        connection.connect();
2514        assertEquals(expected, readAscii(connection.getInputStream(), limit));
2515        ((HttpURLConnection) connection).disconnect();
2516    }
2517
2518    private void assertContent(String expected, URLConnection connection) throws IOException {
2519        assertContent(expected, connection, Integer.MAX_VALUE);
2520    }
2521
2522    private void assertContains(List<String> list, String value) {
2523        assertTrue(list.toString(), list.contains(value));
2524    }
2525
2526    private void assertContainsNoneMatching(List<String> list, String pattern) {
2527        for (String header : list) {
2528            if (header.matches(pattern)) {
2529                fail("Header " + header + " matches " + pattern);
2530            }
2531        }
2532    }
2533
2534    private Set<String> newSet(String... elements) {
2535        return new HashSet<String>(Arrays.asList(elements));
2536    }
2537
2538    enum TransferKind {
2539        CHUNKED() {
2540            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
2541                    throws IOException {
2542                response.setChunkedBody(content, chunkSize);
2543            }
2544        },
2545        FIXED_LENGTH() {
2546            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
2547                response.setBody(content);
2548            }
2549        },
2550        END_OF_STREAM() {
2551            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
2552                response.setBody(content);
2553                response.setSocketPolicy(DISCONNECT_AT_END);
2554                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
2555                    if (h.next().startsWith("Content-Length:")) {
2556                        h.remove();
2557                        break;
2558                    }
2559                }
2560            }
2561        };
2562
2563        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
2564                throws IOException;
2565
2566        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
2567            setBody(response, content.getBytes("UTF-8"), chunkSize);
2568        }
2569    }
2570
2571    enum ProxyConfig {
2572        NO_PROXY() {
2573            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2574                    throws IOException {
2575                return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
2576            }
2577        },
2578
2579        CREATE_ARG() {
2580            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2581                    throws IOException {
2582                return (HttpURLConnection) url.openConnection(server.toProxyAddress());
2583            }
2584        },
2585
2586        PROXY_SYSTEM_PROPERTY() {
2587            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2588                    throws IOException {
2589                System.setProperty("proxyHost", "localhost");
2590                System.setProperty("proxyPort", Integer.toString(server.getPort()));
2591                return (HttpURLConnection) url.openConnection();
2592            }
2593        },
2594
2595        HTTP_PROXY_SYSTEM_PROPERTY() {
2596            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2597                    throws IOException {
2598                System.setProperty("http.proxyHost", "localhost");
2599                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
2600                return (HttpURLConnection) url.openConnection();
2601            }
2602        },
2603
2604        HTTPS_PROXY_SYSTEM_PROPERTY() {
2605            @Override public HttpURLConnection connect(MockWebServer server, URL url)
2606                    throws IOException {
2607                System.setProperty("https.proxyHost", "localhost");
2608                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
2609                return (HttpURLConnection) url.openConnection();
2610            }
2611        };
2612
2613        public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException;
2614    }
2615
2616    private static class RecordingTrustManager implements X509TrustManager {
2617        private final List<String> calls = new ArrayList<String>();
2618
2619        public X509Certificate[] getAcceptedIssuers() {
2620            calls.add("getAcceptedIssuers");
2621            return new X509Certificate[] {};
2622        }
2623
2624        public void checkClientTrusted(X509Certificate[] chain, String authType)
2625                throws CertificateException {
2626            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
2627        }
2628
2629        public void checkServerTrusted(X509Certificate[] chain, String authType)
2630                throws CertificateException {
2631            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
2632        }
2633
2634        private String certificatesToString(X509Certificate[] certificates) {
2635            List<String> result = new ArrayList<String>();
2636            for (X509Certificate certificate : certificates) {
2637                result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
2638            }
2639            return result.toString();
2640        }
2641    }
2642
2643    private static class RecordingHostnameVerifier implements HostnameVerifier {
2644        private final List<String> calls = new ArrayList<String>();
2645
2646        public boolean verify(String hostname, SSLSession session) {
2647            calls.add("verify " + hostname);
2648            return true;
2649        }
2650    }
2651
2652    private static class SimpleAuthenticator extends Authenticator {
2653        /** base64("username:password") */
2654        private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ=";
2655
2656        private String expectedPrompt;
2657        private RequestorType requestorType;
2658        private int requestingPort;
2659        private InetAddress requestingSite;
2660        private String requestingPrompt;
2661        private String requestingProtocol;
2662        private String requestingScheme;
2663
2664        protected PasswordAuthentication getPasswordAuthentication() {
2665            requestorType = getRequestorType();
2666            requestingPort = getRequestingPort();
2667            requestingSite = getRequestingSite();
2668            requestingPrompt = getRequestingPrompt();
2669            requestingProtocol = getRequestingProtocol();
2670            requestingScheme = getRequestingScheme();
2671            return (expectedPrompt == null || expectedPrompt.equals(requestingPrompt))
2672                    ? new PasswordAuthentication("username", "password".toCharArray())
2673                    : null;
2674        }
2675    }
2676
2677    /**
2678     * An SSLSocketFactory that delegates all calls.
2679     */
2680    private static class DelegatingSSLSocketFactory extends SSLSocketFactory {
2681
2682        protected final SSLSocketFactory delegate;
2683
2684        public DelegatingSSLSocketFactory(SSLSocketFactory delegate) {
2685            this.delegate = delegate;
2686        }
2687
2688        @Override
2689        public String[] getDefaultCipherSuites() {
2690            return delegate.getDefaultCipherSuites();
2691        }
2692
2693        @Override
2694        public String[] getSupportedCipherSuites() {
2695            return delegate.getSupportedCipherSuites();
2696        }
2697
2698        @Override
2699        public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose)
2700                throws IOException {
2701            return (SSLSocket) delegate.createSocket(s, host, port, autoClose);
2702        }
2703
2704        @Override
2705        public SSLSocket createSocket() throws IOException {
2706            return (SSLSocket) delegate.createSocket();
2707        }
2708
2709        @Override
2710        public SSLSocket createSocket(String host, int port)
2711                throws IOException, UnknownHostException {
2712            return (SSLSocket) delegate.createSocket(host, port);
2713        }
2714
2715        @Override
2716        public SSLSocket createSocket(String host, int port, InetAddress localHost,
2717                int localPort) throws IOException, UnknownHostException {
2718            return (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
2719        }
2720
2721        @Override
2722        public SSLSocket createSocket(InetAddress host, int port) throws IOException {
2723            return (SSLSocket) delegate.createSocket(host, port);
2724        }
2725
2726        @Override
2727        public SSLSocket createSocket(InetAddress address, int port,
2728                InetAddress localAddress, int localPort) throws IOException {
2729            return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
2730        }
2731
2732    }
2733
2734    /**
2735     * An SSLSocketFactory that delegates calls but limits the enabled protocols for any created
2736     * sockets.
2737     */
2738    private static class LimitedProtocolsSocketFactory extends DelegatingSSLSocketFactory {
2739
2740        private final String[] protocols;
2741
2742        private LimitedProtocolsSocketFactory(SSLSocketFactory delegate, String... protocols) {
2743            super(delegate);
2744            this.protocols = protocols;
2745        }
2746
2747        @Override
2748        public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose)
2749                throws IOException {
2750            SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose);
2751            socket.setEnabledProtocols(protocols);
2752            return socket;
2753        }
2754
2755        @Override
2756        public SSLSocket createSocket() throws IOException {
2757            SSLSocket socket = (SSLSocket) delegate.createSocket();
2758            socket.setEnabledProtocols(protocols);
2759            return socket;
2760        }
2761
2762        @Override
2763        public SSLSocket createSocket(String host, int port)
2764                throws IOException, UnknownHostException {
2765            SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
2766            socket.setEnabledProtocols(protocols);
2767            return socket;
2768        }
2769
2770        @Override
2771        public SSLSocket createSocket(String host, int port, InetAddress localHost,
2772                int localPort) throws IOException, UnknownHostException {
2773            SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
2774            socket.setEnabledProtocols(protocols);
2775            return socket;
2776        }
2777
2778        @Override
2779        public SSLSocket createSocket(InetAddress host, int port) throws IOException {
2780            SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
2781            socket.setEnabledProtocols(protocols);
2782            return socket;
2783        }
2784
2785        @Override
2786        public SSLSocket createSocket(InetAddress address, int port,
2787                InetAddress localAddress, int localPort) throws IOException {
2788            SSLSocket socket =
2789                    (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
2790            socket.setEnabledProtocols(protocols);
2791            return socket;
2792        }
2793    }
2794
2795    /**
2796     * An {@link javax.net.ssl.SSLSocket} that delegates all calls.
2797     */
2798    private static abstract class DelegatingSSLSocket extends SSLSocket {
2799        protected final SSLSocket delegate;
2800
2801        public DelegatingSSLSocket(SSLSocket delegate) {
2802            this.delegate = delegate;
2803        }
2804
2805        @Override public void shutdownInput() throws IOException {
2806            delegate.shutdownInput();
2807        }
2808
2809        @Override public void shutdownOutput() throws IOException {
2810            delegate.shutdownOutput();
2811        }
2812
2813        @Override public String[] getSupportedCipherSuites() {
2814            return delegate.getSupportedCipherSuites();
2815        }
2816
2817        @Override public String[] getEnabledCipherSuites() {
2818            return delegate.getEnabledCipherSuites();
2819        }
2820
2821        @Override public void setEnabledCipherSuites(String[] suites) {
2822            delegate.setEnabledCipherSuites(suites);
2823        }
2824
2825        @Override public String[] getSupportedProtocols() {
2826            return delegate.getSupportedProtocols();
2827        }
2828
2829        @Override public String[] getEnabledProtocols() {
2830            return delegate.getEnabledProtocols();
2831        }
2832
2833        @Override public void setEnabledProtocols(String[] protocols) {
2834            delegate.setEnabledProtocols(protocols);
2835        }
2836
2837        @Override public SSLSession getSession() {
2838            return delegate.getSession();
2839        }
2840
2841        @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
2842            delegate.addHandshakeCompletedListener(listener);
2843        }
2844
2845        @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
2846            delegate.removeHandshakeCompletedListener(listener);
2847        }
2848
2849        @Override public void startHandshake() throws IOException {
2850            delegate.startHandshake();
2851        }
2852
2853        @Override public void setUseClientMode(boolean mode) {
2854            delegate.setUseClientMode(mode);
2855        }
2856
2857        @Override public boolean getUseClientMode() {
2858            return delegate.getUseClientMode();
2859        }
2860
2861        @Override public void setNeedClientAuth(boolean need) {
2862            delegate.setNeedClientAuth(need);
2863        }
2864
2865        @Override public void setWantClientAuth(boolean want) {
2866            delegate.setWantClientAuth(want);
2867        }
2868
2869        @Override public boolean getNeedClientAuth() {
2870            return delegate.getNeedClientAuth();
2871        }
2872
2873        @Override public boolean getWantClientAuth() {
2874            return delegate.getWantClientAuth();
2875        }
2876
2877        @Override public void setEnableSessionCreation(boolean flag) {
2878            delegate.setEnableSessionCreation(flag);
2879        }
2880
2881        @Override public boolean getEnableSessionCreation() {
2882            return delegate.getEnableSessionCreation();
2883        }
2884
2885        @Override public SSLParameters getSSLParameters() {
2886            return delegate.getSSLParameters();
2887        }
2888
2889        @Override public void setSSLParameters(SSLParameters p) {
2890            delegate.setSSLParameters(p);
2891        }
2892
2893        @Override public void close() throws IOException {
2894            delegate.close();
2895        }
2896
2897        @Override public InetAddress getInetAddress() {
2898            return delegate.getInetAddress();
2899        }
2900
2901        @Override public InputStream getInputStream() throws IOException {
2902            return delegate.getInputStream();
2903        }
2904
2905        @Override public boolean getKeepAlive() throws SocketException {
2906            return delegate.getKeepAlive();
2907        }
2908
2909        @Override public InetAddress getLocalAddress() {
2910            return delegate.getLocalAddress();
2911        }
2912
2913        @Override public int getLocalPort() {
2914            return delegate.getLocalPort();
2915        }
2916
2917        @Override public OutputStream getOutputStream() throws IOException {
2918            return delegate.getOutputStream();
2919        }
2920
2921        @Override public int getPort() {
2922            return delegate.getPort();
2923        }
2924
2925        @Override public int getSoLinger() throws SocketException {
2926            return delegate.getSoLinger();
2927        }
2928
2929        @Override public int getReceiveBufferSize() throws SocketException {
2930            return delegate.getReceiveBufferSize();
2931        }
2932
2933        @Override public int getSendBufferSize() throws SocketException {
2934            return delegate.getSendBufferSize();
2935        }
2936
2937        @Override public int getSoTimeout() throws SocketException {
2938            return delegate.getSoTimeout();
2939        }
2940
2941        @Override public boolean getTcpNoDelay() throws SocketException {
2942            return delegate.getTcpNoDelay();
2943        }
2944
2945        @Override public void setKeepAlive(boolean keepAlive) throws SocketException {
2946            delegate.setKeepAlive(keepAlive);
2947        }
2948
2949        @Override public void setSendBufferSize(int size) throws SocketException {
2950            delegate.setSendBufferSize(size);
2951        }
2952
2953        @Override public void setReceiveBufferSize(int size) throws SocketException {
2954            delegate.setReceiveBufferSize(size);
2955        }
2956
2957        @Override public void setSoLinger(boolean on, int timeout) throws SocketException {
2958            delegate.setSoLinger(on, timeout);
2959        }
2960
2961        @Override public void setSoTimeout(int timeout) throws SocketException {
2962            delegate.setSoTimeout(timeout);
2963        }
2964
2965        @Override public void setTcpNoDelay(boolean on) throws SocketException {
2966            delegate.setTcpNoDelay(on);
2967        }
2968
2969        @Override public String toString() {
2970            return delegate.toString();
2971        }
2972
2973        @Override public SocketAddress getLocalSocketAddress() {
2974            return delegate.getLocalSocketAddress();
2975        }
2976
2977        @Override public SocketAddress getRemoteSocketAddress() {
2978            return delegate.getRemoteSocketAddress();
2979        }
2980
2981        @Override public boolean isBound() {
2982            return delegate.isBound();
2983        }
2984
2985        @Override public boolean isConnected() {
2986            return delegate.isConnected();
2987        }
2988
2989        @Override public boolean isClosed() {
2990            return delegate.isClosed();
2991        }
2992
2993        @Override public void bind(SocketAddress localAddr) throws IOException {
2994            delegate.bind(localAddr);
2995        }
2996
2997        @Override public void connect(SocketAddress remoteAddr) throws IOException {
2998            delegate.connect(remoteAddr);
2999        }
3000
3001        @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
3002            delegate.connect(remoteAddr, timeout);
3003        }
3004
3005        @Override public boolean isInputShutdown() {
3006            return delegate.isInputShutdown();
3007        }
3008
3009        @Override public boolean isOutputShutdown() {
3010            return delegate.isOutputShutdown();
3011        }
3012
3013        @Override public void setReuseAddress(boolean reuse) throws SocketException {
3014            delegate.setReuseAddress(reuse);
3015        }
3016
3017        @Override public boolean getReuseAddress() throws SocketException {
3018            return delegate.getReuseAddress();
3019        }
3020
3021        @Override public void setOOBInline(boolean oobinline) throws SocketException {
3022            delegate.setOOBInline(oobinline);
3023        }
3024
3025        @Override public boolean getOOBInline() throws SocketException {
3026            return delegate.getOOBInline();
3027        }
3028
3029        @Override public void setTrafficClass(int value) throws SocketException {
3030            delegate.setTrafficClass(value);
3031        }
3032
3033        @Override public int getTrafficClass() throws SocketException {
3034            return delegate.getTrafficClass();
3035        }
3036
3037        @Override public void sendUrgentData(int value) throws IOException {
3038            delegate.sendUrgentData(value);
3039        }
3040
3041        @Override public SocketChannel getChannel() {
3042            return delegate.getChannel();
3043        }
3044
3045        @Override public void setPerformancePreferences(int connectionTime, int latency,
3046                int bandwidth) {
3047            delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
3048        }
3049    }
3050
3051    /**
3052     * An SSLSocketFactory that delegates calls. It keeps a record of any sockets created.
3053     * If {@link #disableTlsFallbackScsv} is set to {@code true} then sockets created by the
3054     * delegate are wrapped with ones that will not accept the {@link #TLS_FALLBACK_SCSV} cipher,
3055     * thus bypassing server-side fallback checks on platforms that support it. Unfortunately this
3056     * wrapping will disable any reflection-based calls to SSLSocket from Platform.
3057     */
3058    private static class FallbackTestClientSocketFactory extends DelegatingSSLSocketFactory {
3059        /**
3060         * The cipher suite used during TLS connection fallback to indicate a fallback.
3061         * See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
3062         */
3063        public static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV";
3064
3065        private final boolean disableTlsFallbackScsv;
3066        private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>();
3067
3068        public FallbackTestClientSocketFactory(SSLSocketFactory delegate,
3069                boolean disableTlsFallbackScsv) {
3070            super(delegate);
3071            this.disableTlsFallbackScsv = disableTlsFallbackScsv;
3072        }
3073
3074        @Override public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose)
3075                throws IOException {
3076            SSLSocket socket = super.createSocket(s, host, port, autoClose);
3077            if (disableTlsFallbackScsv) {
3078                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3079            }
3080            createdSockets.add(socket);
3081            return socket;
3082        }
3083
3084        @Override public SSLSocket createSocket() throws IOException {
3085            SSLSocket socket = super.createSocket();
3086            if (disableTlsFallbackScsv) {
3087                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3088            }
3089            createdSockets.add(socket);
3090            return socket;
3091        }
3092
3093        @Override public SSLSocket createSocket(String host,int port) throws IOException {
3094            SSLSocket socket = super.createSocket(host, port);
3095            if (disableTlsFallbackScsv) {
3096                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3097            }
3098            createdSockets.add(socket);
3099            return socket;
3100        }
3101
3102        @Override public SSLSocket createSocket(String host,int port, InetAddress localHost,
3103                int localPort) throws IOException {
3104            SSLSocket socket = super.createSocket(host, port, localHost, localPort);
3105            if (disableTlsFallbackScsv) {
3106                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3107            }
3108            createdSockets.add(socket);
3109            return socket;
3110        }
3111
3112        @Override public SSLSocket createSocket(InetAddress host,int port) throws IOException {
3113            SSLSocket socket = super.createSocket(host, port);
3114            if (disableTlsFallbackScsv) {
3115                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3116            }
3117            createdSockets.add(socket);
3118            return socket;
3119        }
3120
3121        @Override public SSLSocket createSocket(InetAddress address,int port,
3122                InetAddress localAddress, int localPort) throws IOException {
3123            SSLSocket socket = super.createSocket(address, port, localAddress, localPort);
3124            if (disableTlsFallbackScsv) {
3125                socket = new TlsFallbackDisabledScsvSSLSocket(socket);
3126            }
3127            createdSockets.add(socket);
3128            return socket;
3129        }
3130
3131        public List<SSLSocket> getCreatedSockets() {
3132            return createdSockets;
3133        }
3134    }
3135
3136    private static class TlsFallbackDisabledScsvSSLSocket extends DelegatingSSLSocket {
3137
3138        private boolean tlsFallbackScsvSet;
3139
3140        public TlsFallbackDisabledScsvSSLSocket(SSLSocket socket) {
3141            super(socket);
3142        }
3143
3144        @Override public void setEnabledCipherSuites(String[] suites) {
3145            List<String> enabledCipherSuites = new ArrayList<String>(suites.length);
3146            for (String suite : suites) {
3147                if (suite.equals(FallbackTestClientSocketFactory.TLS_FALLBACK_SCSV)) {
3148                    // Record that an attempt was made to set TLS_FALLBACK_SCSV, but don't actually
3149                    // set it.
3150                    tlsFallbackScsvSet = true;
3151                } else {
3152                    enabledCipherSuites.add(suite);
3153                }
3154            }
3155            delegate.setEnabledCipherSuites(
3156                    enabledCipherSuites.toArray(new String[enabledCipherSuites.size()]));
3157        }
3158
3159        public boolean wasTlsFallbackScsvSet() {
3160            return tlsFallbackScsvSet;
3161        }
3162    }
3163}
3164