1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package libcore.java.net; 18 19import com.google.mockwebserver.Dispatcher; 20import com.google.mockwebserver.MockResponse; 21import com.google.mockwebserver.MockWebServer; 22import com.google.mockwebserver.RecordedRequest; 23import com.google.mockwebserver.SocketPolicy; 24 25import com.android.okhttp.AndroidShimResponseCache; 26import com.android.okhttp.internal.Platform; 27import com.android.okhttp.internal.tls.TrustRootIndex; 28 29import junit.framework.TestCase; 30 31import java.io.ByteArrayOutputStream; 32import java.io.File; 33import java.io.FileDescriptor; 34import java.io.IOException; 35import java.io.InputStream; 36import java.io.OutputStream; 37import java.net.Authenticator; 38import java.net.CacheRequest; 39import java.net.CacheResponse; 40import java.net.CookieHandler; 41import java.net.CookieManager; 42import java.net.HttpRetryException; 43import java.net.HttpURLConnection; 44import java.net.InetAddress; 45import java.net.PasswordAuthentication; 46import java.net.ProtocolException; 47import java.net.Proxy; 48import java.net.ProxySelector; 49import java.net.ResponseCache; 50import java.net.Socket; 51import java.net.SocketAddress; 52import java.net.SocketException; 53import java.net.SocketTimeoutException; 54import java.net.URI; 55import java.net.URISyntaxException; 56import java.net.URL; 57import java.net.URLConnection; 58import java.net.UnknownHostException; 59import java.nio.channels.SocketChannel; 60import java.security.cert.CertificateException; 61import java.security.cert.X509Certificate; 62import java.util.ArrayList; 63import java.util.Arrays; 64import java.util.Collections; 65import java.util.HashSet; 66import java.util.Iterator; 67import java.util.List; 68import java.util.Map; 69import java.util.Set; 70import java.util.UUID; 71import java.util.concurrent.TimeUnit; 72import java.util.concurrent.atomic.AtomicBoolean; 73import java.util.concurrent.atomic.AtomicReference; 74import java.util.zip.GZIPInputStream; 75import java.util.zip.GZIPOutputStream; 76import javax.net.SocketFactory; 77import javax.net.ssl.HandshakeCompletedListener; 78import javax.net.ssl.HostnameVerifier; 79import javax.net.ssl.HttpsURLConnection; 80import javax.net.ssl.SSLContext; 81import javax.net.ssl.SSLException; 82import javax.net.ssl.SSLHandshakeException; 83import javax.net.ssl.SSLParameters; 84import javax.net.ssl.SSLSession; 85import javax.net.ssl.SSLSocket; 86import javax.net.ssl.SSLSocketFactory; 87import javax.net.ssl.TrustManager; 88import javax.net.ssl.X509TrustManager; 89import libcore.java.security.TestKeyStore; 90import libcore.javax.net.ssl.TestSSLContext; 91 92import tests.net.DelegatingSocketFactory; 93 94import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; 95import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; 96import static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; 97import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; 98import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; 99import static org.junit.Assert.assertEquals; 100import static org.junit.Assert.fail; 101 102public final class URLConnectionTest extends TestCase { 103 104 private MockWebServer server; 105 private AndroidShimResponseCache cache; 106 private String hostName; 107 private List<TestSSLContext> testSSLContextsToClose; 108 109 @Override protected void setUp() throws Exception { 110 super.setUp(); 111 server = new MockWebServer(); 112 hostName = server.getHostName(); 113 testSSLContextsToClose = new ArrayList<>(); 114 } 115 116 @Override protected void tearDown() throws Exception { 117 ResponseCache.setDefault(null); 118 Authenticator.setDefault(null); 119 System.clearProperty("proxyHost"); 120 System.clearProperty("proxyPort"); 121 System.clearProperty("http.proxyHost"); 122 System.clearProperty("http.proxyPort"); 123 System.clearProperty("https.proxyHost"); 124 System.clearProperty("https.proxyPort"); 125 server.shutdown(); 126 server = null; 127 if (cache != null) { 128 cache.delete(); 129 cache = null; 130 } 131 for (TestSSLContext testSSLContext : testSSLContextsToClose) { 132 testSSLContext.close(); 133 } 134 super.tearDown(); 135 } 136 137 public void testRequestHeaderValidation() throws Exception { 138 // Android became more strict after M about which characters were allowed in request header 139 // names and values: previously almost anything was allowed if it didn't contain \0. 140 141 assertForbiddenRequestHeaderName(null); 142 assertForbiddenRequestHeaderName(""); 143 assertForbiddenRequestHeaderName("\n"); 144 assertForbiddenRequestHeaderName("a\nb"); 145 assertForbiddenRequestHeaderName("\u0000"); 146 assertForbiddenRequestHeaderName("\r"); 147 assertForbiddenRequestHeaderName("\t"); 148 assertForbiddenRequestHeaderName("\u001f"); 149 assertForbiddenRequestHeaderName("\u007f"); 150 assertForbiddenRequestHeaderName("\u0080"); 151 assertForbiddenRequestHeaderName("\ud83c\udf69"); 152 153 assertEquals(null, setAndReturnRequestHeaderValue(null)); 154 assertEquals("", setAndReturnRequestHeaderValue("")); 155 assertForbiddenRequestHeaderValue("\u0000"); 156 157 // Workaround for http://b/26422335 , http://b/26889631 , http://b/27606665 : 158 // allow (but strip) trailing \n, \r and \r\n 159 // assertForbiddenRequestHeaderValue("\r"); 160 // End of workaround 161 162 // '\t' in header values can either be (a) forbidden or (b) allowed. 163 // The original version of Android N (API 23) implemented behavior 164 // (a), but OEMs can backport a fix that changes the behavior to (b). 165 // Therefore, this test has been relaxed for Android N CTS to allow 166 // either behavior. It is planned that future versions of Android only 167 // allow behavior (b). 168 try { 169 // throws IAE in case (a), passes in case (b) 170 assertEquals("a valid\tvalue", setAndReturnRequestHeaderValue("a valid\tvalue")); 171 } catch (IllegalArgumentException tolerated) { 172 // verify case (a) 173 assertForbiddenRequestHeaderValue("\t"); 174 } 175 assertForbiddenRequestHeaderValue("\u001f"); 176 assertForbiddenRequestHeaderValue("\u007f"); 177 178 // For http://b/28867041 the allowed character range was changed. 179 // assertForbiddenRequestHeaderValue("\u0080"); 180 // assertForbiddenRequestHeaderValue("\ud83c\udf69"); 181 assertEquals("\u0080", setAndReturnRequestHeaderValue("\u0080")); 182 assertEquals("\ud83c\udf69", setAndReturnRequestHeaderValue("\ud83c\udf69")); 183 184 // Workaround for http://b/26422335 , http://b/26889631 , http://b/27606665 : 185 // allow (but strip) trailing \n, \r and \r\n 186 assertEquals("", setAndReturnRequestHeaderValue("\n")); 187 assertEquals("a", setAndReturnRequestHeaderValue("a\n")); 188 assertEquals("", setAndReturnRequestHeaderValue("\r")); 189 assertEquals("a", setAndReturnRequestHeaderValue("a\r")); 190 assertEquals("", setAndReturnRequestHeaderValue("\r\n")); 191 assertEquals("a", setAndReturnRequestHeaderValue("a\r\n")); 192 assertForbiddenRequestHeaderValue("a\nb"); 193 assertForbiddenRequestHeaderValue("\nb"); 194 assertForbiddenRequestHeaderValue("a\rb"); 195 assertForbiddenRequestHeaderValue("\rb"); 196 assertForbiddenRequestHeaderValue("a\r\nb"); 197 assertForbiddenRequestHeaderValue("\r\nb"); 198 // End of workaround 199 } 200 201 private static void assertForbiddenRequestHeaderName(String name) throws Exception { 202 URL url = new URL("http://www.google.com/"); 203 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 204 try { 205 urlConnection.addRequestProperty(name, "value"); 206 fail("Expected exception"); 207 } catch (IllegalArgumentException expected) { 208 } catch (NullPointerException expectedIfNull) { 209 assertTrue(name == null); 210 } 211 } 212 213 private static void assertForbiddenRequestHeaderValue(String value) throws Exception { 214 URL url = new URL("http://www.google.com/"); 215 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 216 try { 217 urlConnection.addRequestProperty("key", value); 218 fail("Expected exception"); 219 } catch (IllegalArgumentException expected) { 220 } 221 } 222 223 private static String setAndReturnRequestHeaderValue(String value) throws Exception { 224 URL url = new URL("http://www.google.com/"); 225 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 226 urlConnection.addRequestProperty("key", value); 227 return urlConnection.getRequestProperty("key"); 228 } 229 230 public void testRequestHeaders() throws IOException, InterruptedException { 231 server.enqueue(new MockResponse()); 232 server.play(); 233 234 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 235 urlConnection.addRequestProperty("D", "e"); 236 urlConnection.addRequestProperty("D", "f"); 237 assertEquals("f", urlConnection.getRequestProperty("D")); 238 assertEquals("f", urlConnection.getRequestProperty("d")); 239 Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties(); 240 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); 241 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d"))); 242 try { 243 requestHeaders.put("G", Arrays.asList("h")); 244 fail("Modified an unmodifiable view."); 245 } catch (UnsupportedOperationException expected) { 246 } 247 try { 248 requestHeaders.get("D").add("i"); 249 fail("Modified an unmodifiable view."); 250 } catch (UnsupportedOperationException expected) { 251 } 252 try { 253 urlConnection.setRequestProperty(null, "j"); 254 fail(); 255 } catch (NullPointerException expected) { 256 } 257 try { 258 urlConnection.addRequestProperty(null, "k"); 259 fail(); 260 } catch (NullPointerException expected) { 261 } 262 urlConnection.setRequestProperty("NullValue", null); // should fail silently! 263 assertNull(urlConnection.getRequestProperty("NullValue")); 264 urlConnection.addRequestProperty("AnotherNullValue", null); // should fail silently! 265 assertNull(urlConnection.getRequestProperty("AnotherNullValue")); 266 267 urlConnection.getResponseCode(); 268 RecordedRequest request = server.takeRequest(); 269 assertContains(request.getHeaders(), "D: e"); 270 assertContains(request.getHeaders(), "D: f"); 271 assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); 272 assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); 273 assertContainsNoneMatching(request.getHeaders(), "G:.*"); 274 assertContainsNoneMatching(request.getHeaders(), "null:.*"); 275 276 try { 277 urlConnection.addRequestProperty("N", "o"); 278 fail("Set header after connect"); 279 } catch (IllegalStateException expected) { 280 } 281 try { 282 urlConnection.setRequestProperty("P", "q"); 283 fail("Set header after connect"); 284 } catch (IllegalStateException expected) { 285 } 286 try { 287 urlConnection.getRequestProperties(); 288 fail(); 289 } catch (IllegalStateException expected) { 290 } 291 } 292 293 public void testGetRequestPropertyReturnsLastValue() throws Exception { 294 server.play(); 295 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 296 urlConnection.addRequestProperty("A", "value1"); 297 urlConnection.addRequestProperty("A", "value2"); 298 assertEquals("value2", urlConnection.getRequestProperty("A")); 299 } 300 301 public void testResponseHeaders() throws IOException, InterruptedException { 302 server.enqueue(new MockResponse() 303 .setStatus("HTTP/1.0 200 Fantastic") 304 .addHeader("A: c") 305 .addHeader("B: d") 306 .addHeader("A: e") 307 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); 308 server.play(); 309 310 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 311 assertEquals(200, urlConnection.getResponseCode()); 312 assertEquals("Fantastic", urlConnection.getResponseMessage()); 313 assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null)); 314 Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields(); 315 assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null)); 316 assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A"))); 317 assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a"))); 318 try { 319 responseHeaders.put("N", Arrays.asList("o")); 320 fail("Modified an unmodifiable view."); 321 } catch (UnsupportedOperationException expected) { 322 } 323 try { 324 responseHeaders.get("A").add("f"); 325 fail("Modified an unmodifiable view."); 326 } catch (UnsupportedOperationException expected) { 327 } 328 assertEquals("A", urlConnection.getHeaderFieldKey(0)); 329 assertEquals("c", urlConnection.getHeaderField(0)); 330 assertEquals("B", urlConnection.getHeaderFieldKey(1)); 331 assertEquals("d", urlConnection.getHeaderField(1)); 332 assertEquals("A", urlConnection.getHeaderFieldKey(2)); 333 assertEquals("e", urlConnection.getHeaderField(2)); 334 } 335 336 public void testGetErrorStreamOnSuccessfulRequest() throws Exception { 337 server.enqueue(new MockResponse().setBody("A")); 338 server.play(); 339 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 340 assertNull(connection.getErrorStream()); 341 } 342 343 public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception { 344 server.enqueue(new MockResponse().setResponseCode(404).setBody("A")); 345 server.play(); 346 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 347 assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE)); 348 } 349 350 // Check that if we don't read to the end of a response, the next request on the 351 // recycled connection doesn't get the unread tail of the first request's response. 352 // http://code.google.com/p/android/issues/detail?id=2939 353 public void test_2939() throws Exception { 354 MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 355 356 server.enqueue(response); 357 server.enqueue(response); 358 server.play(); 359 360 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 361 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 362 } 363 364 // Check that we recognize a few basic mime types by extension. 365 // http://code.google.com/p/android/issues/detail?id=10100 366 public void test_10100() throws Exception { 367 assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); 368 assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); 369 } 370 371 public void testConnectionsArePooled() throws Exception { 372 MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 373 374 server.enqueue(response); 375 server.enqueue(response); 376 server.enqueue(response); 377 server.play(); 378 379 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); 380 assertEquals(0, server.takeRequest().getSequenceNumber()); 381 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); 382 assertEquals(1, server.takeRequest().getSequenceNumber()); 383 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); 384 assertEquals(2, server.takeRequest().getSequenceNumber()); 385 } 386 387 public void testChunkedConnectionsArePooled() throws Exception { 388 MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); 389 390 server.enqueue(response); 391 server.enqueue(response); 392 server.enqueue(response); 393 server.play(); 394 395 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); 396 assertEquals(0, server.takeRequest().getSequenceNumber()); 397 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); 398 assertEquals(1, server.takeRequest().getSequenceNumber()); 399 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); 400 assertEquals(2, server.takeRequest().getSequenceNumber()); 401 } 402 403 /** 404 * Test that connections are added to the pool as soon as the response has 405 * been consumed. 406 */ 407 public void testConnectionsArePooledWithoutExplicitDisconnect() throws Exception { 408 server.enqueue(new MockResponse().setBody("ABC")); 409 server.enqueue(new MockResponse().setBody("DEF")); 410 server.play(); 411 412 URLConnection connection1 = server.getUrl("/").openConnection(); 413 assertEquals("ABC", readAscii(connection1.getInputStream(), Integer.MAX_VALUE)); 414 assertEquals(0, server.takeRequest().getSequenceNumber()); 415 URLConnection connection2 = server.getUrl("/").openConnection(); 416 assertEquals("DEF", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); 417 assertEquals(1, server.takeRequest().getSequenceNumber()); 418 } 419 420 public void testServerClosesSocket() throws Exception { 421 testServerClosesSocket(DISCONNECT_AT_END); 422 } 423 424 public void testServerShutdownInput() throws Exception { 425 testServerClosesSocket(SHUTDOWN_INPUT_AT_END); 426 } 427 428 private void testServerClosesSocket(SocketPolicy socketPolicy) throws Exception { 429 server.enqueue(new MockResponse() 430 .setBody("This connection won't pool properly") 431 .setSocketPolicy(socketPolicy)); 432 server.enqueue(new MockResponse().setBody("This comes after a busted connection")); 433 server.play(); 434 435 assertContent("This connection won't pool properly", server.getUrl("/a").openConnection()); 436 assertEquals(0, server.takeRequest().getSequenceNumber()); 437 assertContent("This comes after a busted connection", server.getUrl("/b").openConnection()); 438 // sequence number 0 means the HTTP socket connection was not reused 439 assertEquals(0, server.takeRequest().getSequenceNumber()); 440 } 441 442 public void testServerShutdownOutput() throws Exception { 443 // This test causes MockWebServer to log a "connection failed" stack trace 444 445 // Setting the server workerThreads to 1 ensures the responses are generated in the order 446 // the requests are accepted by the server. Without this the second and third requests made 447 // by the client (the request for "/b" and the retry for "/b" when the bad socket is 448 // detected) can be handled by the server out of order leading to test failure. 449 server.setWorkerThreads(1); 450 server.enqueue(new MockResponse() 451 .setBody("Output shutdown after this response") 452 .setSocketPolicy(SHUTDOWN_OUTPUT_AT_END)); 453 server.enqueue(new MockResponse().setBody("This response will fail to write")); 454 server.enqueue(new MockResponse().setBody("This comes after a busted connection")); 455 server.play(); 456 457 assertContent("Output shutdown after this response", server.getUrl("/a").openConnection()); 458 assertEquals(0, server.takeRequest().getSequenceNumber()); 459 assertContent("This comes after a busted connection", server.getUrl("/b").openConnection()); 460 assertEquals(1, server.takeRequest().getSequenceNumber()); 461 assertEquals(0, server.takeRequest().getSequenceNumber()); 462 } 463 464 enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } 465 466 public void test_chunkedUpload_byteByByte() throws Exception { 467 doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 468 } 469 470 public void test_chunkedUpload_smallBuffers() throws Exception { 471 doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); 472 } 473 474 public void test_chunkedUpload_largeBuffers() throws Exception { 475 doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); 476 } 477 478 public void test_fixedLengthUpload_byteByByte() throws Exception { 479 doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 480 } 481 482 public void test_fixedLengthUpload_smallBuffers() throws Exception { 483 doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 484 } 485 486 public void test_fixedLengthUpload_largeBuffers() throws Exception { 487 doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 488 } 489 490 private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { 491 int n = 512*1024; 492 server.setBodyLimit(0); 493 server.enqueue(new MockResponse()); 494 server.play(); 495 496 HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); 497 conn.setDoOutput(true); 498 conn.setRequestMethod("POST"); 499 if (uploadKind == TransferKind.CHUNKED) { 500 conn.setChunkedStreamingMode(-1); 501 } else { 502 conn.setFixedLengthStreamingMode(n); 503 } 504 OutputStream out = conn.getOutputStream(); 505 if (writeKind == WriteKind.BYTE_BY_BYTE) { 506 for (int i = 0; i < n; ++i) { 507 out.write('x'); 508 } 509 } else { 510 byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024]; 511 Arrays.fill(buf, (byte) 'x'); 512 for (int i = 0; i < n; i += buf.length) { 513 out.write(buf, 0, Math.min(buf.length, n - i)); 514 } 515 } 516 out.close(); 517 assertEquals(200, conn.getResponseCode()); 518 RecordedRequest request = server.takeRequest(); 519 assertEquals(n, request.getBodySize()); 520 if (uploadKind == TransferKind.CHUNKED) { 521 assertTrue(request.getChunkSizes().size() > 0); 522 } else { 523 assertTrue(request.getChunkSizes().isEmpty()); 524 } 525 } 526 527 public void testGetResponseCodeNoResponseBody() throws Exception { 528 server.enqueue(new MockResponse() 529 .addHeader("abc: def")); 530 server.play(); 531 532 URL url = server.getUrl("/"); 533 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 534 conn.setDoInput(false); 535 assertEquals("def", conn.getHeaderField("abc")); 536 assertEquals(200, conn.getResponseCode()); 537 try { 538 conn.getInputStream(); 539 fail(); 540 } catch (ProtocolException expected) { 541 } 542 } 543 544 public void testConnectViaHttps() throws IOException, InterruptedException { 545 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 546 547 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 548 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 549 server.play(); 550 551 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 552 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 553 554 assertContent("this response comes via HTTPS", connection); 555 556 RecordedRequest request = server.takeRequest(); 557 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 558 assertEquals("TLSv1.2", request.getSslProtocol()); 559 } 560 561 public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException { 562 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 563 SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory(); 564 565 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 566 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 567 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 568 server.play(); 569 570 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 571 connection.setSSLSocketFactory(clientSocketFactory); 572 assertContent("this response comes via HTTPS", connection); 573 574 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 575 connection.setSSLSocketFactory(clientSocketFactory); 576 assertContent("another response via HTTPS", connection); 577 578 assertEquals(0, server.takeRequest().getSequenceNumber()); 579 assertEquals(1, server.takeRequest().getSequenceNumber()); 580 } 581 582 public void testConnectViaHttpsReusingConnectionsDifferentFactories() 583 throws IOException, InterruptedException { 584 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 585 586 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 587 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 588 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 589 server.play(); 590 591 // install a custom SSL socket factory so the server can be authorized 592 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 593 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 594 assertContent("this response comes via HTTPS", connection); 595 596 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 597 try { 598 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 599 fail("without an SSL socket factory, the connection should fail"); 600 } catch (SSLException expected) { 601 } 602 } 603 604 /** 605 * Verify that we don't retry connections on certificate verification errors. 606 * 607 * http://code.google.com/p/android/issues/detail?id=13178 608 */ 609 public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException { 610 TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(), 611 TestKeyStore.getServer()); 612 testSSLContextsToClose.add(testSSLContext); 613 614 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 615 server.enqueue(new MockResponse()); // unused 616 server.play(); 617 618 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 619 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 620 try { 621 connection.getInputStream(); 622 fail(); 623 } catch (SSLHandshakeException expected) { 624 assertTrue(expected.getCause() instanceof CertificateException); 625 } 626 assertEquals(0, server.getRequestCount()); 627 } 628 629 public void testConnectViaProxy_emptyPath() throws Exception { 630 // expected normalization http://android -> http://android/ per b/30107354 631 checkConnectViaProxy( 632 ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY, "http://android.com", 633 "http://android.com/", "android.com"); 634 } 635 636 public void testConnectViaProxy_complexUrlWithNoPath() throws Exception { 637 checkConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY, 638 "http://android.com:8080?height=100&width=42", 639 "http://android.com:8080/?height=100&width=42", 640 "android.com:8080"); 641 } 642 643 public void testConnectViaProxyUsingProxyArg() throws Exception { 644 checkConnectViaProxy(ProxyConfig.CREATE_ARG); 645 } 646 647 public void testConnectViaProxyUsingProxySystemProperty() throws Exception { 648 checkConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); 649 } 650 651 public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception { 652 checkConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 653 } 654 655 private void checkConnectViaProxy(ProxyConfig proxyConfig) throws Exception { 656 checkConnectViaProxy(proxyConfig, 657 "http://android.com/foo", "http://android.com/foo", "android.com"); 658 } 659 660 private void checkConnectViaProxy(ProxyConfig proxyConfig, String urlString, 661 String expectedUrlInRequestLine, String expectedHost) throws Exception { 662 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 663 server.enqueue(mockResponse); 664 server.play(); 665 666 URL url = new URL(urlString); 667 HttpURLConnection connection = proxyConfig.connect(server, url); 668 assertContent("this response comes via a proxy", connection); 669 670 RecordedRequest request = server.takeRequest(); 671 assertEquals("GET " + expectedUrlInRequestLine + " HTTP/1.1", request.getRequestLine()); 672 assertContains(request.getHeaders(), "Host: " + expectedHost); 673 } 674 675 public void testContentDisagreesWithContentLengthHeader() throws IOException { 676 server.enqueue(new MockResponse() 677 .setBody("abc\r\nYOU SHOULD NOT SEE THIS") 678 .clearHeaders() 679 .addHeader("Content-Length: 3")); 680 server.play(); 681 682 assertContent("abc", server.getUrl("/").openConnection()); 683 } 684 685 public void testContentDisagreesWithChunkedHeader() throws IOException { 686 MockResponse mockResponse = new MockResponse(); 687 mockResponse.setChunkedBody("abc", 3); 688 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 689 bytesOut.write(mockResponse.getBody()); 690 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes()); 691 mockResponse.setBody(bytesOut.toByteArray()); 692 mockResponse.clearHeaders(); 693 mockResponse.addHeader("Transfer-encoding: chunked"); 694 695 server.enqueue(mockResponse); 696 server.play(); 697 698 assertContent("abc", server.getUrl("/").openConnection()); 699 } 700 701 public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { 702 testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); 703 } 704 705 public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { 706 // https should not use http proxy 707 testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 708 } 709 710 private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { 711 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 712 713 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 714 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 715 server.play(); 716 717 URL url = server.getUrl("/foo"); 718 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 719 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 720 721 assertContent("this response comes via HTTPS", connection); 722 723 RecordedRequest request = server.takeRequest(); 724 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 725 } 726 727 728 public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception { 729 testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); 730 } 731 732 /** 733 * We weren't honoring all of the appropriate proxy system properties when 734 * connecting via HTTPS. http://b/3097518 735 */ 736 public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { 737 testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY); 738 } 739 740 public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { 741 testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); 742 } 743 744 /** 745 * We were verifying the wrong hostname when connecting to an HTTPS site 746 * through a proxy. http://b/3097277 747 */ 748 private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { 749 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 750 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 751 752 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 753 server.enqueue(new MockResponse() 754 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 755 .clearHeaders()); 756 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 757 server.play(); 758 759 URL url = new URL("https://android.com/foo"); 760 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 761 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 762 connection.setHostnameVerifier(hostnameVerifier); 763 764 assertContent("this response comes via a secure proxy", connection); 765 766 RecordedRequest connect = server.takeRequest(); 767 assertEquals("Connect line failure on proxy", 768 "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 769 assertContains(connect.getHeaders(), "Host: android.com:443"); 770 771 RecordedRequest get = server.takeRequest(); 772 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 773 assertContains(get.getHeaders(), "Host: android.com"); 774 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 775 } 776 777 778 /** 779 * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912 780 */ 781 public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception { 782 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 783 784 initResponseCache(); 785 786 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 787 788 // The inclusion of a body in the response to the CONNECT is key to reproducing b/6754912. 789 MockResponse badProxyResponse = new MockResponse() 790 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 791 .clearHeaders() 792 .setBody("bogus proxy connect response content"); 793 794 server.enqueue(badProxyResponse); 795 server.enqueue(new MockResponse().setBody("response")); 796 797 server.play(); 798 799 URL url = new URL("https://android.com/foo"); 800 ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY; 801 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 802 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 803 connection.setHostnameVerifier(new RecordingHostnameVerifier()); 804 assertContent("response", connection); 805 806 RecordedRequest connect = server.takeRequest(); 807 assertEquals("CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 808 assertContains(connect.getHeaders(), "Host: android.com:443"); 809 } 810 811 private void initResponseCache() throws IOException { 812 String tmp = System.getProperty("java.io.tmpdir"); 813 File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); 814 cache = AndroidShimResponseCache.create(cacheDir, Integer.MAX_VALUE); 815 ResponseCache.setDefault(cache); 816 } 817 818 /** 819 * Test Etag headers are returned correctly when a client-side cache is not installed. 820 * https://code.google.com/p/android/issues/detail?id=108949 821 */ 822 public void testEtagHeaders_uncached() throws Exception { 823 final String etagValue1 = "686897696a7c876b7e"; 824 final String body1 = "Response with etag 1"; 825 final String etagValue2 = "686897696a7c876b7f"; 826 final String body2 = "Response with etag 2"; 827 828 server.enqueue( 829 new MockResponse() 830 .setBody(body1) 831 .setHeader("Content-Type", "text/plain") 832 .setHeader("Etag", etagValue1)); 833 server.enqueue( 834 new MockResponse() 835 .setBody(body2) 836 .setHeader("Content-Type", "text/plain") 837 .setHeader("Etag", etagValue2)); 838 server.play(); 839 840 URL url = server.getUrl("/"); 841 HttpURLConnection connection1 = (HttpURLConnection) url.openConnection(); 842 assertEquals(etagValue1, connection1.getHeaderField("Etag")); 843 assertContent(body1, connection1); 844 connection1.disconnect(); 845 846 // Discard the server-side record of the request made. 847 server.takeRequest(); 848 849 HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); 850 assertEquals(etagValue2, connection2.getHeaderField("Etag")); 851 assertContent(body2, connection2); 852 connection2.disconnect(); 853 854 // Check the client did not cache. 855 RecordedRequest request = server.takeRequest(); 856 assertNull(request.getHeader("If-None-Match")); 857 } 858 859 /** 860 * Test Etag headers are returned correctly when a client-side cache is installed and the server 861 * data is unchanged. 862 * https://code.google.com/p/android/issues/detail?id=108949 863 */ 864 public void testEtagHeaders_cachedWithServerHit() throws Exception { 865 final String etagValue = "686897696a7c876b7e"; 866 final String body = "Response with etag"; 867 868 server.enqueue( 869 new MockResponse() 870 .setBody(body) 871 .setResponseCode(HttpURLConnection.HTTP_OK) 872 .setHeader("Content-Type", "text/plain") 873 .setHeader("Etag", etagValue)); 874 875 server.enqueue( 876 new MockResponse() 877 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 878 server.play(); 879 880 initResponseCache(); 881 882 URL url = server.getUrl("/"); 883 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 884 assertEquals(etagValue, connection.getHeaderField("Etag")); 885 assertContent(body, connection); 886 connection.disconnect(); 887 888 // Discard the server-side record of the request made. 889 server.takeRequest(); 890 891 // Confirm the cached body is returned along with the original etag header. 892 HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); 893 assertEquals(etagValue, cachedConnection.getHeaderField("Etag")); 894 assertContent(body, cachedConnection); 895 cachedConnection.disconnect(); 896 897 // Check the client formatted the request correctly. 898 RecordedRequest request = server.takeRequest(); 899 assertEquals(etagValue, request.getHeader("If-None-Match")); 900 } 901 902 /** 903 * Test Etag headers are returned correctly when a client-side cache is installed and the server 904 * data has changed. 905 * https://code.google.com/p/android/issues/detail?id=108949 906 */ 907 public void testEtagHeaders_cachedWithServerMiss() throws Exception { 908 final String etagValue1 = "686897696a7c876b7e"; 909 final String body1 = "Response with etag 1"; 910 final String etagValue2 = "686897696a7c876b7f"; 911 final String body2 = "Response with etag 2"; 912 913 server.enqueue( 914 new MockResponse() 915 .setBody(body1) 916 .setResponseCode(HttpURLConnection.HTTP_OK) 917 .setHeader("Content-Type", "text/plain") 918 .setHeader("Etag", etagValue1)); 919 920 server.enqueue( 921 new MockResponse() 922 .setBody(body2) 923 .setResponseCode(HttpURLConnection.HTTP_OK) 924 .setHeader("Content-Type", "text/plain") 925 .setHeader("Etag", etagValue2)); 926 927 server.play(); 928 929 initResponseCache(); 930 931 URL url = server.getUrl("/"); 932 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 933 assertEquals(etagValue1, connection.getHeaderField("Etag")); 934 assertContent(body1, connection); 935 connection.disconnect(); 936 937 // Discard the server-side record of the request made. 938 server.takeRequest(); 939 940 // Confirm the new body is returned along with the new etag header. 941 HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); 942 assertEquals(etagValue2, cachedConnection.getHeaderField("Etag")); 943 assertContent(body2, cachedConnection); 944 cachedConnection.disconnect(); 945 946 // Check the client formatted the request correctly. 947 RecordedRequest request = server.takeRequest(); 948 assertEquals(etagValue1, request.getHeader("If-None-Match")); 949 } 950 951 /** 952 * Test which headers are sent unencrypted to the HTTP proxy. 953 */ 954 public void testProxyConnectIncludesProxyHeadersOnly() 955 throws IOException, InterruptedException { 956 Authenticator.setDefault(new SimpleAuthenticator()); 957 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 958 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 959 960 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 961 server.enqueue(new MockResponse() 962 .setResponseCode(407) 963 .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); 964 server.enqueue(new MockResponse() 965 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 966 .clearHeaders()); 967 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 968 server.play(); 969 970 URL url = new URL("https://android.com/foo"); 971 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 972 server.toProxyAddress()); 973 connection.addRequestProperty("Private", "Secret"); 974 connection.addRequestProperty("User-Agent", "baz"); 975 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 976 connection.setHostnameVerifier(hostnameVerifier); 977 assertContent("encrypted response from the origin server", connection); 978 979 // connect1 and connect2 are tunnel requests which potentially tunnel multiple requests; 980 // Thus we can't expect its headers to exactly match those of the wrapped request. 981 // See https://github.com/square/okhttp/commit/457fb428a729c50c562822571ea9b13e689648f3 982 983 { 984 RecordedRequest connect1 = server.takeRequest(); 985 List<String> headers = connect1.getHeaders(); 986 assertContainsNoneMatching(headers, "Private.*"); 987 assertContainsNoneMatching(headers, "Proxy\\-Authorization.*"); 988 assertHeaderPresent(connect1, "User-Agent"); 989 assertContains(headers, "Host: android.com:443"); 990 assertContains(headers, "Proxy-Connection: Keep-Alive"); 991 } 992 993 { 994 RecordedRequest connect2 = server.takeRequest(); 995 List<String> headers = connect2.getHeaders(); 996 assertContainsNoneMatching(headers, "Private.*"); 997 assertHeaderPresent(connect2, "Proxy-Authorization"); 998 assertHeaderPresent(connect2, "User-Agent"); 999 assertContains(headers, "Host: android.com:443"); 1000 assertContains(headers, "Proxy-Connection: Keep-Alive"); 1001 } 1002 1003 RecordedRequest get = server.takeRequest(); 1004 assertContains(get.getHeaders(), "Private: Secret"); 1005 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 1006 } 1007 1008 public void testProxyAuthenticateOnConnect() throws Exception { 1009 Authenticator.setDefault(new SimpleAuthenticator()); 1010 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 1011 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 1012 server.enqueue(new MockResponse() 1013 .setResponseCode(407) 1014 .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); 1015 server.enqueue(new MockResponse() 1016 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 1017 .clearHeaders()); 1018 server.enqueue(new MockResponse().setBody("A")); 1019 server.play(); 1020 1021 URL url = new URL("https://android.com/foo"); 1022 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 1023 server.toProxyAddress()); 1024 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1025 connection.setHostnameVerifier(new RecordingHostnameVerifier()); 1026 assertContent("A", connection); 1027 1028 RecordedRequest connect1 = server.takeRequest(); 1029 assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine()); 1030 assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*"); 1031 1032 RecordedRequest connect2 = server.takeRequest(); 1033 assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine()); 1034 assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic " 1035 + SimpleAuthenticator.BASE_64_CREDENTIALS); 1036 1037 RecordedRequest get = server.takeRequest(); 1038 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 1039 assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*"); 1040 } 1041 1042 // Don't disconnect after building a tunnel with CONNECT 1043 // http://code.google.com/p/android/issues/detail?id=37221 1044 public void testProxyWithConnectionClose() throws IOException { 1045 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 1046 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 1047 server.enqueue(new MockResponse() 1048 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 1049 .clearHeaders()); 1050 server.enqueue(new MockResponse().setBody("this response comes via a proxy")); 1051 server.play(); 1052 1053 URL url = new URL("https://android.com/foo"); 1054 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 1055 server.toProxyAddress()); 1056 connection.setRequestProperty("Connection", "close"); 1057 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1058 connection.setHostnameVerifier(new RecordingHostnameVerifier()); 1059 1060 assertContent("this response comes via a proxy", connection); 1061 } 1062 1063 public void testDisconnectedConnection() throws IOException { 1064 server.enqueue(new MockResponse() 1065 .throttleBody(2, 100, TimeUnit.MILLISECONDS) 1066 .setBody("ABCD")); 1067 server.play(); 1068 1069 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1070 InputStream in = connection.getInputStream(); 1071 assertEquals('A', (char) in.read()); 1072 connection.disconnect(); 1073 try { 1074 // Reading 'B' may succeed if it's buffered. 1075 in.read(); 1076 // But 'C' shouldn't be buffered (the response is throttled) and this should fail. 1077 in.read(); 1078 fail("Expected a connection closed exception"); 1079 } catch (IOException expected) { 1080 } 1081 } 1082 1083 // http://b/33763156 1084 public void testDisconnectDuringConnect_getInputStream() throws IOException { 1085 checkDisconnectDuringConnect(HttpURLConnection::getInputStream); 1086 } 1087 1088 // http://b/33763156 1089 public void testDisconnectDuringConnect_getOutputStream() throws IOException { 1090 checkDisconnectDuringConnect(HttpURLConnection::getOutputStream); 1091 } 1092 1093 // http://b/33763156 1094 public void testDisconnectDuringConnect_getResponseCode() throws IOException { 1095 checkDisconnectDuringConnect(HttpURLConnection::getResponseCode); 1096 } 1097 1098 // http://b/33763156 1099 public void testDisconnectDuringConnect_getResponseMessage() throws IOException { 1100 checkDisconnectDuringConnect(HttpURLConnection::getResponseMessage); 1101 } 1102 1103 interface ConnectStrategy { 1104 /** 1105 * Causes the given {@code connection}, which was previously disconnected, 1106 * to initiate the connection. 1107 */ 1108 void connect(HttpURLConnection connection) throws IOException; 1109 } 1110 1111 // http://b/33763156 1112 private void checkDisconnectDuringConnect(ConnectStrategy connectStrategy) throws IOException { 1113 server.enqueue(new MockResponse().setBody("This should never be sent")); 1114 server.play(); 1115 1116 final AtomicReference<HttpURLConnection> connectionHolder = new AtomicReference<>(); 1117 class DisconnectingCookieHandler extends CookieManager { 1118 @Override 1119 public Map<String, List<String>> get(URI uri, Map<String, List<String>> map) 1120 throws IOException { 1121 Map<String, List<String>> result = super.get(uri, map); 1122 connectionHolder.get().disconnect(); 1123 return result; 1124 } 1125 } 1126 CookieHandler defaultCookieHandler = CookieHandler.getDefault(); 1127 try { 1128 CookieHandler.setDefault(new DisconnectingCookieHandler()); 1129 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1130 connectionHolder.set(connection); 1131 try { 1132 connectStrategy.connect(connection); 1133 fail(); 1134 } catch (IOException expected) { 1135 assertEquals("Canceled", expected.getMessage()); 1136 } finally { 1137 connection.disconnect(); 1138 } 1139 } finally { 1140 CookieHandler.setDefault(defaultCookieHandler); 1141 } 1142 } 1143 1144 public void testDisconnectBeforeConnect() throws IOException { 1145 server.enqueue(new MockResponse().setBody("A")); 1146 server.play(); 1147 1148 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1149 connection.disconnect(); 1150 1151 assertContent("A", connection); 1152 assertEquals(200, connection.getResponseCode()); 1153 } 1154 1155 public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException { 1156 server.enqueue(new MockResponse() 1157 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 1158 .addHeader("Content-Encoding: gzip")); 1159 server.play(); 1160 1161 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1162 try { 1163 assertEquals(200, connection.getResponseCode()); 1164 } finally { 1165 connection.disconnect(); 1166 } 1167 } 1168 1169 public void testDefaultRequestProperty() throws Exception { 1170 URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A"); 1171 assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")); 1172 } 1173 1174 /** 1175 * Reads {@code count} characters from the stream. If the stream is 1176 * exhausted before {@code count} characters can be read, the remaining 1177 * characters are returned and the stream is closed. 1178 */ 1179 private String readAscii(InputStream in, int count) throws IOException { 1180 StringBuilder result = new StringBuilder(); 1181 for (int i = 0; i < count; i++) { 1182 int value = in.read(); 1183 if (value == -1) { 1184 in.close(); 1185 break; 1186 } 1187 result.append((char) value); 1188 } 1189 return result.toString(); 1190 } 1191 1192 public void testMarkAndResetWithContentLengthHeader() throws IOException { 1193 testMarkAndReset(TransferKind.FIXED_LENGTH); 1194 } 1195 1196 public void testMarkAndResetWithChunkedEncoding() throws IOException { 1197 testMarkAndReset(TransferKind.CHUNKED); 1198 } 1199 1200 public void testMarkAndResetWithNoLengthHeaders() throws IOException { 1201 testMarkAndReset(TransferKind.END_OF_STREAM); 1202 } 1203 1204 private void testMarkAndReset(TransferKind transferKind) throws IOException { 1205 MockResponse response = new MockResponse(); 1206 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 1207 server.enqueue(response); 1208 server.enqueue(response); 1209 server.play(); 1210 1211 InputStream in = server.getUrl("/").openConnection().getInputStream(); 1212 assertFalse("This implementation claims to support mark().", in.markSupported()); 1213 in.mark(5); 1214 assertEquals("ABCDE", readAscii(in, 5)); 1215 try { 1216 in.reset(); 1217 fail(); 1218 } catch (IOException expected) { 1219 } 1220 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 1221 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection()); 1222 } 1223 1224 /** 1225 * We've had a bug where we forget the HTTP response when we see response 1226 * code 401. This causes a new HTTP request to be issued for every call into 1227 * the URLConnection. 1228 */ 1229 public void testUnauthorizedResponseHandling() throws IOException { 1230 MockResponse response = new MockResponse() 1231 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1232 .setResponseCode(401) // UNAUTHORIZED 1233 .setBody("Unauthorized"); 1234 server.enqueue(response); 1235 server.enqueue(response); 1236 server.enqueue(response); 1237 server.play(); 1238 1239 URL url = server.getUrl("/"); 1240 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 1241 1242 assertEquals(401, conn.getResponseCode()); 1243 assertEquals(401, conn.getResponseCode()); 1244 assertEquals(401, conn.getResponseCode()); 1245 assertEquals(1, server.getRequestCount()); 1246 } 1247 1248 public void testNonHexChunkSize() throws IOException { 1249 server.enqueue(new MockResponse() 1250 .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 1251 .clearHeaders() 1252 .addHeader("Transfer-encoding: chunked")); 1253 server.play(); 1254 1255 URLConnection connection = server.getUrl("/").openConnection(); 1256 try { 1257 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1258 fail(); 1259 } catch (IOException e) { 1260 } 1261 } 1262 1263 public void testMissingChunkBody() throws IOException { 1264 server.enqueue(new MockResponse() 1265 .setBody("5") 1266 .clearHeaders() 1267 .addHeader("Transfer-encoding: chunked") 1268 .setSocketPolicy(DISCONNECT_AT_END)); 1269 server.play(); 1270 1271 URLConnection connection = server.getUrl("/").openConnection(); 1272 try { 1273 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1274 fail(); 1275 } catch (IOException e) { 1276 } 1277 } 1278 1279 /** 1280 * This test checks whether connections are gzipped by default. This 1281 * behavior in not required by the API, so a failure of this test does not 1282 * imply a bug in the implementation. 1283 */ 1284 public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { 1285 server.enqueue(new MockResponse() 1286 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 1287 .addHeader("Content-Encoding: gzip")); 1288 server.play(); 1289 1290 URLConnection connection = server.getUrl("/").openConnection(); 1291 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1292 assertNull(connection.getContentEncoding()); 1293 assertEquals(-1, connection.getContentLength()); 1294 1295 RecordedRequest request = server.takeRequest(); 1296 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1297 } 1298 1299 public void testClientConfiguredGzipContentEncoding() throws Exception { 1300 byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")); 1301 server.enqueue(new MockResponse() 1302 .setBody(bodyBytes) 1303 .addHeader("Content-Encoding: gzip") 1304 .addHeader("Content-Length: " + bodyBytes.length)); 1305 server.play(); 1306 1307 URLConnection connection = server.getUrl("/").openConnection(); 1308 connection.addRequestProperty("Accept-Encoding", "gzip"); 1309 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1310 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1311 assertEquals(bodyBytes.length, connection.getContentLength()); 1312 1313 RecordedRequest request = server.takeRequest(); 1314 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1315 assertEquals("gzip", connection.getContentEncoding()); 1316 } 1317 1318 public void testGzipAndConnectionReuseWithFixedLength() throws Exception { 1319 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); 1320 } 1321 1322 public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { 1323 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); 1324 } 1325 1326 public void testClientConfiguredCustomContentEncoding() throws Exception { 1327 server.enqueue(new MockResponse() 1328 .setBody("ABCDE") 1329 .addHeader("Content-Encoding: custom")); 1330 server.play(); 1331 1332 URLConnection connection = server.getUrl("/").openConnection(); 1333 connection.addRequestProperty("Accept-Encoding", "custom"); 1334 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1335 1336 RecordedRequest request = server.takeRequest(); 1337 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 1338 } 1339 1340 /** 1341 * Test a bug where gzip input streams weren't exhausting the input stream, 1342 * which corrupted the request that followed. 1343 * http://code.google.com/p/android/issues/detail?id=7059 1344 */ 1345 private void testClientConfiguredGzipContentEncodingAndConnectionReuse( 1346 TransferKind transferKind) throws Exception { 1347 MockResponse responseOne = new MockResponse(); 1348 responseOne.addHeader("Content-Encoding: gzip"); 1349 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 1350 server.enqueue(responseOne); 1351 MockResponse responseTwo = new MockResponse(); 1352 transferKind.setBody(responseTwo, "two (identity)", 5); 1353 server.enqueue(responseTwo); 1354 server.play(); 1355 1356 URLConnection connection = server.getUrl("/").openConnection(); 1357 connection.addRequestProperty("Accept-Encoding", "gzip"); 1358 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1359 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1360 assertEquals(0, server.takeRequest().getSequenceNumber()); 1361 1362 connection = server.getUrl("/").openConnection(); 1363 assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1364 assertEquals(1, server.takeRequest().getSequenceNumber()); 1365 } 1366 1367 /** 1368 * Test that HEAD requests don't have a body regardless of the response 1369 * headers. http://code.google.com/p/android/issues/detail?id=24672 1370 */ 1371 public void testHeadAndContentLength() throws Exception { 1372 server.enqueue(new MockResponse() 1373 .clearHeaders() 1374 .addHeader("Content-Length: 100")); 1375 server.enqueue(new MockResponse().setBody("A")); 1376 server.play(); 1377 1378 HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/").openConnection(); 1379 connection1.setRequestMethod("HEAD"); 1380 assertEquals("100", connection1.getHeaderField("Content-Length")); 1381 assertContent("", connection1); 1382 1383 HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/").openConnection(); 1384 assertEquals("A", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); 1385 1386 assertEquals(0, server.takeRequest().getSequenceNumber()); 1387 assertEquals(1, server.takeRequest().getSequenceNumber()); 1388 } 1389 1390 /** 1391 * Test that request body chunking works. This test has been relaxed from treating 1392 * the {@link java.net.HttpURLConnection#setChunkedStreamingMode(int)} 1393 * chunk length as being fixed because OkHttp no longer guarantees 1394 * the fixed chunk size. Instead, we check that chunking takes place 1395 * and we force the chunk size with flushes. 1396 */ 1397 public void testSetChunkedStreamingMode() throws IOException, InterruptedException { 1398 server.enqueue(new MockResponse()); 1399 server.play(); 1400 1401 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1402 // Later releases of Android ignore the value for chunkLength if it is > 0 and default to 1403 // a fixed chunkLength. During the change-over period while the chunkLength indicates the 1404 // chunk buffer size (inc. header) the chunkLength has to be >= 8. This enables the flush() 1405 // to dictate the size of the chunks. 1406 urlConnection.setChunkedStreamingMode(50 /* chunkLength */); 1407 urlConnection.setDoOutput(true); 1408 OutputStream outputStream = urlConnection.getOutputStream(); 1409 String outputString = "ABCDEFGH"; 1410 byte[] outputBytes = outputString.getBytes("US-ASCII"); 1411 int targetChunkSize = 3; 1412 for (int i = 0; i < outputBytes.length; i += targetChunkSize) { 1413 int count = i + targetChunkSize < outputBytes.length ? 3 : outputBytes.length - i; 1414 outputStream.write(outputBytes, i, count); 1415 outputStream.flush(); 1416 } 1417 assertEquals(200, urlConnection.getResponseCode()); 1418 1419 RecordedRequest request = server.takeRequest(); 1420 assertEquals(outputString, new String(request.getBody(), "US-ASCII")); 1421 assertEquals(Arrays.asList(3, 3, 2), request.getChunkSizes()); 1422 } 1423 1424 public void testAuthenticateWithFixedLengthStreaming() throws Exception { 1425 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 1426 } 1427 1428 public void testAuthenticateWithChunkedStreaming() throws Exception { 1429 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 1430 } 1431 1432 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 1433 MockResponse pleaseAuthenticate = new MockResponse() 1434 .setResponseCode(401) 1435 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1436 .setBody("Please authenticate."); 1437 server.enqueue(pleaseAuthenticate); 1438 server.play(); 1439 1440 Authenticator.setDefault(new SimpleAuthenticator()); 1441 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1442 connection.setDoOutput(true); 1443 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1444 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1445 connection.setFixedLengthStreamingMode(requestBody.length); 1446 } else if (streamingMode == StreamingMode.CHUNKED) { 1447 connection.setChunkedStreamingMode(0); 1448 } 1449 OutputStream outputStream = connection.getOutputStream(); 1450 outputStream.write(requestBody); 1451 outputStream.close(); 1452 try { 1453 connection.getInputStream(); 1454 fail(); 1455 } catch (HttpRetryException expected) { 1456 } 1457 1458 // no authorization header for the request... 1459 RecordedRequest request = server.takeRequest(); 1460 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1461 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1462 } 1463 1464 public void testSetValidRequestMethod() throws Exception { 1465 server.play(); 1466 assertValidRequestMethod("GET"); 1467 assertValidRequestMethod("DELETE"); 1468 assertValidRequestMethod("HEAD"); 1469 assertValidRequestMethod("OPTIONS"); 1470 assertValidRequestMethod("POST"); 1471 assertValidRequestMethod("PUT"); 1472 assertValidRequestMethod("TRACE"); 1473 } 1474 1475 private void assertValidRequestMethod(String requestMethod) throws Exception { 1476 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1477 connection.setRequestMethod(requestMethod); 1478 assertEquals(requestMethod, connection.getRequestMethod()); 1479 } 1480 1481 public void testSetInvalidRequestMethodLowercase() throws Exception { 1482 server.play(); 1483 assertInvalidRequestMethod("get"); 1484 } 1485 1486 public void testSetInvalidRequestMethodConnect() throws Exception { 1487 server.play(); 1488 assertInvalidRequestMethod("CONNECT"); 1489 } 1490 1491 private void assertInvalidRequestMethod(String requestMethod) throws Exception { 1492 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1493 try { 1494 connection.setRequestMethod(requestMethod); 1495 fail(); 1496 } catch (ProtocolException expected) { 1497 } 1498 } 1499 1500 public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception { 1501 server.play(); 1502 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1503 try { 1504 connection.setFixedLengthStreamingMode(-2); 1505 fail(); 1506 } catch (IllegalArgumentException expected) { 1507 } 1508 } 1509 1510 public void testCanSetNegativeChunkedStreamingMode() throws Exception { 1511 server.play(); 1512 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1513 connection.setChunkedStreamingMode(-2); 1514 } 1515 1516 public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception { 1517 server.enqueue(new MockResponse().setBody("A")); 1518 server.play(); 1519 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1520 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1521 try { 1522 connection.setFixedLengthStreamingMode(1); 1523 fail(); 1524 } catch (IllegalStateException expected) { 1525 } 1526 } 1527 1528 public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception { 1529 server.enqueue(new MockResponse().setBody("A")); 1530 server.play(); 1531 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1532 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1533 try { 1534 connection.setChunkedStreamingMode(1); 1535 fail(); 1536 } catch (IllegalStateException expected) { 1537 } 1538 } 1539 1540 public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { 1541 server.play(); 1542 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1543 connection.setChunkedStreamingMode(1); 1544 try { 1545 connection.setFixedLengthStreamingMode(1); 1546 fail(); 1547 } catch (IllegalStateException expected) { 1548 } 1549 } 1550 1551 public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { 1552 server.play(); 1553 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1554 connection.setFixedLengthStreamingMode(1); 1555 try { 1556 connection.setChunkedStreamingMode(1); 1557 fail(); 1558 } catch (IllegalStateException expected) { 1559 } 1560 } 1561 1562 public void testSecureFixedLengthStreaming() throws Exception { 1563 testSecureStreamingPost(StreamingMode.FIXED_LENGTH); 1564 } 1565 1566 public void testSecureChunkedStreaming() throws Exception { 1567 testSecureStreamingPost(StreamingMode.CHUNKED); 1568 } 1569 1570 /** 1571 * Users have reported problems using HTTPS with streaming request bodies. 1572 * http://code.google.com/p/android/issues/detail?id=12860 1573 */ 1574 private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception { 1575 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 1576 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1577 server.enqueue(new MockResponse().setBody("Success!")); 1578 server.play(); 1579 1580 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1581 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1582 connection.setDoOutput(true); 1583 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1584 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1585 connection.setFixedLengthStreamingMode(requestBody.length); 1586 } else if (streamingMode == StreamingMode.CHUNKED) { 1587 connection.setChunkedStreamingMode(0); 1588 } 1589 OutputStream outputStream = connection.getOutputStream(); 1590 outputStream.write(requestBody); 1591 outputStream.close(); 1592 assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1593 1594 RecordedRequest request = server.takeRequest(); 1595 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1596 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1597 assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes()); 1598 } else if (streamingMode == StreamingMode.CHUNKED) { 1599 assertEquals(Arrays.asList(4), request.getChunkSizes()); 1600 } 1601 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1602 } 1603 1604 enum StreamingMode { 1605 FIXED_LENGTH, CHUNKED 1606 } 1607 1608 public void testAuthenticateWithPost() throws Exception { 1609 MockResponse pleaseAuthenticate = new MockResponse() 1610 .setResponseCode(401) 1611 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1612 .setBody("Please authenticate."); 1613 // fail auth three times... 1614 server.enqueue(pleaseAuthenticate); 1615 server.enqueue(pleaseAuthenticate); 1616 server.enqueue(pleaseAuthenticate); 1617 // ...then succeed the fourth time 1618 server.enqueue(new MockResponse().setBody("Successful auth!")); 1619 server.play(); 1620 1621 Authenticator.setDefault(new SimpleAuthenticator()); 1622 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1623 connection.setDoOutput(true); 1624 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1625 OutputStream outputStream = connection.getOutputStream(); 1626 outputStream.write(requestBody); 1627 outputStream.close(); 1628 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1629 1630 // no authorization header for the first request... 1631 RecordedRequest request = server.takeRequest(); 1632 assertContainsNoneMatching(request.getHeaders(), "Authorization: .*"); 1633 1634 // ...but the three requests that follow include an authorization header 1635 for (int i = 0; i < 3; i++) { 1636 request = server.takeRequest(); 1637 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1638 assertContains(request.getHeaders(), "Authorization: Basic " 1639 + SimpleAuthenticator.BASE_64_CREDENTIALS); 1640 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1641 } 1642 } 1643 1644 public void testAuthenticateWithGet() throws Exception { 1645 MockResponse pleaseAuthenticate = new MockResponse() 1646 .setResponseCode(401) 1647 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1648 .setBody("Please authenticate."); 1649 // fail auth three times... 1650 server.enqueue(pleaseAuthenticate); 1651 server.enqueue(pleaseAuthenticate); 1652 server.enqueue(pleaseAuthenticate); 1653 // ...then succeed the fourth time 1654 server.enqueue(new MockResponse().setBody("Successful auth!")); 1655 server.play(); 1656 1657 SimpleAuthenticator authenticator = new SimpleAuthenticator(); 1658 Authenticator.setDefault(authenticator); 1659 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1660 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1661 assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType); 1662 assertEquals(server.getPort(), authenticator.requestingPort); 1663 assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite); 1664 assertEquals("protected area", authenticator.requestingPrompt); 1665 assertEquals("http", authenticator.requestingProtocol); 1666 assertEquals("Basic", authenticator.requestingScheme); 1667 1668 // no authorization header for the first request... 1669 RecordedRequest request = server.takeRequest(); 1670 assertContainsNoneMatching(request.getHeaders(), "Authorization: .*"); 1671 1672 // ...but the three requests that follow requests include an authorization header 1673 for (int i = 0; i < 3; i++) { 1674 request = server.takeRequest(); 1675 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1676 assertContains(request.getHeaders(), "Authorization: Basic " 1677 + SimpleAuthenticator.BASE_64_CREDENTIALS); 1678 } 1679 } 1680 1681 // bug 11473660 1682 public void testAuthenticateWithLowerCaseHeadersAndScheme() throws Exception { 1683 MockResponse pleaseAuthenticate = new MockResponse() 1684 .setResponseCode(401) 1685 .addHeader("www-authenticate: basic realm=\"protected area\"") 1686 .setBody("Please authenticate."); 1687 // fail auth three times... 1688 server.enqueue(pleaseAuthenticate); 1689 server.enqueue(pleaseAuthenticate); 1690 server.enqueue(pleaseAuthenticate); 1691 // ...then succeed the fourth time 1692 server.enqueue(new MockResponse().setBody("Successful auth!")); 1693 server.play(); 1694 1695 SimpleAuthenticator authenticator = new SimpleAuthenticator(); 1696 Authenticator.setDefault(authenticator); 1697 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1698 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1699 assertEquals(Authenticator.RequestorType.SERVER, authenticator.requestorType); 1700 assertEquals(server.getPort(), authenticator.requestingPort); 1701 assertEquals(InetAddress.getByName(server.getHostName()), authenticator.requestingSite); 1702 assertEquals("protected area", authenticator.requestingPrompt); 1703 assertEquals("http", authenticator.requestingProtocol); 1704 assertEquals("basic", authenticator.requestingScheme); 1705 } 1706 1707 // http://code.google.com/p/android/issues/detail?id=19081 1708 public void testAuthenticateWithCommaSeparatedAuthenticationMethods() throws Exception { 1709 server.enqueue(new MockResponse() 1710 .setResponseCode(401) 1711 .addHeader("WWW-Authenticate: Scheme1 realm=\"a\", Basic realm=\"b\", " 1712 + "Scheme3 realm=\"c\"") 1713 .setBody("Please authenticate.")); 1714 server.enqueue(new MockResponse().setBody("Successful auth!")); 1715 server.play(); 1716 1717 SimpleAuthenticator authenticator = new SimpleAuthenticator(); 1718 authenticator.expectedPrompt = "b"; 1719 Authenticator.setDefault(authenticator); 1720 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1721 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1722 1723 assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); 1724 assertContains(server.takeRequest().getHeaders(), 1725 "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS); 1726 assertEquals("Basic", authenticator.requestingScheme); 1727 } 1728 1729 public void testAuthenticateWithMultipleAuthenticationHeaders() throws Exception { 1730 server.enqueue(new MockResponse() 1731 .setResponseCode(401) 1732 .addHeader("WWW-Authenticate: Scheme1 realm=\"a\"") 1733 .addHeader("WWW-Authenticate: Basic realm=\"b\"") 1734 .addHeader("WWW-Authenticate: Scheme3 realm=\"c\"") 1735 .setBody("Please authenticate.")); 1736 server.enqueue(new MockResponse().setBody("Successful auth!")); 1737 server.play(); 1738 1739 SimpleAuthenticator authenticator = new SimpleAuthenticator(); 1740 authenticator.expectedPrompt = "b"; 1741 Authenticator.setDefault(authenticator); 1742 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1743 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1744 1745 assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); 1746 assertContains(server.takeRequest().getHeaders(), 1747 "Authorization: Basic " + SimpleAuthenticator.BASE_64_CREDENTIALS); 1748 assertEquals("Basic", authenticator.requestingScheme); 1749 } 1750 1751 public void testRedirectedWithChunkedEncoding() throws Exception { 1752 testRedirected(TransferKind.CHUNKED, true); 1753 } 1754 1755 public void testRedirectedWithContentLengthHeader() throws Exception { 1756 testRedirected(TransferKind.FIXED_LENGTH, true); 1757 } 1758 1759 public void testRedirectedWithNoLengthHeaders() throws Exception { 1760 testRedirected(TransferKind.END_OF_STREAM, false); 1761 } 1762 1763 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1764 MockResponse response = new MockResponse() 1765 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1766 .addHeader("Location: /foo"); 1767 transferKind.setBody(response, "This page has moved!", 10); 1768 server.enqueue(response); 1769 server.enqueue(new MockResponse().setBody("This is the new location!")); 1770 server.play(); 1771 1772 URLConnection connection = server.getUrl("/").openConnection(); 1773 assertEquals("This is the new location!", 1774 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1775 1776 RecordedRequest first = server.takeRequest(); 1777 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1778 RecordedRequest retry = server.takeRequest(); 1779 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1780 if (reuse) { 1781 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1782 } 1783 } 1784 1785 public void testRedirectedOnHttps() throws IOException, InterruptedException { 1786 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 1787 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1788 server.enqueue(new MockResponse() 1789 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1790 .addHeader("Location: /foo") 1791 .setBody("This page has moved!")); 1792 server.enqueue(new MockResponse().setBody("This is the new location!")); 1793 server.play(); 1794 1795 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1796 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1797 assertEquals("This is the new location!", 1798 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1799 1800 RecordedRequest first = server.takeRequest(); 1801 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1802 RecordedRequest retry = server.takeRequest(); 1803 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1804 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1805 } 1806 1807 public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1808 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 1809 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1810 server.enqueue(new MockResponse() 1811 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1812 .addHeader("Location: http://anyhost/foo") 1813 .setBody("This page has moved!")); 1814 server.play(); 1815 1816 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1817 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1818 assertEquals("This page has moved!", 1819 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1820 } 1821 1822 public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1823 server.enqueue(new MockResponse() 1824 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1825 .addHeader("Location: https://anyhost/foo") 1826 .setBody("This page has moved!")); 1827 server.play(); 1828 1829 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1830 assertEquals("This page has moved!", 1831 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1832 } 1833 1834 public void testRedirectToAnotherOriginServer() throws Exception { 1835 MockWebServer server2 = new MockWebServer(); 1836 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1837 server2.play(); 1838 1839 server.enqueue(new MockResponse() 1840 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1841 .addHeader("Location: " + server2.getUrl("/").toString()) 1842 .setBody("This page has moved!")); 1843 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1844 server.play(); 1845 1846 URLConnection connection = server.getUrl("/").openConnection(); 1847 assertEquals("This is the 2nd server!", 1848 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1849 assertEquals(server2.getUrl("/"), connection.getURL()); 1850 1851 // make sure the first server was careful to recycle the connection 1852 assertEquals("This is the first server again!", 1853 readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); 1854 1855 RecordedRequest first = server.takeRequest(); 1856 assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort()); 1857 RecordedRequest second = server2.takeRequest(); 1858 assertContains(second.getHeaders(), "Host: " + hostName + ":" + server2.getPort()); 1859 RecordedRequest third = server.takeRequest(); 1860 assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); 1861 1862 server2.shutdown(); 1863 } 1864 1865 // http://b/27590872 - assert we do not throw a runtime exception if a server responds with 1866 // a location that cannot be represented directly by URI. 1867 public void testRedirectWithInvalidRedirectUrl() throws Exception { 1868 // The first server hosts a redirect to a second. We need two so that the ProxySelector 1869 // installed is used for the redirect. Otherwise the second request will be handled via the 1870 // existing keep-alive connection. 1871 server.play(); 1872 1873 MockWebServer server2 = new MockWebServer(); 1874 server2.play(); 1875 1876 String targetPath = "/target"; 1877 // The "%0" in the suffix is invalid without a second digit. 1878 String invalidSuffix = "?foo=%0&bar=%00"; 1879 1880 String redirectPath = server2.getUrl(targetPath).toString(); 1881 String invalidRedirectUri = redirectPath + invalidSuffix; 1882 1883 // Redirect to the invalid URI. 1884 server.enqueue(new MockResponse() 1885 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1886 .addHeader("Location: " + invalidRedirectUri)); 1887 1888 server2.enqueue(new MockResponse().setBody("Target")); 1889 1890 // Assert the target URI is actually invalid. 1891 try { 1892 new URI(invalidRedirectUri); 1893 fail("Target URL is expected to be invalid"); 1894 } catch (URISyntaxException expected) {} 1895 1896 // The ProxySelector requires a URI object, which forces the HttpURLConnectionImpl to create 1897 // a URI object containing a string based on the redirect address, regardless of what it is 1898 // using internally to hold the target address. 1899 ProxySelector originalSelector = ProxySelector.getDefault(); 1900 final List<URI> proxySelectorUris = new ArrayList<>(); 1901 ProxySelector.setDefault(new ProxySelector() { 1902 @Override 1903 public List<Proxy> select(URI uri) { 1904 if (uri.getScheme().equals("http")) { 1905 // Ignore socks proxy lookups. 1906 proxySelectorUris.add(uri); 1907 } 1908 return Collections.singletonList(Proxy.NO_PROXY); 1909 } 1910 1911 @Override 1912 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 1913 // no-op 1914 } 1915 }); 1916 1917 try { 1918 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1919 assertEquals("Target", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1920 1921 // Inspect the redirect request to see what request was actually made. 1922 RecordedRequest actualRequest = server2.takeRequest(); 1923 assertEquals(targetPath + invalidSuffix, actualRequest.getPath()); 1924 1925 // The first URI will be the initial request. We want to inspect the redirect. 1926 URI uri = proxySelectorUris.get(1); 1927 // The proxy is selected by Address alone (not the whole target URI). 1928 // In OkHttp, HttpEngine.createAddress() converts to an Address and the 1929 // RouteSelector converts back to address.url(). 1930 assertEquals(server2.getUrl("/").toString(), uri.toString()); 1931 } finally { 1932 ProxySelector.setDefault(originalSelector); 1933 server2.shutdown(); 1934 } 1935 } 1936 1937 public void testInstanceFollowsRedirects() throws Exception { 1938 testInstanceFollowsRedirects("http://www.google.com/"); 1939 testInstanceFollowsRedirects("https://www.google.com/"); 1940 } 1941 1942 private void testInstanceFollowsRedirects(String spec) throws Exception { 1943 URL url = new URL(spec); 1944 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 1945 urlConnection.setInstanceFollowRedirects(true); 1946 assertTrue(urlConnection.getInstanceFollowRedirects()); 1947 urlConnection.setInstanceFollowRedirects(false); 1948 assertFalse(urlConnection.getInstanceFollowRedirects()); 1949 } 1950 1951 public void testFollowRedirects() throws Exception { 1952 testFollowRedirects("http://www.google.com/"); 1953 testFollowRedirects("https://www.google.com/"); 1954 } 1955 1956 private void testFollowRedirects(String spec) throws Exception { 1957 URL url = new URL(spec); 1958 boolean originalValue = HttpURLConnection.getFollowRedirects(); 1959 try { 1960 HttpURLConnection.setFollowRedirects(false); 1961 assertFalse(HttpURLConnection.getFollowRedirects()); 1962 1963 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 1964 assertFalse(connection.getInstanceFollowRedirects()); 1965 1966 HttpURLConnection.setFollowRedirects(true); 1967 assertTrue(HttpURLConnection.getFollowRedirects()); 1968 1969 HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); 1970 assertTrue(connection2.getInstanceFollowRedirects()); 1971 } finally { 1972 HttpURLConnection.setFollowRedirects(originalValue); 1973 } 1974 } 1975 1976 public void testResponse300MultipleChoiceWithPost() throws Exception { 1977 // Chrome doesn't follow the redirect, but Firefox and the RI both do 1978 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE); 1979 } 1980 1981 public void testResponse301MovedPermanentlyWithPost() throws Exception { 1982 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM); 1983 } 1984 1985 public void testResponse302MovedTemporarilyWithPost() throws Exception { 1986 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP); 1987 } 1988 1989 public void testResponse303SeeOtherWithPost() throws Exception { 1990 testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER); 1991 } 1992 1993 private void testResponseRedirectedWithPost(int redirectCode) throws Exception { 1994 server.enqueue(new MockResponse() 1995 .setResponseCode(redirectCode) 1996 .addHeader("Location: /page2") 1997 .setBody("This page has moved!")); 1998 server.enqueue(new MockResponse().setBody("Page 2")); 1999 server.play(); 2000 2001 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection(); 2002 connection.setDoOutput(true); 2003 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 2004 OutputStream outputStream = connection.getOutputStream(); 2005 outputStream.write(requestBody); 2006 outputStream.close(); 2007 assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2008 assertTrue(connection.getDoOutput()); 2009 2010 RecordedRequest page1 = server.takeRequest(); 2011 assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine()); 2012 assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); 2013 2014 RecordedRequest page2 = server.takeRequest(); 2015 assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); 2016 } 2017 2018 public void testResponse305UseProxy() throws Exception { 2019 server.play(); 2020 server.enqueue(new MockResponse() 2021 .setResponseCode(HttpURLConnection.HTTP_USE_PROXY) 2022 .addHeader("Location: " + server.getUrl("/")) 2023 .setBody("This page has moved!")); 2024 server.enqueue(new MockResponse().setBody("Proxy Response")); 2025 2026 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection(); 2027 // Fails on the RI, which gets "Proxy Response" 2028 assertEquals("This page has moved!", 2029 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2030 2031 RecordedRequest page1 = server.takeRequest(); 2032 assertEquals("GET /foo HTTP/1.1", page1.getRequestLine()); 2033 assertEquals(1, server.getRequestCount()); 2034 } 2035 2036 public void testHttpsWithCustomTrustManager() throws Exception { 2037 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 2038 RecordingTrustManager trustManager = new RecordingTrustManager(); 2039 SSLContext sc = SSLContext.getInstance("TLS"); 2040 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 2041 2042 HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 2043 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 2044 SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 2045 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 2046 try { 2047 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 2048 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 2049 server.enqueue(new MockResponse().setBody("ABC")); 2050 server.enqueue(new MockResponse().setBody("DEF")); 2051 server.enqueue(new MockResponse().setBody("GHI")); 2052 server.play(); 2053 2054 URL url = server.getUrl("/"); 2055 assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); 2056 assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); 2057 assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); 2058 2059 assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls); 2060 assertEquals(Arrays.asList("checkServerTrusted [" 2061 + "CN=Local Host 3, " 2062 + "CN=Test Intermediate Certificate Authority 2, " 2063 + "CN=Test Root Certificate Authority 1" 2064 + "] ECDHE_RSA"), 2065 trustManager.calls); 2066 } finally { 2067 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 2068 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 2069 } 2070 } 2071 2072 public void testSetDefaultSSLSocketFactory_null() { 2073 try { 2074 HttpsURLConnection.setDefaultSSLSocketFactory(null); 2075 fail(); 2076 } catch (IllegalArgumentException expected) { 2077 } 2078 } 2079 2080 /** 2081 * Test that the timeout period is honored. The connect timeout is applied to each socket 2082 * connection attempt. If a hostname resolves to multiple IPs HttpURLConnection will wait the 2083 * full timeout for each. 2084 */ 2085 public void testConnectTimeouts() throws IOException { 2086 // During CTS tests we are limited in what host names we can depend on and unfortunately 2087 // DNS lookups are not pluggable through standard APIs. During manual testing you should be 2088 // able to change this to any name that can be resolved to multiple IPs and it should still 2089 // work. 2090 String hostName = "localhost"; 2091 int expectedConnectionAttempts = InetAddress.getAllByName(hostName).length; 2092 int perSocketTimeout = 1000; 2093 final int[] socketCreationCount = new int[1]; 2094 final int[] socketConnectTimeouts = new int[expectedConnectionAttempts]; 2095 2096 // It is difficult to force socket timeouts reliably so we replace the default SocketFactory 2097 // and have it create Sockets that always time out when connect(SocketAddress, int) is 2098 // called. 2099 SocketFactory originalDefault = SocketFactory.getDefault(); 2100 try { 2101 // Override the default SocketFactory so we can intercept socket creation. 2102 SocketFactory.setDefault(new DelegatingSocketFactory(originalDefault) { 2103 @Override 2104 protected Socket configureSocket(Socket socket) throws IOException { 2105 final int attemptNumber = socketCreationCount[0]++; 2106 Socket socketWrapper = new DelegatingSocket(socket) { 2107 @Override 2108 public void connect(SocketAddress endpoint, int timeout) 2109 throws IOException { 2110 socketConnectTimeouts[attemptNumber] = timeout; 2111 throw new SocketTimeoutException("Simulated timeout after " + timeout); 2112 } 2113 }; 2114 return socketWrapper; 2115 } 2116 }); 2117 2118 URLConnection urlConnection = new URL("http://" + hostName + "/").openConnection(); 2119 urlConnection.setConnectTimeout(perSocketTimeout); 2120 urlConnection.getInputStream(); 2121 fail(); 2122 } catch (SocketTimeoutException e) { 2123 assertEquals(expectedConnectionAttempts, socketCreationCount[0]); 2124 for (int i = 0; i < expectedConnectionAttempts; i++) { 2125 assertEquals(perSocketTimeout, socketConnectTimeouts[i]); 2126 } 2127 } finally { 2128 SocketFactory.setDefault(originalDefault); 2129 } 2130 } 2131 2132 public void testReadTimeouts() throws IOException { 2133 /* 2134 * This relies on the fact that MockWebServer doesn't close the 2135 * connection after a response has been sent. This causes the client to 2136 * try to read more bytes than are sent, which results in a timeout. 2137 */ 2138 MockResponse timeout = new MockResponse() 2139 .setBody("ABC") 2140 .clearHeaders() 2141 .addHeader("Content-Length: 4"); 2142 server.enqueue(timeout); 2143 server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive 2144 server.play(); 2145 2146 URLConnection urlConnection = server.getUrl("/").openConnection(); 2147 urlConnection.setReadTimeout(1000); 2148 InputStream in = urlConnection.getInputStream(); 2149 assertEquals('A', in.read()); 2150 assertEquals('B', in.read()); 2151 assertEquals('C', in.read()); 2152 try { 2153 in.read(); // if Content-Length was accurate, this would return -1 immediately 2154 fail(); 2155 } catch (SocketTimeoutException expected) { 2156 } 2157 } 2158 2159 public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 2160 server.enqueue(new MockResponse()); 2161 server.play(); 2162 2163 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 2164 urlConnection.setRequestProperty("Transfer-encoding", "chunked"); 2165 urlConnection.setDoOutput(true); 2166 urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); 2167 assertEquals(200, urlConnection.getResponseCode()); 2168 2169 RecordedRequest request = server.takeRequest(); 2170 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 2171 } 2172 2173 public void testConnectionCloseInRequest() throws IOException, InterruptedException { 2174 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 2175 server.enqueue(new MockResponse()); 2176 server.play(); 2177 2178 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 2179 a.setRequestProperty("Connection", "close"); 2180 assertEquals(200, a.getResponseCode()); 2181 2182 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 2183 assertEquals(200, b.getResponseCode()); 2184 2185 assertEquals(0, server.takeRequest().getSequenceNumber()); 2186 assertEquals("When connection: close is used, each request should get its own connection", 2187 0, server.takeRequest().getSequenceNumber()); 2188 } 2189 2190 public void testConnectionCloseInResponse() throws IOException, InterruptedException { 2191 server.enqueue(new MockResponse().addHeader("Connection: close")); 2192 server.enqueue(new MockResponse()); 2193 server.play(); 2194 2195 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 2196 assertEquals(200, a.getResponseCode()); 2197 2198 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 2199 assertEquals(200, b.getResponseCode()); 2200 2201 assertEquals(0, server.takeRequest().getSequenceNumber()); 2202 assertEquals("When connection: close is used, each request should get its own connection", 2203 0, server.takeRequest().getSequenceNumber()); 2204 } 2205 2206 public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { 2207 MockResponse response = new MockResponse() 2208 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2209 .addHeader("Location: /foo") 2210 .addHeader("Connection: close"); 2211 server.enqueue(response); 2212 server.enqueue(new MockResponse().setBody("This is the new location!")); 2213 server.play(); 2214 2215 URLConnection connection = server.getUrl("/").openConnection(); 2216 assertEquals("This is the new location!", 2217 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2218 2219 assertEquals(0, server.takeRequest().getSequenceNumber()); 2220 assertEquals("When connection: close is used, each request should get its own connection", 2221 0, server.takeRequest().getSequenceNumber()); 2222 } 2223 2224 public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 2225 server.enqueue(new MockResponse() 2226 .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 2227 .setBody("This body is not allowed!")); 2228 server.play(); 2229 2230 URLConnection connection = server.getUrl("/").openConnection(); 2231 assertEquals("This body is not allowed!", 2232 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2233 } 2234 2235 public void testSingleByteReadIsSigned() throws IOException { 2236 server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); 2237 server.play(); 2238 2239 URLConnection connection = server.getUrl("/").openConnection(); 2240 InputStream in = connection.getInputStream(); 2241 assertEquals(254, in.read()); 2242 assertEquals(255, in.read()); 2243 assertEquals(-1, in.read()); 2244 } 2245 2246 public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException { 2247 testFlushAfterStreamTransmitted(TransferKind.CHUNKED); 2248 } 2249 2250 public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException { 2251 testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); 2252 } 2253 2254 public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { 2255 testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); 2256 } 2257 2258 /** 2259 * We explicitly permit apps to close the upload stream even after it has 2260 * been transmitted. We also permit flush so that buffered streams can 2261 * do a no-op flush when they are closed. http://b/3038470 2262 */ 2263 private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { 2264 server.enqueue(new MockResponse().setBody("abc")); 2265 server.play(); 2266 2267 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2268 connection.setDoOutput(true); 2269 byte[] upload = "def".getBytes("UTF-8"); 2270 2271 if (transferKind == TransferKind.CHUNKED) { 2272 connection.setChunkedStreamingMode(0); 2273 } else if (transferKind == TransferKind.FIXED_LENGTH) { 2274 connection.setFixedLengthStreamingMode(upload.length); 2275 } 2276 2277 OutputStream out = connection.getOutputStream(); 2278 out.write(upload); 2279 assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2280 2281 out.flush(); // dubious but permitted 2282 try { 2283 out.write("ghi".getBytes("UTF-8")); 2284 fail(); 2285 } catch (IOException expected) { 2286 } 2287 } 2288 2289 public void testGetHeadersThrows() throws IOException { 2290 server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); 2291 server.play(); 2292 2293 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2294 try { 2295 connection.getInputStream(); 2296 fail(); 2297 } catch (IOException expected) { 2298 } 2299 2300 try { 2301 connection.getInputStream(); 2302 fail(); 2303 } catch (IOException expected) { 2304 } 2305 } 2306 2307 public void testReadTimeoutsOnRecycledConnections() throws Exception { 2308 server.enqueue(new MockResponse().setBody("ABC")); 2309 server.play(); 2310 2311 // The request should work once and then fail 2312 URLConnection connection = server.getUrl("").openConnection(); 2313 // Read timeout of a day, sure to cause the test to timeout and fail. 2314 connection.setReadTimeout(24 * 3600 * 1000); 2315 InputStream input = connection.getInputStream(); 2316 assertEquals("ABC", readAscii(input, Integer.MAX_VALUE)); 2317 input.close(); 2318 try { 2319 connection = server.getUrl("").openConnection(); 2320 // Set the read timeout back to 100ms, this request will time out 2321 // because we've only enqueued one response. 2322 connection.setReadTimeout(100); 2323 connection.getInputStream(); 2324 fail(); 2325 } catch (IOException expected) { 2326 } 2327 } 2328 2329 /** 2330 * This test goes through the exhaustive set of interesting ASCII characters 2331 * because most of those characters are interesting in some way according to 2332 * RFC 2396 and RFC 2732. http://b/1158780 2333 * After M, Android's HttpURLConnection started canonicalizing hostnames to lower case, IDN 2334 * encoding and being more strict about invalid characters. 2335 */ 2336 public void testUrlCharacterMapping() throws Exception { 2337 server.setDispatcher(new Dispatcher() { 2338 @Override public MockResponse dispatch(RecordedRequest request) 2339 throws InterruptedException { 2340 return new MockResponse(); 2341 } 2342 }); 2343 server.play(); 2344 2345 // alphanum 2346 testUrlToUriMapping("abzABZ09", "abzabz09", "abzABZ09", "abzABZ09", "abzABZ09"); 2347 testUrlToRequestMapping("abzABZ09", "abzABZ09", "abzABZ09"); 2348 2349 // control characters 2350 2351 // On JB-MR2 and below, we would allow a host containing \u0000 2352 // and then generate a request with a Host header that violated RFC2616. 2353 // We now reject such hosts. 2354 // 2355 // The ideal behaviour here is to be "lenient" about the host and rewrite 2356 // it, but attempting to do so introduces a new range of incompatible 2357 // behaviours. 2358 testUrlToUriMapping("\u0000", null, "%00", "%00", "%00"); // RI fails this 2359 testUrlToRequestMapping("\u0000", "%00", "%00"); 2360 2361 testUrlToUriMapping("\u0001", null, "%01", "%01", "%01"); 2362 testUrlToRequestMapping("\u0001", "%01", "%01"); 2363 2364 testUrlToUriMapping("\u001f", null, "%1F", "%1F", "%1F"); 2365 testUrlToRequestMapping("\u001f", "%1F", "%1F"); 2366 2367 // ascii characters 2368 testUrlToUriMapping("%20", null, "%20", "%20", "%20"); 2369 testUrlToRequestMapping("%20", "%20", "%20"); 2370 testUrlToUriMapping(" ", null, "%20", "%20", "%20"); 2371 testUrlToRequestMapping(" ", "%20", "%20"); 2372 testUrlToUriMapping("!", "!", "!", "!", "!"); 2373 testUrlToRequestMapping("!", "!", "!"); 2374 testUrlToUriMapping("\"", null, "%22", "%22", "%22"); 2375 testUrlToRequestMapping("\"", "%22", "%22"); 2376 testUrlToUriMapping("#", null, null, null, "%23"); 2377 testUrlToRequestMapping("#", null, null); 2378 testUrlToUriMapping("$", "$", "$", "$", "$"); 2379 testUrlToRequestMapping("$", "$", "$"); 2380 testUrlToUriMapping("&", "&", "&", "&", "&"); 2381 testUrlToRequestMapping("&", "&", "&"); 2382 2383 // http://b/30405333 - upstream OkHttp encodes single quote (') as %27 in query parameters 2384 // but this breaks iTunes remote apps: iTunes currently does not accept %27 so we have a 2385 // local patch to retain the historic Android behavior of not encoding single quote. 2386 testUrlToUriMapping("'", "'", "'", "'", "'"); 2387 testUrlToRequestMapping("'", "'", "'"); 2388 2389 testUrlToUriMapping("(", "(", "(", "(", "("); 2390 testUrlToRequestMapping("(", "(", "("); 2391 testUrlToUriMapping(")", ")", ")", ")", ")"); 2392 testUrlToRequestMapping(")", ")", ")"); 2393 testUrlToUriMapping("*", "*", "*", "*", "*"); 2394 testUrlToRequestMapping("*", "*", "*"); 2395 testUrlToUriMapping("+", "+", "+", "+", "+"); 2396 testUrlToRequestMapping("+", "+", "+"); 2397 testUrlToUriMapping(",", ",", ",", ",", ","); 2398 testUrlToRequestMapping(",", ",", ","); 2399 testUrlToUriMapping("-", "-", "-", "-", "-"); 2400 testUrlToRequestMapping("-", "-", "-"); 2401 testUrlToUriMapping(".", null, ".", ".", "."); 2402 testUrlToRequestMapping(".", ".", "."); 2403 testUrlToUriMapping(".foo", ".foo", ".foo", ".foo", ".foo"); 2404 testUrlToRequestMapping(".foo", ".foo", ".foo"); 2405 testUrlToUriMapping("/", null, "/", "/", "/"); 2406 testUrlToRequestMapping("/", "/", "/"); 2407 testUrlToUriMapping(":", null, ":", ":", ":"); 2408 testUrlToRequestMapping(":", ":", ":"); 2409 testUrlToUriMapping(";", ";", ";", ";", ";"); 2410 testUrlToRequestMapping(";", ";", ";"); 2411 testUrlToUriMapping("<", null, "%3C", "%3C", "%3C"); 2412 testUrlToRequestMapping("<", "%3C", "%3C"); 2413 testUrlToUriMapping("=", "=", "=", "=", "="); 2414 testUrlToRequestMapping("=", "=", "="); 2415 testUrlToUriMapping(">", null, "%3E", "%3E", "%3E"); 2416 testUrlToRequestMapping(">", "%3E", "%3E"); 2417 testUrlToUriMapping("?", null, null, "?", "?"); 2418 testUrlToRequestMapping("?", null, "?"); 2419 testUrlToUriMapping("@", null, "@", "@", "@"); 2420 testUrlToRequestMapping("@", "@", "@"); 2421 testUrlToUriMapping("[", null, null, null, "%5B"); 2422 testUrlToRequestMapping("[", null, null); 2423 testUrlToUriMapping("\\", null, null, null, "%5C"); 2424 testUrlToRequestMapping("\\", null, null); 2425 testUrlToUriMapping("]", null, null, null, "%5D"); 2426 testUrlToRequestMapping("]", null, null); 2427 testUrlToUriMapping("^", null, "%5E", null, "%5E"); 2428 testUrlToRequestMapping("^", "%5E", null); 2429 testUrlToUriMapping("_", "_", "_", "_", "_"); 2430 testUrlToRequestMapping("_", "_", "_"); 2431 testUrlToUriMapping("`", null, "%60", null, "%60"); 2432 testUrlToRequestMapping("`", "%60", null); 2433 testUrlToUriMapping("{", null, "%7B", null, "%7B"); 2434 testUrlToRequestMapping("{", "%7B", null); 2435 testUrlToUriMapping("|", null, "%7C", null, "%7C"); 2436 testUrlToRequestMapping("|", "%7C", null); 2437 testUrlToUriMapping("}", null, "%7D", null, "%7D"); 2438 testUrlToRequestMapping("}", "%7D", null); 2439 testUrlToUriMapping("~", "~", "~", "~", "~"); 2440 testUrlToRequestMapping("~", "~", "~"); 2441 testUrlToUriMapping("\u007f", null, "%7F", "%7F", "%7F"); 2442 testUrlToRequestMapping("\u007f", "%7F", "%7F"); 2443 2444 // beyond ASCII 2445 2446 // 0x80 is the code point for the Euro sign in CP1252 (but not 8859-15 or Unicode). 2447 // Unicode code point 0x80 is a control character and maps to {0xC2, 0x80} in UTF-8. 2448 // 0x80 is outside of the ASCII range and is not supported by IDN in hostnames. 2449 testUrlToUriMapping("\u0080", null, "%C2%80", "%C2%80", "%C2%80"); 2450 testUrlToRequestMapping("\u0080", "%C2%80", "%C2%80"); 2451 2452 // More complicated transformations for the authorities below. 2453 2454 // 0x20AC is the code point for the Euro sign in Unicode. 2455 // Unicode code point 0x20AC maps to {0xE2, 0x82, 0xAC} in UTF-8 2456 // 0x20AC is not supported by all registrars but there are some legacy domains that 2457 // use it and Android currently supports IDN conversion for it. 2458 testUrlToUriMapping("\u20ac", null /* skip */, "%E2%82%AC", "%E2%82%AC", "%E2%82%AC"); 2459 testUrlToUriMappingAuthority("http://host\u20ac.tld/", "http://xn--host-yv7a.tld/"); 2460 testUrlToRequestMapping("\u20ac", "%E2%82%AC", "%E2%82%AC"); 2461 2462 // UTF-16 {0xD842, 0xDF9F} -> Unicode 0x20B9F (a Kanji character) 2463 // Unicode code point 0x20B9F maps to {0xF0, 0xA0, 0xAE, 0x9F} in UTF-8 2464 // IDN can deal with this code point. 2465 testUrlToUriMapping("\ud842\udf9f", null /* skip */, "%F0%A0%AE%9F", "%F0%A0%AE%9F", 2466 "%F0%A0%AE%9F"); 2467 testUrlToUriMappingAuthority("http://host\uD842\uDF9F.tld/", "http://xn--host-ov06c.tld/"); 2468 testUrlToRequestMapping("\ud842\udf9f", "%F0%A0%AE%9F", "%F0%A0%AE%9F"); 2469 } 2470 2471 private void testUrlToUriMappingAuthority(String urlString, String expectedUriString) 2472 throws Exception { 2473 URI authorityUri = backdoorUrlToUri(new URL(urlString)); 2474 assertEquals(expectedUriString, authorityUri.toString()); 2475 } 2476 2477 /** 2478 * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI, 2479 * HttpURLConnection recovers from URLs with unescaped but unsupported URI 2480 * characters like '{' and '|' by escaping these characters. 2481 */ 2482 private URI backdoorUrlToUri(URL url) throws Exception { 2483 final AtomicReference<URI> uriReference = new AtomicReference<URI>(); 2484 2485 ResponseCache.setDefault(new ResponseCache() { 2486 @Override public CacheRequest put(URI uri, URLConnection connection) 2487 throws IOException { 2488 return null; 2489 } 2490 @Override public CacheResponse get(URI uri, String requestMethod, 2491 Map<String, List<String>> requestHeaders) throws IOException { 2492 uriReference.set(uri); 2493 throw new UnsupportedOperationException(); 2494 } 2495 }); 2496 2497 try { 2498 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 2499 connection.getResponseCode(); 2500 } catch (Exception expected) { 2501 } 2502 2503 return uriReference.get(); 2504 } 2505 2506 /* 2507 * Test the request that would be made by making an actual request a MockWebServer and capturing 2508 * the request made. 2509 * 2510 * Any "as" values that are null are not tested. 2511 */ 2512 private void testUrlToRequestMapping( 2513 String string, String asFile, String asQuery) throws Exception { 2514 if (asFile != null) { 2515 URL fileUrl = server.getUrl("/file" + string + "/#discarded"); 2516 HttpURLConnection urlConnection = (HttpURLConnection) fileUrl.openConnection(); 2517 // Bypass the cache. 2518 urlConnection.setUseCaches(false); 2519 2520 assertEquals(200, urlConnection.getResponseCode()); 2521 assertEquals("/file" + asFile + "/", server.takeRequest().getPath()); 2522 } 2523 if (asQuery != null) { 2524 URL queryUrl = server.getUrl("/file?q" + string + "=x#discarded"); 2525 HttpURLConnection urlConnection = (HttpURLConnection) queryUrl.openConnection(); 2526 // Bypass the cache. 2527 urlConnection.setUseCaches(false); 2528 2529 assertEquals(200, urlConnection.getResponseCode()); 2530 assertEquals("/file?q" + asQuery + "=x", server.takeRequest().getPath()); 2531 } 2532 } 2533 2534 /* 2535 * Test the request that would be made by looking at the URI presented to the cache. This 2536 * includes the likely host name that would be used if a request were made. The cache throws an 2537 * exception so no request is actually made. 2538 * 2539 * Any "as" values that are null are not tested. 2540 */ 2541 private void testUrlToUriMapping(String string, String asAuthority, String asFile, 2542 String asQuery, String asFragment) throws Exception { 2543 if (asAuthority != null) { 2544 URI authorityUri = backdoorUrlToUri(new URL("http://host" + string + ".tld/")); 2545 assertEquals("http://host" + asAuthority + ".tld/", authorityUri.toString()); 2546 } 2547 if (asFile != null) { 2548 URI fileUri = backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")); 2549 assertEquals("http://host.tld/file" + asFile + "/", fileUri.toString()); 2550 } 2551 if (asQuery != null) { 2552 URI queryUri = backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")); 2553 assertEquals("http://host.tld/file?q" + asQuery + "=x", queryUri.toString()); 2554 } 2555 assertEquals("http://host.tld/file#" + asFragment + "-x", 2556 backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString()); 2557 } 2558 2559 public void testHostWithNul() throws Exception { 2560 URL url = new URL("http://host\u0000/"); 2561 try { 2562 url.openStream(); 2563 fail(); 2564 } catch (UnknownHostException expected) {} 2565 } 2566 2567 /** 2568 * Don't explode if the cache returns a null body. http://b/3373699 2569 */ 2570 public void testResponseCacheReturnsNullOutputStream() throws Exception { 2571 final AtomicBoolean aborted = new AtomicBoolean(); 2572 ResponseCache.setDefault(new ResponseCache() { 2573 @Override public CacheResponse get(URI uri, String requestMethod, 2574 Map<String, List<String>> requestHeaders) throws IOException { 2575 return null; 2576 } 2577 @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { 2578 return new CacheRequest() { 2579 @Override public void abort() { 2580 aborted.set(true); 2581 } 2582 @Override public OutputStream getBody() throws IOException { 2583 return null; 2584 } 2585 }; 2586 } 2587 }); 2588 2589 server.enqueue(new MockResponse().setBody("abcdef")); 2590 server.play(); 2591 2592 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2593 InputStream in = connection.getInputStream(); 2594 assertEquals("abc", readAscii(in, 3)); 2595 in.close(); 2596 assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here 2597 } 2598 2599 2600 /** 2601 * http://code.google.com/p/android/issues/detail?id=14562 2602 */ 2603 public void testReadAfterLastByte() throws Exception { 2604 server.enqueue(new MockResponse() 2605 .setBody("ABC") 2606 .clearHeaders() 2607 .addHeader("Connection: close") 2608 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); 2609 server.play(); 2610 2611 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2612 InputStream in = connection.getInputStream(); 2613 assertEquals("ABC", readAscii(in, 3)); 2614 assertEquals(-1, in.read()); 2615 assertEquals(-1, in.read()); // throws IOException in Gingerbread 2616 } 2617 2618 public void testGetContent() throws Exception { 2619 server.enqueue(new MockResponse().setBody("A")); 2620 server.play(); 2621 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2622 InputStream in = (InputStream) connection.getContent(); 2623 assertEquals("A", readAscii(in, Integer.MAX_VALUE)); 2624 } 2625 2626 public void testGetContentOfType() throws Exception { 2627 server.enqueue(new MockResponse().setBody("A")); 2628 server.play(); 2629 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2630 try { 2631 connection.getContent(null); 2632 fail(); 2633 } catch (NullPointerException expected) { 2634 } 2635 try { 2636 connection.getContent(new Class[] { null }); 2637 fail(); 2638 } catch (NullPointerException expected) { 2639 } 2640 assertNull(connection.getContent(new Class[] { getClass() })); 2641 connection.disconnect(); 2642 } 2643 2644 public void testGetOutputStreamOnGetFails() throws Exception { 2645 server.enqueue(new MockResponse()); 2646 server.play(); 2647 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2648 try { 2649 connection.getOutputStream(); 2650 fail(); 2651 } catch (ProtocolException expected) { 2652 } 2653 } 2654 2655 public void testGetOutputAfterGetInputStreamFails() throws Exception { 2656 server.enqueue(new MockResponse()); 2657 server.play(); 2658 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2659 connection.setDoOutput(true); 2660 try { 2661 connection.getInputStream(); 2662 connection.getOutputStream(); 2663 fail(); 2664 } catch (ProtocolException expected) { 2665 } 2666 } 2667 2668 public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception { 2669 server.enqueue(new MockResponse()); 2670 server.play(); 2671 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2672 connection.connect(); 2673 try { 2674 connection.setDoOutput(true); 2675 fail(); 2676 } catch (IllegalStateException expected) { 2677 } 2678 try { 2679 connection.setDoInput(true); 2680 fail(); 2681 } catch (IllegalStateException expected) { 2682 } 2683 connection.disconnect(); 2684 } 2685 2686 public void testLastModified() throws Exception { 2687 server.enqueue(new MockResponse() 2688 .addHeader("Last-Modified", "Wed, 27 Nov 2013 11:26:00 GMT") 2689 .setBody("Hello")); 2690 server.play(); 2691 2692 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2693 connection.connect(); 2694 2695 assertEquals(1385551560000L, connection.getLastModified()); 2696 assertEquals(1385551560000L, connection.getHeaderFieldDate("Last-Modified", -1)); 2697 } 2698 2699 public void testClientSendsContentLength() throws Exception { 2700 server.enqueue(new MockResponse().setBody("A")); 2701 server.play(); 2702 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2703 connection.setDoOutput(true); 2704 OutputStream out = connection.getOutputStream(); 2705 out.write(new byte[] { 'A', 'B', 'C' }); 2706 out.close(); 2707 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2708 RecordedRequest request = server.takeRequest(); 2709 assertContains(request.getHeaders(), "Content-Length: 3"); 2710 } 2711 2712 public void testGetContentLengthConnects() throws Exception { 2713 server.enqueue(new MockResponse().setBody("ABC")); 2714 server.play(); 2715 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2716 assertEquals(3, connection.getContentLength()); 2717 connection.disconnect(); 2718 } 2719 2720 public void testGetContentTypeConnects() throws Exception { 2721 server.enqueue(new MockResponse() 2722 .addHeader("Content-Type: text/plain") 2723 .setBody("ABC")); 2724 server.play(); 2725 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2726 assertEquals("text/plain", connection.getContentType()); 2727 connection.disconnect(); 2728 } 2729 2730 public void testGetContentEncodingConnects() throws Exception { 2731 server.enqueue(new MockResponse() 2732 .addHeader("Content-Encoding: identity") 2733 .setBody("ABC")); 2734 server.play(); 2735 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 2736 assertEquals("identity", connection.getContentEncoding()); 2737 connection.disconnect(); 2738 } 2739 2740 // http://b/4361656 2741 public void testUrlContainsQueryButNoPath() throws Exception { 2742 server.enqueue(new MockResponse().setBody("A")); 2743 server.play(); 2744 URL url = new URL("http", server.getHostName(), server.getPort(), "?query"); 2745 assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE)); 2746 RecordedRequest request = server.takeRequest(); 2747 assertEquals("GET /?query HTTP/1.1", request.getRequestLine()); 2748 } 2749 2750 // http://code.google.com/p/android/issues/detail?id=20442 2751 public void testInputStreamAvailableWithChunkedEncoding() throws Exception { 2752 testInputStreamAvailable(TransferKind.CHUNKED); 2753 } 2754 2755 public void testInputStreamAvailableWithContentLengthHeader() throws Exception { 2756 testInputStreamAvailable(TransferKind.FIXED_LENGTH); 2757 } 2758 2759 public void testInputStreamAvailableWithNoLengthHeaders() throws Exception { 2760 testInputStreamAvailable(TransferKind.END_OF_STREAM); 2761 } 2762 2763 private void testInputStreamAvailable(TransferKind transferKind) throws IOException { 2764 String body = "ABCDEFGH"; 2765 MockResponse response = new MockResponse(); 2766 transferKind.setBody(response, body, 4); 2767 server.enqueue(response); 2768 server.play(); 2769 URLConnection connection = server.getUrl("/").openConnection(); 2770 InputStream in = connection.getInputStream(); 2771 for (int i = 0; i < body.length(); i++) { 2772 assertTrue(in.available() >= 0); 2773 assertEquals(body.charAt(i), in.read()); 2774 } 2775 assertEquals(0, in.available()); 2776 assertEquals(-1, in.read()); 2777 } 2778 2779 // http://code.google.com/p/android/issues/detail?id=28095 2780 public void testInvalidIpv4Address() throws Exception { 2781 try { 2782 URI uri = new URI("http://1111.111.111.111/index.html"); 2783 uri.toURL().openConnection().connect(); 2784 fail(); 2785 } catch (UnknownHostException expected) { 2786 } 2787 } 2788 2789 public void testConnectIpv6() throws Exception { 2790 server.enqueue(new MockResponse().setBody("testConnectIpv6 body")); 2791 server.play(); 2792 URL url = new URL("http://[::1]:" + server.getPort() + "/"); 2793 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 2794 assertContent("testConnectIpv6 body", connection); 2795 } 2796 2797 // http://code.google.com/p/android/issues/detail?id=16895 2798 public void testUrlWithSpaceInHost() throws Exception { 2799 URLConnection urlConnection = new URL("http://and roid.com/").openConnection(); 2800 try { 2801 urlConnection.getInputStream(); 2802 fail(); 2803 } catch (UnknownHostException expected) { 2804 } 2805 } 2806 2807 // http://code.google.com/p/android/issues/detail?id=16895 2808 public void testUrlWithSpaceInHostViaHttpProxy() throws Exception { 2809 server.enqueue(new MockResponse()); 2810 server.play(); 2811 URLConnection urlConnection = new URL("http://and roid.com/") 2812 .openConnection(server.toProxyAddress()); 2813 2814 try { 2815 // This test is to check that a NullPointerException is not thrown. 2816 urlConnection.getInputStream(); 2817 fail(); // the RI makes a bogus proxy request for "GET http://and roid.com/ HTTP/1.1" 2818 } catch (UnknownHostException expected) { 2819 } 2820 } 2821 2822 /** Checks that if the first TLS handshake fails, no fallback is attempted. */ 2823 private void checkNoFallbackOnFailedHandshake(SSLSocketFactory clientSocketFactory, 2824 SSLSocketFactory serverSocketFactory, 2825 String... expectedProtocols) 2826 throws Exception { 2827 server.useHttps(serverSocketFactory, false); 2828 server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); 2829 server.enqueue(new MockResponse().setBody("This required fallbacks")); 2830 server.play(); 2831 2832 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 2833 // Keeps track of the client sockets created so that we can interrogate them. 2834 final boolean disableFallbackScsv = true; 2835 FallbackTestClientSocketFactory fallbackTestClientSocketFactory = 2836 new FallbackTestClientSocketFactory(clientSocketFactory, disableFallbackScsv); 2837 connection.setSSLSocketFactory(fallbackTestClientSocketFactory); 2838 try { 2839 connection.getInputStream().read(); 2840 fail(); 2841 } catch (SSLHandshakeException expected) { 2842 } 2843 List<SSLSocket> createdSockets = fallbackTestClientSocketFactory.getCreatedSockets(); 2844 assertEquals(1, createdSockets.size()); 2845 assertSslSocket((TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0), 2846 false /* expectedWasFallbackScsvSet */, expectedProtocols); 2847 } 2848 2849 public void testNoSslFallback_specifiedProtocols() throws Exception { 2850 String[] enabledProtocols = { "TLSv1.2", "TLSv1.1" }; 2851 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 2852 SSLSocketFactory serverSocketFactory = 2853 new LimitedProtocolsSocketFactory( 2854 testSSLContext.serverContext.getSocketFactory(), 2855 enabledProtocols); 2856 SSLSocketFactory clientSocketFactory = new LimitedProtocolsSocketFactory( 2857 testSSLContext.clientContext.getSocketFactory(), enabledProtocols); 2858 checkNoFallbackOnFailedHandshake(clientSocketFactory, serverSocketFactory, 2859 enabledProtocols); 2860 } 2861 2862 public void testNoSslFallback_defaultProtocols() throws Exception { 2863 // Will need to be updated if the enabled protocols in Android's SSLSocketFactory change 2864 String[] expectedEnabledProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1" }; 2865 2866 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 2867 SSLSocketFactory serverSocketFactory = testSSLContext.serverContext.getSocketFactory(); 2868 SSLSocketFactory clientSocketFactory = testSSLContext.clientContext.getSocketFactory(); 2869 checkNoFallbackOnFailedHandshake(clientSocketFactory, serverSocketFactory, 2870 expectedEnabledProtocols); 2871 } 2872 2873 private static void assertSslSocket(TlsFallbackDisabledScsvSSLSocket socket, 2874 boolean expectedWasFallbackScsvSet, String... expectedEnabledProtocols) { 2875 Set<String> enabledProtocols = 2876 new HashSet<String>(Arrays.asList(socket.getEnabledProtocols())); 2877 Set<String> expectedProtocolsSet = new HashSet<String>(Arrays.asList(expectedEnabledProtocols)); 2878 assertEquals(expectedProtocolsSet, enabledProtocols); 2879 assertEquals(expectedWasFallbackScsvSet, socket.wasTlsFallbackScsvSet()); 2880 } 2881 2882 public void testInspectSslBeforeConnect() throws Exception { 2883 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 2884 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 2885 server.enqueue(new MockResponse()); 2886 server.play(); 2887 2888 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 2889 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 2890 assertNotNull(connection.getHostnameVerifier()); 2891 try { 2892 connection.getLocalCertificates(); 2893 fail(); 2894 } catch (IllegalStateException expected) { 2895 } 2896 try { 2897 connection.getServerCertificates(); 2898 fail(); 2899 } catch (IllegalStateException expected) { 2900 } 2901 try { 2902 connection.getCipherSuite(); 2903 fail(); 2904 } catch (IllegalStateException expected) { 2905 } 2906 try { 2907 connection.getPeerPrincipal(); 2908 fail(); 2909 } catch (IllegalStateException expected) { 2910 } 2911 } 2912 2913 /** 2914 * Test that we can inspect the SSL session after connect(). 2915 * http://code.google.com/p/android/issues/detail?id=24431 2916 */ 2917 public void testInspectSslAfterConnect() throws Exception { 2918 TestSSLContext testSSLContext = createDefaultTestSSLContext(); 2919 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 2920 server.enqueue(new MockResponse()); 2921 server.play(); 2922 2923 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 2924 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 2925 connection.connect(); 2926 try { 2927 assertNotNull(connection.getHostnameVerifier()); 2928 assertNull(connection.getLocalCertificates()); 2929 assertNotNull(connection.getServerCertificates()); 2930 assertNotNull(connection.getCipherSuite()); 2931 assertNotNull(connection.getPeerPrincipal()); 2932 } finally { 2933 connection.disconnect(); 2934 } 2935 } 2936 2937 /** 2938 * Checks that OkHttp's certificate pinning logic is not used for the common case 2939 * of HttpsUrlConnections. 2940 * 2941 * <p>OkHttp 2.7 introduced logic for Certificate Pinning. We deliberately don't 2942 * expose any API surface that would interact with OkHttp's implementation because 2943 * Android has its own API / implementation for certificate pinning. We can't 2944 * easily test that there is *no* code path that would invoke OkHttp's certificate 2945 * pinning logic, so this test only covers the *common* code path of a 2946 * HttpsURLConnection as a sanity check. 2947 * 2948 * <p>To check whether OkHttp performs certificate pinning under the hood, this 2949 * test disables two {@link Platform} methods. In OkHttp 2.7.5, these two methods 2950 * are exclusively used in relation to certificate pinning. Android only provides 2951 * the minimal implementation of these methods to get OkHttp's tests to pass, so 2952 * they should never be invoked outside of OkHttp's tests. 2953 */ 2954 public void testTrustManagerAndTrustRootIndex_unusedForHttpsConnection() throws Exception { 2955 Platform platform = Platform.getAndSetForTest(new PlatformWithoutTrustManager()); 2956 try { 2957 testConnectViaHttps(); 2958 } finally { 2959 Platform.getAndSetForTest(platform); 2960 } 2961 } 2962 2963 /** 2964 * Similar to {@link #testTrustManagerAndTrustRootIndex_unusedForHttpsConnection()}, 2965 * but for the HTTP case. In the HTTP case, no certificate or trust management 2966 * related logic should ever be involved at all, so some pretty basic things must 2967 * be going wrong in order for this test to (unexpectedly) invoke the corresponding 2968 * Platform methods. 2969 */ 2970 public void testTrustManagerAndTrustRootIndex_unusedForHttpConnection() throws Exception { 2971 Platform platform = Platform.getAndSetForTest(new PlatformWithoutTrustManager()); 2972 try { 2973 server.enqueue(new MockResponse().setBody("response").setResponseCode(200)); 2974 server.play(); 2975 HttpURLConnection urlConnection = 2976 (HttpURLConnection) server.getUrl("/").openConnection(); 2977 assertEquals(200, urlConnection.getResponseCode()); 2978 } finally { 2979 Platform.getAndSetForTest(platform); 2980 } 2981 } 2982 2983 /** 2984 * A {@link Platform} that doesn't support two methods that, in OkHttp 2.7.5, 2985 * are exclusively used to provide custom CertificatePinning. 2986 */ 2987 static class PlatformWithoutTrustManager extends Platform { 2988 @Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { 2989 throw new AssertionError("Unexpected call"); 2990 } 2991 @Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { 2992 throw new AssertionError("Unexpected call"); 2993 } 2994 } 2995 2996 /** 2997 * Returns a gzipped copy of {@code bytes}. 2998 */ 2999 public byte[] gzip(byte[] bytes) throws IOException { 3000 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 3001 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 3002 gzippedOut.write(bytes); 3003 gzippedOut.close(); 3004 return bytesOut.toByteArray(); 3005 } 3006 3007 /** 3008 * Reads at most {@code limit} characters from {@code in} and asserts that 3009 * content equals {@code expected}. 3010 */ 3011 private void assertContent(String expected, URLConnection connection, int limit) 3012 throws IOException { 3013 connection.connect(); 3014 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 3015 ((HttpURLConnection) connection).disconnect(); 3016 } 3017 3018 private void assertContent(String expected, URLConnection connection) throws IOException { 3019 assertContent(expected, connection, Integer.MAX_VALUE); 3020 } 3021 3022 private static void assertHeaderPresent(RecordedRequest request, String headerName) { 3023 assertNotNull(headerName + " missing: " + request.getHeaders(), 3024 request.getHeader(headerName)); 3025 } 3026 3027 private void assertContains(List<String> list, String value) { 3028 assertTrue(list.toString(), list.contains(value)); 3029 } 3030 3031 private void assertContainsNoneMatching(List<String> list, String pattern) { 3032 for (String header : list) { 3033 if (header.matches(pattern)) { 3034 fail("Header " + header + " matches " + pattern); 3035 } 3036 } 3037 } 3038 3039 private Set<String> newSet(String... elements) { 3040 return new HashSet<String>(Arrays.asList(elements)); 3041 } 3042 3043 private TestSSLContext createDefaultTestSSLContext() { 3044 TestSSLContext result = TestSSLContext.create(); 3045 testSSLContextsToClose.add(result); 3046 return result; 3047 } 3048 3049 enum TransferKind { 3050 CHUNKED() { 3051 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 3052 throws IOException { 3053 response.setChunkedBody(content, chunkSize); 3054 } 3055 }, 3056 FIXED_LENGTH() { 3057 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 3058 response.setBody(content); 3059 } 3060 }, 3061 END_OF_STREAM() { 3062 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 3063 response.setBody(content); 3064 response.setSocketPolicy(DISCONNECT_AT_END); 3065 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 3066 if (h.next().startsWith("Content-Length:")) { 3067 h.remove(); 3068 break; 3069 } 3070 } 3071 } 3072 }; 3073 3074 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 3075 throws IOException; 3076 3077 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 3078 setBody(response, content.getBytes("UTF-8"), chunkSize); 3079 } 3080 } 3081 3082 enum ProxyConfig { 3083 NO_PROXY() { 3084 @Override public HttpURLConnection connect(MockWebServer server, URL url) 3085 throws IOException { 3086 return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); 3087 } 3088 }, 3089 3090 CREATE_ARG() { 3091 @Override public HttpURLConnection connect(MockWebServer server, URL url) 3092 throws IOException { 3093 return (HttpURLConnection) url.openConnection(server.toProxyAddress()); 3094 } 3095 }, 3096 3097 PROXY_SYSTEM_PROPERTY() { 3098 @Override public HttpURLConnection connect(MockWebServer server, URL url) 3099 throws IOException { 3100 System.setProperty("proxyHost", "localhost"); 3101 System.setProperty("proxyPort", Integer.toString(server.getPort())); 3102 return (HttpURLConnection) url.openConnection(); 3103 } 3104 }, 3105 3106 HTTP_PROXY_SYSTEM_PROPERTY() { 3107 @Override public HttpURLConnection connect(MockWebServer server, URL url) 3108 throws IOException { 3109 System.setProperty("http.proxyHost", "localhost"); 3110 System.setProperty("http.proxyPort", Integer.toString(server.getPort())); 3111 return (HttpURLConnection) url.openConnection(); 3112 } 3113 }, 3114 3115 HTTPS_PROXY_SYSTEM_PROPERTY() { 3116 @Override public HttpURLConnection connect(MockWebServer server, URL url) 3117 throws IOException { 3118 System.setProperty("https.proxyHost", "localhost"); 3119 System.setProperty("https.proxyPort", Integer.toString(server.getPort())); 3120 return (HttpURLConnection) url.openConnection(); 3121 } 3122 }; 3123 3124 public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException; 3125 } 3126 3127 private static class RecordingTrustManager implements X509TrustManager { 3128 private final List<String> calls = new ArrayList<String>(); 3129 3130 public X509Certificate[] getAcceptedIssuers() { 3131 calls.add("getAcceptedIssuers"); 3132 return new X509Certificate[] {}; 3133 } 3134 3135 public void checkClientTrusted(X509Certificate[] chain, String authType) 3136 throws CertificateException { 3137 calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); 3138 } 3139 3140 public void checkServerTrusted(X509Certificate[] chain, String authType) 3141 throws CertificateException { 3142 calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); 3143 } 3144 3145 private String certificatesToString(X509Certificate[] certificates) { 3146 List<String> result = new ArrayList<String>(); 3147 for (X509Certificate certificate : certificates) { 3148 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 3149 } 3150 return result.toString(); 3151 } 3152 } 3153 3154 private static class RecordingHostnameVerifier implements HostnameVerifier { 3155 private final List<String> calls = new ArrayList<String>(); 3156 3157 public boolean verify(String hostname, SSLSession session) { 3158 calls.add("verify " + hostname); 3159 return true; 3160 } 3161 } 3162 3163 private static class SimpleAuthenticator extends Authenticator { 3164 /** base64("username:password") */ 3165 private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ="; 3166 3167 private String expectedPrompt; 3168 private RequestorType requestorType; 3169 private int requestingPort; 3170 private InetAddress requestingSite; 3171 private String requestingPrompt; 3172 private String requestingProtocol; 3173 private String requestingScheme; 3174 3175 protected PasswordAuthentication getPasswordAuthentication() { 3176 requestorType = getRequestorType(); 3177 requestingPort = getRequestingPort(); 3178 requestingSite = getRequestingSite(); 3179 requestingPrompt = getRequestingPrompt(); 3180 requestingProtocol = getRequestingProtocol(); 3181 requestingScheme = getRequestingScheme(); 3182 return (expectedPrompt == null || expectedPrompt.equals(requestingPrompt)) 3183 ? new PasswordAuthentication("username", "password".toCharArray()) 3184 : null; 3185 } 3186 } 3187 3188 /** 3189 * An SSLSocketFactory that delegates all calls. 3190 */ 3191 private static class DelegatingSSLSocketFactory extends SSLSocketFactory { 3192 3193 protected final SSLSocketFactory delegate; 3194 3195 public DelegatingSSLSocketFactory(SSLSocketFactory delegate) { 3196 this.delegate = delegate; 3197 } 3198 3199 @Override 3200 public String[] getDefaultCipherSuites() { 3201 return delegate.getDefaultCipherSuites(); 3202 } 3203 3204 @Override 3205 public String[] getSupportedCipherSuites() { 3206 return delegate.getSupportedCipherSuites(); 3207 } 3208 3209 @Override 3210 public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) 3211 throws IOException { 3212 return (SSLSocket) delegate.createSocket(s, host, port, autoClose); 3213 } 3214 3215 @Override 3216 public SSLSocket createSocket() throws IOException { 3217 return (SSLSocket) delegate.createSocket(); 3218 } 3219 3220 @Override 3221 public SSLSocket createSocket(String host, int port) 3222 throws IOException, UnknownHostException { 3223 return (SSLSocket) delegate.createSocket(host, port); 3224 } 3225 3226 @Override 3227 public SSLSocket createSocket(String host, int port, InetAddress localHost, 3228 int localPort) throws IOException, UnknownHostException { 3229 return (SSLSocket) delegate.createSocket(host, port, localHost, localPort); 3230 } 3231 3232 @Override 3233 public SSLSocket createSocket(InetAddress host, int port) throws IOException { 3234 return (SSLSocket) delegate.createSocket(host, port); 3235 } 3236 3237 @Override 3238 public SSLSocket createSocket(InetAddress address, int port, 3239 InetAddress localAddress, int localPort) throws IOException { 3240 return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); 3241 } 3242 3243 } 3244 3245 /** 3246 * An SSLSocketFactory that delegates calls but limits the enabled protocols for any created 3247 * sockets. 3248 */ 3249 private static class LimitedProtocolsSocketFactory extends DelegatingSSLSocketFactory { 3250 3251 private final String[] protocols; 3252 3253 private LimitedProtocolsSocketFactory(SSLSocketFactory delegate, String... protocols) { 3254 super(delegate); 3255 this.protocols = protocols; 3256 } 3257 3258 @Override 3259 public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) 3260 throws IOException { 3261 SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose); 3262 socket.setEnabledProtocols(protocols); 3263 return socket; 3264 } 3265 3266 @Override 3267 public SSLSocket createSocket() throws IOException { 3268 SSLSocket socket = (SSLSocket) delegate.createSocket(); 3269 socket.setEnabledProtocols(protocols); 3270 return socket; 3271 } 3272 3273 @Override 3274 public SSLSocket createSocket(String host, int port) 3275 throws IOException, UnknownHostException { 3276 SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); 3277 socket.setEnabledProtocols(protocols); 3278 return socket; 3279 } 3280 3281 @Override 3282 public SSLSocket createSocket(String host, int port, InetAddress localHost, 3283 int localPort) throws IOException, UnknownHostException { 3284 SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort); 3285 socket.setEnabledProtocols(protocols); 3286 return socket; 3287 } 3288 3289 @Override 3290 public SSLSocket createSocket(InetAddress host, int port) throws IOException { 3291 SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); 3292 socket.setEnabledProtocols(protocols); 3293 return socket; 3294 } 3295 3296 @Override 3297 public SSLSocket createSocket(InetAddress address, int port, 3298 InetAddress localAddress, int localPort) throws IOException { 3299 SSLSocket socket = 3300 (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); 3301 socket.setEnabledProtocols(protocols); 3302 return socket; 3303 } 3304 } 3305 3306 /** 3307 * A Socket that forwards all calls to public or protected methods, except for those 3308 * that Socket inherits from Object, to a delegate. 3309 */ 3310 private static abstract class DelegatingSocket extends Socket { 3311 private final Socket delegate; 3312 3313 public DelegatingSocket(Socket delegate) { 3314 if (delegate == null) { 3315 throw new NullPointerException(); 3316 } 3317 this.delegate = delegate; 3318 } 3319 3320 @Override public void bind(SocketAddress bindpoint) throws IOException { delegate.bind(bindpoint); } 3321 @Override public void close() throws IOException { delegate.close(); } 3322 @Override public void connect(SocketAddress endpoint) throws IOException { delegate.connect(endpoint); } 3323 @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { delegate.connect(endpoint, timeout); } 3324 @Override public SocketChannel getChannel() { return delegate.getChannel(); } 3325 @Override public FileDescriptor getFileDescriptor$() { return delegate.getFileDescriptor$(); } 3326 @Override public InetAddress getInetAddress() { return delegate.getInetAddress(); } 3327 @Override public InputStream getInputStream() throws IOException { return delegate.getInputStream(); } 3328 @Override public boolean getKeepAlive() throws SocketException { return delegate.getKeepAlive(); } 3329 @Override public InetAddress getLocalAddress() { return delegate.getLocalAddress(); } 3330 @Override public int getLocalPort() { return delegate.getLocalPort(); } 3331 @Override public SocketAddress getLocalSocketAddress() { return delegate.getLocalSocketAddress(); } 3332 @Override public boolean getOOBInline() throws SocketException { return delegate.getOOBInline(); } 3333 @Override public OutputStream getOutputStream() throws IOException { return delegate.getOutputStream(); } 3334 @Override public int getPort() { return delegate.getPort(); } 3335 @Override public int getReceiveBufferSize() throws SocketException { return delegate.getReceiveBufferSize(); } 3336 @Override public SocketAddress getRemoteSocketAddress() { return delegate.getRemoteSocketAddress(); } 3337 @Override public boolean getReuseAddress() throws SocketException { return delegate.getReuseAddress(); } 3338 @Override public int getSendBufferSize() throws SocketException { return delegate.getSendBufferSize(); } 3339 @Override public int getSoLinger() throws SocketException { return delegate.getSoLinger(); } 3340 @Override public int getSoTimeout() throws SocketException { return delegate.getSoTimeout(); } 3341 @Override public boolean getTcpNoDelay() throws SocketException { return delegate.getTcpNoDelay(); } 3342 @Override public int getTrafficClass() throws SocketException { return delegate.getTrafficClass(); } 3343 @Override public boolean isBound() { return delegate.isBound(); } 3344 @Override public boolean isClosed() { return delegate.isClosed(); } 3345 @Override public boolean isConnected() { return delegate.isConnected(); } 3346 @Override public boolean isInputShutdown() { return delegate.isInputShutdown(); } 3347 @Override public boolean isOutputShutdown() { return delegate.isOutputShutdown(); } 3348 @Override public void sendUrgentData(int data) throws IOException { delegate.sendUrgentData(data); } 3349 @Override public void setKeepAlive(boolean on) throws SocketException { delegate.setKeepAlive(on); } 3350 @Override public void setOOBInline(boolean on) throws SocketException { delegate.setOOBInline(on); } 3351 @Override public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { delegate.setPerformancePreferences(connectionTime, latency, bandwidth); } 3352 @Override public void setReceiveBufferSize(int size) throws SocketException { delegate.setReceiveBufferSize(size); } 3353 @Override public void setReuseAddress(boolean on) throws SocketException { delegate.setReuseAddress(on); } 3354 @Override public void setSendBufferSize(int size) throws SocketException { delegate.setSendBufferSize(size); } 3355 @Override public void setSoLinger(boolean on, int linger) throws SocketException { delegate.setSoLinger(on, linger); } 3356 @Override public void setSoTimeout(int timeout) throws SocketException { delegate.setSoTimeout(timeout); } 3357 @Override public void setTcpNoDelay(boolean on) throws SocketException { delegate.setTcpNoDelay(on); } 3358 @Override public void setTrafficClass(int tc) throws SocketException { delegate.setTrafficClass(tc); } 3359 @Override public void shutdownInput() throws IOException { delegate.shutdownInput(); } 3360 @Override public void shutdownOutput() throws IOException { delegate.shutdownOutput(); } 3361 @Override public String toString() { return delegate.toString(); } 3362 } 3363 3364 /** 3365 * An {@link javax.net.ssl.SSLSocket} that delegates all calls. 3366 */ 3367 private static abstract class DelegatingSSLSocket extends SSLSocket { 3368 protected final SSLSocket delegate; 3369 3370 public DelegatingSSLSocket(SSLSocket delegate) { 3371 this.delegate = delegate; 3372 } 3373 3374 @Override public void shutdownInput() throws IOException { 3375 delegate.shutdownInput(); 3376 } 3377 3378 @Override public void shutdownOutput() throws IOException { 3379 delegate.shutdownOutput(); 3380 } 3381 3382 @Override public String[] getSupportedCipherSuites() { 3383 return delegate.getSupportedCipherSuites(); 3384 } 3385 3386 @Override public String[] getEnabledCipherSuites() { 3387 return delegate.getEnabledCipherSuites(); 3388 } 3389 3390 @Override public void setEnabledCipherSuites(String[] suites) { 3391 delegate.setEnabledCipherSuites(suites); 3392 } 3393 3394 @Override public String[] getSupportedProtocols() { 3395 return delegate.getSupportedProtocols(); 3396 } 3397 3398 @Override public String[] getEnabledProtocols() { 3399 return delegate.getEnabledProtocols(); 3400 } 3401 3402 @Override public void setEnabledProtocols(String[] protocols) { 3403 delegate.setEnabledProtocols(protocols); 3404 } 3405 3406 @Override public SSLSession getSession() { 3407 return delegate.getSession(); 3408 } 3409 3410 @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { 3411 delegate.addHandshakeCompletedListener(listener); 3412 } 3413 3414 @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { 3415 delegate.removeHandshakeCompletedListener(listener); 3416 } 3417 3418 @Override public void startHandshake() throws IOException { 3419 delegate.startHandshake(); 3420 } 3421 3422 @Override public void setUseClientMode(boolean mode) { 3423 delegate.setUseClientMode(mode); 3424 } 3425 3426 @Override public boolean getUseClientMode() { 3427 return delegate.getUseClientMode(); 3428 } 3429 3430 @Override public void setNeedClientAuth(boolean need) { 3431 delegate.setNeedClientAuth(need); 3432 } 3433 3434 @Override public void setWantClientAuth(boolean want) { 3435 delegate.setWantClientAuth(want); 3436 } 3437 3438 @Override public boolean getNeedClientAuth() { 3439 return delegate.getNeedClientAuth(); 3440 } 3441 3442 @Override public boolean getWantClientAuth() { 3443 return delegate.getWantClientAuth(); 3444 } 3445 3446 @Override public void setEnableSessionCreation(boolean flag) { 3447 delegate.setEnableSessionCreation(flag); 3448 } 3449 3450 @Override public boolean getEnableSessionCreation() { 3451 return delegate.getEnableSessionCreation(); 3452 } 3453 3454 @Override public SSLParameters getSSLParameters() { 3455 return delegate.getSSLParameters(); 3456 } 3457 3458 @Override public void setSSLParameters(SSLParameters p) { 3459 delegate.setSSLParameters(p); 3460 } 3461 3462 @Override public void close() throws IOException { 3463 delegate.close(); 3464 } 3465 3466 @Override public InetAddress getInetAddress() { 3467 return delegate.getInetAddress(); 3468 } 3469 3470 @Override public InputStream getInputStream() throws IOException { 3471 return delegate.getInputStream(); 3472 } 3473 3474 @Override public boolean getKeepAlive() throws SocketException { 3475 return delegate.getKeepAlive(); 3476 } 3477 3478 @Override public InetAddress getLocalAddress() { 3479 return delegate.getLocalAddress(); 3480 } 3481 3482 @Override public int getLocalPort() { 3483 return delegate.getLocalPort(); 3484 } 3485 3486 @Override public OutputStream getOutputStream() throws IOException { 3487 return delegate.getOutputStream(); 3488 } 3489 3490 @Override public int getPort() { 3491 return delegate.getPort(); 3492 } 3493 3494 @Override public int getSoLinger() throws SocketException { 3495 return delegate.getSoLinger(); 3496 } 3497 3498 @Override public int getReceiveBufferSize() throws SocketException { 3499 return delegate.getReceiveBufferSize(); 3500 } 3501 3502 @Override public int getSendBufferSize() throws SocketException { 3503 return delegate.getSendBufferSize(); 3504 } 3505 3506 @Override public int getSoTimeout() throws SocketException { 3507 return delegate.getSoTimeout(); 3508 } 3509 3510 @Override public boolean getTcpNoDelay() throws SocketException { 3511 return delegate.getTcpNoDelay(); 3512 } 3513 3514 @Override public void setKeepAlive(boolean keepAlive) throws SocketException { 3515 delegate.setKeepAlive(keepAlive); 3516 } 3517 3518 @Override public void setSendBufferSize(int size) throws SocketException { 3519 delegate.setSendBufferSize(size); 3520 } 3521 3522 @Override public void setReceiveBufferSize(int size) throws SocketException { 3523 delegate.setReceiveBufferSize(size); 3524 } 3525 3526 @Override public void setSoLinger(boolean on, int timeout) throws SocketException { 3527 delegate.setSoLinger(on, timeout); 3528 } 3529 3530 @Override public void setSoTimeout(int timeout) throws SocketException { 3531 delegate.setSoTimeout(timeout); 3532 } 3533 3534 @Override public void setTcpNoDelay(boolean on) throws SocketException { 3535 delegate.setTcpNoDelay(on); 3536 } 3537 3538 @Override public String toString() { 3539 return delegate.toString(); 3540 } 3541 3542 @Override public SocketAddress getLocalSocketAddress() { 3543 return delegate.getLocalSocketAddress(); 3544 } 3545 3546 @Override public SocketAddress getRemoteSocketAddress() { 3547 return delegate.getRemoteSocketAddress(); 3548 } 3549 3550 @Override public boolean isBound() { 3551 return delegate.isBound(); 3552 } 3553 3554 @Override public boolean isConnected() { 3555 return delegate.isConnected(); 3556 } 3557 3558 @Override public boolean isClosed() { 3559 return delegate.isClosed(); 3560 } 3561 3562 @Override public void bind(SocketAddress localAddr) throws IOException { 3563 delegate.bind(localAddr); 3564 } 3565 3566 @Override public void connect(SocketAddress remoteAddr) throws IOException { 3567 delegate.connect(remoteAddr); 3568 } 3569 3570 @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException { 3571 delegate.connect(remoteAddr, timeout); 3572 } 3573 3574 @Override public boolean isInputShutdown() { 3575 return delegate.isInputShutdown(); 3576 } 3577 3578 @Override public boolean isOutputShutdown() { 3579 return delegate.isOutputShutdown(); 3580 } 3581 3582 @Override public void setReuseAddress(boolean reuse) throws SocketException { 3583 delegate.setReuseAddress(reuse); 3584 } 3585 3586 @Override public boolean getReuseAddress() throws SocketException { 3587 return delegate.getReuseAddress(); 3588 } 3589 3590 @Override public void setOOBInline(boolean oobinline) throws SocketException { 3591 delegate.setOOBInline(oobinline); 3592 } 3593 3594 @Override public boolean getOOBInline() throws SocketException { 3595 return delegate.getOOBInline(); 3596 } 3597 3598 @Override public void setTrafficClass(int value) throws SocketException { 3599 delegate.setTrafficClass(value); 3600 } 3601 3602 @Override public int getTrafficClass() throws SocketException { 3603 return delegate.getTrafficClass(); 3604 } 3605 3606 @Override public void sendUrgentData(int value) throws IOException { 3607 delegate.sendUrgentData(value); 3608 } 3609 3610 @Override public SocketChannel getChannel() { 3611 return delegate.getChannel(); 3612 } 3613 3614 @Override public void setPerformancePreferences(int connectionTime, int latency, 3615 int bandwidth) { 3616 delegate.setPerformancePreferences(connectionTime, latency, bandwidth); 3617 } 3618 } 3619 3620 /** 3621 * An SSLSocketFactory that delegates calls. It keeps a record of any sockets created. 3622 * If {@link #disableTlsFallbackScsv} is set to {@code true} then sockets created by the 3623 * delegate are wrapped with ones that will not accept the {@link #TLS_FALLBACK_SCSV} cipher, 3624 * thus bypassing server-side fallback checks on platforms that support it. Unfortunately this 3625 * wrapping will disable any reflection-based calls to SSLSocket from Platform. 3626 */ 3627 private static class FallbackTestClientSocketFactory extends DelegatingSSLSocketFactory { 3628 /** 3629 * The cipher suite used during TLS connection fallback to indicate a fallback. 3630 * See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 3631 */ 3632 public static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"; 3633 3634 private final boolean disableTlsFallbackScsv; 3635 private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>(); 3636 3637 public FallbackTestClientSocketFactory(SSLSocketFactory delegate, 3638 boolean disableTlsFallbackScsv) { 3639 super(delegate); 3640 this.disableTlsFallbackScsv = disableTlsFallbackScsv; 3641 } 3642 3643 @Override public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) 3644 throws IOException { 3645 SSLSocket socket = super.createSocket(s, host, port, autoClose); 3646 if (disableTlsFallbackScsv) { 3647 socket = new TlsFallbackDisabledScsvSSLSocket(socket); 3648 } 3649 createdSockets.add(socket); 3650 return socket; 3651 } 3652 3653 @Override public SSLSocket createSocket() throws IOException { 3654 SSLSocket socket = super.createSocket(); 3655 if (disableTlsFallbackScsv) { 3656 socket = new TlsFallbackDisabledScsvSSLSocket(socket); 3657 } 3658 createdSockets.add(socket); 3659 return socket; 3660 } 3661 3662 @Override public SSLSocket createSocket(String host,int port) throws IOException { 3663 SSLSocket socket = super.createSocket(host, port); 3664 if (disableTlsFallbackScsv) { 3665 socket = new TlsFallbackDisabledScsvSSLSocket(socket); 3666 } 3667 createdSockets.add(socket); 3668 return socket; 3669 } 3670 3671 @Override public SSLSocket createSocket(String host,int port, InetAddress localHost, 3672 int localPort) throws IOException { 3673 SSLSocket socket = super.createSocket(host, port, localHost, localPort); 3674 if (disableTlsFallbackScsv) { 3675 socket = new TlsFallbackDisabledScsvSSLSocket(socket); 3676 } 3677 createdSockets.add(socket); 3678 return socket; 3679 } 3680 3681 @Override public SSLSocket createSocket(InetAddress host,int port) throws IOException { 3682 SSLSocket socket = super.createSocket(host, port); 3683 if (disableTlsFallbackScsv) { 3684 socket = new TlsFallbackDisabledScsvSSLSocket(socket); 3685 } 3686 createdSockets.add(socket); 3687 return socket; 3688 } 3689 3690 @Override public SSLSocket createSocket(InetAddress address,int port, 3691 InetAddress localAddress, int localPort) throws IOException { 3692 SSLSocket socket = super.createSocket(address, port, localAddress, localPort); 3693 if (disableTlsFallbackScsv) { 3694 socket = new TlsFallbackDisabledScsvSSLSocket(socket); 3695 } 3696 createdSockets.add(socket); 3697 return socket; 3698 } 3699 3700 public List<SSLSocket> getCreatedSockets() { 3701 return createdSockets; 3702 } 3703 } 3704 3705 private static class TlsFallbackDisabledScsvSSLSocket extends DelegatingSSLSocket { 3706 3707 private boolean tlsFallbackScsvSet; 3708 3709 public TlsFallbackDisabledScsvSSLSocket(SSLSocket socket) { 3710 super(socket); 3711 } 3712 3713 @Override public void setEnabledCipherSuites(String[] suites) { 3714 List<String> enabledCipherSuites = new ArrayList<String>(suites.length); 3715 for (String suite : suites) { 3716 if (suite.equals(FallbackTestClientSocketFactory.TLS_FALLBACK_SCSV)) { 3717 // Record that an attempt was made to set TLS_FALLBACK_SCSV, but don't actually 3718 // set it. 3719 tlsFallbackScsvSet = true; 3720 } else { 3721 enabledCipherSuites.add(suite); 3722 } 3723 } 3724 delegate.setEnabledCipherSuites( 3725 enabledCipherSuites.toArray(new String[enabledCipherSuites.size()])); 3726 } 3727 3728 public boolean wasTlsFallbackScsvSet() { 3729 return tlsFallbackScsvSet; 3730 } 3731 } 3732} 3733