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