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