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