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