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