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