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