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