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