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