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