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