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