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