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