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