URLConnectionTest.java revision f241d462634527692b7d99335cdc8c11883ac966
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 java.io.BufferedReader; 20import java.io.ByteArrayOutputStream; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.InputStreamReader; 24import java.io.OutputStream; 25import java.net.Authenticator; 26import java.net.CacheRequest; 27import java.net.CacheResponse; 28import java.net.HttpRetryException; 29import java.net.HttpURLConnection; 30import java.net.InetAddress; 31import java.net.PasswordAuthentication; 32import java.net.Proxy; 33import java.net.ResponseCache; 34import java.net.ServerSocket; 35import java.net.Socket; 36import java.net.SocketTimeoutException; 37import java.net.URI; 38import java.net.URISyntaxException; 39import java.net.URL; 40import java.net.URLConnection; 41import java.security.cert.CertificateException; 42import java.security.cert.X509Certificate; 43import java.util.ArrayList; 44import java.util.Arrays; 45import java.util.Collections; 46import java.util.HashSet; 47import java.util.Iterator; 48import java.util.List; 49import java.util.Map; 50import java.util.Set; 51import java.util.concurrent.atomic.AtomicReference; 52import java.util.zip.GZIPInputStream; 53import java.util.zip.GZIPOutputStream; 54import javax.net.ssl.HostnameVerifier; 55import javax.net.ssl.HttpsURLConnection; 56import javax.net.ssl.SSLContext; 57import javax.net.ssl.SSLException; 58import javax.net.ssl.SSLHandshakeException; 59import javax.net.ssl.SSLSession; 60import javax.net.ssl.SSLSocketFactory; 61import javax.net.ssl.TrustManager; 62import javax.net.ssl.X509TrustManager; 63import libcore.java.security.TestKeyStore; 64import libcore.javax.net.ssl.TestSSLContext; 65import tests.http.DefaultResponseCache; 66import tests.http.MockResponse; 67import tests.http.MockWebServer; 68import tests.http.RecordedRequest; 69 70public class URLConnectionTest extends junit.framework.TestCase { 71 72 private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() { 73 protected PasswordAuthentication getPasswordAuthentication() { 74 return new PasswordAuthentication("username", "password".toCharArray()); 75 } 76 }; 77 78 private MockWebServer server = new MockWebServer(); 79 private String hostname; 80 81 @Override protected void setUp() throws Exception { 82 super.setUp(); 83 hostname = InetAddress.getLocalHost().getHostName(); 84 } 85 86 @Override protected void tearDown() throws Exception { 87 ResponseCache.setDefault(null); 88 Authenticator.setDefault(null); 89 System.clearProperty("proxyHost"); 90 System.clearProperty("proxyPort"); 91 System.clearProperty("http.proxyHost"); 92 System.clearProperty("http.proxyPort"); 93 System.clearProperty("https.proxyHost"); 94 System.clearProperty("https.proxyPort"); 95 server.shutdown(); 96 super.tearDown(); 97 } 98 99 public void testRequestHeaders() throws IOException, InterruptedException { 100 server.enqueue(new MockResponse()); 101 server.play(); 102 103 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 104 urlConnection.addRequestProperty("D", "e"); 105 urlConnection.addRequestProperty("D", "f"); 106 Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties(); 107 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); 108 try { 109 requestHeaders.put("G", Arrays.asList("h")); 110 fail("Modified an unmodifiable view."); 111 } catch (UnsupportedOperationException expected) { 112 } 113 try { 114 requestHeaders.get("D").add("i"); 115 fail("Modified an unmodifiable view."); 116 } catch (UnsupportedOperationException expected) { 117 } 118 try { 119 urlConnection.setRequestProperty(null, "j"); 120 fail(); 121 } catch (NullPointerException expected) { 122 } 123 try { 124 urlConnection.addRequestProperty(null, "k"); 125 fail(); 126 } catch (NullPointerException expected) { 127 } 128 urlConnection.setRequestProperty("NullValue", null); // should fail silently! 129 urlConnection.addRequestProperty("AnotherNullValue", null); // should fail silently! 130 131 urlConnection.getResponseCode(); 132 RecordedRequest request = server.takeRequest(); 133 assertContains(request.getHeaders(), "D: e"); 134 assertContains(request.getHeaders(), "D: f"); 135 assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); 136 assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); 137 assertContainsNoneMatching(request.getHeaders(), "G:.*"); 138 assertContainsNoneMatching(request.getHeaders(), "null:.*"); 139 140 try { 141 urlConnection.addRequestProperty("N", "o"); 142 fail("Set header after connect"); 143 } catch (IllegalStateException expected) { 144 } 145 try { 146 urlConnection.setRequestProperty("P", "q"); 147 fail("Set header after connect"); 148 } catch (IllegalStateException expected) { 149 } 150 } 151 152 public void testResponseHeaders() throws IOException, InterruptedException { 153 server.enqueue(new MockResponse() 154 .setStatus("HTTP/1.0 200 Fantastic") 155 .addHeader("A: b") 156 .addHeader("A: c") 157 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); 158 server.play(); 159 160 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 161 assertEquals(200, urlConnection.getResponseCode()); 162 assertEquals("Fantastic", urlConnection.getResponseMessage()); 163 Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields(); 164 assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A"))); 165 try { 166 responseHeaders.put("N", Arrays.asList("o")); 167 fail("Modified an unmodifiable view."); 168 } catch (UnsupportedOperationException expected) { 169 } 170 try { 171 responseHeaders.get("A").add("d"); 172 fail("Modified an unmodifiable view."); 173 } catch (UnsupportedOperationException expected) { 174 } 175 } 176 177 // Check that if we don't read to the end of a response, the next request on the 178 // recycled connection doesn't get the unread tail of the first request's response. 179 // http://code.google.com/p/android/issues/detail?id=2939 180 public void test_2939() throws Exception { 181 MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 182 183 server.enqueue(response); 184 server.enqueue(response); 185 server.play(); 186 187 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 188 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 189 } 190 191 // Check that we recognize a few basic mime types by extension. 192 // http://code.google.com/p/android/issues/detail?id=10100 193 public void test_10100() throws Exception { 194 assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); 195 assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); 196 } 197 198 public void testConnectionsArePooled() throws Exception { 199 MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 200 201 server.enqueue(response); 202 server.enqueue(response); 203 server.enqueue(response); 204 server.play(); 205 206 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); 207 assertEquals(0, server.takeRequest().getSequenceNumber()); 208 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); 209 assertEquals(1, server.takeRequest().getSequenceNumber()); 210 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); 211 assertEquals(2, server.takeRequest().getSequenceNumber()); 212 } 213 214 public void testChunkedConnectionsArePooled() throws Exception { 215 MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); 216 217 server.enqueue(response); 218 server.enqueue(response); 219 server.enqueue(response); 220 server.play(); 221 222 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); 223 assertEquals(0, server.takeRequest().getSequenceNumber()); 224 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); 225 assertEquals(1, server.takeRequest().getSequenceNumber()); 226 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); 227 assertEquals(2, server.takeRequest().getSequenceNumber()); 228 } 229 230 enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } 231 232 public void test_chunkedUpload_byteByByte() throws Exception { 233 doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 234 } 235 236 public void test_chunkedUpload_smallBuffers() throws Exception { 237 doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); 238 } 239 240 public void test_chunkedUpload_largeBuffers() throws Exception { 241 doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); 242 } 243 244 public void test_fixedLengthUpload_byteByByte() throws Exception { 245 doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 246 } 247 248 public void test_fixedLengthUpload_smallBuffers() throws Exception { 249 doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 250 } 251 252 public void test_fixedLengthUpload_largeBuffers() throws Exception { 253 doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 254 } 255 256 private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { 257 int n = 512*1024; 258 server.setBodyLimit(0); 259 server.enqueue(new MockResponse()); 260 server.play(); 261 262 HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); 263 conn.setDoOutput(true); 264 conn.setRequestMethod("POST"); 265 if (uploadKind == TransferKind.CHUNKED) { 266 conn.setChunkedStreamingMode(-1); 267 } else { 268 conn.setFixedLengthStreamingMode(n); 269 } 270 OutputStream out = conn.getOutputStream(); 271 if (writeKind == WriteKind.BYTE_BY_BYTE) { 272 for (int i = 0; i < n; ++i) { 273 out.write('x'); 274 } 275 } else { 276 byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024]; 277 Arrays.fill(buf, (byte) 'x'); 278 for (int i = 0; i < n; i += buf.length) { 279 out.write(buf, 0, Math.min(buf.length, n - i)); 280 } 281 } 282 out.close(); 283 assertEquals(200, conn.getResponseCode()); 284 RecordedRequest request = server.takeRequest(); 285 assertEquals(n, request.getBodySize()); 286 if (uploadKind == TransferKind.CHUNKED) { 287 assertTrue(request.getChunkSizes().size() > 0); 288 } else { 289 assertTrue(request.getChunkSizes().isEmpty()); 290 } 291 } 292 293 /** 294 * Test that response caching is consistent with the RI and the spec. 295 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 296 */ 297 public void test_responseCaching() throws Exception { 298 // Test each documented HTTP/1.1 code, plus the first unused value in each range. 299 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 300 301 // We can't test 100 because it's not really a response. 302 // assertCached(false, 100); 303 assertCached(false, 101); 304 assertCached(false, 102); 305 assertCached(true, 200); 306 assertCached(false, 201); 307 assertCached(false, 202); 308 assertCached(true, 203); 309 assertCached(false, 204); 310 assertCached(false, 205); 311 assertCached(true, 206); 312 assertCached(false, 207); 313 // (See test_responseCaching_300.) 314 assertCached(true, 301); 315 for (int i = 302; i <= 308; ++i) { 316 assertCached(false, i); 317 } 318 for (int i = 400; i <= 406; ++i) { 319 assertCached(false, i); 320 } 321 // (See test_responseCaching_407.) 322 assertCached(false, 408); 323 assertCached(false, 409); 324 // (See test_responseCaching_410.) 325 for (int i = 411; i <= 418; ++i) { 326 assertCached(false, i); 327 } 328 for (int i = 500; i <= 506; ++i) { 329 assertCached(false, i); 330 } 331 } 332 333 public void test_responseCaching_300() throws Exception { 334 // TODO: fix this for android 335 assertCached(false, 300); 336 } 337 338 /** 339 * Response code 407 should only come from proxy servers. Android's client 340 * throws if it is sent by an origin server. 341 */ 342 public void testOriginServerSends407() throws Exception { 343 server.enqueue(new MockResponse().setResponseCode(407)); 344 server.play(); 345 346 URL url = server.getUrl("/"); 347 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 348 try { 349 conn.getResponseCode(); 350 fail(); 351 } catch (IOException expected) { 352 } 353 } 354 355 public void test_responseCaching_410() throws Exception { 356 // the HTTP spec permits caching 410s, but the RI doesn't. 357 assertCached(false, 410); 358 } 359 360 private void assertCached(boolean shouldPut, int responseCode) throws Exception { 361 server = new MockWebServer(); 362 server.enqueue(new MockResponse() 363 .setResponseCode(responseCode) 364 .setBody("ABCDE") 365 .addHeader("WWW-Authenticate: challenge")); 366 server.play(); 367 368 DefaultResponseCache responseCache = new DefaultResponseCache(); 369 ResponseCache.setDefault(responseCache); 370 URL url = server.getUrl("/"); 371 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 372 assertEquals(responseCode, conn.getResponseCode()); 373 374 // exhaust the content stream 375 try { 376 // TODO: remove special case once testUnauthorizedResponseHandling() is fixed 377 if (responseCode != 401) { 378 readAscii(conn.getInputStream(), Integer.MAX_VALUE); 379 } 380 } catch (IOException ignored) { 381 } 382 383 Set<URI> expectedCachedUris = shouldPut 384 ? Collections.singleton(url.toURI()) 385 : Collections.<URI>emptySet(); 386 assertEquals(Integer.toString(responseCode), 387 expectedCachedUris, responseCache.getContents().keySet()); 388 server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers 389 } 390 391 public void testConnectViaHttps() throws IOException, InterruptedException { 392 TestSSLContext testSSLContext = TestSSLContext.create(); 393 394 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 395 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 396 server.play(); 397 398 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 399 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 400 401 assertContent("this response comes via HTTPS", connection); 402 403 RecordedRequest request = server.takeRequest(); 404 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 405 } 406 407 public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException { 408 TestSSLContext testSSLContext = TestSSLContext.create(); 409 410 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 411 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 412 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 413 server.play(); 414 415 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 416 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 417 assertContent("this response comes via HTTPS", connection); 418 419 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 420 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 421 assertContent("another response via HTTPS", connection); 422 423 assertEquals(0, server.takeRequest().getSequenceNumber()); 424 assertEquals(1, server.takeRequest().getSequenceNumber()); 425 } 426 427 public void testConnectViaHttpsReusingConnectionsDifferentFactories() 428 throws IOException, InterruptedException { 429 TestSSLContext testSSLContext = TestSSLContext.create(); 430 431 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 432 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 433 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 434 server.play(); 435 436 // install a custom SSL socket factory so the server can be authorized 437 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 438 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 439 assertContent("this response comes via HTTPS", connection); 440 441 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 442 try { 443 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 444 fail("without an SSL socket factory, the connection should fail"); 445 } catch (SSLException expected) { 446 } 447 } 448 449 public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException { 450 TestSSLContext testSSLContext = TestSSLContext.create(); 451 452 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 453 server.enqueue(new MockResponse().setDisconnectAtStart(true)); 454 server.enqueue(new MockResponse().setBody("this response comes via SSL")); 455 server.play(); 456 457 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 458 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 459 460 assertContent("this response comes via SSL", connection); 461 462 RecordedRequest request = server.takeRequest(); 463 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 464 } 465 466 /** 467 * Verify that we don't retry connections on certificate verification errors. 468 * 469 * http://code.google.com/p/android/issues/detail?id=13178 470 */ 471 public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException { 472 TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(), 473 TestKeyStore.getServer()); 474 475 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 476 server.enqueue(new MockResponse()); // unused 477 server.play(); 478 479 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 480 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 481 try { 482 connection.getInputStream(); 483 fail(); 484 } catch (SSLHandshakeException expected) { 485 assertTrue(expected.getCause() instanceof CertificateException); 486 } 487 assertEquals(0, server.getRequestCount()); 488 } 489 490 public void testConnectViaProxyUsingProxyArg() throws Exception { 491 testConnectViaProxy(ProxyConfig.CREATE_ARG); 492 } 493 494 public void testConnectViaProxyUsingProxySystemProperty() throws Exception { 495 testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); 496 } 497 498 public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception { 499 testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 500 } 501 502 private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception { 503 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 504 server.enqueue(mockResponse); 505 server.play(); 506 507 URL url = new URL("http://android.com/foo"); 508 HttpURLConnection connection = proxyConfig.connect(server, url); 509 assertContent("this response comes via a proxy", connection); 510 511 RecordedRequest request = server.takeRequest(); 512 assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); 513 assertContains(request.getHeaders(), "Host: android.com"); 514 } 515 516 public void testContentDisagreesWithContentLengthHeader() throws IOException { 517 server.enqueue(new MockResponse() 518 .setBody("abc\r\nYOU SHOULD NOT SEE THIS") 519 .clearHeaders() 520 .addHeader("Content-Length: 3")); 521 server.play(); 522 523 assertContent("abc", server.getUrl("/").openConnection()); 524 } 525 526 public void testContentDisagreesWithChunkedHeader() throws IOException { 527 MockResponse mockResponse = new MockResponse(); 528 mockResponse.setChunkedBody("abc", 3); 529 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 530 bytesOut.write(mockResponse.getBody()); 531 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes()); 532 mockResponse.setBody(bytesOut.toByteArray()); 533 mockResponse.clearHeaders(); 534 mockResponse.addHeader("Transfer-encoding: chunked"); 535 536 server.enqueue(mockResponse); 537 server.play(); 538 539 assertContent("abc", server.getUrl("/").openConnection()); 540 } 541 542 public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { 543 testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); 544 } 545 546 public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { 547 // https should not use http proxy 548 testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 549 } 550 551 private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { 552 TestSSLContext testSSLContext = TestSSLContext.create(); 553 554 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 555 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 556 server.play(); 557 558 URL url = server.getUrl("/foo"); 559 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 560 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 561 562 assertContent("this response comes via HTTPS", connection); 563 564 RecordedRequest request = server.takeRequest(); 565 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 566 } 567 568 569 public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception { 570 testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); 571 } 572 573 public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { 574 testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); 575 } 576 577 /** 578 * We were verifying the wrong hostname when connecting to an HTTPS site 579 * through a proxy. http://b/3097277 580 */ 581 private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { 582 TestSSLContext testSSLContext = TestSSLContext.create(); 583 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 584 585 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 586 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 587 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 588 server.play(); 589 590 URL url = new URL("https://android.com/foo"); 591 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 592 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 593 connection.setHostnameVerifier(hostnameVerifier); 594 595 assertContent("this response comes via a secure proxy", connection); 596 597 RecordedRequest connect = server.takeRequest(); 598 assertEquals("Connect line failure on proxy", 599 "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 600 assertContains(connect.getHeaders(), "Host: android.com"); 601 602 RecordedRequest get = server.takeRequest(); 603 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 604 assertContains(get.getHeaders(), "Host: android.com"); 605 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 606 } 607 608 /** 609 * Test which headers are sent unencrypted to the HTTP proxy. 610 */ 611 public void testProxyConnectIncludesProxyHeadersOnly() 612 throws IOException, InterruptedException { 613 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 614 TestSSLContext testSSLContext = TestSSLContext.create(); 615 616 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 617 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 618 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 619 server.play(); 620 621 URL url = new URL("https://android.com/foo"); 622 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 623 server.toProxyAddress()); 624 connection.addRequestProperty("Private", "Secret"); 625 connection.addRequestProperty("Proxy-Authorization", "bar"); 626 connection.addRequestProperty("User-Agent", "baz"); 627 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 628 connection.setHostnameVerifier(hostnameVerifier); 629 assertContent("encrypted response from the origin server", connection); 630 631 RecordedRequest connect = server.takeRequest(); 632 assertContainsNoneMatching(connect.getHeaders(), "Private.*"); 633 assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); 634 assertContains(connect.getHeaders(), "User-Agent: baz"); 635 assertContains(connect.getHeaders(), "Host: android.com"); 636 assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); 637 638 RecordedRequest get = server.takeRequest(); 639 assertContains(get.getHeaders(), "Private: Secret"); 640 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 641 } 642 643 public void testDisconnectedConnection() throws IOException { 644 server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); 645 server.play(); 646 647 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 648 InputStream in = connection.getInputStream(); 649 assertEquals('A', (char) in.read()); 650 connection.disconnect(); 651 try { 652 in.read(); 653 fail("Expected a connection closed exception"); 654 } catch (IOException expected) { 655 } 656 } 657 658 public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { 659 testResponseCaching(TransferKind.FIXED_LENGTH); 660 } 661 662 public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { 663 testResponseCaching(TransferKind.CHUNKED); 664 } 665 666 public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { 667 testResponseCaching(TransferKind.END_OF_STREAM); 668 } 669 670 /** 671 * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption 672 * http://code.google.com/p/android/issues/detail?id=8175 673 */ 674 private void testResponseCaching(TransferKind transferKind) throws IOException { 675 MockResponse response = new MockResponse(); 676 transferKind.setBody(response, "I love puppies but hate spiders", 1); 677 server.enqueue(response); 678 server.play(); 679 680 DefaultResponseCache cache = new DefaultResponseCache(); 681 ResponseCache.setDefault(cache); 682 683 // Make sure that calling skip() doesn't omit bytes from the cache. 684 URLConnection urlConnection = server.getUrl("/").openConnection(); 685 InputStream in = urlConnection.getInputStream(); 686 assertEquals("I love ", readAscii(in, "I love ".length())); 687 reliableSkip(in, "puppies but hate ".length()); 688 assertEquals("spiders", readAscii(in, "spiders".length())); 689 assertEquals(-1, in.read()); 690 in.close(); 691 assertEquals(1, cache.getSuccessCount()); 692 assertEquals(0, cache.getAbortCount()); 693 694 urlConnection = server.getUrl("/").openConnection(); // this response is cached! 695 in = urlConnection.getInputStream(); 696 assertEquals("I love puppies but hate spiders", 697 readAscii(in, "I love puppies but hate spiders".length())); 698 assertEquals(-1, in.read()); 699 assertEquals(1, cache.getMissCount()); 700 assertEquals(1, cache.getHitCount()); 701 assertEquals(1, cache.getSuccessCount()); 702 assertEquals(0, cache.getAbortCount()); 703 } 704 705 public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException { 706 server.enqueue(new MockResponse().setBody("ABC")); 707 server.play(); 708 709 final AtomicReference<Map<String, List<String>>> requestHeadersRef 710 = new AtomicReference<Map<String, List<String>>>(); 711 ResponseCache.setDefault(new ResponseCache() { 712 @Override public CacheResponse get(URI uri, String requestMethod, 713 Map<String, List<String>> requestHeaders) throws IOException { 714 requestHeadersRef.set(requestHeaders); 715 return null; 716 } 717 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 718 return null; 719 } 720 }); 721 722 URL url = server.getUrl("/"); 723 URLConnection urlConnection = url.openConnection(); 724 urlConnection.addRequestProperty("A", "android"); 725 readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE); 726 assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A")); 727 } 728 729 private void reliableSkip(InputStream in, int length) throws IOException { 730 while (length > 0) { 731 length -= in.skip(length); 732 } 733 } 734 735 /** 736 * Reads {@code count} characters from the stream. If the stream is 737 * exhausted before {@code count} characters can be read, the remaining 738 * characters are returned and the stream is closed. 739 */ 740 private String readAscii(InputStream in, int count) throws IOException { 741 StringBuilder result = new StringBuilder(); 742 for (int i = 0; i < count; i++) { 743 int value = in.read(); 744 if (value == -1) { 745 in.close(); 746 break; 747 } 748 result.append((char) value); 749 } 750 return result.toString(); 751 } 752 753 public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException { 754 testServerPrematureDisconnect(TransferKind.FIXED_LENGTH); 755 } 756 757 public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException { 758 testServerPrematureDisconnect(TransferKind.CHUNKED); 759 } 760 761 public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { 762 /* 763 * Intentionally empty. This case doesn't make sense because there's no 764 * such thing as a premature disconnect when the disconnect itself 765 * indicates the end of the data stream. 766 */ 767 } 768 769 private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException { 770 MockResponse response = new MockResponse(); 771 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16); 772 server.enqueue(truncateViolently(response, 16)); 773 server.enqueue(new MockResponse().setBody("Request #2")); 774 server.play(); 775 776 DefaultResponseCache cache = new DefaultResponseCache(); 777 ResponseCache.setDefault(cache); 778 779 BufferedReader reader = new BufferedReader(new InputStreamReader( 780 server.getUrl("/").openConnection().getInputStream())); 781 assertEquals("ABCDE", reader.readLine()); 782 try { 783 reader.readLine(); 784 fail("This implementation silently ignored a truncated HTTP body."); 785 } catch (IOException expected) { 786 } 787 788 assertEquals(1, cache.getAbortCount()); 789 assertEquals(0, cache.getSuccessCount()); 790 assertContent("Request #2", server.getUrl("/").openConnection()); 791 assertEquals(1, cache.getAbortCount()); 792 assertEquals(1, cache.getSuccessCount()); 793 } 794 795 public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { 796 testClientPrematureDisconnect(TransferKind.FIXED_LENGTH); 797 } 798 799 public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException { 800 testClientPrematureDisconnect(TransferKind.CHUNKED); 801 } 802 803 public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException { 804 testClientPrematureDisconnect(TransferKind.END_OF_STREAM); 805 } 806 807 private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException { 808 MockResponse response = new MockResponse(); 809 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024); 810 server.enqueue(response); 811 server.enqueue(new MockResponse().setBody("Request #2")); 812 server.play(); 813 814 DefaultResponseCache cache = new DefaultResponseCache(); 815 ResponseCache.setDefault(cache); 816 817 InputStream in = server.getUrl("/").openConnection().getInputStream(); 818 assertEquals("ABCDE", readAscii(in, 5)); 819 in.close(); 820 try { 821 in.read(); 822 fail("Expected an IOException because the stream is closed."); 823 } catch (IOException expected) { 824 } 825 826 assertEquals(1, cache.getAbortCount()); 827 assertEquals(0, cache.getSuccessCount()); 828 assertContent("Request #2", server.getUrl("/").openConnection()); 829 assertEquals(1, cache.getAbortCount()); 830 assertEquals(1, cache.getSuccessCount()); 831 } 832 833 /** 834 * Shortens the body of {@code response} but not the corresponding headers. 835 * Only useful to test how clients respond to the premature conclusion of 836 * the HTTP body. 837 */ 838 private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) { 839 response.setDisconnectAtEnd(true); 840 List<String> headers = new ArrayList<String>(response.getHeaders()); 841 response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep)); 842 response.getHeaders().clear(); 843 response.getHeaders().addAll(headers); 844 return response; 845 } 846 847 public void testMarkAndResetWithContentLengthHeader() throws IOException { 848 testMarkAndReset(TransferKind.FIXED_LENGTH); 849 } 850 851 public void testMarkAndResetWithChunkedEncoding() throws IOException { 852 testMarkAndReset(TransferKind.CHUNKED); 853 } 854 855 public void testMarkAndResetWithNoLengthHeaders() throws IOException { 856 testMarkAndReset(TransferKind.END_OF_STREAM); 857 } 858 859 public void testMarkAndReset(TransferKind transferKind) throws IOException { 860 MockResponse response = new MockResponse(); 861 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 862 server.enqueue(response); 863 server.play(); 864 865 DefaultResponseCache cache = new DefaultResponseCache(); 866 ResponseCache.setDefault(cache); 867 868 InputStream in = server.getUrl("/").openConnection().getInputStream(); 869 assertFalse("This implementation claims to support mark().", in.markSupported()); 870 in.mark(5); 871 assertEquals("ABCDE", readAscii(in, 5)); 872 try { 873 in.reset(); 874 fail(); 875 } catch (IOException expected) { 876 } 877 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 878 879 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection()); 880 assertEquals(1, cache.getSuccessCount()); 881 assertEquals(1, cache.getHitCount()); 882 } 883 884 /** 885 * We've had a bug where we forget the HTTP response when we see response 886 * code 401. This causes a new HTTP request to be issued for every call into 887 * the URLConnection. 888 */ 889 public void testUnauthorizedResponseHandling() throws IOException { 890 MockResponse response = new MockResponse() 891 .addHeader("WWW-Authenticate: challenge") 892 .setResponseCode(401) // UNAUTHORIZED 893 .setBody("Unauthorized"); 894 server.enqueue(response); 895 server.enqueue(response); 896 server.enqueue(response); 897 server.play(); 898 899 URL url = server.getUrl("/"); 900 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 901 902 assertEquals(401, conn.getResponseCode()); 903 assertEquals(401, conn.getResponseCode()); 904 assertEquals(401, conn.getResponseCode()); 905 assertEquals(1, server.getRequestCount()); 906 } 907 908 public void testNonHexChunkSize() throws IOException { 909 server.enqueue(new MockResponse() 910 .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 911 .clearHeaders() 912 .addHeader("Transfer-encoding: chunked")); 913 server.play(); 914 915 URLConnection connection = server.getUrl("/").openConnection(); 916 try { 917 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 918 fail(); 919 } catch (IOException e) { 920 } 921 } 922 923 public void testMissingChunkBody() throws IOException { 924 server.enqueue(new MockResponse() 925 .setBody("5") 926 .clearHeaders() 927 .addHeader("Transfer-encoding: chunked") 928 .setDisconnectAtEnd(true)); 929 server.play(); 930 931 URLConnection connection = server.getUrl("/").openConnection(); 932 try { 933 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 934 fail(); 935 } catch (IOException e) { 936 } 937 } 938 939 /** 940 * This test checks whether connections are gzipped by default. This 941 * behavior in not required by the API, so a failure of this test does not 942 * imply a bug in the implementation. 943 */ 944 public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { 945 server.enqueue(new MockResponse() 946 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 947 .addHeader("Content-Encoding: gzip")); 948 server.play(); 949 950 URLConnection connection = server.getUrl("/").openConnection(); 951 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 952 assertNull(connection.getContentEncoding()); 953 954 RecordedRequest request = server.takeRequest(); 955 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 956 } 957 958 public void testClientConfiguredGzipContentEncoding() throws Exception { 959 server.enqueue(new MockResponse() 960 .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"))) 961 .addHeader("Content-Encoding: gzip")); 962 server.play(); 963 964 URLConnection connection = server.getUrl("/").openConnection(); 965 connection.addRequestProperty("Accept-Encoding", "gzip"); 966 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 967 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 968 969 RecordedRequest request = server.takeRequest(); 970 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 971 } 972 973 public void testGzipAndConnectionReuseWithFixedLength() throws Exception { 974 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); 975 } 976 977 public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { 978 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); 979 } 980 981 public void testClientConfiguredCustomContentEncoding() throws Exception { 982 server.enqueue(new MockResponse() 983 .setBody("ABCDE") 984 .addHeader("Content-Encoding: custom")); 985 server.play(); 986 987 URLConnection connection = server.getUrl("/").openConnection(); 988 connection.addRequestProperty("Accept-Encoding", "custom"); 989 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 990 991 RecordedRequest request = server.takeRequest(); 992 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 993 } 994 995 /** 996 * Test a bug where gzip input streams weren't exhausting the input stream, 997 * which corrupted the request that followed. 998 * http://code.google.com/p/android/issues/detail?id=7059 999 */ 1000 private void testClientConfiguredGzipContentEncodingAndConnectionReuse( 1001 TransferKind transferKind) throws Exception { 1002 MockResponse responseOne = new MockResponse(); 1003 responseOne.addHeader("Content-Encoding: gzip"); 1004 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 1005 server.enqueue(responseOne); 1006 MockResponse responseTwo = new MockResponse(); 1007 transferKind.setBody(responseTwo, "two (identity)", 5); 1008 server.enqueue(responseTwo); 1009 server.play(); 1010 1011 URLConnection connection = server.getUrl("/").openConnection(); 1012 connection.addRequestProperty("Accept-Encoding", "gzip"); 1013 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1014 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1015 assertEquals(0, server.takeRequest().getSequenceNumber()); 1016 1017 connection = server.getUrl("/").openConnection(); 1018 assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1019 assertEquals(1, server.takeRequest().getSequenceNumber()); 1020 } 1021 1022 /** 1023 * Obnoxiously test that the chunk sizes transmitted exactly equal the 1024 * requested data+chunk header size. Although setChunkedStreamingMode() 1025 * isn't specific about whether the size applies to the data or the 1026 * complete chunk, the RI interprets it as a complete chunk. 1027 */ 1028 public void testSetChunkedStreamingMode() throws IOException, InterruptedException { 1029 server.enqueue(new MockResponse()); 1030 server.play(); 1031 1032 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1033 urlConnection.setChunkedStreamingMode(8); 1034 urlConnection.setDoOutput(true); 1035 OutputStream outputStream = urlConnection.getOutputStream(); 1036 outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII")); 1037 assertEquals(200, urlConnection.getResponseCode()); 1038 1039 RecordedRequest request = server.takeRequest(); 1040 assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII")); 1041 assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes()); 1042 } 1043 1044 public void testAuthenticateWithFixedLengthStreaming() throws Exception { 1045 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 1046 } 1047 1048 public void testAuthenticateWithChunkedStreaming() throws Exception { 1049 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 1050 } 1051 1052 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 1053 MockResponse pleaseAuthenticate = new MockResponse() 1054 .setResponseCode(401) 1055 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1056 .setBody("Please authenticate."); 1057 server.enqueue(pleaseAuthenticate); 1058 server.play(); 1059 1060 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1061 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1062 connection.setDoOutput(true); 1063 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1064 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1065 connection.setFixedLengthStreamingMode(requestBody.length); 1066 } else if (streamingMode == StreamingMode.CHUNKED) { 1067 connection.setChunkedStreamingMode(0); 1068 } 1069 OutputStream outputStream = connection.getOutputStream(); 1070 outputStream.write(requestBody); 1071 outputStream.close(); 1072 try { 1073 connection.getInputStream(); 1074 fail(); 1075 } catch (HttpRetryException expected) { 1076 } 1077 1078 // no authorization header for the request... 1079 RecordedRequest request = server.takeRequest(); 1080 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1081 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1082 } 1083 1084 enum StreamingMode { 1085 FIXED_LENGTH, CHUNKED 1086 } 1087 1088 public void testAuthenticateWithPost() throws Exception { 1089 MockResponse pleaseAuthenticate = new MockResponse() 1090 .setResponseCode(401) 1091 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1092 .setBody("Please authenticate."); 1093 // fail auth three times... 1094 server.enqueue(pleaseAuthenticate); 1095 server.enqueue(pleaseAuthenticate); 1096 server.enqueue(pleaseAuthenticate); 1097 // ...then succeed the fourth time 1098 server.enqueue(new MockResponse().setBody("Successful auth!")); 1099 server.play(); 1100 1101 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1102 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1103 connection.setDoOutput(true); 1104 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1105 OutputStream outputStream = connection.getOutputStream(); 1106 outputStream.write(requestBody); 1107 outputStream.close(); 1108 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1109 1110 // no authorization header for the first request... 1111 RecordedRequest request = server.takeRequest(); 1112 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1113 1114 // ...but the three requests that follow include an authorization header 1115 for (int i = 0; i < 3; i++) { 1116 request = server.takeRequest(); 1117 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1118 assertContains(request.getHeaders(), "Authorization: Basic " 1119 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1120 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1121 } 1122 } 1123 1124 public void testAuthenticateWithGet() throws Exception { 1125 MockResponse pleaseAuthenticate = new MockResponse() 1126 .setResponseCode(401) 1127 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1128 .setBody("Please authenticate."); 1129 // fail auth three times... 1130 server.enqueue(pleaseAuthenticate); 1131 server.enqueue(pleaseAuthenticate); 1132 server.enqueue(pleaseAuthenticate); 1133 // ...then succeed the fourth time 1134 server.enqueue(new MockResponse().setBody("Successful auth!")); 1135 server.play(); 1136 1137 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1138 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1139 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1140 1141 // no authorization header for the first request... 1142 RecordedRequest request = server.takeRequest(); 1143 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1144 1145 // ...but the three requests that follow requests include an authorization header 1146 for (int i = 0; i < 3; i++) { 1147 request = server.takeRequest(); 1148 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1149 assertContains(request.getHeaders(), "Authorization: Basic " 1150 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1151 } 1152 } 1153 1154 public void testRedirectedWithChunkedEncoding() throws Exception { 1155 testRedirected(TransferKind.CHUNKED, true); 1156 } 1157 1158 public void testRedirectedWithContentLengthHeader() throws Exception { 1159 testRedirected(TransferKind.FIXED_LENGTH, true); 1160 } 1161 1162 public void testRedirectedWithNoLengthHeaders() throws Exception { 1163 testRedirected(TransferKind.END_OF_STREAM, false); 1164 } 1165 1166 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1167 MockResponse response = new MockResponse() 1168 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1169 .addHeader("Location: /foo"); 1170 transferKind.setBody(response, "This page has moved!", 10); 1171 server.enqueue(response); 1172 server.enqueue(new MockResponse().setBody("This is the new location!")); 1173 server.play(); 1174 1175 URLConnection connection = server.getUrl("/").openConnection(); 1176 assertEquals("This is the new location!", 1177 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1178 1179 RecordedRequest first = server.takeRequest(); 1180 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1181 RecordedRequest retry = server.takeRequest(); 1182 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1183 if (reuse) { 1184 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1185 } 1186 } 1187 1188 public void testRedirectedOnHttps() throws IOException, InterruptedException { 1189 TestSSLContext testSSLContext = TestSSLContext.create(); 1190 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1191 server.enqueue(new MockResponse() 1192 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1193 .addHeader("Location: /foo") 1194 .setBody("This page has moved!")); 1195 server.enqueue(new MockResponse().setBody("This is the new location!")); 1196 server.play(); 1197 1198 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1199 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1200 assertEquals("This is the new location!", 1201 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1202 1203 RecordedRequest first = server.takeRequest(); 1204 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1205 RecordedRequest retry = server.takeRequest(); 1206 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1207 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1208 } 1209 1210 public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1211 TestSSLContext testSSLContext = TestSSLContext.create(); 1212 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1213 server.enqueue(new MockResponse() 1214 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1215 .addHeader("Location: http://anyhost/foo") 1216 .setBody("This page has moved!")); 1217 server.play(); 1218 1219 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1220 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1221 assertEquals("This page has moved!", 1222 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1223 } 1224 1225 public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1226 server.enqueue(new MockResponse() 1227 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1228 .addHeader("Location: https://anyhost/foo") 1229 .setBody("This page has moved!")); 1230 server.play(); 1231 1232 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1233 assertEquals("This page has moved!", 1234 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1235 } 1236 1237 public void testRedirectToAnotherOriginServer() throws Exception { 1238 MockWebServer server2 = new MockWebServer(); 1239 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1240 server2.play(); 1241 1242 server.enqueue(new MockResponse() 1243 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1244 .addHeader("Location: " + server2.getUrl("/").toString()) 1245 .setBody("This page has moved!")); 1246 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1247 server.play(); 1248 1249 URLConnection connection = server.getUrl("/").openConnection(); 1250 assertEquals("This is the 2nd server!", 1251 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1252 assertEquals(server2.getUrl("/"), connection.getURL()); 1253 1254 // make sure the first server was careful to recycle the connection 1255 assertEquals("This is the first server again!", 1256 readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); 1257 1258 RecordedRequest first = server.takeRequest(); 1259 assertContains(first.getHeaders(), "Host: " + hostname + ":" + server.getPort()); 1260 RecordedRequest second = server2.takeRequest(); 1261 assertContains(second.getHeaders(), "Host: " + hostname + ":" + server2.getPort()); 1262 RecordedRequest third = server.takeRequest(); 1263 assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); 1264 1265 server2.shutdown(); 1266 } 1267 1268 public void testHttpsWithCustomTrustManager() throws Exception { 1269 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1270 RecordingTrustManager trustManager = new RecordingTrustManager(); 1271 SSLContext sc = SSLContext.getInstance("TLS"); 1272 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 1273 1274 HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 1275 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 1276 SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 1277 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 1278 try { 1279 TestSSLContext testSSLContext = TestSSLContext.create(); 1280 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1281 server.enqueue(new MockResponse().setBody("ABC")); 1282 server.enqueue(new MockResponse().setBody("DEF")); 1283 server.enqueue(new MockResponse().setBody("GHI")); 1284 server.play(); 1285 1286 URL url = server.getUrl("/"); 1287 assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); 1288 assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); 1289 assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); 1290 1291 assertEquals(Arrays.asList("verify " + hostname), hostnameVerifier.calls); 1292 assertEquals(Arrays.asList("checkServerTrusted [" 1293 + "CN=" + hostname + " 1, " 1294 + "CN=Test Intermediate Certificate Authority 1, " 1295 + "CN=Test Root Certificate Authority 1" 1296 + "] RSA"), 1297 trustManager.calls); 1298 } finally { 1299 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 1300 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 1301 } 1302 } 1303 1304 public void testConnectTimeouts() throws IOException { 1305 // Set a backlog and use it up so that we can expect the 1306 // URLConnection to properly timeout. According to Steven's 1307 // 4.5 "listen function", linux adds 3 to the specified 1308 // backlog, so we need to connect 4 times before it will hang. 1309 ServerSocket serverSocket = new ServerSocket(0, 1); 1310 int serverPort = serverSocket.getLocalPort(); 1311 Socket[] sockets = new Socket[4]; 1312 for (int i = 0; i < sockets.length; i++) { 1313 sockets[i] = new Socket("localhost", serverPort); 1314 } 1315 1316 URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection(); 1317 urlConnection.setConnectTimeout(1000); 1318 try { 1319 urlConnection.getInputStream(); 1320 fail(); 1321 } catch (SocketTimeoutException expected) { 1322 } 1323 1324 for (Socket s : sockets) { 1325 s.close(); 1326 } 1327 } 1328 1329 public void testReadTimeouts() throws IOException { 1330 /* 1331 * This relies on the fact that MockWebServer doesn't close the 1332 * connection after a response has been sent. This causes the client to 1333 * try to read more bytes than are sent, which results in a timeout. 1334 */ 1335 MockResponse timeout = new MockResponse() 1336 .setBody("ABC") 1337 .clearHeaders() 1338 .addHeader("Content-Length: 4"); 1339 server.enqueue(timeout); 1340 server.play(); 1341 1342 URLConnection urlConnection = server.getUrl("/").openConnection(); 1343 urlConnection.setReadTimeout(1000); 1344 InputStream in = urlConnection.getInputStream(); 1345 assertEquals('A', in.read()); 1346 assertEquals('B', in.read()); 1347 assertEquals('C', in.read()); 1348 try { 1349 in.read(); // if Content-Length was accurate, this would return -1 immediately 1350 fail(); 1351 } catch (SocketTimeoutException expected) { 1352 } 1353 } 1354 1355 public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 1356 server.enqueue(new MockResponse()); 1357 server.play(); 1358 1359 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1360 urlConnection.setRequestProperty("Transfer-encoding", "chunked"); 1361 urlConnection.setDoOutput(true); 1362 urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); 1363 assertEquals(200, urlConnection.getResponseCode()); 1364 1365 RecordedRequest request = server.takeRequest(); 1366 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 1367 } 1368 1369 public void testConnectionCloseInRequest() throws IOException, InterruptedException { 1370 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 1371 server.enqueue(new MockResponse()); 1372 server.play(); 1373 1374 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1375 a.setRequestProperty("Connection", "close"); 1376 assertEquals(200, a.getResponseCode()); 1377 1378 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1379 assertEquals(200, b.getResponseCode()); 1380 1381 assertEquals(0, server.takeRequest().getSequenceNumber()); 1382 assertEquals("When connection: close is used, each request should get its own connection", 1383 0, server.takeRequest().getSequenceNumber()); 1384 } 1385 1386 public void testConnectionCloseInResponse() throws IOException, InterruptedException { 1387 server.enqueue(new MockResponse().addHeader("Connection: close")); 1388 server.enqueue(new MockResponse()); 1389 server.play(); 1390 1391 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1392 assertEquals(200, a.getResponseCode()); 1393 1394 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1395 assertEquals(200, b.getResponseCode()); 1396 1397 assertEquals(0, server.takeRequest().getSequenceNumber()); 1398 assertEquals("When connection: close is used, each request should get its own connection", 1399 0, server.takeRequest().getSequenceNumber()); 1400 } 1401 1402 public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { 1403 MockResponse response = new MockResponse() 1404 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1405 .addHeader("Location: /foo") 1406 .addHeader("Connection: close"); 1407 server.enqueue(response); 1408 server.enqueue(new MockResponse().setBody("This is the new location!")); 1409 server.play(); 1410 1411 URLConnection connection = server.getUrl("/").openConnection(); 1412 assertEquals("This is the new location!", 1413 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1414 1415 assertEquals(0, server.takeRequest().getSequenceNumber()); 1416 assertEquals("When connection: close is used, each request should get its own connection", 1417 0, server.takeRequest().getSequenceNumber()); 1418 } 1419 1420 public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 1421 server.enqueue(new MockResponse() 1422 .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 1423 .setBody("This body is not allowed!")); 1424 server.play(); 1425 1426 URLConnection connection = server.getUrl("/").openConnection(); 1427 assertEquals("This body is not allowed!", 1428 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1429 } 1430 1431 public void testSingleByteReadIsSigned() throws IOException { 1432 server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); 1433 server.play(); 1434 1435 URLConnection connection = server.getUrl("/").openConnection(); 1436 InputStream in = connection.getInputStream(); 1437 assertEquals(254, in.read()); 1438 assertEquals(255, in.read()); 1439 assertEquals(-1, in.read()); 1440 } 1441 1442 public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException { 1443 testFlushAfterStreamTransmitted(TransferKind.CHUNKED); 1444 } 1445 1446 public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException { 1447 testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); 1448 } 1449 1450 public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { 1451 testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); 1452 } 1453 1454 /** 1455 * We explicitly permit apps to close the upload stream even after it has 1456 * been transmitted. We also permit flush so that buffered streams can 1457 * do a no-op flush when they are closed. http://b/3038470 1458 */ 1459 private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { 1460 server.enqueue(new MockResponse().setBody("abc")); 1461 server.play(); 1462 1463 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1464 connection.setDoOutput(true); 1465 byte[] upload = "def".getBytes("UTF-8"); 1466 1467 if (transferKind == TransferKind.CHUNKED) { 1468 connection.setChunkedStreamingMode(0); 1469 } else if (transferKind == TransferKind.FIXED_LENGTH) { 1470 connection.setFixedLengthStreamingMode(upload.length); 1471 } 1472 1473 OutputStream out = connection.getOutputStream(); 1474 out.write(upload); 1475 assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1476 1477 out.flush(); // dubious but permitted 1478 try { 1479 out.write("ghi".getBytes("UTF-8")); 1480 fail(); 1481 } catch (IOException expected) { 1482 } 1483 } 1484 1485 public void testGetHeadersThrows() throws IOException { 1486 server.enqueue(new MockResponse().setDisconnectAtStart(true)); 1487 server.play(); 1488 1489 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1490 try { 1491 connection.getInputStream(); 1492 fail(); 1493 } catch (IOException expected) { 1494 } 1495 1496 try { 1497 connection.getInputStream(); 1498 fail(); 1499 } catch (IOException expected) { 1500 } 1501 } 1502 1503 /** 1504 * http://code.google.com/p/android/issues/detail?id=14562 1505 */ 1506 public void testReadAfterLastByte() throws Exception { 1507 server.enqueue(new MockResponse() 1508 .setBody("ABC") 1509 .clearHeaders() 1510 .addHeader("Connection: close") 1511 .setDisconnectAtEnd(true)); 1512 server.play(); 1513 1514 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1515 InputStream in = connection.getInputStream(); 1516 assertEquals("ABC", readAscii(in, 3)); 1517 assertEquals(-1, in.read()); 1518 assertEquals(-1, in.read()); // throws IOException in Gingerbread 1519 } 1520 1521 /** 1522 * Encodes the response body using GZIP and adds the corresponding header. 1523 */ 1524 public byte[] gzip(byte[] bytes) throws IOException { 1525 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1526 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 1527 gzippedOut.write(bytes); 1528 gzippedOut.close(); 1529 return bytesOut.toByteArray(); 1530 } 1531 1532 /** 1533 * Reads at most {@code limit} characters from {@code in} and asserts that 1534 * content equals {@code expected}. 1535 */ 1536 private void assertContent(String expected, URLConnection connection, int limit) 1537 throws IOException { 1538 connection.connect(); 1539 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 1540 ((HttpURLConnection) connection).disconnect(); 1541 } 1542 1543 private void assertContent(String expected, URLConnection connection) throws IOException { 1544 assertContent(expected, connection, Integer.MAX_VALUE); 1545 } 1546 1547 private void assertContains(List<String> headers, String header) { 1548 assertTrue(headers.toString(), headers.contains(header)); 1549 } 1550 1551 private void assertContainsNoneMatching(List<String> headers, String pattern) { 1552 for (String header : headers) { 1553 if (header.matches(pattern)) { 1554 fail("Header " + header + " matches " + pattern); 1555 } 1556 } 1557 } 1558 1559 private Set<String> newSet(String... elements) { 1560 return new HashSet<String>(Arrays.asList(elements)); 1561 } 1562 1563 enum TransferKind { 1564 CHUNKED() { 1565 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 1566 throws IOException { 1567 response.setChunkedBody(content, chunkSize); 1568 } 1569 }, 1570 FIXED_LENGTH() { 1571 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1572 response.setBody(content); 1573 } 1574 }, 1575 END_OF_STREAM() { 1576 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1577 response.setBody(content); 1578 response.setDisconnectAtEnd(true); 1579 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 1580 if (h.next().startsWith("Content-Length:")) { 1581 h.remove(); 1582 break; 1583 } 1584 } 1585 } 1586 }; 1587 1588 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 1589 throws IOException; 1590 1591 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 1592 setBody(response, content.getBytes("UTF-8"), chunkSize); 1593 } 1594 } 1595 1596 enum ProxyConfig { 1597 NO_PROXY() { 1598 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1599 throws IOException { 1600 return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); 1601 } 1602 }, 1603 1604 CREATE_ARG() { 1605 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1606 throws IOException { 1607 return (HttpURLConnection) url.openConnection(server.toProxyAddress()); 1608 } 1609 }, 1610 1611 PROXY_SYSTEM_PROPERTY() { 1612 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1613 throws IOException { 1614 System.setProperty("proxyHost", "localhost"); 1615 System.setProperty("proxyPort", Integer.toString(server.getPort())); 1616 return (HttpURLConnection) url.openConnection(); 1617 } 1618 }, 1619 1620 HTTP_PROXY_SYSTEM_PROPERTY() { 1621 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1622 throws IOException { 1623 System.setProperty("http.proxyHost", "localhost"); 1624 System.setProperty("http.proxyPort", Integer.toString(server.getPort())); 1625 return (HttpURLConnection) url.openConnection(); 1626 } 1627 }, 1628 1629 HTTPS_PROXY_SYSTEM_PROPERTY() { 1630 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1631 throws IOException { 1632 System.setProperty("https.proxyHost", "localhost"); 1633 System.setProperty("https.proxyPort", Integer.toString(server.getPort())); 1634 return (HttpURLConnection) url.openConnection(); 1635 } 1636 }; 1637 1638 public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException; 1639 } 1640 1641 private static class RecordingTrustManager implements X509TrustManager { 1642 private final List<String> calls = new ArrayList<String>(); 1643 1644 public X509Certificate[] getAcceptedIssuers() { 1645 calls.add("getAcceptedIssuers"); 1646 return new X509Certificate[] {}; 1647 } 1648 1649 public void checkClientTrusted(X509Certificate[] chain, String authType) 1650 throws CertificateException { 1651 calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); 1652 } 1653 1654 public void checkServerTrusted(X509Certificate[] chain, String authType) 1655 throws CertificateException { 1656 calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); 1657 } 1658 1659 private String certificatesToString(X509Certificate[] certificates) { 1660 List<String> result = new ArrayList<String>(); 1661 for (X509Certificate certificate : certificates) { 1662 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 1663 } 1664 return result.toString(); 1665 } 1666 } 1667 1668 private static class RecordingHostnameVerifier implements HostnameVerifier { 1669 private final List<String> calls = new ArrayList<String>(); 1670 1671 public boolean verify(String hostname, SSLSession session) { 1672 calls.add("verify " + hostname); 1673 return true; 1674 } 1675 } 1676} 1677