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