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