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