JavaApiConverterTest.java revision 78092f38ebd93018ead53a87b53118dc829cbb8a
13c827367444ee418f129b2c238299f49d3264554Jarkko Poyry/*
23c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * Copyright (C) 2014 Square, Inc.
33c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
43c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * Licensed under the Apache License, Version 2.0 (the "License");
53c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * you may not use this file except in compliance with the License.
63c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * You may obtain a copy of the License at
73c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
83c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *      http://www.apache.org/licenses/LICENSE-2.0
93c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * Unless required by applicable law or agreed to in writing, software
113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * distributed under the License is distributed on an "AS IS" BASIS,
123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * See the License for the specific language governing permissions and
143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * limitations under the License.
153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry */
163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
173c827367444ee418f129b2c238299f49d3264554Jarkko Poyrypackage com.squareup.okhttp.internal.http;
183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
193c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.Handshake;
203c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.Headers;
213c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.MediaType;
223c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.OkHttpClient;
233c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.Request;
243c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.Response;
253c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.internal.SslContextBuilder;
263c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.internal.Util;
273c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.mockwebserver.MockResponse;
283c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport com.squareup.okhttp.mockwebserver.MockWebServer;
293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
303c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.junit.After;
313c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.junit.Before;
323c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.junit.Test;
333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
343c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.ByteArrayInputStream;
353c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.ByteArrayOutputStream;
363c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.IOException;
373c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.InputStream;
383c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.net.CacheResponse;
393c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.net.HttpURLConnection;
403c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.net.SecureCacheResponse;
413c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.net.URI;
423c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.net.URL;
433c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.nio.charset.StandardCharsets;
443c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.security.Principal;
453c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.security.cert.Certificate;
463c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.security.cert.CertificateException;
473c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.security.cert.CertificateFactory;
483c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.security.cert.X509Certificate;
493c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.util.Arrays;
503c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.util.Collections;
513c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.util.HashMap;
523c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.util.LinkedHashSet;
533c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.util.List;
54import java.util.Map;
55import java.util.Set;
56import javax.net.ssl.HostnameVerifier;
57import javax.net.ssl.HttpsURLConnection;
58import javax.net.ssl.SSLContext;
59import javax.net.ssl.SSLPeerUnverifiedException;
60import javax.net.ssl.SSLSession;
61
62import static org.junit.Assert.assertArrayEquals;
63import static org.junit.Assert.assertEquals;
64import static org.junit.Assert.assertFalse;
65import static org.junit.Assert.assertNotNull;
66import static org.junit.Assert.assertNull;
67import static org.junit.Assert.assertSame;
68import static org.junit.Assert.assertTrue;
69import static org.junit.Assert.fail;
70
71/**
72 * Tests for {@link JavaApiConverter}.
73 */
74public class JavaApiConverterTest {
75
76  // $ openssl req -x509 -nodes -days 36500 -subj '/CN=localhost' -config ./cert.cnf \
77  //     -newkey rsa:512 -out cert.pem
78  private static final X509Certificate LOCAL_CERT = certificate(""
79      + "-----BEGIN CERTIFICATE-----\n"
80      + "MIIBWDCCAQKgAwIBAgIJANS1EtICX2AZMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\n"
81      + "BAMTCWxvY2FsaG9zdDAgFw0xMjAxMDIxOTA4NThaGA8yMTExMTIwOTE5MDg1OFow\n"
82      + "FDESMBAGA1UEAxMJbG9jYWxob3N0MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPpt\n"
83      + "atK8r4/hf4hSIs0os/BSlQLbRBaK9AfBReM4QdAklcQqe6CHsStKfI8pp0zs7Ptg\n"
84      + "PmMdpbttL0O7mUboBC8CAwEAAaM1MDMwMQYDVR0RBCowKIIVbG9jYWxob3N0Lmxv\n"
85      + "Y2FsZG9tYWlugglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADQQD0ntfL\n"
86      + "DCzOCv9Ma6Lv5o5jcYWVxvBSTsnt22hsJpWD1K7iY9lbkLwl0ivn73pG2evsAn9G\n"
87      + "X8YKH52fnHsCrhSD\n"
88      + "-----END CERTIFICATE-----");
89
90  // openssl req -x509 -nodes -days 36500 -subj '/CN=*.0.0.1' -newkey rsa:512 -out cert.pem
91  private static final X509Certificate SERVER_CERT = certificate(""
92      + "-----BEGIN CERTIFICATE-----\n"
93      + "MIIBkjCCATygAwIBAgIJAMdemqOwd/BEMA0GCSqGSIb3DQEBBQUAMBIxEDAOBgNV\n"
94      + "BAMUByouMC4wLjEwIBcNMTAxMjIwMTY0NDI1WhgPMjExMDExMjYxNjQ0MjVaMBIx\n"
95      + "EDAOBgNVBAMUByouMC4wLjEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAqY8c9Qrt\n"
96      + "YPWCvb7lclI+aDHM6fgbJcHsS9Zg8nUOh5dWrS7AgeA25wyaokFl4plBbbHQe2j+\n"
97      + "cCjsRiJIcQo9HwIDAQABo3MwcTAdBgNVHQ4EFgQUJ436TZPJvwCBKklZZqIvt1Yt\n"
98      + "JjEwQgYDVR0jBDswOYAUJ436TZPJvwCBKklZZqIvt1YtJjGhFqQUMBIxEDAOBgNV\n"
99      + "BAMUByouMC4wLjGCCQDHXpqjsHfwRDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n"
100      + "BQUAA0EAk9i88xdjWoewqvE+iMC9tD2obMchgFDaHH0ogxxiRaIKeEly3g0uGxIt\n"
101      + "fl2WRY8hb4x+zRrwsFaLEpdEvqcjOQ==\n"
102      + "-----END CERTIFICATE-----");
103
104  private static final SSLContext sslContext = SslContextBuilder.localhost();
105  private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() {
106    public boolean verify(String hostname, SSLSession session) {
107      return true;
108    }
109  };
110
111  private MockWebServer server;
112
113  private OkHttpClient client;
114
115  private HttpURLConnection connection;
116
117  @Before public void setUp() throws Exception {
118    server = new MockWebServer();
119    client = new OkHttpClient();
120  }
121
122  @After public void tearDown() throws Exception {
123    if (connection != null) {
124      connection.disconnect();
125    }
126    server.shutdown();
127  }
128
129  @Test public void createOkResponse_fromOkHttpUrlConnection() throws Exception {
130    testCreateOkResponseInternal(new OkHttpURLConnectionFactory(client), false /* isSecure */);
131  }
132
133  @Test public void createOkResponse_fromJavaHttpUrlConnection() throws Exception {
134    testCreateOkResponseInternal(new JavaHttpURLConnectionFactory(), false /* isSecure */);
135  }
136
137  @Test public void createOkResponse_fromOkHttpsUrlConnection() throws Exception {
138    testCreateOkResponseInternal(new OkHttpURLConnectionFactory(client), true /* isSecure */);
139  }
140
141  @Test public void createOkResponse_fromJavaHttpsUrlConnection() throws Exception {
142    testCreateOkResponseInternal(new JavaHttpURLConnectionFactory(), true /* isSecure */);
143  }
144
145  private void testCreateOkResponseInternal(HttpURLConnectionFactory httpUrlConnectionFactory,
146      boolean isSecure) throws Exception {
147    String statusLine = "HTTP/1.1 200 Fantastic";
148    String body = "Nothing happens";
149    final URL serverUrl;
150    MockResponse mockResponse = new MockResponse()
151        .setStatus(statusLine)
152        .addHeader("xyzzy", "baz")
153        .setBody(body);
154    if (isSecure) {
155      serverUrl = configureHttpsServer(
156          mockResponse);
157
158      assertEquals("https", serverUrl.getProtocol());
159    } else {
160      serverUrl = configureServer(
161          mockResponse);
162      assertEquals("http", serverUrl.getProtocol());
163    }
164
165    connection = httpUrlConnectionFactory.open(serverUrl);
166    if (isSecure) {
167      HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) connection;
168      httpsUrlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
169      httpsUrlConnection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
170    }
171    connection.setRequestProperty("snake", "bird");
172    connection.connect();
173    Response response = JavaApiConverter.createOkResponse(serverUrl.toURI(), connection);
174
175    // Check the response.request()
176    Request request = response.request();
177    assertEquals(isSecure, request.isHttps());
178    assertEquals(serverUrl.toURI(), request.uri());
179    assertNull(request.body());
180    Headers okRequestHeaders = request.headers();
181    // In Java the request headers are unavailable for a connected HttpURLConnection.
182    assertEquals(0, okRequestHeaders.size());
183    assertEquals("GET", request.method());
184
185    // Check the response
186    assertEquals(statusLine, response.statusLine());
187    Headers okResponseHeaders = response.headers();
188    assertEquals("baz", okResponseHeaders.get("xyzzy"));
189    assertEquals(body, response.body().string());
190    if (isSecure) {
191      Handshake handshake = response.handshake();
192      assertNotNull(handshake);
193      HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection;
194      assertNotNullAndEquals(httpsURLConnection.getCipherSuite(), handshake.cipherSuite());
195      assertEquals(httpsURLConnection.getLocalPrincipal(), handshake.localPrincipal());
196      assertNotNullAndEquals(httpsURLConnection.getPeerPrincipal(), handshake.peerPrincipal());
197      assertNotNull(httpsURLConnection.getServerCertificates());
198      assertEquals(Arrays.asList(httpsURLConnection.getServerCertificates()),
199          handshake.peerCertificates());
200      assertNull(httpsURLConnection.getLocalCertificates());
201    } else {
202      assertNull(response.handshake());
203    }
204  }
205
206  @Test public void createOkResponse_fromCacheResponse() throws Exception {
207    final String statusLine = "HTTP/1.1 200 Fantastic";
208    URI uri = new URI("http://foo/bar");
209    Request request = new Request.Builder().url(uri.toURL()).method("GET", null).build();
210    CacheResponse cacheResponse = new CacheResponse() {
211      @Override
212      public Map<String, List<String>> getHeaders() throws IOException {
213        Map<String, List<String>> headers = new HashMap<String, List<String>>();
214        headers.put(null, Collections.singletonList(statusLine));
215        headers.put("xyzzy", Arrays.asList("bar", "baz"));
216        return headers;
217      }
218
219      @Override
220      public InputStream getBody() throws IOException {
221        return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8));
222      }
223    };
224
225    Response response = JavaApiConverter.createOkResponse(request, cacheResponse);
226    assertSame(request, response.request());
227
228    assertNotNullAndEquals(statusLine, response.statusLine());
229    Headers okResponseHeaders = response.headers();
230    assertEquals("baz", okResponseHeaders.get("xyzzy"));
231    assertEquals("HelloWorld", response.body().string());
232    assertNull(response.handshake());
233  }
234
235  @Test public void createOkResponse_fromSecureCacheResponse() throws Exception {
236    final String statusLine = "HTTP/1.1 200 Fantastic";
237    final Principal localPrincipal = LOCAL_CERT.getSubjectX500Principal();
238    final List<Certificate> localCertificates = Arrays.<Certificate>asList(LOCAL_CERT);
239    final Principal serverPrincipal = SERVER_CERT.getSubjectX500Principal();
240    final List<Certificate> serverCertificates = Arrays.<Certificate>asList(SERVER_CERT);
241    URI uri = new URI("https://foo/bar");
242    Request request = new Request.Builder().url(uri.toURL()).method("GET", null).build();
243    SecureCacheResponse cacheResponse = new SecureCacheResponse() {
244      @Override
245      public Map<String, List<String>> getHeaders() throws IOException {
246        Map<String, List<String>> headers = new HashMap<String, List<String>>();
247        headers.put(null, Collections.singletonList(statusLine));
248        headers.put("xyzzy", Arrays.asList("bar", "baz"));
249        return headers;
250      }
251
252      @Override
253      public InputStream getBody() throws IOException {
254        return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8));
255      }
256
257      @Override
258      public String getCipherSuite() {
259        return "SuperSecure";
260      }
261
262      @Override
263      public List<Certificate> getLocalCertificateChain() {
264        return localCertificates;
265      }
266
267      @Override
268      public List<Certificate> getServerCertificateChain() throws SSLPeerUnverifiedException {
269        return serverCertificates;
270      }
271
272      @Override
273      public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
274        return serverPrincipal;
275      }
276
277      @Override
278      public Principal getLocalPrincipal() {
279        return localPrincipal;
280      }
281    };
282
283    Response response = JavaApiConverter.createOkResponse(request, cacheResponse);
284    assertSame(request, response.request());
285
286    assertNotNullAndEquals(statusLine, response.statusLine());
287    Headers okResponseHeaders = response.headers();
288    assertEquals("baz", okResponseHeaders.get("xyzzy"));
289    assertEquals("HelloWorld", response.body().string());
290
291    Handshake handshake = response.handshake();
292    assertNotNull(handshake);
293    assertNotNullAndEquals("SuperSecure", handshake.cipherSuite());
294    assertEquals(localPrincipal, handshake.localPrincipal());
295    assertEquals(serverPrincipal, handshake.peerPrincipal());
296    assertEquals(serverCertificates, handshake.peerCertificates());
297    assertEquals(localCertificates, handshake.localCertificates());
298  }
299
300  @Test public void createOkRequest_nullRequestHeaders() throws Exception {
301    URI uri = new URI("http://foo/bar");
302
303    Map<String,List<String>> javaRequestHeaders = null;
304    Request request = JavaApiConverter.createOkRequest(uri, "POST", javaRequestHeaders);
305    assertFalse(request.isHttps());
306    assertEquals(uri, request.uri());
307    assertNull(request.body());
308    Headers okRequestHeaders = request.headers();
309    assertEquals(0, okRequestHeaders.size());
310    assertEquals("POST", request.method());
311  }
312
313  @Test public void createOkRequest_nonNullRequestHeaders() throws Exception {
314    URI uri = new URI("https://foo/bar");
315
316    Map<String,List<String>> javaRequestHeaders = new HashMap<String, List<String>>();
317    javaRequestHeaders.put("Foo", Arrays.asList("Bar"));
318    Request request = JavaApiConverter.createOkRequest(uri, "POST", javaRequestHeaders);
319    assertTrue(request.isHttps());
320    assertEquals(uri, request.uri());
321    assertNull(request.body());
322    Headers okRequestHeaders = request.headers();
323    assertEquals(1, okRequestHeaders.size());
324    assertEquals("Bar", okRequestHeaders.get("Foo"));
325    assertEquals("POST", request.method());
326  }
327
328  // Older versions of OkHttp would store the "request line" as a header with a
329  // null key. To support the Android usecase where an old version of OkHttp uses
330  // a newer, Android-bundled, version of HttpResponseCache the null key must be
331  // explicitly ignored.
332  @Test public void createOkRequest_nullRequestHeaderKey() throws Exception {
333    URI uri = new URI("https://foo/bar");
334
335    Map<String,List<String>> javaRequestHeaders = new HashMap<String, List<String>>();
336    javaRequestHeaders.put(null, Arrays.asList("GET / HTTP 1.1"));
337    javaRequestHeaders.put("Foo", Arrays.asList("Bar"));
338    Request request = JavaApiConverter.createOkRequest(uri, "POST", javaRequestHeaders);
339    assertTrue(request.isHttps());
340    assertEquals(uri, request.uri());
341    assertNull(request.body());
342    Headers okRequestHeaders = request.headers();
343    assertEquals(1, okRequestHeaders.size());
344    assertEquals("Bar", okRequestHeaders.get("Foo"));
345    assertEquals("POST", request.method());
346  }
347
348  @Test public void createJavaUrlConnection_requestChangesForbidden() throws Exception {
349    Response okResponse = createArbitraryOkResponse();
350    HttpURLConnection httpUrlConnection = JavaApiConverter.createJavaUrlConnection(okResponse);
351    // Check an arbitrary (not complete) set of methods that can be used to modify the
352    // request.
353    try {
354      httpUrlConnection.setRequestProperty("key", "value");
355      fail();
356    } catch (UnsupportedOperationException expected) {
357    }
358    try {
359      httpUrlConnection.setFixedLengthStreamingMode(1234);
360      fail();
361    } catch (UnsupportedOperationException expected) {
362    }
363    try {
364      httpUrlConnection.setRequestMethod("PUT");
365      fail();
366    } catch (UnsupportedOperationException expected) {
367    }
368    try {
369      httpUrlConnection.getHeaderFields().put("key", Collections.singletonList("value"));
370      fail();
371    } catch (UnsupportedOperationException expected) {
372    }
373    try {
374      httpUrlConnection.getOutputStream();
375      fail();
376    } catch (UnsupportedOperationException expected) {
377    }
378  }
379
380  @Test public void createJavaUrlConnection_connectionChangesForbidden() throws Exception {
381    Response okResponse = createArbitraryOkResponse();
382    HttpURLConnection httpUrlConnection = JavaApiConverter.createJavaUrlConnection(okResponse);
383    try {
384      httpUrlConnection.connect();
385      fail();
386    } catch (UnsupportedOperationException expected) {
387    }
388    try {
389      httpUrlConnection.disconnect();
390      fail();
391    } catch (UnsupportedOperationException expected) {
392    }
393  }
394
395  @Test public void createJavaUrlConnection_responseChangesForbidden() throws Exception {
396    Response okResponse = createArbitraryOkResponse();
397    HttpURLConnection httpUrlConnection = JavaApiConverter.createJavaUrlConnection(okResponse);
398    // Check an arbitrary (not complete) set of methods that can be used to access the response
399    // body.
400    try {
401      httpUrlConnection.getInputStream();
402      fail();
403    } catch (UnsupportedOperationException expected) {
404    }
405    try {
406      httpUrlConnection.getContent();
407      fail();
408    } catch (UnsupportedOperationException expected) {
409    }
410    try {
411      httpUrlConnection.setFixedLengthStreamingMode(1234);
412      fail();
413    } catch (UnsupportedOperationException expected) {
414    }
415    try {
416      httpUrlConnection.setRequestMethod("PUT");
417      fail();
418    } catch (UnsupportedOperationException expected) {
419    }
420    try {
421      httpUrlConnection.getHeaderFields().put("key", Collections.singletonList("value"));
422      fail();
423    } catch (UnsupportedOperationException expected) {
424    }
425  }
426
427  @Test public void createJavaUrlConnection_responseHeadersOk() throws Exception {
428    final String statusLine = "HTTP/1.1 200 Fantastic";
429    Response.Body responseBody =
430        createResponseBody("text/plain", "BodyText".getBytes(StandardCharsets.UTF_8));
431    Response okResponse = new Response.Builder()
432        .request(createArbitraryOkRequest())
433        .statusLine(statusLine)
434        .addHeader("A", "c")
435        .addHeader("B", "d")
436        .addHeader("A", "e")
437        .addHeader("Content-Length", Long.toString(responseBody.contentLength()))
438        .body(responseBody)
439        .build();
440
441    HttpURLConnection httpUrlConnection = JavaApiConverter.createJavaUrlConnection(okResponse);
442    assertEquals(200, httpUrlConnection.getResponseCode());
443    assertEquals("Fantastic", httpUrlConnection.getResponseMessage());
444    assertEquals(responseBody.contentLength(), httpUrlConnection.getContentLength());
445
446    // Check retrieval by string key.
447    assertEquals(statusLine, httpUrlConnection.getHeaderField(null));
448    assertEquals("e", httpUrlConnection.getHeaderField("A"));
449    // The RI and OkHttp supports case-insensitive matching for this method.
450    assertEquals("e", httpUrlConnection.getHeaderField("a"));
451
452    // Check retrieval using a Map.
453    Map<String, List<String>> responseHeaders = httpUrlConnection.getHeaderFields();
454    assertEquals(Arrays.asList(statusLine), responseHeaders.get(null));
455    assertEquals(newSet("c", "e"), newSet(responseHeaders.get("A")));
456    // OkHttp supports case-insensitive matching here. The RI does not.
457    assertEquals(newSet("c", "e"), newSet(responseHeaders.get("a")));
458
459    // Check the Map iterator contains the expected mappings.
460    assertHeadersContainsMapping(responseHeaders, null, statusLine);
461    assertHeadersContainsMapping(responseHeaders, "A", "c", "e");
462    assertHeadersContainsMapping(responseHeaders, "B", "d");
463
464    // Check immutability of the headers Map.
465    try {
466      responseHeaders.put("N", Arrays.asList("o"));
467      fail("Modified an unmodifiable view.");
468    } catch (UnsupportedOperationException expected) {
469    }
470    try {
471      responseHeaders.get("A").add("f");
472      fail("Modified an unmodifiable view.");
473    } catch (UnsupportedOperationException expected) {
474    }
475
476    // Check retrieval of headers by index.
477    assertEquals(null, httpUrlConnection.getHeaderFieldKey(0));
478    assertEquals(statusLine, httpUrlConnection.getHeaderField(0));
479    // After header zero there may be additional entries provided at the beginning or end by the
480    // implementation. It's probably important that the relative ordering of the headers is
481    // preserved, particularly if there are multiple value for the same key.
482    int i = 1;
483    while (!httpUrlConnection.getHeaderFieldKey(i).equals("A")) {
484      i++;
485    }
486    // Check the ordering of the headers set by app code.
487    assertResponseHeaderAtIndex(httpUrlConnection, i++, "A", "c");
488    assertResponseHeaderAtIndex(httpUrlConnection, i++, "B", "d");
489    assertResponseHeaderAtIndex(httpUrlConnection, i++, "A", "e");
490    // There may be some additional headers provided by the implementation.
491    while (httpUrlConnection.getHeaderField(i) != null) {
492      assertNotNull(httpUrlConnection.getHeaderFieldKey(i));
493      i++;
494    }
495    // Confirm the correct behavior when the index is out-of-range.
496    assertNull(httpUrlConnection.getHeaderFieldKey(i));
497  }
498
499  private static void assertResponseHeaderAtIndex(HttpURLConnection httpUrlConnection,
500      int headerIndex, String expectedKey, String expectedValue) {
501    assertEquals(expectedKey, httpUrlConnection.getHeaderFieldKey(headerIndex));
502    assertEquals(expectedValue, httpUrlConnection.getHeaderField(headerIndex));
503  }
504
505  private void assertHeadersContainsMapping(Map<String, List<String>> headers, String expectedKey,
506      String... expectedValues) {
507    assertTrue(headers.containsKey(expectedKey));
508    assertEquals(newSet(expectedValues), newSet(headers.get(expectedKey)));
509  }
510
511  @Test public void createJavaUrlConnection_accessibleRequestInfo_GET() throws Exception {
512    Request okRequest = createArbitraryOkRequest().newBuilder()
513        .method("GET", null)
514        .build();
515    Response okResponse = createArbitraryOkResponse(okRequest);
516    HttpURLConnection httpUrlConnection = JavaApiConverter.createJavaUrlConnection(okResponse);
517
518    assertEquals("GET", httpUrlConnection.getRequestMethod());
519    assertTrue(httpUrlConnection.getDoInput());
520    assertFalse(httpUrlConnection.getDoOutput());
521  }
522
523  @Test public void createJavaUrlConnection_accessibleRequestInfo_POST() throws Exception {
524    Request okRequest = createArbitraryOkRequest().newBuilder()
525        .method("POST", createRequestBody("PostBody"))
526        .build();
527    Response okResponse = createArbitraryOkResponse(okRequest);
528    HttpURLConnection httpUrlConnection = JavaApiConverter.createJavaUrlConnection(okResponse);
529
530    assertEquals("POST", httpUrlConnection.getRequestMethod());
531    assertTrue(httpUrlConnection.getDoInput());
532    assertTrue(httpUrlConnection.getDoOutput());
533  }
534
535  @Test public void createJavaUrlConnection_https_extraHttpsMethods() throws Exception {
536    Request okRequest = createArbitraryOkRequest().newBuilder()
537        .method("GET", null)
538        .url("https://secure/request")
539        .build();
540    Handshake handshake = Handshake.get("SecureCipher", Arrays.<Certificate>asList(SERVER_CERT),
541        Arrays.<Certificate>asList(LOCAL_CERT));
542    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
543        .handshake(handshake)
544        .build();
545    HttpsURLConnection httpsUrlConnection =
546        (HttpsURLConnection) JavaApiConverter.createJavaUrlConnection(okResponse);
547
548    assertEquals("SecureCipher", httpsUrlConnection.getCipherSuite());
549    assertEquals(SERVER_CERT.getSubjectX500Principal(), httpsUrlConnection.getPeerPrincipal());
550    assertArrayEquals(new Certificate[] { LOCAL_CERT }, httpsUrlConnection.getLocalCertificates());
551    assertArrayEquals(new Certificate[] { SERVER_CERT },
552        httpsUrlConnection.getServerCertificates());
553    assertEquals(LOCAL_CERT.getSubjectX500Principal(), httpsUrlConnection.getLocalPrincipal());
554  }
555
556  @Test public void createJavaUrlConnection_https_forbiddenFields() throws Exception {
557    Request okRequest = createArbitraryOkRequest().newBuilder()
558        .url("https://secure/request")
559        .build();
560    Response okResponse = createArbitraryOkResponse(okRequest);
561    HttpsURLConnection httpsUrlConnection =
562        (HttpsURLConnection) JavaApiConverter.createJavaUrlConnection(okResponse);
563
564    try {
565      httpsUrlConnection.getHostnameVerifier();
566      fail();
567    } catch (UnsupportedOperationException expected) {
568    }
569    try {
570      httpsUrlConnection.getSSLSocketFactory();
571      fail();
572    } catch (UnsupportedOperationException expected) {
573    }
574  }
575
576  @Test public void createJavaCacheResponse_httpGet() throws Exception {
577    Request okRequest =
578        createArbitraryOkRequest().newBuilder()
579            .url("http://insecure/request")
580            .method("GET", null)
581            .build();
582    String statusLine = "HTTP/1.1 200 Fantastic";
583    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
584        .statusLine(statusLine)
585        .addHeader("key1", "value1_1")
586        .addHeader("key2", "value2")
587        .addHeader("key1", "value1_2")
588        .body(null)
589        .build();
590    CacheResponse javaCacheResponse = JavaApiConverter.createJavaCacheResponse(okResponse);
591    assertFalse(javaCacheResponse instanceof SecureCacheResponse);
592    Map<String, List<String>> javaHeaders = javaCacheResponse.getHeaders();
593    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
594    assertEquals(Arrays.asList(statusLine), javaHeaders.get(null));
595    assertNull(javaCacheResponse.getBody());
596  }
597
598  @Test public void createJavaCacheResponse_httpPost() throws Exception {
599    Request okRequest =
600        createArbitraryOkRequest().newBuilder()
601            .url("http://insecure/request")
602            .method("POST", createRequestBody("RequestBody") )
603            .build();
604    String statusLine = "HTTP/1.1 200 Fantastic";
605    Response.Body responseBody =
606        createResponseBody("text/plain", "ResponseBody".getBytes(StandardCharsets.UTF_8));
607    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
608        .statusLine(statusLine)
609        .addHeader("key1", "value1_1")
610        .addHeader("key2", "value2")
611        .addHeader("key1", "value1_2")
612        .body(responseBody)
613        .build();
614    CacheResponse javaCacheResponse = JavaApiConverter.createJavaCacheResponse(okResponse);
615    assertFalse(javaCacheResponse instanceof SecureCacheResponse);
616    Map<String, List<String>> javaHeaders = javaCacheResponse.getHeaders();
617    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
618    assertEquals(Arrays.asList(statusLine), javaHeaders.get(null));
619    assertArrayEquals(responseBody.bytes(), readAll(javaCacheResponse.getBody()));
620  }
621
622  @Test public void createJavaCacheResponse_httpsPost() throws Exception {
623    Request okRequest =
624        createArbitraryOkRequest().newBuilder()
625            .url("https://secure/request")
626            .method("POST", createRequestBody("RequestBody") )
627            .build();
628    String statusLine = "HTTP/1.1 200 Fantastic";
629    Response.Body responseBody =
630        createResponseBody("text/plain", "ResponseBody".getBytes(StandardCharsets.UTF_8));
631    Handshake handshake = Handshake.get("SecureCipher", Arrays.<Certificate>asList(SERVER_CERT),
632        Arrays.<Certificate>asList(LOCAL_CERT));
633    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
634        .statusLine(statusLine)
635        .addHeader("key1", "value1_1")
636        .addHeader("key2", "value2")
637        .addHeader("key1", "value1_2")
638        .body(responseBody)
639        .handshake(handshake)
640        .build();
641    SecureCacheResponse javaCacheResponse =
642        (SecureCacheResponse) JavaApiConverter.createJavaCacheResponse(okResponse);
643    Map<String, List<String>> javaHeaders = javaCacheResponse.getHeaders();
644    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
645    assertEquals(Arrays.asList(statusLine), javaHeaders.get(null));
646    assertArrayEquals(responseBody.bytes(), readAll(javaCacheResponse.getBody()));
647    assertEquals(handshake.cipherSuite(), javaCacheResponse.getCipherSuite());
648    assertEquals(handshake.localCertificates(), javaCacheResponse.getLocalCertificateChain());
649    assertEquals(handshake.peerCertificates(), javaCacheResponse.getServerCertificateChain());
650    assertEquals(handshake.localPrincipal(), javaCacheResponse.getLocalPrincipal());
651    assertEquals(handshake.peerPrincipal(), javaCacheResponse.getPeerPrincipal());
652  }
653
654  @Test public void extractJavaHeaders() throws Exception {
655    Request okRequest = createArbitraryOkRequest().newBuilder()
656        .addHeader("key1", "value1_1")
657        .addHeader("key2", "value2")
658        .addHeader("key1", "value1_2")
659        .build();
660    Map<String, List<String>> javaHeaders = JavaApiConverter.extractJavaHeaders(okRequest);
661
662    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
663    assertEquals(Arrays.asList("value2"), javaHeaders.get("key2"));
664  }
665
666  @Test public void extractOkHeaders() {
667    Map<String, List<String>> javaResponseHeaders = new HashMap<String, List<String>>();
668    javaResponseHeaders.put(null, Arrays.asList("StatusLine"));
669    javaResponseHeaders.put("key1", Arrays.asList("value1_1", "value1_2"));
670    javaResponseHeaders.put("key2", Arrays.asList("value2"));
671
672    Headers okHeaders = JavaApiConverter.extractOkHeaders(javaResponseHeaders);
673    assertEquals(3, okHeaders.size()); // null entry should be stripped out
674    assertEquals(Arrays.asList("value1_1", "value1_2"), okHeaders.values("key1"));
675    assertEquals(Arrays.asList("value2"), okHeaders.values("key2"));
676  }
677
678  @Test public void extractStatusLine() {
679    Map<String, List<String>> javaResponseHeaders = new HashMap<String, List<String>>();
680    javaResponseHeaders.put(null, Arrays.asList("StatusLine"));
681    javaResponseHeaders.put("key1", Arrays.asList("value1_1", "value1_2"));
682    javaResponseHeaders.put("key2", Arrays.asList("value2"));
683    assertEquals("StatusLine", JavaApiConverter.extractStatusLine(javaResponseHeaders));
684
685    assertNull(JavaApiConverter.extractStatusLine(Collections.<String, List<String>>emptyMap()));
686  }
687
688  private URL configureServer(MockResponse mockResponse) throws Exception {
689    server.enqueue(mockResponse);
690    server.play();
691    return server.getUrl("/");
692  }
693
694  private URL configureHttpsServer(MockResponse mockResponse) throws Exception {
695    server.useHttps(sslContext.getSocketFactory(), false /* tunnelProxy */);
696    server.enqueue(mockResponse);
697    server.play();
698    return server.getUrl("/");
699  }
700
701  private static <T> void assertNotNullAndEquals(T expected, T actual) {
702    assertNotNull(actual);
703    assertEquals(expected, actual);
704  }
705
706  private interface HttpURLConnectionFactory {
707    public HttpURLConnection open(URL serverUrl) throws IOException;
708  }
709
710  private static class OkHttpURLConnectionFactory implements HttpURLConnectionFactory {
711    protected final OkHttpClient client;
712
713    private OkHttpURLConnectionFactory(OkHttpClient client) {
714      this.client = client;
715    }
716
717    @Override
718    public HttpURLConnection open(URL serverUrl) {
719      return client.open(serverUrl);
720    }
721  }
722
723  private static class JavaHttpURLConnectionFactory implements HttpURLConnectionFactory {
724    @Override
725    public HttpURLConnection open(URL serverUrl) throws IOException {
726      return (HttpURLConnection) serverUrl.openConnection();
727    }
728  }
729
730  private static X509Certificate certificate(String certificate) {
731    try {
732      return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
733          new ByteArrayInputStream(certificate.getBytes(Util.UTF_8)));
734    } catch (CertificateException e) {
735      fail();
736      return null;
737    }
738  }
739
740  private static <T> Set<T> newSet(T... elements) {
741    return newSet(Arrays.asList(elements));
742  }
743
744  private static <T> Set<T> newSet(List<T> elements) {
745    return new LinkedHashSet<T>(elements);
746  }
747
748  private static Request createArbitraryOkRequest() {
749    return new Request.Builder()
750        .url("http://arbitrary/url")
751        .method("GET", null)
752        .build();
753  }
754
755  private static Response createArbitraryOkResponse(Request request) {
756    return new Response.Builder()
757        .request(request)
758        .statusLine("HTTP/1.1 200 Arbitrary")
759        .build();
760  }
761
762  private static Response createArbitraryOkResponse() {
763    return createArbitraryOkResponse(createArbitraryOkRequest());
764  }
765
766  private static Request.Body createRequestBody(String bodyText) {
767    return Request.Body.create(MediaType.parse("text/plain"), bodyText);
768  }
769
770  private static Response.Body createResponseBody(final String contentType, final byte[] bytes) {
771    return new Response.Body() {
772
773      @Override
774      public boolean ready() throws IOException {
775        return true;
776      }
777
778      @Override
779      public MediaType contentType() {
780        return MediaType.parse(contentType);
781      }
782
783      @Override
784      public long contentLength() {
785        return bytes.length;
786      }
787
788      @Override
789      public InputStream byteStream() {
790        return new ByteArrayInputStream(bytes);
791      }
792    };
793  }
794
795  private byte[] readAll(InputStream in) throws IOException {
796    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
797    int value;
798    while ((value = in.read()) != -1) {
799      buffer.write(value);
800    }
801    in.close();
802    return buffer.toByteArray();
803  }
804
805}
806