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