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 com.squareup.okhttp.internal.http; 18 19import com.squareup.okhttp.ConnectionPool; 20import com.squareup.okhttp.HttpResponseCache; 21import com.squareup.okhttp.OkAuthenticator.Challenge; 22import com.squareup.okhttp.OkAuthenticator.Credential; 23import com.squareup.okhttp.OkHttpClient; 24import com.squareup.okhttp.Protocol; 25import com.squareup.okhttp.internal.RecordingAuthenticator; 26import com.squareup.okhttp.internal.RecordingHostnameVerifier; 27import com.squareup.okhttp.internal.RecordingOkAuthenticator; 28import com.squareup.okhttp.internal.SslContextBuilder; 29import com.squareup.okhttp.mockwebserver.MockResponse; 30import com.squareup.okhttp.mockwebserver.MockWebServer; 31import com.squareup.okhttp.mockwebserver.RecordedRequest; 32import com.squareup.okhttp.mockwebserver.SocketPolicy; 33import java.io.ByteArrayOutputStream; 34import java.io.File; 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.OutputStream; 38import java.net.Authenticator; 39import java.net.CacheRequest; 40import java.net.CacheResponse; 41import java.net.ConnectException; 42import java.net.HttpRetryException; 43import java.net.HttpURLConnection; 44import java.net.InetAddress; 45import java.net.ProtocolException; 46import java.net.Proxy; 47import java.net.ProxySelector; 48import java.net.ResponseCache; 49import java.net.Socket; 50import java.net.SocketAddress; 51import java.net.SocketTimeoutException; 52import java.net.URI; 53import java.net.URL; 54import java.net.URLConnection; 55import java.net.UnknownHostException; 56import java.security.cert.CertificateException; 57import java.security.cert.X509Certificate; 58import java.util.ArrayList; 59import java.util.Arrays; 60import java.util.Collections; 61import java.util.HashSet; 62import java.util.Iterator; 63import java.util.List; 64import java.util.Map; 65import java.util.Random; 66import java.util.Set; 67import java.util.UUID; 68import java.util.concurrent.TimeUnit; 69import java.util.concurrent.atomic.AtomicBoolean; 70import java.util.zip.GZIPInputStream; 71import java.util.zip.GZIPOutputStream; 72import javax.net.SocketFactory; 73import javax.net.ssl.HttpsURLConnection; 74import javax.net.ssl.SSLContext; 75import javax.net.ssl.SSLException; 76import javax.net.ssl.SSLHandshakeException; 77import javax.net.ssl.SSLSocket; 78import javax.net.ssl.SSLSocketFactory; 79import javax.net.ssl.TrustManager; 80import javax.net.ssl.X509TrustManager; 81import org.junit.After; 82import org.junit.Before; 83import org.junit.Ignore; 84import org.junit.Test; 85 86import static com.squareup.okhttp.internal.Util.UTF_8; 87import static com.squareup.okhttp.internal.http.OkHeaders.SELECTED_PROTOCOL; 88import static com.squareup.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT; 89import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END; 90import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START; 91import static com.squareup.okhttp.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; 92import static com.squareup.okhttp.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; 93import static java.util.concurrent.TimeUnit.MILLISECONDS; 94import static java.util.concurrent.TimeUnit.NANOSECONDS; 95import static org.junit.Assert.assertEquals; 96import static org.junit.Assert.assertFalse; 97import static org.junit.Assert.assertNotNull; 98import static org.junit.Assert.assertNull; 99import static org.junit.Assert.assertTrue; 100import static org.junit.Assert.fail; 101 102/** Android's URLConnectionTest. */ 103public final class URLConnectionTest { 104 private static final SSLContext sslContext = SslContextBuilder.localhost(); 105 106 private MockWebServer server = new MockWebServer(); 107 private MockWebServer server2 = new MockWebServer(); 108 109 private final OkHttpClient client = new OkHttpClient(); 110 private HttpURLConnection connection; 111 private HttpResponseCache cache; 112 private String hostName; 113 114 @Before public void setUp() throws Exception { 115 hostName = server.getHostName(); 116 server.setNpnEnabled(false); 117 } 118 119 @After public void tearDown() throws Exception { 120 Authenticator.setDefault(null); 121 System.clearProperty("proxyHost"); 122 System.clearProperty("proxyPort"); 123 System.clearProperty("http.proxyHost"); 124 System.clearProperty("http.proxyPort"); 125 System.clearProperty("https.proxyHost"); 126 System.clearProperty("https.proxyPort"); 127 server.shutdown(); 128 server2.shutdown(); 129 if (cache != null) { 130 cache.delete(); 131 } 132 } 133 134 @Test public void requestHeaders() throws IOException, InterruptedException { 135 server.enqueue(new MockResponse()); 136 server.play(); 137 138 connection = client.open(server.getUrl("/")); 139 connection.addRequestProperty("D", "e"); 140 connection.addRequestProperty("D", "f"); 141 assertEquals("f", connection.getRequestProperty("D")); 142 assertEquals("f", connection.getRequestProperty("d")); 143 Map<String, List<String>> requestHeaders = connection.getRequestProperties(); 144 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); 145 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d"))); 146 try { 147 requestHeaders.put("G", Arrays.asList("h")); 148 fail("Modified an unmodifiable view."); 149 } catch (UnsupportedOperationException expected) { 150 } 151 try { 152 requestHeaders.get("D").add("i"); 153 fail("Modified an unmodifiable view."); 154 } catch (UnsupportedOperationException expected) { 155 } 156 try { 157 connection.setRequestProperty(null, "j"); 158 fail(); 159 } catch (NullPointerException expected) { 160 } 161 try { 162 connection.addRequestProperty(null, "k"); 163 fail(); 164 } catch (NullPointerException expected) { 165 } 166 connection.setRequestProperty("NullValue", null); 167 assertNull(connection.getRequestProperty("NullValue")); 168 connection.addRequestProperty("AnotherNullValue", null); 169 assertNull(connection.getRequestProperty("AnotherNullValue")); 170 171 connection.getResponseCode(); 172 RecordedRequest request = server.takeRequest(); 173 assertContains(request.getHeaders(), "D: e"); 174 assertContains(request.getHeaders(), "D: f"); 175 assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); 176 assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); 177 assertContainsNoneMatching(request.getHeaders(), "G:.*"); 178 assertContainsNoneMatching(request.getHeaders(), "null:.*"); 179 180 try { 181 connection.addRequestProperty("N", "o"); 182 fail("Set header after connect"); 183 } catch (IllegalStateException expected) { 184 } 185 try { 186 connection.setRequestProperty("P", "q"); 187 fail("Set header after connect"); 188 } catch (IllegalStateException expected) { 189 } 190 try { 191 connection.getRequestProperties(); 192 fail(); 193 } catch (IllegalStateException expected) { 194 } 195 } 196 197 @Test public void getRequestPropertyReturnsLastValue() throws Exception { 198 server.play(); 199 connection = client.open(server.getUrl("/")); 200 connection.addRequestProperty("A", "value1"); 201 connection.addRequestProperty("A", "value2"); 202 assertEquals("value2", connection.getRequestProperty("A")); 203 } 204 205 @Test public void responseHeaders() throws IOException, InterruptedException { 206 server.enqueue(new MockResponse().setStatus("HTTP/1.0 200 Fantastic") 207 .addHeader("A: c") 208 .addHeader("B: d") 209 .addHeader("A: e") 210 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); 211 server.play(); 212 213 connection = client.open(server.getUrl("/")); 214 assertEquals(200, connection.getResponseCode()); 215 assertEquals("Fantastic", connection.getResponseMessage()); 216 assertEquals("HTTP/1.0 200 Fantastic", connection.getHeaderField(null)); 217 Map<String, List<String>> responseHeaders = connection.getHeaderFields(); 218 assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null)); 219 assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A"))); 220 assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a"))); 221 try { 222 responseHeaders.put("N", Arrays.asList("o")); 223 fail("Modified an unmodifiable view."); 224 } catch (UnsupportedOperationException expected) { 225 } 226 try { 227 responseHeaders.get("A").add("f"); 228 fail("Modified an unmodifiable view."); 229 } catch (UnsupportedOperationException expected) { 230 } 231 assertEquals("A", connection.getHeaderFieldKey(0)); 232 assertEquals("c", connection.getHeaderField(0)); 233 assertEquals("B", connection.getHeaderFieldKey(1)); 234 assertEquals("d", connection.getHeaderField(1)); 235 assertEquals("A", connection.getHeaderFieldKey(2)); 236 assertEquals("e", connection.getHeaderField(2)); 237 } 238 239 @Test public void serverSendsInvalidResponseHeaders() throws Exception { 240 server.enqueue(new MockResponse().setStatus("HTP/1.1 200 OK")); 241 server.play(); 242 243 connection = client.open(server.getUrl("/")); 244 try { 245 connection.getResponseCode(); 246 fail(); 247 } catch (IOException expected) { 248 } 249 } 250 251 @Test public void serverSendsInvalidCodeTooLarge() throws Exception { 252 server.enqueue(new MockResponse().setStatus("HTTP/1.1 2147483648 OK")); 253 server.play(); 254 255 connection = client.open(server.getUrl("/")); 256 try { 257 connection.getResponseCode(); 258 fail(); 259 } catch (IOException expected) { 260 } 261 } 262 263 @Test public void serverSendsInvalidCodeNotANumber() throws Exception { 264 server.enqueue(new MockResponse().setStatus("HTTP/1.1 00a OK")); 265 server.play(); 266 267 connection = client.open(server.getUrl("/")); 268 try { 269 connection.getResponseCode(); 270 fail(); 271 } catch (IOException expected) { 272 } 273 } 274 275 @Test public void serverSendsUnnecessaryWhitespace() throws Exception { 276 server.enqueue(new MockResponse().setStatus(" HTTP/1.1 2147483648 OK")); 277 server.play(); 278 279 connection = client.open(server.getUrl("/")); 280 try { 281 connection.getResponseCode(); 282 fail(); 283 } catch (IOException expected) { 284 } 285 } 286 287 @Test public void connectRetriesUntilConnectedOrFailed() throws Exception { 288 server.play(); 289 URL url = server.getUrl("/foo"); 290 server.shutdown(); 291 292 connection = client.open(url); 293 try { 294 connection.connect(); 295 fail(); 296 } catch (IOException expected) { 297 } 298 } 299 300 @Test public void requestBodySurvivesRetriesWithFixedLength() throws Exception { 301 testRequestBodySurvivesRetries(TransferKind.FIXED_LENGTH); 302 } 303 304 @Test public void requestBodySurvivesRetriesWithChunkedStreaming() throws Exception { 305 testRequestBodySurvivesRetries(TransferKind.CHUNKED); 306 } 307 308 @Test public void requestBodySurvivesRetriesWithBufferedBody() throws Exception { 309 testRequestBodySurvivesRetries(TransferKind.END_OF_STREAM); 310 } 311 312 private void testRequestBodySurvivesRetries(TransferKind transferKind) throws Exception { 313 server.enqueue(new MockResponse().setBody("abc")); 314 server.play(); 315 316 // Use a misconfigured proxy to guarantee that the request is retried. 317 server2.play(); 318 FakeProxySelector proxySelector = new FakeProxySelector(); 319 proxySelector.proxies.add(server2.toProxyAddress()); 320 client.setProxySelector(proxySelector); 321 server2.shutdown(); 322 323 connection = client.open(server.getUrl("/def")); 324 connection.setDoOutput(true); 325 transferKind.setForRequest(connection, 4); 326 connection.getOutputStream().write("body".getBytes("UTF-8")); 327 assertContent("abc", connection); 328 329 assertEquals("body", server.takeRequest().getUtf8Body()); 330 } 331 332 @Test public void getErrorStreamOnSuccessfulRequest() throws Exception { 333 server.enqueue(new MockResponse().setBody("A")); 334 server.play(); 335 connection = client.open(server.getUrl("/")); 336 assertNull(connection.getErrorStream()); 337 } 338 339 @Test public void getErrorStreamOnUnsuccessfulRequest() throws Exception { 340 server.enqueue(new MockResponse().setResponseCode(404).setBody("A")); 341 server.play(); 342 connection = client.open(server.getUrl("/")); 343 assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE)); 344 } 345 346 // Check that if we don't read to the end of a response, the next request on the 347 // recycled connection doesn't get the unread tail of the first request's response. 348 // http://code.google.com/p/android/issues/detail?id=2939 349 @Test public void bug2939() throws Exception { 350 MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 351 352 server.enqueue(response); 353 server.enqueue(response); 354 server.play(); 355 356 assertContent("ABCDE", client.open(server.getUrl("/")), 5); 357 assertContent("ABCDE", client.open(server.getUrl("/")), 5); 358 } 359 360 // Check that we recognize a few basic mime types by extension. 361 // http://code.google.com/p/android/issues/detail?id=10100 362 @Test public void bug10100() throws Exception { 363 assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); 364 assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); 365 } 366 367 @Test public void connectionsArePooled() throws Exception { 368 MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 369 370 server.enqueue(response); 371 server.enqueue(response); 372 server.enqueue(response); 373 server.play(); 374 375 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo"))); 376 assertEquals(0, server.takeRequest().getSequenceNumber()); 377 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux"))); 378 assertEquals(1, server.takeRequest().getSequenceNumber()); 379 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z"))); 380 assertEquals(2, server.takeRequest().getSequenceNumber()); 381 } 382 383 @Test public void chunkedConnectionsArePooled() throws Exception { 384 MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); 385 386 server.enqueue(response); 387 server.enqueue(response); 388 server.enqueue(response); 389 server.play(); 390 391 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo"))); 392 assertEquals(0, server.takeRequest().getSequenceNumber()); 393 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux"))); 394 assertEquals(1, server.takeRequest().getSequenceNumber()); 395 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z"))); 396 assertEquals(2, server.takeRequest().getSequenceNumber()); 397 } 398 399 @Test public void serverClosesSocket() throws Exception { 400 testServerClosesOutput(DISCONNECT_AT_END); 401 } 402 403 @Test public void serverShutdownInput() throws Exception { 404 testServerClosesOutput(SHUTDOWN_INPUT_AT_END); 405 } 406 407 @Test public void serverShutdownOutput() throws Exception { 408 testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END); 409 } 410 411 @Test public void invalidHost() throws Exception { 412 // Note that 1234.1.1.1 is an invalid host in a URI, but URL isn't as strict. 413 URL url = new URL("http://1234.1.1.1/index.html"); 414 HttpURLConnection connection = client.open(url); 415 try { 416 connection.connect(); 417 fail(); 418 } catch (UnknownHostException expected) { 419 } 420 } 421 422 private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception { 423 server.enqueue(new MockResponse().setBody("This connection won't pool properly") 424 .setSocketPolicy(socketPolicy)); 425 MockResponse responseAfter = new MockResponse().setBody("This comes after a busted connection"); 426 server.enqueue(responseAfter); 427 server.enqueue(responseAfter); // Enqueue 2x because the broken connection may be reused. 428 server.play(); 429 430 HttpURLConnection connection1 = client.open(server.getUrl("/a")); 431 connection1.setReadTimeout(100); 432 assertContent("This connection won't pool properly", connection1); 433 434 // Give the server time to enact the socket policy if it's one that could happen after the 435 // client has received the response. 436 Thread.sleep(500); 437 438 assertEquals(0, server.takeRequest().getSequenceNumber()); 439 HttpURLConnection connection2 = client.open(server.getUrl("/b")); 440 connection2.setReadTimeout(100); 441 assertContent("This comes after a busted connection", connection2); 442 443 // Check that a fresh connection was created, either immediately or after attempting reuse. 444 RecordedRequest requestAfter = server.takeRequest(); 445 if (server.getRequestCount() == 3) { 446 requestAfter = server.takeRequest(); // The failure consumed a response. 447 } 448 // sequence number 0 means the HTTP socket connection was not reused 449 assertEquals(0, requestAfter.getSequenceNumber()); 450 } 451 452 enum WriteKind {BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS} 453 454 @Test public void chunkedUpload_byteByByte() throws Exception { 455 doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 456 } 457 458 @Test public void chunkedUpload_smallBuffers() throws Exception { 459 doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); 460 } 461 462 @Test public void chunkedUpload_largeBuffers() throws Exception { 463 doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); 464 } 465 466 @Test public void fixedLengthUpload_byteByByte() throws Exception { 467 doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 468 } 469 470 @Test public void fixedLengthUpload_smallBuffers() throws Exception { 471 doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 472 } 473 474 @Test public void fixedLengthUpload_largeBuffers() throws Exception { 475 doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 476 } 477 478 private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { 479 int n = 512 * 1024; 480 server.setBodyLimit(0); 481 server.enqueue(new MockResponse()); 482 server.play(); 483 484 HttpURLConnection conn = client.open(server.getUrl("/")); 485 conn.setDoOutput(true); 486 conn.setRequestMethod("POST"); 487 if (uploadKind == TransferKind.CHUNKED) { 488 conn.setChunkedStreamingMode(-1); 489 } else { 490 conn.setFixedLengthStreamingMode(n); 491 } 492 OutputStream out = conn.getOutputStream(); 493 if (writeKind == WriteKind.BYTE_BY_BYTE) { 494 for (int i = 0; i < n; ++i) { 495 out.write('x'); 496 } 497 } else { 498 byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64 * 1024]; 499 Arrays.fill(buf, (byte) 'x'); 500 for (int i = 0; i < n; i += buf.length) { 501 out.write(buf, 0, Math.min(buf.length, n - i)); 502 } 503 } 504 out.close(); 505 assertEquals(200, conn.getResponseCode()); 506 RecordedRequest request = server.takeRequest(); 507 assertEquals(n, request.getBodySize()); 508 if (uploadKind == TransferKind.CHUNKED) { 509 assertTrue(request.getChunkSizes().size() > 0); 510 } else { 511 assertTrue(request.getChunkSizes().isEmpty()); 512 } 513 } 514 515 @Test public void getResponseCodeNoResponseBody() throws Exception { 516 server.enqueue(new MockResponse().addHeader("abc: def")); 517 server.play(); 518 519 URL url = server.getUrl("/"); 520 HttpURLConnection conn = client.open(url); 521 conn.setDoInput(false); 522 assertEquals("def", conn.getHeaderField("abc")); 523 assertEquals(200, conn.getResponseCode()); 524 try { 525 conn.getInputStream(); 526 fail(); 527 } catch (ProtocolException expected) { 528 } 529 } 530 531 @Test public void connectViaHttps() throws Exception { 532 server.useHttps(sslContext.getSocketFactory(), false); 533 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 534 server.play(); 535 536 client.setSslSocketFactory(sslContext.getSocketFactory()); 537 client.setHostnameVerifier(new RecordingHostnameVerifier()); 538 connection = client.open(server.getUrl("/foo")); 539 540 assertContent("this response comes via HTTPS", connection); 541 542 RecordedRequest request = server.takeRequest(); 543 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 544 } 545 546 @Test public void inspectHandshakeThroughoutRequestLifecycle() throws Exception { 547 server.useHttps(sslContext.getSocketFactory(), false); 548 server.enqueue(new MockResponse()); 549 server.play(); 550 551 client.setSslSocketFactory(sslContext.getSocketFactory()); 552 client.setHostnameVerifier(new RecordingHostnameVerifier()); 553 554 HttpsURLConnection httpsConnection = (HttpsURLConnection) client.open(server.getUrl("/foo")); 555 556 // Prior to calling connect(), getting the cipher suite is forbidden. 557 try { 558 httpsConnection.getCipherSuite(); 559 fail(); 560 } catch (IllegalStateException expected) { 561 } 562 563 // Calling connect establishes a handshake... 564 httpsConnection.connect(); 565 assertNotNull(httpsConnection.getCipherSuite()); 566 567 // ...which remains after we read the response body... 568 assertContent("", httpsConnection); 569 assertNotNull(httpsConnection.getCipherSuite()); 570 571 // ...and after we disconnect. 572 httpsConnection.disconnect(); 573 assertNotNull(httpsConnection.getCipherSuite()); 574 } 575 576 @Test public void connectViaHttpsReusingConnections() throws IOException, InterruptedException { 577 server.useHttps(sslContext.getSocketFactory(), false); 578 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 579 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 580 server.play(); 581 582 // The pool will only reuse sockets if the SSL socket factories are the same. 583 SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); 584 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 585 586 client.setSslSocketFactory(clientSocketFactory); 587 client.setHostnameVerifier(hostnameVerifier); 588 connection = client.open(server.getUrl("/")); 589 assertContent("this response comes via HTTPS", connection); 590 591 connection = client.open(server.getUrl("/")); 592 assertContent("another response via HTTPS", connection); 593 594 assertEquals(0, server.takeRequest().getSequenceNumber()); 595 assertEquals(1, server.takeRequest().getSequenceNumber()); 596 } 597 598 @Test public void connectViaHttpsReusingConnectionsDifferentFactories() 599 throws IOException, InterruptedException { 600 server.useHttps(sslContext.getSocketFactory(), false); 601 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 602 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 603 server.play(); 604 605 // install a custom SSL socket factory so the server can be authorized 606 client.setSslSocketFactory(sslContext.getSocketFactory()); 607 client.setHostnameVerifier(new RecordingHostnameVerifier()); 608 HttpURLConnection connection1 = client.open(server.getUrl("/")); 609 assertContent("this response comes via HTTPS", connection1); 610 611 client.setSslSocketFactory(null); 612 HttpURLConnection connection2 = client.open(server.getUrl("/")); 613 try { 614 readAscii(connection2.getInputStream(), Integer.MAX_VALUE); 615 fail("without an SSL socket factory, the connection should fail"); 616 } catch (SSLException expected) { 617 } 618 } 619 620 @Test public void connectViaHttpsWithSSLFallback() throws IOException, InterruptedException { 621 // Android-specific changes below related to http://b/17750026 622 // Android's server sockets will fail the handshake if the client sets TLS_FALLBACK_SCSV, 623 // attempts a retry over SSLv3 and the server has newer protocols enabled. It is not 624 // possible to turn TLS_FALLBACK_SCSV behavior off on the server so we fail the first connection 625 // by simulating a handshake failure and we set the server to only accept the SSLv3 626 // protocol (satisfying the TLS_FALLBACK_SCSV check for the second connection). This is as close 627 // as we can get to simulating a server that fails TLSv1.X and which also does not support 628 // TLS_FALLBACK_SCSV. 629 SSLSocketFactory serverSocketFactory = 630 new LimitedProtocolsSocketFactory(sslContext.getSocketFactory(), "SSLv3"); 631 server.useHttps(serverSocketFactory, false); 632 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); 633 server.enqueue(new MockResponse().setBody("this response comes via SSL")); 634 server.play(); 635 636 RecordingSocketFactory clientSocketFactory = 637 new RecordingSocketFactory(sslContext.getSocketFactory()); 638 client.setSslSocketFactory(clientSocketFactory); 639 client.setHostnameVerifier(new RecordingHostnameVerifier()); 640 connection = client.open(server.getUrl("/foo")); 641 642 assertContent("this response comes via SSL", connection); 643 644 assertEquals(2, server.getRequestCount()); 645 RecordedRequest request = server.takeRequest(); 646 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 647 assertEquals("SSLv3", request.getSslProtocol()); 648 assertEquals(2, clientSocketFactory.getCreatedSockets().size()); 649 } 650 651 // Android-specific changes below related to http://b/17750026 652 // After the introduction of the TLS_FALLBACK_SCSV we expect a failure if the initial 653 // handshake fails and the server supports TLS_FALLBACK_SCSV. MockWebServer on Android uses 654 // sockets that enforced TLS_FALLBACK_SCSV checks by default. 655 @Test public void connectViaHttpsWithSSLFallback_scsvFailure() throws Exception { 656 server.useHttps(sslContext.getSocketFactory(), false); 657 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); 658 server.play(); 659 660 RecordingSocketFactory clientSocketFactory = 661 new RecordingSocketFactory(sslContext.getSocketFactory()); 662 client.setSslSocketFactory(clientSocketFactory); 663 client.setHostnameVerifier(new RecordingHostnameVerifier()); 664 try { 665 connection = client.open(server.getUrl("/foo")); 666 connection.getInputStream(); 667 fail(); 668 } catch (SSLHandshakeException expected) { 669 } 670 671 // The first request is registered by MockWebServer and intentionally failed. The second is 672 // failed by the socket layer. 673 assertEquals(1, server.getRequestCount()); 674 assertEquals(2, clientSocketFactory.getCreatedSockets().size()); 675 } 676 677 /** 678 * When a pooled connection fails, don't blame the route. Otherwise pooled 679 * connection failures can cause unnecessary SSL fallbacks. 680 * 681 * https://github.com/square/okhttp/issues/515 682 */ 683 @Test public void sslFallbackNotUsedWhenRecycledConnectionFails() throws Exception { 684 server.useHttps(sslContext.getSocketFactory(), false); 685 server.enqueue(new MockResponse() 686 .setBody("abc") 687 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); 688 server.enqueue(new MockResponse().setBody("def")); 689 server.play(); 690 691 client.setSslSocketFactory(sslContext.getSocketFactory()); 692 client.setHostnameVerifier(new RecordingHostnameVerifier()); 693 694 assertContent("abc", client.open(server.getUrl("/"))); 695 696 // Give the server time to disconnect. 697 Thread.sleep(500); 698 699 assertContent("def", client.open(server.getUrl("/"))); 700 701 RecordedRequest request1 = server.takeRequest(); 702 assertEquals("TLSv1", request1.getSslProtocol()); // OkHttp's current best TLS version. 703 704 RecordedRequest request2 = server.takeRequest(); 705 assertEquals("TLSv1", request2.getSslProtocol()); // OkHttp's current best TLS version. 706 } 707 708 /** 709 * Verify that we don't retry connections on certificate verification errors. 710 * 711 * http://code.google.com/p/android/issues/detail?id=13178 712 */ 713 @Test public void connectViaHttpsToUntrustedServer() throws IOException, InterruptedException { 714 server.useHttps(sslContext.getSocketFactory(), false); 715 server.enqueue(new MockResponse()); // unused 716 server.play(); 717 718 connection = client.open(server.getUrl("/foo")); 719 try { 720 connection.getInputStream(); 721 fail(); 722 } catch (SSLHandshakeException expected) { 723 assertTrue(expected.getCause() instanceof CertificateException); 724 } 725 assertEquals(0, server.getRequestCount()); 726 } 727 728 @Test public void connectViaProxyUsingProxyArg() throws Exception { 729 testConnectViaProxy(ProxyConfig.CREATE_ARG); 730 } 731 732 @Test public void connectViaProxyUsingProxySystemProperty() throws Exception { 733 testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); 734 } 735 736 @Test public void connectViaProxyUsingHttpProxySystemProperty() throws Exception { 737 testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 738 } 739 740 private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception { 741 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 742 server.enqueue(mockResponse); 743 server.play(); 744 745 URL url = new URL("http://android.com/foo"); 746 connection = proxyConfig.connect(server, client, url); 747 assertContent("this response comes via a proxy", connection); 748 assertTrue(connection.usingProxy()); 749 750 RecordedRequest request = server.takeRequest(); 751 assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); 752 assertContains(request.getHeaders(), "Host: android.com"); 753 } 754 755 @Test public void contentDisagreesWithContentLengthHeader() throws IOException { 756 server.enqueue(new MockResponse().setBody("abc\r\nYOU SHOULD NOT SEE THIS") 757 .clearHeaders() 758 .addHeader("Content-Length: 3")); 759 server.play(); 760 761 assertContent("abc", client.open(server.getUrl("/"))); 762 } 763 764 public void testConnectViaSocketFactory(boolean useHttps) throws IOException { 765 SocketFactory uselessSocketFactory = new SocketFactory() { 766 public Socket createSocket() { throw new IllegalArgumentException("useless"); } 767 public Socket createSocket(InetAddress host, int port) { return null; } 768 public Socket createSocket(InetAddress address, int port, InetAddress localAddress, 769 int localPort) { return null; } 770 public Socket createSocket(String host, int port) { return null; } 771 public Socket createSocket(String host, int port, InetAddress localHost, int localPort) { 772 return null; 773 } 774 }; 775 776 if (useHttps) { 777 server.useHttps(sslContext.getSocketFactory(), false); 778 client.setSslSocketFactory(sslContext.getSocketFactory()); 779 client.setHostnameVerifier(new RecordingHostnameVerifier()); 780 } 781 782 server.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK")); 783 server.play(); 784 785 client.setSocketFactory(uselessSocketFactory); 786 connection = client.open(server.getUrl("/")); 787 try { 788 connection.getResponseCode(); 789 fail(); 790 } catch (IllegalArgumentException expected) { 791 } 792 793 client.setSocketFactory(SocketFactory.getDefault()); 794 connection = client.open(server.getUrl("/")); 795 assertEquals(200, connection.getResponseCode()); 796 } 797 798 @Test public void connectHttpViaSocketFactory() throws Exception { 799 testConnectViaSocketFactory(false); 800 } 801 802 @Test public void connectHttpsViaSocketFactory() throws Exception { 803 testConnectViaSocketFactory(true); 804 } 805 806 @Test public void contentDisagreesWithChunkedHeader() throws IOException { 807 MockResponse mockResponse = new MockResponse(); 808 mockResponse.setChunkedBody("abc", 3); 809 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 810 bytesOut.write(mockResponse.getBody()); 811 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes("UTF-8")); 812 mockResponse.setBody(bytesOut.toByteArray()); 813 mockResponse.clearHeaders(); 814 mockResponse.addHeader("Transfer-encoding: chunked"); 815 816 server.enqueue(mockResponse); 817 server.play(); 818 819 assertContent("abc", client.open(server.getUrl("/"))); 820 } 821 822 @Test public void connectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { 823 testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); 824 } 825 826 @Test public void connectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { 827 // https should not use http proxy 828 testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 829 } 830 831 private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { 832 server.useHttps(sslContext.getSocketFactory(), false); 833 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 834 server.play(); 835 836 URL url = server.getUrl("/foo"); 837 client.setSslSocketFactory(sslContext.getSocketFactory()); 838 client.setHostnameVerifier(new RecordingHostnameVerifier()); 839 connection = proxyConfig.connect(server, client, url); 840 841 assertContent("this response comes via HTTPS", connection); 842 843 RecordedRequest request = server.takeRequest(); 844 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 845 } 846 847 @Test public void connectViaHttpProxyToHttpsUsingProxyArg() throws Exception { 848 testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); 849 } 850 851 /** 852 * We weren't honoring all of the appropriate proxy system properties when 853 * connecting via HTTPS. http://b/3097518 854 */ 855 @Test public void connectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { 856 testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY); 857 } 858 859 @Test public void connectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { 860 testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); 861 } 862 863 /** 864 * We were verifying the wrong hostname when connecting to an HTTPS site 865 * through a proxy. http://b/3097277 866 */ 867 private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { 868 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 869 870 server.useHttps(sslContext.getSocketFactory(), true); 871 server.enqueue( 872 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 873 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 874 server.play(); 875 876 URL url = new URL("https://android.com/foo"); 877 client.setSslSocketFactory(sslContext.getSocketFactory()); 878 client.setHostnameVerifier(hostnameVerifier); 879 connection = proxyConfig.connect(server, client, url); 880 881 assertContent("this response comes via a secure proxy", connection); 882 883 RecordedRequest connect = server.takeRequest(); 884 assertEquals("Connect line failure on proxy", "CONNECT android.com:443 HTTP/1.1", 885 connect.getRequestLine()); 886 assertContains(connect.getHeaders(), "Host: android.com"); 887 888 RecordedRequest get = server.takeRequest(); 889 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 890 assertContains(get.getHeaders(), "Host: android.com"); 891 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 892 } 893 894 /** Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912 */ 895 @Test public void connectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception { 896 initResponseCache(); 897 898 server.useHttps(sslContext.getSocketFactory(), true); 899 MockResponse response = new MockResponse() // Key to reproducing b/6754912 900 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 901 .setBody("bogus proxy connect response content"); 902 903 // Enqueue a pair of responses for every IP address held by localhost, because the 904 // route selector will try each in sequence. 905 // TODO: use the fake Dns implementation instead of a loop 906 for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { 907 server.enqueue(response); // For the first TLS tolerant connection 908 server.enqueue(response); // For the backwards-compatible SSLv3 retry 909 } 910 server.play(); 911 client.setProxy(server.toProxyAddress()); 912 913 URL url = new URL("https://android.com/foo"); 914 client.setSslSocketFactory(sslContext.getSocketFactory()); 915 connection = client.open(url); 916 917 try { 918 connection.getResponseCode(); 919 fail(); 920 } catch (IOException expected) { 921 // Thrown when the connect causes SSLSocket.startHandshake() to throw 922 // when it sees the "bogus proxy connect response content" 923 // instead of a ServerHello handshake message. 924 } 925 926 RecordedRequest connect = server.takeRequest(); 927 assertEquals("Connect line failure on proxy", "CONNECT android.com:443 HTTP/1.1", 928 connect.getRequestLine()); 929 assertContains(connect.getHeaders(), "Host: android.com"); 930 } 931 932 private void initResponseCache() throws IOException { 933 String tmp = System.getProperty("java.io.tmpdir"); 934 File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); 935 cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); 936 client.setOkResponseCache(cache); 937 } 938 939 /** Test which headers are sent unencrypted to the HTTP proxy. */ 940 @Test public void proxyConnectIncludesProxyHeadersOnly() 941 throws IOException, InterruptedException { 942 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 943 944 server.useHttps(sslContext.getSocketFactory(), true); 945 server.enqueue( 946 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 947 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 948 server.play(); 949 client.setProxy(server.toProxyAddress()); 950 951 URL url = new URL("https://android.com/foo"); 952 client.setSslSocketFactory(sslContext.getSocketFactory()); 953 client.setHostnameVerifier(hostnameVerifier); 954 connection = client.open(url); 955 connection.addRequestProperty("Private", "Secret"); 956 connection.addRequestProperty("Proxy-Authorization", "bar"); 957 connection.addRequestProperty("User-Agent", "baz"); 958 assertContent("encrypted response from the origin server", connection); 959 960 RecordedRequest connect = server.takeRequest(); 961 assertContainsNoneMatching(connect.getHeaders(), "Private.*"); 962 assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); 963 assertContains(connect.getHeaders(), "User-Agent: baz"); 964 assertContains(connect.getHeaders(), "Host: android.com"); 965 assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); 966 967 RecordedRequest get = server.takeRequest(); 968 assertContains(get.getHeaders(), "Private: Secret"); 969 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 970 } 971 972 @Test public void proxyAuthenticateOnConnect() throws Exception { 973 Authenticator.setDefault(new RecordingAuthenticator()); 974 server.useHttps(sslContext.getSocketFactory(), true); 975 server.enqueue(new MockResponse().setResponseCode(407) 976 .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); 977 server.enqueue( 978 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 979 server.enqueue(new MockResponse().setBody("A")); 980 server.play(); 981 client.setProxy(server.toProxyAddress()); 982 983 URL url = new URL("https://android.com/foo"); 984 client.setSslSocketFactory(sslContext.getSocketFactory()); 985 client.setHostnameVerifier(new RecordingHostnameVerifier()); 986 connection = client.open(url); 987 assertContent("A", connection); 988 989 RecordedRequest connect1 = server.takeRequest(); 990 assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine()); 991 assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*"); 992 993 RecordedRequest connect2 = server.takeRequest(); 994 assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine()); 995 assertContains(connect2.getHeaders(), 996 "Proxy-Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 997 998 RecordedRequest get = server.takeRequest(); 999 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 1000 assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*"); 1001 } 1002 1003 // Don't disconnect after building a tunnel with CONNECT 1004 // http://code.google.com/p/android/issues/detail?id=37221 1005 @Test public void proxyWithConnectionClose() throws IOException { 1006 server.useHttps(sslContext.getSocketFactory(), true); 1007 server.enqueue( 1008 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 1009 server.enqueue(new MockResponse().setBody("this response comes via a proxy")); 1010 server.play(); 1011 client.setProxy(server.toProxyAddress()); 1012 1013 URL url = new URL("https://android.com/foo"); 1014 client.setSslSocketFactory(sslContext.getSocketFactory()); 1015 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1016 connection = client.open(url); 1017 connection.setRequestProperty("Connection", "close"); 1018 1019 assertContent("this response comes via a proxy", connection); 1020 } 1021 1022 @Test public void proxyWithConnectionReuse() throws IOException { 1023 SSLSocketFactory socketFactory = sslContext.getSocketFactory(); 1024 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1025 1026 server.useHttps(socketFactory, true); 1027 server.enqueue( 1028 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 1029 server.enqueue(new MockResponse().setBody("response 1")); 1030 server.enqueue(new MockResponse().setBody("response 2")); 1031 server.play(); 1032 client.setProxy(server.toProxyAddress()); 1033 1034 URL url = new URL("https://android.com/foo"); 1035 client.setSslSocketFactory(socketFactory); 1036 client.setHostnameVerifier(hostnameVerifier); 1037 assertContent("response 1", client.open(url)); 1038 assertContent("response 2", client.open(url)); 1039 } 1040 1041 @Test public void disconnectedConnection() throws IOException { 1042 server.enqueue(new MockResponse() 1043 .throttleBody(2, 100, TimeUnit.MILLISECONDS) 1044 .setBody("ABCD")); 1045 server.play(); 1046 1047 connection = client.open(server.getUrl("/")); 1048 InputStream in = connection.getInputStream(); 1049 assertEquals('A', (char) in.read()); 1050 connection.disconnect(); 1051 try { 1052 // Reading 'B' may succeed if it's buffered. 1053 in.read(); 1054 1055 // But 'C' shouldn't be buffered (the response is throttled) and this should fail. 1056 in.read(); 1057 fail("Expected a connection closed exception"); 1058 } catch (IOException expected) { 1059 } 1060 } 1061 1062 @Test public void disconnectBeforeConnect() throws IOException { 1063 server.enqueue(new MockResponse().setBody("A")); 1064 server.play(); 1065 1066 connection = client.open(server.getUrl("/")); 1067 connection.disconnect(); 1068 assertContent("A", connection); 1069 assertEquals(200, connection.getResponseCode()); 1070 } 1071 1072 @SuppressWarnings("deprecation") @Test public void defaultRequestProperty() throws Exception { 1073 URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A"); 1074 assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")); 1075 } 1076 1077 /** 1078 * Reads {@code count} characters from the stream. If the stream is 1079 * exhausted before {@code count} characters can be read, the remaining 1080 * characters are returned and the stream is closed. 1081 */ 1082 private String readAscii(InputStream in, int count) throws IOException { 1083 StringBuilder result = new StringBuilder(); 1084 for (int i = 0; i < count; i++) { 1085 int value = in.read(); 1086 if (value == -1) { 1087 in.close(); 1088 break; 1089 } 1090 result.append((char) value); 1091 } 1092 return result.toString(); 1093 } 1094 1095 @Test public void markAndResetWithContentLengthHeader() throws IOException { 1096 testMarkAndReset(TransferKind.FIXED_LENGTH); 1097 } 1098 1099 @Test public void markAndResetWithChunkedEncoding() throws IOException { 1100 testMarkAndReset(TransferKind.CHUNKED); 1101 } 1102 1103 @Test public void markAndResetWithNoLengthHeaders() throws IOException { 1104 testMarkAndReset(TransferKind.END_OF_STREAM); 1105 } 1106 1107 private void testMarkAndReset(TransferKind transferKind) throws IOException { 1108 MockResponse response = new MockResponse(); 1109 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 1110 server.enqueue(response); 1111 server.enqueue(response); 1112 server.play(); 1113 1114 InputStream in = client.open(server.getUrl("/")).getInputStream(); 1115 assertFalse("This implementation claims to support mark().", in.markSupported()); 1116 in.mark(5); 1117 assertEquals("ABCDE", readAscii(in, 5)); 1118 try { 1119 in.reset(); 1120 fail(); 1121 } catch (IOException expected) { 1122 } 1123 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 1124 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", client.open(server.getUrl("/"))); 1125 } 1126 1127 /** 1128 * We've had a bug where we forget the HTTP response when we see response 1129 * code 401. This causes a new HTTP request to be issued for every call into 1130 * the URLConnection. 1131 */ 1132 @Test public void unauthorizedResponseHandling() throws IOException { 1133 MockResponse response = new MockResponse().addHeader("WWW-Authenticate: challenge") 1134 .setResponseCode(401) // UNAUTHORIZED 1135 .setBody("Unauthorized"); 1136 server.enqueue(response); 1137 server.enqueue(response); 1138 server.enqueue(response); 1139 server.play(); 1140 1141 URL url = server.getUrl("/"); 1142 HttpURLConnection conn = client.open(url); 1143 1144 assertEquals(401, conn.getResponseCode()); 1145 assertEquals(401, conn.getResponseCode()); 1146 assertEquals(401, conn.getResponseCode()); 1147 assertEquals(1, server.getRequestCount()); 1148 } 1149 1150 @Test public void nonHexChunkSize() throws IOException { 1151 server.enqueue(new MockResponse().setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 1152 .clearHeaders() 1153 .addHeader("Transfer-encoding: chunked")); 1154 server.play(); 1155 1156 URLConnection connection = client.open(server.getUrl("/")); 1157 try { 1158 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1159 fail(); 1160 } catch (IOException e) { 1161 } 1162 } 1163 1164 @Test public void missingChunkBody() throws IOException { 1165 server.enqueue(new MockResponse().setBody("5") 1166 .clearHeaders() 1167 .addHeader("Transfer-encoding: chunked") 1168 .setSocketPolicy(DISCONNECT_AT_END)); 1169 server.play(); 1170 1171 URLConnection connection = client.open(server.getUrl("/")); 1172 try { 1173 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1174 fail(); 1175 } catch (IOException e) { 1176 } 1177 } 1178 1179 /** 1180 * This test checks whether connections are gzipped by default. This 1181 * behavior in not required by the API, so a failure of this test does not 1182 * imply a bug in the implementation. 1183 */ 1184 @Test public void gzipEncodingEnabledByDefault() throws IOException, InterruptedException { 1185 server.enqueue(new MockResponse().setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 1186 .addHeader("Content-Encoding: gzip")); 1187 server.play(); 1188 1189 URLConnection connection = client.open(server.getUrl("/")); 1190 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1191 assertNull(connection.getContentEncoding()); 1192 assertEquals(-1, connection.getContentLength()); 1193 1194 RecordedRequest request = server.takeRequest(); 1195 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1196 } 1197 1198 @Test public void clientConfiguredGzipContentEncoding() throws Exception { 1199 byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")); 1200 server.enqueue(new MockResponse() 1201 .setBody(bodyBytes) 1202 .addHeader("Content-Encoding: gzip")); 1203 server.play(); 1204 1205 URLConnection connection = client.open(server.getUrl("/")); 1206 connection.addRequestProperty("Accept-Encoding", "gzip"); 1207 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1208 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1209 assertEquals(bodyBytes.length, connection.getContentLength()); 1210 1211 RecordedRequest request = server.takeRequest(); 1212 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1213 } 1214 1215 @Test public void gzipAndConnectionReuseWithFixedLength() throws Exception { 1216 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false); 1217 } 1218 1219 @Test public void gzipAndConnectionReuseWithChunkedEncoding() throws Exception { 1220 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false); 1221 } 1222 1223 @Test public void gzipAndConnectionReuseWithFixedLengthAndTls() throws Exception { 1224 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true); 1225 } 1226 1227 @Test public void gzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception { 1228 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true); 1229 } 1230 1231 @Test public void clientConfiguredCustomContentEncoding() throws Exception { 1232 server.enqueue(new MockResponse().setBody("ABCDE").addHeader("Content-Encoding: custom")); 1233 server.play(); 1234 1235 URLConnection connection = client.open(server.getUrl("/")); 1236 connection.addRequestProperty("Accept-Encoding", "custom"); 1237 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1238 1239 RecordedRequest request = server.takeRequest(); 1240 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 1241 } 1242 1243 /** 1244 * Test a bug where gzip input streams weren't exhausting the input stream, 1245 * which corrupted the request that followed or prevented connection reuse. 1246 * http://code.google.com/p/android/issues/detail?id=7059 1247 * http://code.google.com/p/android/issues/detail?id=38817 1248 */ 1249 private void testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind transferKind, 1250 boolean tls) throws Exception { 1251 if (tls) { 1252 SSLSocketFactory socketFactory = sslContext.getSocketFactory(); 1253 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1254 server.useHttps(socketFactory, false); 1255 client.setSslSocketFactory(socketFactory); 1256 client.setHostnameVerifier(hostnameVerifier); 1257 } 1258 1259 MockResponse responseOne = new MockResponse(); 1260 responseOne.addHeader("Content-Encoding: gzip"); 1261 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 1262 server.enqueue(responseOne); 1263 MockResponse responseTwo = new MockResponse(); 1264 transferKind.setBody(responseTwo, "two (identity)", 5); 1265 server.enqueue(responseTwo); 1266 server.play(); 1267 1268 HttpURLConnection connection1 = client.open(server.getUrl("/")); 1269 connection1.addRequestProperty("Accept-Encoding", "gzip"); 1270 InputStream gunzippedIn = new GZIPInputStream(connection1.getInputStream()); 1271 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1272 assertEquals(0, server.takeRequest().getSequenceNumber()); 1273 1274 HttpURLConnection connection2 = client.open(server.getUrl("/")); 1275 assertEquals("two (identity)", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); 1276 assertEquals(1, server.takeRequest().getSequenceNumber()); 1277 } 1278 1279 @Test public void transparentGzipWorksAfterExceptionRecovery() throws Exception { 1280 server.enqueue(new MockResponse() 1281 .setBody("a") 1282 .setSocketPolicy(SHUTDOWN_INPUT_AT_END)); 1283 server.enqueue(new MockResponse() 1284 .addHeader("Content-Encoding: gzip") 1285 .setBody(gzip("b".getBytes(UTF_8)))); 1286 server.play(); 1287 1288 // Seed the pool with a bad connection. 1289 assertContent("a", client.open(server.getUrl("/"))); 1290 1291 // Give the server time to disconnect. 1292 Thread.sleep(500); 1293 1294 // This connection will need to be recovered. When it is, transparent gzip should still work! 1295 assertContent("b", client.open(server.getUrl("/"))); 1296 1297 assertEquals(0, server.takeRequest().getSequenceNumber()); 1298 assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled. 1299 } 1300 1301 @Test public void endOfStreamResponseIsNotPooled() throws Exception { 1302 server.enqueue(new MockResponse() 1303 .setBody("{}") 1304 .clearHeaders() 1305 .setSocketPolicy(DISCONNECT_AT_END)); 1306 server.play(); 1307 1308 ConnectionPool pool = ConnectionPool.getDefault(); 1309 pool.evictAll(); 1310 client.setConnectionPool(pool); 1311 1312 HttpURLConnection connection = client.open(server.getUrl("/")); 1313 assertContent("{}", connection); 1314 assertEquals(0, client.getConnectionPool().getConnectionCount()); 1315 } 1316 1317 @Test public void earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() throws Exception { 1318 testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED); 1319 } 1320 1321 @Test public void earlyDisconnectDoesntHarmPoolingWithFixedLengthEncoding() throws Exception { 1322 testEarlyDisconnectDoesntHarmPooling(TransferKind.FIXED_LENGTH); 1323 } 1324 1325 private void testEarlyDisconnectDoesntHarmPooling(TransferKind transferKind) throws Exception { 1326 MockResponse response1 = new MockResponse(); 1327 transferKind.setBody(response1, "ABCDEFGHIJK", 1024); 1328 server.enqueue(response1); 1329 1330 MockResponse response2 = new MockResponse(); 1331 transferKind.setBody(response2, "LMNOPQRSTUV", 1024); 1332 server.enqueue(response2); 1333 1334 server.play(); 1335 1336 HttpURLConnection connection1 = client.open(server.getUrl("/")); 1337 InputStream in1 = connection1.getInputStream(); 1338 assertEquals("ABCDE", readAscii(in1, 5)); 1339 in1.close(); 1340 connection1.disconnect(); 1341 1342 HttpURLConnection connection2 = client.open(server.getUrl("/")); 1343 InputStream in2 = connection2.getInputStream(); 1344 assertEquals("LMNOP", readAscii(in2, 5)); 1345 in2.close(); 1346 connection2.disconnect(); 1347 1348 assertEquals(0, server.takeRequest().getSequenceNumber()); 1349 assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection is pooled! 1350 } 1351 1352 @Test public void streamDiscardingIsTimely() throws Exception { 1353 // This response takes at least a full second to serve: 10,000 bytes served 100 bytes at a time. 1354 server.enqueue(new MockResponse() 1355 .setBody(new byte[10000]) 1356 .throttleBody(100, 10, MILLISECONDS)); 1357 server.enqueue(new MockResponse().setBody("A")); 1358 server.play(); 1359 1360 long startNanos = System.nanoTime(); 1361 URLConnection connection1 = client.open(server.getUrl("/")); 1362 InputStream in = connection1.getInputStream(); 1363 in.close(); 1364 long elapsedNanos = System.nanoTime() - startNanos; 1365 long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos); 1366 1367 // If we're working correctly, this should be greater than 100ms, but less than double that. 1368 // Previously we had a bug where we would download the entire response body as long as no 1369 // individual read took longer than 100ms. 1370 assertTrue(String.format("Time to close: %sms", elapsedMillis), elapsedMillis < 500); 1371 1372 // Do another request to confirm that the discarded connection was not pooled. 1373 assertContent("A", client.open(server.getUrl("/"))); 1374 1375 assertEquals(0, server.takeRequest().getSequenceNumber()); 1376 assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled. 1377 } 1378 1379 @Test public void setChunkedStreamingMode() throws IOException, InterruptedException { 1380 server.enqueue(new MockResponse()); 1381 server.play(); 1382 1383 String body = "ABCDEFGHIJKLMNOPQ"; 1384 connection = client.open(server.getUrl("/")); 1385 connection.setChunkedStreamingMode(0); // OkHttp does not honor specific chunk sizes. 1386 connection.setDoOutput(true); 1387 OutputStream outputStream = connection.getOutputStream(); 1388 outputStream.write(body.getBytes("US-ASCII")); 1389 assertEquals(200, connection.getResponseCode()); 1390 1391 RecordedRequest request = server.takeRequest(); 1392 assertEquals(body, new String(request.getBody(), "US-ASCII")); 1393 assertEquals(Arrays.asList(body.length()), request.getChunkSizes()); 1394 } 1395 1396 @Test public void authenticateWithFixedLengthStreaming() throws Exception { 1397 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 1398 } 1399 1400 @Test public void authenticateWithChunkedStreaming() throws Exception { 1401 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 1402 } 1403 1404 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 1405 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1406 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1407 .setBody("Please authenticate."); 1408 server.enqueue(pleaseAuthenticate); 1409 server.play(); 1410 1411 Authenticator.setDefault(new RecordingAuthenticator()); 1412 connection = client.open(server.getUrl("/")); 1413 connection.setDoOutput(true); 1414 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1415 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1416 connection.setFixedLengthStreamingMode(requestBody.length); 1417 } else if (streamingMode == StreamingMode.CHUNKED) { 1418 connection.setChunkedStreamingMode(0); 1419 } 1420 OutputStream outputStream = connection.getOutputStream(); 1421 outputStream.write(requestBody); 1422 outputStream.close(); 1423 try { 1424 connection.getInputStream(); 1425 fail(); 1426 } catch (HttpRetryException expected) { 1427 } 1428 1429 // no authorization header for the request... 1430 RecordedRequest request = server.takeRequest(); 1431 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1432 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1433 } 1434 1435 @Test public void nonStandardAuthenticationScheme() throws Exception { 1436 List<String> calls = authCallsForHeader("WWW-Authenticate: Foo"); 1437 assertEquals(Collections.<String>emptyList(), calls); 1438 } 1439 1440 @Test public void nonStandardAuthenticationSchemeWithRealm() throws Exception { 1441 List<String> calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\""); 1442 assertEquals(0, calls.size()); 1443 } 1444 1445 // Digest auth is currently unsupported. Test that digest requests should fail reasonably. 1446 // http://code.google.com/p/android/issues/detail?id=11140 1447 @Test public void digestAuthentication() throws Exception { 1448 List<String> calls = authCallsForHeader("WWW-Authenticate: Digest " 1449 + "realm=\"testrealm@host.com\", qop=\"auth,auth-int\", " 1450 + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " 1451 + "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""); 1452 assertEquals(0, calls.size()); 1453 } 1454 1455 @Test public void allAttributesSetInServerAuthenticationCallbacks() throws Exception { 1456 List<String> calls = authCallsForHeader("WWW-Authenticate: Basic realm=\"Bar\""); 1457 assertEquals(1, calls.size()); 1458 URL url = server.getUrl("/"); 1459 String call = calls.get(0); 1460 assertTrue(call, call.contains("host=" + url.getHost())); 1461 assertTrue(call, call.contains("port=" + url.getPort())); 1462 assertTrue(call, call.contains("site=" + InetAddress.getAllByName(url.getHost())[0])); 1463 assertTrue(call, call.contains("url=" + url)); 1464 assertTrue(call, call.contains("type=" + Authenticator.RequestorType.SERVER)); 1465 assertTrue(call, call.contains("prompt=Bar")); 1466 assertTrue(call, call.contains("protocol=http")); 1467 assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI. 1468 } 1469 1470 @Test public void allAttributesSetInProxyAuthenticationCallbacks() throws Exception { 1471 List<String> calls = authCallsForHeader("Proxy-Authenticate: Basic realm=\"Bar\""); 1472 assertEquals(1, calls.size()); 1473 URL url = server.getUrl("/"); 1474 String call = calls.get(0); 1475 assertTrue(call, call.contains("host=" + url.getHost())); 1476 assertTrue(call, call.contains("port=" + url.getPort())); 1477 assertTrue(call, call.contains("site=" + InetAddress.getAllByName(url.getHost())[0])); 1478 assertTrue(call, call.contains("url=http://android.com")); 1479 assertTrue(call, call.contains("type=" + Authenticator.RequestorType.PROXY)); 1480 assertTrue(call, call.contains("prompt=Bar")); 1481 assertTrue(call, call.contains("protocol=http")); 1482 assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI. 1483 } 1484 1485 private List<String> authCallsForHeader(String authHeader) throws IOException { 1486 boolean proxy = authHeader.startsWith("Proxy-"); 1487 int responseCode = proxy ? 407 : 401; 1488 RecordingAuthenticator authenticator = new RecordingAuthenticator(null); 1489 Authenticator.setDefault(authenticator); 1490 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(responseCode) 1491 .addHeader(authHeader) 1492 .setBody("Please authenticate."); 1493 server.enqueue(pleaseAuthenticate); 1494 server.play(); 1495 1496 if (proxy) { 1497 client.setProxy(server.toProxyAddress()); 1498 connection = client.open(new URL("http://android.com")); 1499 } else { 1500 connection = client.open(server.getUrl("/")); 1501 } 1502 assertEquals(responseCode, connection.getResponseCode()); 1503 return authenticator.calls; 1504 } 1505 1506 @Test public void setValidRequestMethod() throws Exception { 1507 server.play(); 1508 assertValidRequestMethod("GET"); 1509 assertValidRequestMethod("DELETE"); 1510 assertValidRequestMethod("HEAD"); 1511 assertValidRequestMethod("OPTIONS"); 1512 assertValidRequestMethod("POST"); 1513 assertValidRequestMethod("PUT"); 1514 assertValidRequestMethod("TRACE"); 1515 assertValidRequestMethod("PATCH"); 1516 } 1517 1518 private void assertValidRequestMethod(String requestMethod) throws Exception { 1519 connection = client.open(server.getUrl("/")); 1520 connection.setRequestMethod(requestMethod); 1521 assertEquals(requestMethod, connection.getRequestMethod()); 1522 } 1523 1524 @Test public void setInvalidRequestMethodLowercase() throws Exception { 1525 server.play(); 1526 assertInvalidRequestMethod("get"); 1527 } 1528 1529 @Test public void setInvalidRequestMethodConnect() throws Exception { 1530 server.play(); 1531 assertInvalidRequestMethod("CONNECT"); 1532 } 1533 1534 private void assertInvalidRequestMethod(String requestMethod) throws Exception { 1535 connection = client.open(server.getUrl("/")); 1536 try { 1537 connection.setRequestMethod(requestMethod); 1538 fail(); 1539 } catch (ProtocolException expected) { 1540 } 1541 } 1542 1543 @Test public void shoutcast() throws Exception { 1544 server.enqueue(new MockResponse().setStatus("ICY 200 OK") 1545 // .addHeader("HTTP/1.0 200 OK") 1546 .addHeader("Accept-Ranges: none") 1547 .addHeader("Content-Type: audio/mpeg") 1548 .addHeader("icy-br:128") 1549 .addHeader("ice-audio-info: bitrate=128;samplerate=44100;channels=2") 1550 .addHeader("icy-br:128") 1551 .addHeader("icy-description:Rock") 1552 .addHeader("icy-genre:riders") 1553 .addHeader("icy-name:A2RRock") 1554 .addHeader("icy-pub:1") 1555 .addHeader("icy-url:http://www.A2Rradio.com") 1556 .addHeader("Server: Icecast 2.3.3-kh8") 1557 .addHeader("Cache-Control: no-cache") 1558 .addHeader("Pragma: no-cache") 1559 .addHeader("Expires: Mon, 26 Jul 1997 05:00:00 GMT") 1560 .addHeader("icy-metaint:16000") 1561 .setBody("mp3 data")); 1562 server.play(); 1563 connection = client.open(server.getUrl("/")); 1564 assertEquals(200, connection.getResponseCode()); 1565 assertEquals("OK", connection.getResponseMessage()); 1566 assertContent("mp3 data", connection); 1567 } 1568 1569 @Test public void cannotSetNegativeFixedLengthStreamingMode() throws Exception { 1570 server.play(); 1571 connection = client.open(server.getUrl("/")); 1572 try { 1573 connection.setFixedLengthStreamingMode(-2); 1574 fail(); 1575 } catch (IllegalArgumentException expected) { 1576 } 1577 } 1578 1579 @Test public void canSetNegativeChunkedStreamingMode() throws Exception { 1580 server.play(); 1581 connection = client.open(server.getUrl("/")); 1582 connection.setChunkedStreamingMode(-2); 1583 } 1584 1585 @Test public void cannotSetFixedLengthStreamingModeAfterConnect() throws Exception { 1586 server.enqueue(new MockResponse().setBody("A")); 1587 server.play(); 1588 connection = client.open(server.getUrl("/")); 1589 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1590 try { 1591 connection.setFixedLengthStreamingMode(1); 1592 fail(); 1593 } catch (IllegalStateException expected) { 1594 } 1595 } 1596 1597 @Test public void cannotSetChunkedStreamingModeAfterConnect() throws Exception { 1598 server.enqueue(new MockResponse().setBody("A")); 1599 server.play(); 1600 connection = client.open(server.getUrl("/")); 1601 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1602 try { 1603 connection.setChunkedStreamingMode(1); 1604 fail(); 1605 } catch (IllegalStateException expected) { 1606 } 1607 } 1608 1609 @Test public void cannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { 1610 server.play(); 1611 connection = client.open(server.getUrl("/")); 1612 connection.setChunkedStreamingMode(1); 1613 try { 1614 connection.setFixedLengthStreamingMode(1); 1615 fail(); 1616 } catch (IllegalStateException expected) { 1617 } 1618 } 1619 1620 @Test public void cannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { 1621 server.play(); 1622 connection = client.open(server.getUrl("/")); 1623 connection.setFixedLengthStreamingMode(1); 1624 try { 1625 connection.setChunkedStreamingMode(1); 1626 fail(); 1627 } catch (IllegalStateException expected) { 1628 } 1629 } 1630 1631 @Test public void secureFixedLengthStreaming() throws Exception { 1632 testSecureStreamingPost(StreamingMode.FIXED_LENGTH); 1633 } 1634 1635 @Test public void secureChunkedStreaming() throws Exception { 1636 testSecureStreamingPost(StreamingMode.CHUNKED); 1637 } 1638 1639 /** 1640 * Users have reported problems using HTTPS with streaming request bodies. 1641 * http://code.google.com/p/android/issues/detail?id=12860 1642 */ 1643 private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception { 1644 server.useHttps(sslContext.getSocketFactory(), false); 1645 server.enqueue(new MockResponse().setBody("Success!")); 1646 server.play(); 1647 1648 client.setSslSocketFactory(sslContext.getSocketFactory()); 1649 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1650 connection = client.open(server.getUrl("/")); 1651 connection.setDoOutput(true); 1652 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1653 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1654 connection.setFixedLengthStreamingMode(requestBody.length); 1655 } else if (streamingMode == StreamingMode.CHUNKED) { 1656 connection.setChunkedStreamingMode(0); 1657 } 1658 OutputStream outputStream = connection.getOutputStream(); 1659 outputStream.write(requestBody); 1660 outputStream.close(); 1661 assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1662 1663 RecordedRequest request = server.takeRequest(); 1664 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1665 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1666 assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes()); 1667 } else if (streamingMode == StreamingMode.CHUNKED) { 1668 assertEquals(Arrays.asList(4), request.getChunkSizes()); 1669 } 1670 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1671 } 1672 1673 enum StreamingMode { 1674 FIXED_LENGTH, CHUNKED 1675 } 1676 1677 @Test public void authenticateWithPost() throws Exception { 1678 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1679 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1680 .setBody("Please authenticate."); 1681 // fail auth three times... 1682 server.enqueue(pleaseAuthenticate); 1683 server.enqueue(pleaseAuthenticate); 1684 server.enqueue(pleaseAuthenticate); 1685 // ...then succeed the fourth time 1686 server.enqueue(new MockResponse().setBody("Successful auth!")); 1687 server.play(); 1688 1689 Authenticator.setDefault(new RecordingAuthenticator()); 1690 connection = client.open(server.getUrl("/")); 1691 connection.setDoOutput(true); 1692 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1693 OutputStream outputStream = connection.getOutputStream(); 1694 outputStream.write(requestBody); 1695 outputStream.close(); 1696 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1697 1698 // no authorization header for the first request... 1699 RecordedRequest request = server.takeRequest(); 1700 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1701 1702 // ...but the three requests that follow include an authorization header 1703 for (int i = 0; i < 3; i++) { 1704 request = server.takeRequest(); 1705 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1706 assertContains(request.getHeaders(), 1707 "Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 1708 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1709 } 1710 } 1711 1712 @Test public void authenticateWithGet() throws Exception { 1713 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1714 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1715 .setBody("Please authenticate."); 1716 // fail auth three times... 1717 server.enqueue(pleaseAuthenticate); 1718 server.enqueue(pleaseAuthenticate); 1719 server.enqueue(pleaseAuthenticate); 1720 // ...then succeed the fourth time 1721 server.enqueue(new MockResponse().setBody("Successful auth!")); 1722 server.play(); 1723 1724 Authenticator.setDefault(new RecordingAuthenticator()); 1725 connection = client.open(server.getUrl("/")); 1726 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1727 1728 // no authorization header for the first request... 1729 RecordedRequest request = server.takeRequest(); 1730 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1731 1732 // ...but the three requests that follow requests include an authorization header 1733 for (int i = 0; i < 3; i++) { 1734 request = server.takeRequest(); 1735 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1736 assertContains(request.getHeaders(), 1737 "Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 1738 } 1739 } 1740 1741 /** https://code.google.com/p/android/issues/detail?id=74026 */ 1742 @Test public void authenticateWithGetAndTransparentGzip() throws Exception { 1743 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1744 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1745 .setBody("Please authenticate."); 1746 // fail auth three times... 1747 server.enqueue(pleaseAuthenticate); 1748 server.enqueue(pleaseAuthenticate); 1749 server.enqueue(pleaseAuthenticate); 1750 // ...then succeed the fourth time 1751 MockResponse successfulResponse = new MockResponse() 1752 .addHeader("Content-Encoding", "gzip") 1753 .setBody(gzip("Successful auth!".getBytes("UTF-8"))); 1754 server.enqueue(successfulResponse); 1755 server.play(); 1756 1757 Authenticator.setDefault(new RecordingAuthenticator()); 1758 connection = client.open(server.getUrl("/")); 1759 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1760 1761 // no authorization header for the first request... 1762 RecordedRequest request = server.takeRequest(); 1763 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1764 1765 // ...but the three requests that follow requests include an authorization header 1766 for (int i = 0; i < 3; i++) { 1767 request = server.takeRequest(); 1768 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1769 assertContains(request.getHeaders(), 1770 "Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 1771 } 1772 } 1773 1774 /** https://github.com/square/okhttp/issues/342 */ 1775 @Test public void authenticateRealmUppercase() throws Exception { 1776 server.enqueue(new MockResponse().setResponseCode(401) 1777 .addHeader("wWw-aUtHeNtIcAtE: bAsIc rEaLm=\"pRoTeCtEd aReA\"") 1778 .setBody("Please authenticate.")); 1779 server.enqueue(new MockResponse().setBody("Successful auth!")); 1780 server.play(); 1781 1782 Authenticator.setDefault(new RecordingAuthenticator()); 1783 connection = client.open(server.getUrl("/")); 1784 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1785 } 1786 1787 @Test public void redirectedWithChunkedEncoding() throws Exception { 1788 testRedirected(TransferKind.CHUNKED, true); 1789 } 1790 1791 @Test public void redirectedWithContentLengthHeader() throws Exception { 1792 testRedirected(TransferKind.FIXED_LENGTH, true); 1793 } 1794 1795 @Test public void redirectedWithNoLengthHeaders() throws Exception { 1796 testRedirected(TransferKind.END_OF_STREAM, false); 1797 } 1798 1799 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1800 MockResponse response = new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1801 .addHeader("Location: /foo"); 1802 transferKind.setBody(response, "This page has moved!", 10); 1803 server.enqueue(response); 1804 server.enqueue(new MockResponse().setBody("This is the new location!")); 1805 server.play(); 1806 1807 URLConnection connection = client.open(server.getUrl("/")); 1808 assertEquals("This is the new location!", 1809 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1810 1811 RecordedRequest first = server.takeRequest(); 1812 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1813 RecordedRequest retry = server.takeRequest(); 1814 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1815 if (reuse) { 1816 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1817 } 1818 } 1819 1820 @Test public void redirectedOnHttps() throws IOException, InterruptedException { 1821 server.useHttps(sslContext.getSocketFactory(), false); 1822 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1823 .addHeader("Location: /foo") 1824 .setBody("This page has moved!")); 1825 server.enqueue(new MockResponse().setBody("This is the new location!")); 1826 server.play(); 1827 1828 client.setSslSocketFactory(sslContext.getSocketFactory()); 1829 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1830 connection = client.open(server.getUrl("/")); 1831 assertEquals("This is the new location!", 1832 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1833 1834 RecordedRequest first = server.takeRequest(); 1835 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1836 RecordedRequest retry = server.takeRequest(); 1837 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1838 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1839 } 1840 1841 @Test public void notRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1842 server.useHttps(sslContext.getSocketFactory(), false); 1843 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1844 .addHeader("Location: http://anyhost/foo") 1845 .setBody("This page has moved!")); 1846 server.play(); 1847 1848 client.setFollowProtocolRedirects(false); 1849 client.setSslSocketFactory(sslContext.getSocketFactory()); 1850 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1851 connection = client.open(server.getUrl("/")); 1852 assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1853 } 1854 1855 @Test public void notRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1856 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1857 .addHeader("Location: https://anyhost/foo") 1858 .setBody("This page has moved!")); 1859 server.play(); 1860 1861 client.setFollowProtocolRedirects(false); 1862 connection = client.open(server.getUrl("/")); 1863 assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1864 } 1865 1866 @Test public void redirectedFromHttpsToHttpFollowingProtocolRedirects() throws Exception { 1867 server2 = new MockWebServer(); 1868 server2.enqueue(new MockResponse().setBody("This is insecure HTTP!")); 1869 server2.play(); 1870 1871 server.useHttps(sslContext.getSocketFactory(), false); 1872 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1873 .addHeader("Location: " + server2.getUrl("/")) 1874 .setBody("This page has moved!")); 1875 server.play(); 1876 1877 client.setSslSocketFactory(sslContext.getSocketFactory()); 1878 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1879 client.setFollowProtocolRedirects(true); 1880 HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/")); 1881 assertContent("This is insecure HTTP!", connection); 1882 assertNull(connection.getCipherSuite()); 1883 assertNull(connection.getLocalCertificates()); 1884 assertNull(connection.getServerCertificates()); 1885 assertNull(connection.getPeerPrincipal()); 1886 assertNull(connection.getLocalPrincipal()); 1887 } 1888 1889 @Test public void redirectedFromHttpToHttpsFollowingProtocolRedirects() throws Exception { 1890 server2 = new MockWebServer(); 1891 server2.useHttps(sslContext.getSocketFactory(), false); 1892 server2.enqueue(new MockResponse().setBody("This is secure HTTPS!")); 1893 server2.play(); 1894 1895 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1896 .addHeader("Location: " + server2.getUrl("/")) 1897 .setBody("This page has moved!")); 1898 server.play(); 1899 1900 client.setSslSocketFactory(sslContext.getSocketFactory()); 1901 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1902 client.setFollowProtocolRedirects(true); 1903 connection = client.open(server.getUrl("/")); 1904 assertContent("This is secure HTTPS!", connection); 1905 assertFalse(connection instanceof HttpsURLConnection); 1906 } 1907 1908 @Test public void redirectToAnotherOriginServer() throws Exception { 1909 redirectToAnotherOriginServer(false); 1910 } 1911 1912 @Test public void redirectToAnotherOriginServerWithHttps() throws Exception { 1913 redirectToAnotherOriginServer(true); 1914 } 1915 1916 private void redirectToAnotherOriginServer(boolean https) throws Exception { 1917 server2 = new MockWebServer(); 1918 if (https) { 1919 server.useHttps(sslContext.getSocketFactory(), false); 1920 server2.useHttps(sslContext.getSocketFactory(), false); 1921 server2.setNpnEnabled(false); 1922 client.setSslSocketFactory(sslContext.getSocketFactory()); 1923 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1924 } 1925 1926 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1927 server2.enqueue(new MockResponse().setBody("This is the 2nd server, again!")); 1928 server2.play(); 1929 1930 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1931 .addHeader("Location: " + server2.getUrl("/").toString()) 1932 .setBody("This page has moved!")); 1933 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1934 server.play(); 1935 1936 connection = client.open(server.getUrl("/")); 1937 assertContent("This is the 2nd server!", connection); 1938 assertEquals(server2.getUrl("/"), connection.getURL()); 1939 1940 // make sure the first server was careful to recycle the connection 1941 assertContent("This is the first server again!", client.open(server.getUrl("/"))); 1942 assertContent("This is the 2nd server, again!", client.open(server2.getUrl("/"))); 1943 1944 String server1Host = hostName + ":" + server.getPort(); 1945 String server2Host = hostName + ":" + server2.getPort(); 1946 assertContains(server.takeRequest().getHeaders(), "Host: " + server1Host); 1947 assertContains(server2.takeRequest().getHeaders(), "Host: " + server2Host); 1948 assertEquals("Expected connection reuse", 1, server.takeRequest().getSequenceNumber()); 1949 assertEquals("Expected connection reuse", 1, server2.takeRequest().getSequenceNumber()); 1950 } 1951 1952 @Test public void redirectWithProxySelector() throws Exception { 1953 final List<URI> proxySelectionRequests = new ArrayList<URI>(); 1954 client.setProxySelector(new ProxySelector() { 1955 @Override public List<Proxy> select(URI uri) { 1956 proxySelectionRequests.add(uri); 1957 MockWebServer proxyServer = (uri.getPort() == server.getPort()) ? server : server2; 1958 return Arrays.asList(proxyServer.toProxyAddress()); 1959 } 1960 @Override public void connectFailed(URI uri, SocketAddress address, IOException failure) { 1961 throw new AssertionError(); 1962 } 1963 }); 1964 1965 server2 = new MockWebServer(); 1966 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1967 server2.play(); 1968 1969 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1970 .addHeader("Location: " + server2.getUrl("/b").toString()) 1971 .setBody("This page has moved!")); 1972 server.play(); 1973 1974 assertContent("This is the 2nd server!", client.open(server.getUrl("/a"))); 1975 1976 assertEquals(Arrays.asList(server.getUrl("/a").toURI(), server2.getUrl("/b").toURI()), 1977 proxySelectionRequests); 1978 1979 server2.shutdown(); 1980 } 1981 1982 @Test public void response300MultipleChoiceWithPost() throws Exception { 1983 // Chrome doesn't follow the redirect, but Firefox and the RI both do 1984 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE, TransferKind.END_OF_STREAM); 1985 } 1986 1987 @Test public void response301MovedPermanentlyWithPost() throws Exception { 1988 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM, TransferKind.END_OF_STREAM); 1989 } 1990 1991 @Test public void response302MovedTemporarilyWithPost() throws Exception { 1992 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.END_OF_STREAM); 1993 } 1994 1995 @Test public void response303SeeOtherWithPost() throws Exception { 1996 testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER, TransferKind.END_OF_STREAM); 1997 } 1998 1999 @Test public void postRedirectToGetWithChunkedRequest() throws Exception { 2000 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.CHUNKED); 2001 } 2002 2003 @Test public void postRedirectToGetWithStreamedRequest() throws Exception { 2004 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.FIXED_LENGTH); 2005 } 2006 2007 private void testResponseRedirectedWithPost(int redirectCode, TransferKind transferKind) 2008 throws Exception { 2009 server.enqueue(new MockResponse().setResponseCode(redirectCode) 2010 .addHeader("Location: /page2") 2011 .setBody("This page has moved!")); 2012 server.enqueue(new MockResponse().setBody("Page 2")); 2013 server.play(); 2014 2015 connection = client.open(server.getUrl("/page1")); 2016 connection.setDoOutput(true); 2017 transferKind.setForRequest(connection, 4); 2018 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 2019 OutputStream outputStream = connection.getOutputStream(); 2020 outputStream.write(requestBody); 2021 outputStream.close(); 2022 assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2023 assertTrue(connection.getDoOutput()); 2024 2025 RecordedRequest page1 = server.takeRequest(); 2026 assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine()); 2027 assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); 2028 2029 RecordedRequest page2 = server.takeRequest(); 2030 assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); 2031 } 2032 2033 @Test public void redirectedPostStripsRequestBodyHeaders() throws Exception { 2034 server.enqueue(new MockResponse() 2035 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2036 .addHeader("Location: /page2")); 2037 server.enqueue(new MockResponse().setBody("Page 2")); 2038 server.play(); 2039 2040 connection = client.open(server.getUrl("/page1")); 2041 connection.setDoOutput(true); 2042 connection.addRequestProperty("Content-Length", "4"); 2043 connection.addRequestProperty("Content-Type", "text/plain; charset=utf-8"); 2044 connection.addRequestProperty("Transfer-Encoding", "identity"); 2045 OutputStream outputStream = connection.getOutputStream(); 2046 outputStream.write("ABCD".getBytes("UTF-8")); 2047 outputStream.close(); 2048 assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2049 2050 assertEquals("POST /page1 HTTP/1.1", server.takeRequest().getRequestLine()); 2051 2052 RecordedRequest page2 = server.takeRequest(); 2053 assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); 2054 assertContainsNoneMatching(page2.getHeaders(), "Content-Length"); 2055 assertContains(page2.getHeaders(), "Content-Type: text/plain; charset=utf-8"); 2056 assertContains(page2.getHeaders(), "Transfer-Encoding: identity"); 2057 } 2058 2059 @Test public void response305UseProxy() throws Exception { 2060 server.play(); 2061 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_USE_PROXY) 2062 .addHeader("Location: " + server.getUrl("/")) 2063 .setBody("This page has moved!")); 2064 server.enqueue(new MockResponse().setBody("Proxy Response")); 2065 2066 connection = client.open(server.getUrl("/foo")); 2067 // Fails on the RI, which gets "Proxy Response" 2068 assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2069 2070 RecordedRequest page1 = server.takeRequest(); 2071 assertEquals("GET /foo HTTP/1.1", page1.getRequestLine()); 2072 assertEquals(1, server.getRequestCount()); 2073 } 2074 2075 @Test public void response307WithGet() throws Exception { 2076 test307Redirect("GET"); 2077 } 2078 2079 @Test public void response307WithHead() throws Exception { 2080 test307Redirect("HEAD"); 2081 } 2082 2083 @Test public void response307WithOptions() throws Exception { 2084 test307Redirect("OPTIONS"); 2085 } 2086 2087 @Test public void response307WithPost() throws Exception { 2088 test307Redirect("POST"); 2089 } 2090 2091 private void test307Redirect(String method) throws Exception { 2092 MockResponse response1 = new MockResponse() 2093 .setResponseCode(HTTP_TEMP_REDIRECT) 2094 .addHeader("Location: /page2"); 2095 if (!method.equals("HEAD")) { 2096 response1.setBody("This page has moved!"); 2097 } 2098 server.enqueue(response1); 2099 server.enqueue(new MockResponse().setBody("Page 2")); 2100 server.play(); 2101 2102 connection = client.open(server.getUrl("/page1")); 2103 connection.setRequestMethod(method); 2104 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 2105 if (method.equals("POST")) { 2106 connection.setDoOutput(true); 2107 OutputStream outputStream = connection.getOutputStream(); 2108 outputStream.write(requestBody); 2109 outputStream.close(); 2110 } 2111 2112 String response = readAscii(connection.getInputStream(), Integer.MAX_VALUE); 2113 2114 RecordedRequest page1 = server.takeRequest(); 2115 assertEquals(method + " /page1 HTTP/1.1", page1.getRequestLine()); 2116 2117 if (method.equals("GET")) { 2118 assertEquals("Page 2", response); 2119 } else if (method.equals("HEAD")) { 2120 assertEquals("", response); 2121 } else { 2122 // Methods other than GET/HEAD shouldn't follow the redirect 2123 if (method.equals("POST")) { 2124 assertTrue(connection.getDoOutput()); 2125 assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); 2126 } 2127 assertEquals(1, server.getRequestCount()); 2128 assertEquals("This page has moved!", response); 2129 return; 2130 } 2131 2132 // GET/HEAD requests should have followed the redirect with the same method 2133 assertFalse(connection.getDoOutput()); 2134 assertEquals(2, server.getRequestCount()); 2135 RecordedRequest page2 = server.takeRequest(); 2136 assertEquals(method + " /page2 HTTP/1.1", page2.getRequestLine()); 2137 } 2138 2139 @Test public void follow20Redirects() throws Exception { 2140 for (int i = 0; i < 20; i++) { 2141 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2142 .addHeader("Location: /" + (i + 1)) 2143 .setBody("Redirecting to /" + (i + 1))); 2144 } 2145 server.enqueue(new MockResponse().setBody("Success!")); 2146 server.play(); 2147 2148 connection = client.open(server.getUrl("/0")); 2149 assertContent("Success!", connection); 2150 assertEquals(server.getUrl("/20"), connection.getURL()); 2151 } 2152 2153 @Test public void doesNotFollow21Redirects() throws Exception { 2154 for (int i = 0; i < 21; i++) { 2155 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2156 .addHeader("Location: /" + (i + 1)) 2157 .setBody("Redirecting to /" + (i + 1))); 2158 } 2159 server.play(); 2160 2161 connection = client.open(server.getUrl("/0")); 2162 try { 2163 connection.getInputStream(); 2164 fail(); 2165 } catch (ProtocolException expected) { 2166 assertEquals(HttpURLConnection.HTTP_MOVED_TEMP, connection.getResponseCode()); 2167 assertEquals("Too many redirects: 21", expected.getMessage()); 2168 assertContent("Redirecting to /21", connection); 2169 assertEquals(server.getUrl("/20"), connection.getURL()); 2170 } 2171 } 2172 2173 @Test public void httpsWithCustomTrustManager() throws Exception { 2174 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 2175 RecordingTrustManager trustManager = new RecordingTrustManager(); 2176 SSLContext sc = SSLContext.getInstance("TLS"); 2177 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 2178 2179 client.setHostnameVerifier(hostnameVerifier); 2180 client.setSslSocketFactory(sc.getSocketFactory()); 2181 server.useHttps(sslContext.getSocketFactory(), false); 2182 server.enqueue(new MockResponse().setBody("ABC")); 2183 server.enqueue(new MockResponse().setBody("DEF")); 2184 server.enqueue(new MockResponse().setBody("GHI")); 2185 server.play(); 2186 2187 URL url = server.getUrl("/"); 2188 assertContent("ABC", client.open(url)); 2189 assertContent("DEF", client.open(url)); 2190 assertContent("GHI", client.open(url)); 2191 2192 assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls); 2193 assertEquals(Arrays.asList("checkServerTrusted [CN=" + hostName + " 1]"), trustManager.calls); 2194 } 2195 2196 @Test public void readTimeouts() throws IOException { 2197 // This relies on the fact that MockWebServer doesn't close the 2198 // connection after a response has been sent. This causes the client to 2199 // try to read more bytes than are sent, which results in a timeout. 2200 MockResponse timeout = 2201 new MockResponse().setBody("ABC").clearHeaders().addHeader("Content-Length: 4"); 2202 server.enqueue(timeout); 2203 server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive 2204 server.play(); 2205 2206 URLConnection connection = client.open(server.getUrl("/")); 2207 connection.setReadTimeout(1000); 2208 InputStream in = connection.getInputStream(); 2209 assertEquals('A', in.read()); 2210 assertEquals('B', in.read()); 2211 assertEquals('C', in.read()); 2212 try { 2213 in.read(); // if Content-Length was accurate, this would return -1 immediately 2214 fail(); 2215 } catch (SocketTimeoutException expected) { 2216 } 2217 } 2218 2219 @Test public void setChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 2220 server.enqueue(new MockResponse()); 2221 server.play(); 2222 2223 connection = client.open(server.getUrl("/")); 2224 connection.setRequestProperty("Transfer-encoding", "chunked"); 2225 connection.setDoOutput(true); 2226 connection.getOutputStream().write("ABC".getBytes("UTF-8")); 2227 assertEquals(200, connection.getResponseCode()); 2228 2229 RecordedRequest request = server.takeRequest(); 2230 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 2231 } 2232 2233 @Test public void connectionCloseInRequest() throws IOException, InterruptedException { 2234 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 2235 server.enqueue(new MockResponse()); 2236 server.play(); 2237 2238 HttpURLConnection a = client.open(server.getUrl("/")); 2239 a.setRequestProperty("Connection", "close"); 2240 assertEquals(200, a.getResponseCode()); 2241 2242 HttpURLConnection b = client.open(server.getUrl("/")); 2243 assertEquals(200, b.getResponseCode()); 2244 2245 assertEquals(0, server.takeRequest().getSequenceNumber()); 2246 assertEquals("When connection: close is used, each request should get its own connection", 0, 2247 server.takeRequest().getSequenceNumber()); 2248 } 2249 2250 @Test public void connectionCloseInResponse() throws IOException, InterruptedException { 2251 server.enqueue(new MockResponse().addHeader("Connection: close")); 2252 server.enqueue(new MockResponse()); 2253 server.play(); 2254 2255 HttpURLConnection a = client.open(server.getUrl("/")); 2256 assertEquals(200, a.getResponseCode()); 2257 2258 HttpURLConnection b = client.open(server.getUrl("/")); 2259 assertEquals(200, b.getResponseCode()); 2260 2261 assertEquals(0, server.takeRequest().getSequenceNumber()); 2262 assertEquals("When connection: close is used, each request should get its own connection", 0, 2263 server.takeRequest().getSequenceNumber()); 2264 } 2265 2266 @Test public void connectionCloseWithRedirect() throws IOException, InterruptedException { 2267 MockResponse response = new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2268 .addHeader("Location: /foo") 2269 .addHeader("Connection: close"); 2270 server.enqueue(response); 2271 server.enqueue(new MockResponse().setBody("This is the new location!")); 2272 server.play(); 2273 2274 URLConnection connection = client.open(server.getUrl("/")); 2275 assertEquals("This is the new location!", 2276 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2277 2278 assertEquals(0, server.takeRequest().getSequenceNumber()); 2279 assertEquals("When connection: close is used, each request should get its own connection", 0, 2280 server.takeRequest().getSequenceNumber()); 2281 } 2282 2283 /** 2284 * Retry redirects if the socket is closed. 2285 * https://code.google.com/p/android/issues/detail?id=41576 2286 */ 2287 @Test public void sameConnectionRedirectAndReuse() throws Exception { 2288 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2289 .setSocketPolicy(SHUTDOWN_INPUT_AT_END) 2290 .addHeader("Location: /foo")); 2291 server.enqueue(new MockResponse().setBody("This is the new page!")); 2292 server.play(); 2293 2294 assertContent("This is the new page!", client.open(server.getUrl("/"))); 2295 2296 assertEquals(0, server.takeRequest().getSequenceNumber()); 2297 assertEquals(0, server.takeRequest().getSequenceNumber()); 2298 } 2299 2300 @Test public void responseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 2301 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 2302 .setBody("This body is not allowed!")); 2303 server.play(); 2304 2305 URLConnection connection = client.open(server.getUrl("/")); 2306 assertEquals("This body is not allowed!", 2307 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2308 } 2309 2310 @Test public void singleByteReadIsSigned() throws IOException { 2311 server.enqueue(new MockResponse().setBody(new byte[] {-2, -1})); 2312 server.play(); 2313 2314 connection = client.open(server.getUrl("/")); 2315 InputStream in = connection.getInputStream(); 2316 assertEquals(254, in.read()); 2317 assertEquals(255, in.read()); 2318 assertEquals(-1, in.read()); 2319 } 2320 2321 @Test public void flushAfterStreamTransmittedWithChunkedEncoding() throws IOException { 2322 testFlushAfterStreamTransmitted(TransferKind.CHUNKED); 2323 } 2324 2325 @Test public void flushAfterStreamTransmittedWithFixedLength() throws IOException { 2326 testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); 2327 } 2328 2329 @Test public void flushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { 2330 testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); 2331 } 2332 2333 /** 2334 * We explicitly permit apps to close the upload stream even after it has 2335 * been transmitted. We also permit flush so that buffered streams can 2336 * do a no-op flush when they are closed. http://b/3038470 2337 */ 2338 private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { 2339 server.enqueue(new MockResponse().setBody("abc")); 2340 server.play(); 2341 2342 connection = client.open(server.getUrl("/")); 2343 connection.setDoOutput(true); 2344 byte[] upload = "def".getBytes("UTF-8"); 2345 2346 if (transferKind == TransferKind.CHUNKED) { 2347 connection.setChunkedStreamingMode(0); 2348 } else if (transferKind == TransferKind.FIXED_LENGTH) { 2349 connection.setFixedLengthStreamingMode(upload.length); 2350 } 2351 2352 OutputStream out = connection.getOutputStream(); 2353 out.write(upload); 2354 assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2355 2356 out.flush(); // Dubious but permitted. 2357 try { 2358 out.write("ghi".getBytes("UTF-8")); 2359 fail(); 2360 } catch (IOException expected) { 2361 } 2362 } 2363 2364 @Test public void getHeadersThrows() throws IOException { 2365 // Enqueue a response for every IP address held by localhost, because the route selector 2366 // will try each in sequence. 2367 // TODO: use the fake Dns implementation instead of a loop 2368 for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { 2369 server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); 2370 } 2371 server.play(); 2372 2373 connection = client.open(server.getUrl("/")); 2374 try { 2375 connection.getInputStream(); 2376 fail(); 2377 } catch (IOException expected) { 2378 } 2379 2380 try { 2381 connection.getInputStream(); 2382 fail(); 2383 } catch (IOException expected) { 2384 } 2385 } 2386 2387 @Test public void dnsFailureThrowsIOException() throws IOException { 2388 connection = client.open(new URL("http://host.unlikelytld")); 2389 try { 2390 connection.connect(); 2391 fail(); 2392 } catch (IOException expected) { 2393 } 2394 } 2395 2396 @Test public void malformedUrlThrowsUnknownHostException() throws IOException { 2397 connection = client.open(new URL("http:///foo.html")); 2398 try { 2399 connection.connect(); 2400 fail(); 2401 } catch (UnknownHostException expected) { 2402 } 2403 } 2404 2405 @Test public void getKeepAlive() throws Exception { 2406 MockWebServer server = new MockWebServer(); 2407 server.enqueue(new MockResponse().setBody("ABC")); 2408 server.play(); 2409 2410 // The request should work once and then fail 2411 HttpURLConnection connection1 = client.open(server.getUrl("")); 2412 connection1.setReadTimeout(100); 2413 InputStream input = connection1.getInputStream(); 2414 assertEquals("ABC", readAscii(input, Integer.MAX_VALUE)); 2415 server.shutdown(); 2416 try { 2417 HttpURLConnection connection2 = client.open(server.getUrl("")); 2418 connection2.setReadTimeout(100); 2419 connection2.getInputStream(); 2420 fail(); 2421 } catch (ConnectException expected) { 2422 } 2423 } 2424 2425 /** Don't explode if the cache returns a null body. http://b/3373699 */ 2426 @Test public void responseCacheReturnsNullOutputStream() throws Exception { 2427 final AtomicBoolean aborted = new AtomicBoolean(); 2428 client.setResponseCache(new ResponseCache() { 2429 @Override public CacheResponse get(URI uri, String requestMethod, 2430 Map<String, List<String>> requestHeaders) throws IOException { 2431 return null; 2432 } 2433 2434 @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { 2435 return new CacheRequest() { 2436 @Override public void abort() { 2437 aborted.set(true); 2438 } 2439 2440 @Override public OutputStream getBody() throws IOException { 2441 return null; 2442 } 2443 }; 2444 } 2445 }); 2446 2447 server.enqueue(new MockResponse().setBody("abcdef")); 2448 server.play(); 2449 2450 HttpURLConnection connection = client.open(server.getUrl("/")); 2451 InputStream in = connection.getInputStream(); 2452 assertEquals("abc", readAscii(in, 3)); 2453 in.close(); 2454 assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here 2455 } 2456 2457 /** http://code.google.com/p/android/issues/detail?id=14562 */ 2458 @Test public void readAfterLastByte() throws Exception { 2459 server.enqueue(new MockResponse().setBody("ABC") 2460 .clearHeaders() 2461 .addHeader("Connection: close") 2462 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); 2463 server.play(); 2464 2465 connection = client.open(server.getUrl("/")); 2466 InputStream in = connection.getInputStream(); 2467 assertEquals("ABC", readAscii(in, 3)); 2468 assertEquals(-1, in.read()); 2469 assertEquals(-1, in.read()); // throws IOException in Gingerbread 2470 } 2471 2472 @Test public void getContent() throws Exception { 2473 server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("A")); 2474 server.play(); 2475 connection = client.open(server.getUrl("/")); 2476 InputStream in = (InputStream) connection.getContent(); 2477 assertEquals("A", readAscii(in, Integer.MAX_VALUE)); 2478 } 2479 2480 @Test public void getContentOfType() throws Exception { 2481 server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("A")); 2482 server.play(); 2483 connection = client.open(server.getUrl("/")); 2484 try { 2485 connection.getContent(null); 2486 fail(); 2487 } catch (NullPointerException expected) { 2488 } 2489 try { 2490 connection.getContent(new Class[] { null }); 2491 fail(); 2492 } catch (NullPointerException expected) { 2493 } 2494 assertNull(connection.getContent(new Class[] {getClass()})); 2495 } 2496 2497 @Test public void getOutputStreamOnGetFails() throws Exception { 2498 server.enqueue(new MockResponse()); 2499 server.play(); 2500 connection = client.open(server.getUrl("/")); 2501 try { 2502 connection.getOutputStream(); 2503 fail(); 2504 } catch (ProtocolException expected) { 2505 } 2506 } 2507 2508 @Test public void getOutputAfterGetInputStreamFails() throws Exception { 2509 server.enqueue(new MockResponse()); 2510 server.play(); 2511 connection = client.open(server.getUrl("/")); 2512 connection.setDoOutput(true); 2513 try { 2514 connection.getInputStream(); 2515 connection.getOutputStream(); 2516 fail(); 2517 } catch (ProtocolException expected) { 2518 } 2519 } 2520 2521 @Test public void setDoOutputOrDoInputAfterConnectFails() throws Exception { 2522 server.enqueue(new MockResponse()); 2523 server.play(); 2524 connection = client.open(server.getUrl("/")); 2525 connection.connect(); 2526 try { 2527 connection.setDoOutput(true); 2528 fail(); 2529 } catch (IllegalStateException expected) { 2530 } 2531 try { 2532 connection.setDoInput(true); 2533 fail(); 2534 } catch (IllegalStateException expected) { 2535 } 2536 } 2537 2538 @Test public void clientSendsContentLength() throws Exception { 2539 server.enqueue(new MockResponse().setBody("A")); 2540 server.play(); 2541 connection = client.open(server.getUrl("/")); 2542 connection.setDoOutput(true); 2543 OutputStream out = connection.getOutputStream(); 2544 out.write(new byte[] { 'A', 'B', 'C' }); 2545 out.close(); 2546 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2547 RecordedRequest request = server.takeRequest(); 2548 assertContains(request.getHeaders(), "Content-Length: 3"); 2549 } 2550 2551 @Test public void getContentLengthConnects() throws Exception { 2552 server.enqueue(new MockResponse().setBody("ABC")); 2553 server.play(); 2554 connection = client.open(server.getUrl("/")); 2555 assertEquals(3, connection.getContentLength()); 2556 } 2557 2558 @Test public void getContentTypeConnects() throws Exception { 2559 server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("ABC")); 2560 server.play(); 2561 connection = client.open(server.getUrl("/")); 2562 assertEquals("text/plain", connection.getContentType()); 2563 } 2564 2565 @Test public void getContentEncodingConnects() throws Exception { 2566 server.enqueue(new MockResponse().addHeader("Content-Encoding: identity").setBody("ABC")); 2567 server.play(); 2568 connection = client.open(server.getUrl("/")); 2569 assertEquals("identity", connection.getContentEncoding()); 2570 } 2571 2572 // http://b/4361656 2573 @Test public void urlContainsQueryButNoPath() throws Exception { 2574 server.enqueue(new MockResponse().setBody("A")); 2575 server.play(); 2576 URL url = new URL("http", server.getHostName(), server.getPort(), "?query"); 2577 assertEquals("A", readAscii(client.open(url).getInputStream(), Integer.MAX_VALUE)); 2578 RecordedRequest request = server.takeRequest(); 2579 assertEquals("GET /?query HTTP/1.1", request.getRequestLine()); 2580 } 2581 2582 // http://code.google.com/p/android/issues/detail?id=20442 2583 @Test public void inputStreamAvailableWithChunkedEncoding() throws Exception { 2584 testInputStreamAvailable(TransferKind.CHUNKED); 2585 } 2586 2587 @Test public void inputStreamAvailableWithContentLengthHeader() throws Exception { 2588 testInputStreamAvailable(TransferKind.FIXED_LENGTH); 2589 } 2590 2591 @Test public void inputStreamAvailableWithNoLengthHeaders() throws Exception { 2592 testInputStreamAvailable(TransferKind.END_OF_STREAM); 2593 } 2594 2595 private void testInputStreamAvailable(TransferKind transferKind) throws IOException { 2596 String body = "ABCDEFGH"; 2597 MockResponse response = new MockResponse(); 2598 transferKind.setBody(response, body, 4); 2599 server.enqueue(response); 2600 server.play(); 2601 connection = client.open(server.getUrl("/")); 2602 InputStream in = connection.getInputStream(); 2603 for (int i = 0; i < body.length(); i++) { 2604 assertTrue(in.available() >= 0); 2605 assertEquals(body.charAt(i), in.read()); 2606 } 2607 assertEquals(0, in.available()); 2608 assertEquals(-1, in.read()); 2609 } 2610 2611 @Test public void postFailsWithBufferedRequestForSmallRequest() throws Exception { 2612 reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 1024); 2613 } 2614 2615 // This test is ignored because we don't (yet) reliably recover for large request bodies. 2616 @Test public void postFailsWithBufferedRequestForLargeRequest() throws Exception { 2617 reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 16384); 2618 } 2619 2620 @Test public void postFailsWithChunkedRequestForSmallRequest() throws Exception { 2621 reusedConnectionFailsWithPost(TransferKind.CHUNKED, 1024); 2622 } 2623 2624 @Test public void postFailsWithChunkedRequestForLargeRequest() throws Exception { 2625 reusedConnectionFailsWithPost(TransferKind.CHUNKED, 16384); 2626 } 2627 2628 @Test public void postFailsWithFixedLengthRequestForSmallRequest() throws Exception { 2629 reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 1024); 2630 } 2631 2632 @Test public void postFailsWithFixedLengthRequestForLargeRequest() throws Exception { 2633 reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 16384); 2634 } 2635 2636 private void reusedConnectionFailsWithPost(TransferKind transferKind, int requestSize) 2637 throws Exception { 2638 server.enqueue(new MockResponse().setBody("A").setSocketPolicy(SHUTDOWN_INPUT_AT_END)); 2639 server.enqueue(new MockResponse().setBody("B")); 2640 server.enqueue(new MockResponse().setBody("C")); 2641 server.play(); 2642 2643 assertContent("A", client.open(server.getUrl("/a"))); 2644 2645 // Give the server time to disconnect. 2646 Thread.sleep(500); 2647 2648 // If the request body is larger than OkHttp's replay buffer, the failure may still occur. 2649 byte[] requestBody = new byte[requestSize]; 2650 new Random(0).nextBytes(requestBody); 2651 2652 connection = client.open(server.getUrl("/b")); 2653 connection.setRequestMethod("POST"); 2654 transferKind.setForRequest(connection, requestBody.length); 2655 for (int i = 0; i < requestBody.length; i += 1024) { 2656 connection.getOutputStream().write(requestBody, i, 1024); 2657 } 2658 connection.getOutputStream().close(); 2659 assertContent("B", connection); 2660 2661 RecordedRequest requestA = server.takeRequest(); 2662 assertEquals("/a", requestA.getPath()); 2663 RecordedRequest requestB = server.takeRequest(); 2664 assertEquals("/b", requestB.getPath()); 2665 assertEquals(Arrays.toString(requestBody), Arrays.toString(requestB.getBody())); 2666 } 2667 2668 @Test public void fullyBufferedPostIsTooShort() throws Exception { 2669 server.enqueue(new MockResponse().setBody("A")); 2670 server.play(); 2671 2672 connection = client.open(server.getUrl("/b")); 2673 connection.setRequestProperty("Content-Length", "4"); 2674 connection.setRequestMethod("POST"); 2675 OutputStream out = connection.getOutputStream(); 2676 out.write('a'); 2677 out.write('b'); 2678 out.write('c'); 2679 try { 2680 out.close(); 2681 fail(); 2682 } catch (IOException expected) { 2683 } 2684 } 2685 2686 @Test public void fullyBufferedPostIsTooLong() throws Exception { 2687 server.enqueue(new MockResponse().setBody("A")); 2688 server.play(); 2689 2690 connection = client.open(server.getUrl("/b")); 2691 connection.setRequestProperty("Content-Length", "3"); 2692 connection.setRequestMethod("POST"); 2693 OutputStream out = connection.getOutputStream(); 2694 out.write('a'); 2695 out.write('b'); 2696 out.write('c'); 2697 try { 2698 out.write('d'); 2699 out.flush(); 2700 fail(); 2701 } catch (IOException expected) { 2702 } 2703 } 2704 2705 @Test @Ignore public void testPooledConnectionsDetectHttp10() { 2706 // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1) 2707 fail("TODO"); 2708 } 2709 2710 @Test @Ignore public void postBodiesRetransmittedOnAuthProblems() { 2711 fail("TODO"); 2712 } 2713 2714 @Test @Ignore public void cookiesAndTrailers() { 2715 // Do cookie headers get processed too many times? 2716 fail("TODO"); 2717 } 2718 2719 @Test @Ignore public void headerNamesContainingNullCharacter() { 2720 // This is relevant for SPDY 2721 fail("TODO"); 2722 } 2723 2724 @Test @Ignore public void headerValuesContainingNullCharacter() { 2725 // This is relevant for SPDY 2726 fail("TODO"); 2727 } 2728 2729 @Test public void emptyRequestHeaderValueIsAllowed() throws Exception { 2730 server.enqueue(new MockResponse().setBody("body")); 2731 server.play(); 2732 connection = client.open(server.getUrl("/")); 2733 connection.addRequestProperty("B", ""); 2734 assertContent("body", connection); 2735 assertEquals("", connection.getRequestProperty("B")); 2736 } 2737 2738 @Test public void emptyResponseHeaderValueIsAllowed() throws Exception { 2739 server.enqueue(new MockResponse().addHeader("A:").setBody("body")); 2740 server.play(); 2741 connection = client.open(server.getUrl("/")); 2742 assertContent("body", connection); 2743 assertEquals("", connection.getHeaderField("A")); 2744 } 2745 2746 @Test public void emptyRequestHeaderNameIsStrict() throws Exception { 2747 server.enqueue(new MockResponse().setBody("body")); 2748 server.play(); 2749 connection = client.open(server.getUrl("/")); 2750 try { 2751 connection.setRequestProperty("", "A"); 2752 fail(); 2753 } catch (IllegalArgumentException expected) { 2754 } 2755 } 2756 2757 @Test public void emptyResponseHeaderNameIsLenient() throws Exception { 2758 server.enqueue(new MockResponse().addHeader(":A").setBody("body")); 2759 server.play(); 2760 connection = client.open(server.getUrl("/")); 2761 connection.getResponseCode(); 2762 assertEquals("A", connection.getHeaderField("")); 2763 } 2764 2765 @Test @Ignore public void deflateCompression() { 2766 fail("TODO"); 2767 } 2768 2769 @Test @Ignore public void postBodiesRetransmittedOnIpAddressProblems() { 2770 fail("TODO"); 2771 } 2772 2773 @Test @Ignore public void pooledConnectionProblemsNotReportedToProxySelector() { 2774 fail("TODO"); 2775 } 2776 2777 @Test public void customAuthenticator() throws Exception { 2778 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 2779 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 2780 .setBody("Please authenticate."); 2781 server.enqueue(pleaseAuthenticate); 2782 server.enqueue(new MockResponse().setBody("A")); 2783 server.play(); 2784 2785 Credential credential = Credential.basic("jesse", "peanutbutter"); 2786 RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator(credential); 2787 client.setAuthenticator(authenticator); 2788 assertContent("A", client.open(server.getUrl("/private"))); 2789 2790 assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); 2791 assertContains(server.takeRequest().getHeaders(), 2792 "Authorization: " + credential.getHeaderValue()); 2793 2794 assertEquals(Proxy.NO_PROXY, authenticator.onlyProxy()); 2795 URL url = authenticator.onlyUrl(); 2796 assertEquals("/private", url.getPath()); 2797 assertEquals(Arrays.asList(new Challenge("Basic", "protected area")), authenticator.onlyChallenge()); 2798 } 2799 2800 @Test public void npnSetsProtocolHeader_SPDY_3() throws Exception { 2801 npnSetsProtocolHeader(Protocol.SPDY_3); 2802 } 2803 2804 @Test public void npnSetsProtocolHeader_HTTP_2() throws Exception { 2805 npnSetsProtocolHeader(Protocol.HTTP_2); 2806 } 2807 2808 private void npnSetsProtocolHeader(Protocol protocol) throws IOException { 2809 enableNpn(protocol); 2810 server.enqueue(new MockResponse().setBody("A")); 2811 server.play(); 2812 client.setProtocols(Arrays.asList(Protocol.HTTP_11, protocol)); 2813 connection = client.open(server.getUrl("/")); 2814 List<String> protocolValues = connection.getHeaderFields().get(SELECTED_PROTOCOL); 2815 assertEquals(Arrays.asList(protocol.name.utf8()), protocolValues); 2816 assertContent("A", connection); 2817 } 2818 2819 /** For example, empty Protobuf RPC messages end up as a zero-length POST. */ 2820 @Test public void zeroLengthPost() throws IOException, InterruptedException { 2821 zeroLengthPayload("POST"); 2822 } 2823 2824 @Test public void zeroLengthPost_SPDY_3() throws Exception { 2825 enableNpn(Protocol.SPDY_3); 2826 zeroLengthPost(); 2827 } 2828 2829 @Test public void zeroLengthPost_HTTP_2() throws Exception { 2830 enableNpn(Protocol.HTTP_2); 2831 zeroLengthPost(); 2832 } 2833 2834 /** For example, creating an Amazon S3 bucket ends up as a zero-length POST. */ 2835 @Test public void zeroLengthPut() throws IOException, InterruptedException { 2836 zeroLengthPayload("PUT"); 2837 } 2838 2839 @Test public void zeroLengthPut_SPDY_3() throws Exception { 2840 enableNpn(Protocol.SPDY_3); 2841 zeroLengthPut(); 2842 } 2843 2844 @Test public void zeroLengthPut_HTTP_2() throws Exception { 2845 enableNpn(Protocol.HTTP_2); 2846 zeroLengthPut(); 2847 } 2848 2849 private void zeroLengthPayload(String method) 2850 throws IOException, InterruptedException { 2851 server.enqueue(new MockResponse()); 2852 server.play(); 2853 connection = client.open(server.getUrl("/")); 2854 connection.setRequestProperty("Content-Length", "0"); 2855 connection.setRequestMethod(method); 2856 connection.setFixedLengthStreamingMode(0); 2857 connection.setDoOutput(true); 2858 assertContent("", connection); 2859 RecordedRequest zeroLengthPayload = server.takeRequest(); 2860 assertEquals(method, zeroLengthPayload.getMethod()); 2861 assertEquals("0", zeroLengthPayload.getHeader("content-length")); 2862 assertEquals(0L, zeroLengthPayload.getBodySize()); 2863 } 2864 2865 @Test public void setProtocols() throws Exception { 2866 server.enqueue(new MockResponse().setBody("A")); 2867 server.play(); 2868 client.setProtocols(Arrays.asList(Protocol.HTTP_11)); 2869 assertContent("A", client.open(server.getUrl("/"))); 2870 } 2871 2872 @Test public void setProtocolsWithoutHttp11() throws Exception { 2873 try { 2874 client.setProtocols(Arrays.asList(Protocol.SPDY_3)); 2875 fail(); 2876 } catch (IllegalArgumentException expected) { 2877 } 2878 } 2879 2880 @Test public void setProtocolsWithNull() throws Exception { 2881 try { 2882 client.setProtocols(Arrays.asList(Protocol.HTTP_11, null)); 2883 fail(); 2884 } catch (IllegalArgumentException expected) { 2885 } 2886 } 2887 2888 @Test public void veryLargeFixedLengthRequest() throws Exception { 2889 server.setBodyLimit(0); 2890 server.enqueue(new MockResponse()); 2891 server.play(); 2892 2893 connection = client.open(server.getUrl("/")); 2894 connection.setDoOutput(true); 2895 long contentLength = Integer.MAX_VALUE + 1L; 2896 connection.setFixedLengthStreamingMode(contentLength); 2897 OutputStream out = connection.getOutputStream(); 2898 byte[] buffer = new byte[1024 * 1024]; 2899 for (long bytesWritten = 0; bytesWritten < contentLength; ) { 2900 int byteCount = (int) Math.min(buffer.length, contentLength - bytesWritten); 2901 out.write(buffer, 0, byteCount); 2902 bytesWritten += byteCount; 2903 } 2904 assertContent("", connection); 2905 2906 RecordedRequest request = server.takeRequest(); 2907 assertEquals(Long.toString(contentLength), request.getHeader("Content-Length")); 2908 } 2909 2910 /** 2911 * We had a bug where we attempted to gunzip responses that didn't have a 2912 * body. This only came up with 304s since that response code can include 2913 * headers (like "Content-Encoding") without any content to go along with it. 2914 * https://github.com/square/okhttp/issues/358 2915 */ 2916 @Test public void noTransparentGzipFor304NotModified() throws Exception { 2917 server.enqueue(new MockResponse() 2918 .clearHeaders() 2919 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED) 2920 .addHeader("Content-Encoding: gzip")); 2921 server.enqueue(new MockResponse().setBody("b")); 2922 2923 server.play(); 2924 2925 HttpURLConnection connection1 = client.open(server.getUrl("/")); 2926 assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection1.getResponseCode()); 2927 assertContent("", connection1); 2928 2929 HttpURLConnection connection2 = client.open(server.getUrl("/")); 2930 assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode()); 2931 assertContent("b", connection2); 2932 2933 RecordedRequest requestA = server.takeRequest(); 2934 assertEquals(0, requestA.getSequenceNumber()); 2935 2936 RecordedRequest requestB = server.takeRequest(); 2937 assertEquals(1, requestB.getSequenceNumber()); 2938 } 2939 2940 /** 2941 * We had a bug where we weren't closing Gzip streams on redirects. 2942 * https://github.com/square/okhttp/issues/441 2943 */ 2944 @Test public void gzipWithRedirectAndConnectionReuse() throws Exception { 2945 server.enqueue(new MockResponse() 2946 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2947 .addHeader("Location: /foo") 2948 .addHeader("Content-Encoding: gzip") 2949 .setBody(gzip("Moved! Moved! Moved!".getBytes(UTF_8)))); 2950 server.enqueue(new MockResponse().setBody("This is the new page!")); 2951 server.play(); 2952 2953 HttpURLConnection connection = client.open(server.getUrl("/")); 2954 assertContent("This is the new page!", connection); 2955 2956 RecordedRequest requestA = server.takeRequest(); 2957 assertEquals(0, requestA.getSequenceNumber()); 2958 2959 RecordedRequest requestB = server.takeRequest(); 2960 assertEquals(1, requestB.getSequenceNumber()); 2961 } 2962 2963 /** 2964 * Tolerate bad https proxy response when using HttpResponseCache. Android bug 6754912. 2965 */ 2966 @Test 2967 public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception { 2968 initResponseCache(); 2969 2970 server.useHttps(sslContext.getSocketFactory(), true); 2971 // The inclusion of a body in the response to a CONNECT is key to reproducing b/6754912. 2972 MockResponse 2973 badProxyResponse = new MockResponse() 2974 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 2975 .clearHeaders() 2976 .setBody("bogus proxy connect response content"); 2977 2978 server.enqueue(badProxyResponse); 2979 server.enqueue(new MockResponse().setBody("response")); 2980 2981 server.play(); 2982 2983 URL url = new URL("https://android.com/foo"); 2984 client.setSslSocketFactory(sslContext.getSocketFactory()); 2985 client.setHostnameVerifier(new RecordingHostnameVerifier()); 2986 2987 ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY; 2988 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, client, url); 2989 assertContent("response", connection); 2990 2991 RecordedRequest connect = server.takeRequest(); 2992 assertEquals("CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 2993 assertContains(connect.getHeaders(), "Host: android.com"); 2994 } 2995 2996 /** 2997 * The RFC is unclear in this regard as it only specifies that this should 2998 * invalidate the cache entry (if any). 2999 */ 3000 @Test public void bodyPermittedOnDelete() throws Exception { 3001 server.enqueue(new MockResponse()); 3002 server.play(); 3003 3004 HttpURLConnection connection = client.open(server.getUrl("/")); 3005 connection.setRequestMethod("DELETE"); 3006 connection.setDoOutput(true); 3007 connection.getOutputStream().write("BODY".getBytes(UTF_8)); 3008 assertEquals(200, connection.getResponseCode()); 3009 3010 RecordedRequest request = server.takeRequest(); 3011 assertEquals("DELETE", request.getMethod()); 3012 assertEquals("BODY", new String(request.getBody(), UTF_8)); 3013 } 3014 3015 /** Returns a gzipped copy of {@code bytes}. */ 3016 public byte[] gzip(byte[] bytes) throws IOException { 3017 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 3018 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 3019 gzippedOut.write(bytes); 3020 gzippedOut.close(); 3021 return bytesOut.toByteArray(); 3022 } 3023 3024 /** 3025 * Reads at most {@code limit} characters from {@code in} and asserts that 3026 * content equals {@code expected}. 3027 */ 3028 private void assertContent(String expected, HttpURLConnection connection, int limit) 3029 throws IOException { 3030 connection.connect(); 3031 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 3032 } 3033 3034 private void assertContent(String expected, HttpURLConnection connection) throws IOException { 3035 assertContent(expected, connection, Integer.MAX_VALUE); 3036 } 3037 3038 private void assertContains(List<String> headers, String header) { 3039 assertTrue(headers.toString(), headers.contains(header)); 3040 } 3041 3042 private void assertContainsNoneMatching(List<String> headers, String pattern) { 3043 for (String header : headers) { 3044 if (header.matches(pattern)) { 3045 fail("Header " + header + " matches " + pattern); 3046 } 3047 } 3048 } 3049 3050 private Set<String> newSet(String... elements) { 3051 return new HashSet<String>(Arrays.asList(elements)); 3052 } 3053 3054 enum TransferKind { 3055 CHUNKED() { 3056 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 3057 throws IOException { 3058 response.setChunkedBody(content, chunkSize); 3059 } 3060 @Override void setForRequest(HttpURLConnection connection, int contentLength) { 3061 connection.setChunkedStreamingMode(5); 3062 } 3063 }, 3064 FIXED_LENGTH() { 3065 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 3066 response.setBody(content); 3067 } 3068 @Override void setForRequest(HttpURLConnection connection, int contentLength) { 3069 connection.setFixedLengthStreamingMode(contentLength); 3070 } 3071 }, 3072 END_OF_STREAM() { 3073 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 3074 response.setBody(content); 3075 response.setSocketPolicy(DISCONNECT_AT_END); 3076 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 3077 if (h.next().startsWith("Content-Length:")) { 3078 h.remove(); 3079 break; 3080 } 3081 } 3082 } 3083 @Override void setForRequest(HttpURLConnection connection, int contentLength) { 3084 } 3085 }; 3086 3087 abstract void setBody(MockResponse response, byte[] content, int chunkSize) throws IOException; 3088 3089 abstract void setForRequest(HttpURLConnection connection, int contentLength); 3090 3091 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 3092 setBody(response, content.getBytes("UTF-8"), chunkSize); 3093 } 3094 } 3095 3096 enum ProxyConfig { 3097 NO_PROXY() { 3098 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 3099 throws IOException { 3100 client.setProxy(Proxy.NO_PROXY); 3101 return client.open(url); 3102 } 3103 }, 3104 3105 CREATE_ARG() { 3106 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 3107 throws IOException { 3108 client.setProxy(server.toProxyAddress()); 3109 return client.open(url); 3110 } 3111 }, 3112 3113 PROXY_SYSTEM_PROPERTY() { 3114 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 3115 throws IOException { 3116 System.setProperty("proxyHost", "localhost"); 3117 System.setProperty("proxyPort", Integer.toString(server.getPort())); 3118 return client.open(url); 3119 } 3120 }, 3121 3122 HTTP_PROXY_SYSTEM_PROPERTY() { 3123 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 3124 throws IOException { 3125 System.setProperty("http.proxyHost", "localhost"); 3126 System.setProperty("http.proxyPort", Integer.toString(server.getPort())); 3127 return client.open(url); 3128 } 3129 }, 3130 3131 HTTPS_PROXY_SYSTEM_PROPERTY() { 3132 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 3133 throws IOException { 3134 System.setProperty("https.proxyHost", "localhost"); 3135 System.setProperty("https.proxyPort", Integer.toString(server.getPort())); 3136 return client.open(url); 3137 } 3138 }; 3139 3140 public abstract HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 3141 throws IOException; 3142 } 3143 3144 private static class RecordingTrustManager implements X509TrustManager { 3145 private final List<String> calls = new ArrayList<String>(); 3146 3147 public X509Certificate[] getAcceptedIssuers() { 3148 return new X509Certificate[] { }; 3149 } 3150 3151 public void checkClientTrusted(X509Certificate[] chain, String authType) 3152 throws CertificateException { 3153 calls.add("checkClientTrusted " + certificatesToString(chain)); 3154 } 3155 3156 public void checkServerTrusted(X509Certificate[] chain, String authType) 3157 throws CertificateException { 3158 calls.add("checkServerTrusted " + certificatesToString(chain)); 3159 } 3160 3161 private String certificatesToString(X509Certificate[] certificates) { 3162 List<String> result = new ArrayList<String>(); 3163 for (X509Certificate certificate : certificates) { 3164 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 3165 } 3166 return result.toString(); 3167 } 3168 } 3169 3170 private static class FakeProxySelector extends ProxySelector { 3171 List<Proxy> proxies = new ArrayList<Proxy>(); 3172 3173 @Override public List<Proxy> select(URI uri) { 3174 // Don't handle 'socket' schemes, which the RI's Socket class may request (for SOCKS). 3175 return uri.getScheme().equals("http") || uri.getScheme().equals("https") ? proxies 3176 : Collections.singletonList(Proxy.NO_PROXY); 3177 } 3178 3179 @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 3180 } 3181 } 3182 3183 /** 3184 * Tests that use this will fail unless boot classpath is set. Ex. {@code 3185 * -Xbootclasspath/p:/tmp/npn-boot-8.1.2.v20120308.jar} 3186 */ 3187 private void enableNpn(Protocol protocol) { 3188 client.setSslSocketFactory(sslContext.getSocketFactory()); 3189 client.setHostnameVerifier(new RecordingHostnameVerifier()); 3190 client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_11)); 3191 server.useHttps(sslContext.getSocketFactory(), false); 3192 server.setNpnEnabled(true); 3193 server.setNpnProtocols(client.getProtocols()); 3194 } 3195 3196 /** 3197 * An {@link SSLSocketFactory} that delegates all method calls. 3198 */ 3199 private static abstract class DelegatingSSLSocketFactory extends SSLSocketFactory { 3200 3201 private final SSLSocketFactory delegate; 3202 3203 public DelegatingSSLSocketFactory(SSLSocketFactory delegate) { 3204 this.delegate = delegate; 3205 } 3206 3207 @Override 3208 public String[] getDefaultCipherSuites() { 3209 return delegate.getDefaultCipherSuites(); 3210 } 3211 3212 @Override 3213 public String[] getSupportedCipherSuites() { 3214 return delegate.getSupportedCipherSuites(); 3215 } 3216 3217 @Override 3218 public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) 3219 throws IOException { 3220 return (SSLSocket) delegate.createSocket(s, host, port, autoClose); 3221 } 3222 3223 @Override 3224 public SSLSocket createSocket() throws IOException { 3225 return (SSLSocket) delegate.createSocket(); 3226 } 3227 3228 @Override 3229 public SSLSocket createSocket(String host, int port) throws IOException, UnknownHostException { 3230 return (SSLSocket) delegate.createSocket(host, port); 3231 } 3232 3233 @Override 3234 public SSLSocket createSocket(String host, int port, InetAddress localHost, 3235 int localPort) throws IOException, UnknownHostException { 3236 return (SSLSocket) delegate.createSocket(host, port, localHost, localPort); 3237 } 3238 3239 @Override 3240 public SSLSocket createSocket(InetAddress host, int port) throws IOException { 3241 return (SSLSocket) delegate.createSocket(host, port); 3242 } 3243 3244 @Override 3245 public SSLSocket createSocket(InetAddress address, int port, 3246 InetAddress localAddress, int localPort) throws IOException { 3247 return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); 3248 } 3249 } 3250 3251 /** 3252 * An {@link SSLSocketFactory} that creates sockets using a delegate, but overrides the enabled 3253 * protocols for any created sockets. 3254 */ 3255 private static class LimitedProtocolsSocketFactory extends DelegatingSSLSocketFactory { 3256 3257 private final String[] enabledProtocols; 3258 3259 public LimitedProtocolsSocketFactory(SSLSocketFactory delegate, String... enabledProtocols) { 3260 super(delegate); 3261 this.enabledProtocols = enabledProtocols; 3262 } 3263 3264 @Override 3265 public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) 3266 throws IOException { 3267 SSLSocket socket = super.createSocket(s, host, port, autoClose); 3268 socket.setEnabledProtocols(enabledProtocols); 3269 return socket; 3270 } 3271 3272 @Override 3273 public SSLSocket createSocket() throws IOException { 3274 SSLSocket socket = super.createSocket(); 3275 socket.setEnabledProtocols(enabledProtocols); 3276 return socket; 3277 } 3278 3279 @Override 3280 public SSLSocket createSocket(String host, int port) throws IOException, UnknownHostException { 3281 SSLSocket socket = super.createSocket(host, port); 3282 socket.setEnabledProtocols(enabledProtocols); 3283 return socket; 3284 } 3285 3286 @Override 3287 public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) 3288 throws IOException, UnknownHostException { 3289 SSLSocket socket = super.createSocket(host, port, localHost, localPort); 3290 socket.setEnabledProtocols(enabledProtocols); 3291 return socket; 3292 } 3293 3294 @Override 3295 public SSLSocket createSocket(InetAddress host, int port) throws IOException { 3296 SSLSocket socket = super.createSocket(host, port); 3297 socket.setEnabledProtocols(enabledProtocols); 3298 return socket; 3299 } 3300 3301 @Override 3302 public SSLSocket createSocket(InetAddress address, int port, InetAddress localAddress, 3303 int localPort) throws IOException { 3304 SSLSocket socket = super.createSocket(address, port, localAddress, localPort); 3305 socket.setEnabledProtocols(enabledProtocols); 3306 return socket; 3307 } 3308 } 3309 3310 /** 3311 * An SSLSocketFactory that delegates calls and keeps a record of any sockets created. 3312 */ 3313 private static class RecordingSocketFactory extends DelegatingSSLSocketFactory { 3314 3315 private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>(); 3316 3317 public RecordingSocketFactory(SSLSocketFactory delegate) { 3318 super(delegate); 3319 } 3320 3321 @Override 3322 public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) 3323 throws IOException { 3324 SSLSocket socket = super.createSocket(s, host, port, autoClose); 3325 createdSockets.add(socket); 3326 return socket; 3327 } 3328 3329 @Override 3330 public SSLSocket createSocket() throws IOException { 3331 SSLSocket socket = super.createSocket(); 3332 createdSockets.add(socket); 3333 return socket; 3334 } 3335 3336 @Override 3337 public SSLSocket createSocket(String host, int port) throws IOException, UnknownHostException { 3338 SSLSocket socket = super.createSocket(host, port); 3339 createdSockets.add(socket); 3340 return socket; 3341 } 3342 3343 @Override 3344 public SSLSocket createSocket(String host, int port, InetAddress localHost, 3345 int localPort) throws IOException, UnknownHostException { 3346 SSLSocket socket = super.createSocket(host, port, localHost, localPort); 3347 createdSockets.add(socket); 3348 return socket; 3349 } 3350 3351 @Override 3352 public SSLSocket createSocket(InetAddress host, int port) throws IOException { 3353 SSLSocket socket = super.createSocket(host, port); 3354 createdSockets.add(socket); 3355 return socket; 3356 } 3357 3358 @Override 3359 public SSLSocket createSocket(InetAddress address, int port, 3360 InetAddress localAddress, int localPort) throws IOException { 3361 SSLSocket socket = super.createSocket(address, port, localAddress, localPort); 3362 createdSockets.add(socket); 3363 return socket; 3364 } 3365 3366 public List<SSLSocket> getCreatedSockets() { 3367 return createdSockets; 3368 } 3369 } 3370} 3371