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