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