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