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