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