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