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