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