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