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