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