1/*
2 * Copyright (C) 2014 Square, Inc.
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 */
16package com.squareup.okhttp.internal.huc;
17
18import com.squareup.okhttp.Handshake;
19import com.squareup.okhttp.Headers;
20import com.squareup.okhttp.MediaType;
21import com.squareup.okhttp.Protocol;
22import com.squareup.okhttp.Request;
23import com.squareup.okhttp.RequestBody;
24import com.squareup.okhttp.Response;
25import com.squareup.okhttp.ResponseBody;
26import com.squareup.okhttp.internal.Internal;
27import com.squareup.okhttp.internal.Util;
28import com.squareup.okhttp.mockwebserver.MockWebServer;
29import java.io.ByteArrayInputStream;
30import java.io.ByteArrayOutputStream;
31import java.io.IOException;
32import java.io.InputStream;
33import java.net.CacheResponse;
34import java.net.HttpURLConnection;
35import java.net.SecureCacheResponse;
36import java.net.URI;
37import java.nio.charset.StandardCharsets;
38import java.security.Principal;
39import java.security.cert.Certificate;
40import java.security.cert.CertificateException;
41import java.security.cert.CertificateFactory;
42import java.security.cert.X509Certificate;
43import java.util.Arrays;
44import java.util.Collections;
45import java.util.HashMap;
46import java.util.LinkedHashSet;
47import java.util.List;
48import java.util.Map;
49import java.util.Set;
50import javax.net.ssl.HttpsURLConnection;
51import javax.net.ssl.SSLPeerUnverifiedException;
52import okio.Buffer;
53import okio.BufferedSource;
54import org.junit.Before;
55import org.junit.Rule;
56import org.junit.Test;
57
58import static org.junit.Assert.assertArrayEquals;
59import static org.junit.Assert.assertEquals;
60import static org.junit.Assert.assertFalse;
61import static org.junit.Assert.assertNotNull;
62import static org.junit.Assert.assertNull;
63import static org.junit.Assert.assertTrue;
64import static org.junit.Assert.fail;
65
66public class JavaApiConverterTest {
67
68  // $ openssl req -x509 -nodes -days 36500 -subj '/CN=localhost' -config ./cert.cnf \
69  //     -newkey rsa:512 -out cert.pem
70  private static final X509Certificate LOCAL_CERT = certificate(""
71      + "-----BEGIN CERTIFICATE-----\n"
72      + "MIIBWDCCAQKgAwIBAgIJANS1EtICX2AZMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\n"
73      + "BAMTCWxvY2FsaG9zdDAgFw0xMjAxMDIxOTA4NThaGA8yMTExMTIwOTE5MDg1OFow\n"
74      + "FDESMBAGA1UEAxMJbG9jYWxob3N0MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPpt\n"
75      + "atK8r4/hf4hSIs0os/BSlQLbRBaK9AfBReM4QdAklcQqe6CHsStKfI8pp0zs7Ptg\n"
76      + "PmMdpbttL0O7mUboBC8CAwEAAaM1MDMwMQYDVR0RBCowKIIVbG9jYWxob3N0Lmxv\n"
77      + "Y2FsZG9tYWlugglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADQQD0ntfL\n"
78      + "DCzOCv9Ma6Lv5o5jcYWVxvBSTsnt22hsJpWD1K7iY9lbkLwl0ivn73pG2evsAn9G\n"
79      + "X8YKH52fnHsCrhSD\n"
80      + "-----END CERTIFICATE-----");
81
82  // openssl req -x509 -nodes -days 36500 -subj '/CN=*.0.0.1' -newkey rsa:512 -out cert.pem
83  private static final X509Certificate SERVER_CERT = certificate(""
84      + "-----BEGIN CERTIFICATE-----\n"
85      + "MIIBkjCCATygAwIBAgIJAMdemqOwd/BEMA0GCSqGSIb3DQEBBQUAMBIxEDAOBgNV\n"
86      + "BAMUByouMC4wLjEwIBcNMTAxMjIwMTY0NDI1WhgPMjExMDExMjYxNjQ0MjVaMBIx\n"
87      + "EDAOBgNVBAMUByouMC4wLjEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAqY8c9Qrt\n"
88      + "YPWCvb7lclI+aDHM6fgbJcHsS9Zg8nUOh5dWrS7AgeA25wyaokFl4plBbbHQe2j+\n"
89      + "cCjsRiJIcQo9HwIDAQABo3MwcTAdBgNVHQ4EFgQUJ436TZPJvwCBKklZZqIvt1Yt\n"
90      + "JjEwQgYDVR0jBDswOYAUJ436TZPJvwCBKklZZqIvt1YtJjGhFqQUMBIxEDAOBgNV\n"
91      + "BAMUByouMC4wLjGCCQDHXpqjsHfwRDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n"
92      + "BQUAA0EAk9i88xdjWoewqvE+iMC9tD2obMchgFDaHH0ogxxiRaIKeEly3g0uGxIt\n"
93      + "fl2WRY8hb4x+zRrwsFaLEpdEvqcjOQ==\n"
94      + "-----END CERTIFICATE-----");
95
96  @Rule public MockWebServer server = new MockWebServer();
97
98  @Before public void setUp() throws Exception {
99    Internal.initializeInstanceForTests();
100  }
101
102  @Test public void createOkResponseForCacheGet() throws Exception {
103    final String statusLine = "HTTP/1.1 200 Fantastic";
104    URI uri = new URI("http://foo/bar");
105    Request request = new Request.Builder().url(uri.toURL()).build();
106    CacheResponse cacheResponse = new CacheResponse() {
107      @Override public Map<String, List<String>> getHeaders() throws IOException {
108        Map<String, List<String>> headers = new HashMap<>();
109        headers.put(null, Collections.singletonList(statusLine));
110        headers.put("xyzzy", Arrays.asList("bar", "baz"));
111        return headers;
112      }
113
114      @Override public InputStream getBody() throws IOException {
115        return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8));
116      }
117    };
118
119    Response response = JavaApiConverter.createOkResponseForCacheGet(request, cacheResponse);
120    Request cacheRequest = response.request();
121    assertEquals(request.httpUrl(), cacheRequest.httpUrl());
122    assertEquals(request.method(), cacheRequest.method());
123    assertEquals(0, request.headers().size());
124
125    assertEquals(Protocol.HTTP_1_1, response.protocol());
126    assertEquals(200, response.code());
127    assertEquals("Fantastic", response.message());
128    Headers okResponseHeaders = response.headers();
129    assertEquals("baz", okResponseHeaders.get("xyzzy"));
130    assertEquals("HelloWorld", response.body().string());
131    assertNull(response.handshake());
132  }
133
134  /** Test for https://code.google.com/p/android/issues/detail?id=160522 */
135  @Test public void createOkResponseForCacheGet_withMissingStatusLine() throws Exception {
136    URI uri = new URI("http://foo/bar");
137    Request request = new Request.Builder().url(uri.toURL()).build();
138    CacheResponse cacheResponse = new CacheResponse() {
139      @Override public Map<String, List<String>> getHeaders() throws IOException {
140        Map<String, List<String>> headers = new HashMap<>();
141        // Headers is deliberately missing an entry with a null key.
142        headers.put("xyzzy", Arrays.asList("bar", "baz"));
143        return headers;
144      }
145
146      @Override public InputStream getBody() throws IOException {
147        return null; // Should never be called
148      }
149    };
150
151    try {
152      JavaApiConverter.createOkResponseForCacheGet(request, cacheResponse);
153      fail();
154    } catch (IOException expected) {
155    }
156  }
157
158  @Test public void createOkResponseForCacheGet_secure() throws Exception {
159    final String statusLine = "HTTP/1.1 200 Fantastic";
160    final Principal localPrincipal = LOCAL_CERT.getSubjectX500Principal();
161    final List<Certificate> localCertificates = Arrays.<Certificate>asList(LOCAL_CERT);
162    final Principal serverPrincipal = SERVER_CERT.getSubjectX500Principal();
163    final List<Certificate> serverCertificates = Arrays.<Certificate>asList(SERVER_CERT);
164    URI uri = new URI("https://foo/bar");
165    Request request = new Request.Builder().url(uri.toURL()).build();
166    SecureCacheResponse cacheResponse = new SecureCacheResponse() {
167      @Override public Map<String, List<String>> getHeaders() throws IOException {
168        Map<String, List<String>> headers = new HashMap<>();
169        headers.put(null, Collections.singletonList(statusLine));
170        headers.put("xyzzy", Arrays.asList("bar", "baz"));
171        return headers;
172      }
173
174      @Override public InputStream getBody() throws IOException {
175        return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8));
176      }
177
178      @Override public String getCipherSuite() {
179        return "SuperSecure";
180      }
181
182      @Override public List<Certificate> getLocalCertificateChain() {
183        return localCertificates;
184      }
185
186      @Override public List<Certificate> getServerCertificateChain() throws SSLPeerUnverifiedException {
187        return serverCertificates;
188      }
189
190      @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
191        return serverPrincipal;
192      }
193
194      @Override public Principal getLocalPrincipal() {
195        return localPrincipal;
196      }
197    };
198
199    Response response = JavaApiConverter.createOkResponseForCacheGet(request, cacheResponse);
200    Request cacheRequest = response.request();
201    assertEquals(request.httpUrl(), cacheRequest.httpUrl());
202    assertEquals(request.method(), cacheRequest.method());
203    assertEquals(0, request.headers().size());
204
205    assertEquals(Protocol.HTTP_1_1, response.protocol());
206    assertEquals(200, response.code());
207    assertEquals("Fantastic", response.message());
208    Headers okResponseHeaders = response.headers();
209    assertEquals("baz", okResponseHeaders.get("xyzzy"));
210    assertEquals("HelloWorld", response.body().string());
211
212    Handshake handshake = response.handshake();
213    assertNotNull(handshake);
214    assertNotNullAndEquals("SuperSecure", handshake.cipherSuite());
215    assertEquals(localPrincipal, handshake.localPrincipal());
216    assertEquals(serverPrincipal, handshake.peerPrincipal());
217    assertEquals(serverCertificates, handshake.peerCertificates());
218    assertEquals(localCertificates, handshake.localCertificates());
219  }
220
221  @Test public void createOkRequest_nullRequestHeaders() throws Exception {
222    URI uri = new URI("http://foo/bar");
223
224    Map<String,List<String>> javaRequestHeaders = null;
225    Request request = JavaApiConverter.createOkRequest(uri, "POST", javaRequestHeaders);
226    assertFalse(request.isHttps());
227    assertEquals(uri, request.uri());
228    Headers okRequestHeaders = request.headers();
229    assertEquals(0, okRequestHeaders.size());
230    assertEquals("POST", request.method());
231  }
232
233  @Test public void createOkRequest_nonNullRequestHeaders() throws Exception {
234    URI uri = new URI("https://foo/bar");
235
236    Map<String,List<String>> javaRequestHeaders = new HashMap<>();
237    javaRequestHeaders.put("Foo", Arrays.asList("Bar"));
238    Request request = JavaApiConverter.createOkRequest(uri, "POST", javaRequestHeaders);
239    assertTrue(request.isHttps());
240    assertEquals(uri, request.uri());
241    Headers okRequestHeaders = request.headers();
242    assertEquals(1, okRequestHeaders.size());
243    assertEquals("Bar", okRequestHeaders.get("Foo"));
244    assertEquals("POST", request.method());
245  }
246
247  // Older versions of OkHttp would store the "request line" as a header with a
248  // null key. To support the Android usecase where an old version of OkHttp uses
249  // a newer, Android-bundled, version of HttpResponseCache the null key must be
250  // explicitly ignored.
251  @Test public void createOkRequest_nullRequestHeaderKey() throws Exception {
252    URI uri = new URI("https://foo/bar");
253
254    Map<String,List<String>> javaRequestHeaders = new HashMap<>();
255    javaRequestHeaders.put(null, Arrays.asList("GET / HTTP 1.1"));
256    javaRequestHeaders.put("Foo", Arrays.asList("Bar"));
257    Request request = JavaApiConverter.createOkRequest(uri, "POST", javaRequestHeaders);
258    assertTrue(request.isHttps());
259    assertEquals(uri, request.uri());
260    Headers okRequestHeaders = request.headers();
261    assertEquals(1, okRequestHeaders.size());
262    assertEquals("Bar", okRequestHeaders.get("Foo"));
263    assertEquals("POST", request.method());
264  }
265
266  @Test public void createJavaUrlConnection_requestChangesForbidden() throws Exception {
267    Response okResponse = createArbitraryOkResponse();
268    HttpURLConnection httpUrlConnection =
269        JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
270    // Check an arbitrary (not complete) set of methods that can be used to modify the
271    // request.
272    try {
273      httpUrlConnection.setRequestProperty("key", "value");
274      fail();
275    } catch (UnsupportedOperationException expected) {
276    }
277    try {
278      httpUrlConnection.setFixedLengthStreamingMode(1234);
279      fail();
280    } catch (UnsupportedOperationException expected) {
281    }
282    try {
283      httpUrlConnection.setRequestMethod("PUT");
284      fail();
285    } catch (UnsupportedOperationException expected) {
286    }
287    try {
288      httpUrlConnection.getHeaderFields().put("key", Collections.singletonList("value"));
289      fail();
290    } catch (UnsupportedOperationException expected) {
291    }
292    try {
293      httpUrlConnection.getOutputStream();
294      fail();
295    } catch (UnsupportedOperationException expected) {
296    }
297  }
298
299  @Test public void createJavaUrlConnection_connectionChangesForbidden() throws Exception {
300    Response okResponse = createArbitraryOkResponse();
301    HttpURLConnection httpUrlConnection =
302        JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
303    try {
304      httpUrlConnection.connect();
305      fail();
306    } catch (UnsupportedOperationException expected) {
307    }
308    try {
309      httpUrlConnection.disconnect();
310      fail();
311    } catch (UnsupportedOperationException expected) {
312    }
313  }
314
315  @Test public void createJavaUrlConnection_responseChangesForbidden() throws Exception {
316    Response okResponse = createArbitraryOkResponse();
317    HttpURLConnection httpUrlConnection =
318        JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
319    // Check an arbitrary (not complete) set of methods that can be used to access the response
320    // body.
321    try {
322      httpUrlConnection.getInputStream();
323      fail();
324    } catch (UnsupportedOperationException expected) {
325    }
326    try {
327      httpUrlConnection.getContent();
328      fail();
329    } catch (UnsupportedOperationException expected) {
330    }
331    try {
332      httpUrlConnection.setFixedLengthStreamingMode(1234);
333      fail();
334    } catch (UnsupportedOperationException expected) {
335    }
336    try {
337      httpUrlConnection.setRequestMethod("PUT");
338      fail();
339    } catch (UnsupportedOperationException expected) {
340    }
341    try {
342      httpUrlConnection.getHeaderFields().put("key", Collections.singletonList("value"));
343      fail();
344    } catch (UnsupportedOperationException expected) {
345    }
346  }
347
348  @Test public void createJavaUrlConnection_responseHeadersOk() throws Exception {
349    ResponseBody responseBody = createResponseBody("BodyText");
350    Response okResponse = new Response.Builder()
351        .request(createArbitraryOkRequest())
352        .protocol(Protocol.HTTP_1_1)
353        .code(200)
354        .message("Fantastic")
355        .addHeader("A", "c")
356        .addHeader("B", "d")
357        .addHeader("A", "e")
358        .addHeader("Content-Length", Long.toString(responseBody.contentLength()))
359        .body(responseBody)
360        .build();
361
362    HttpURLConnection httpUrlConnection =
363        JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
364    assertEquals(200, httpUrlConnection.getResponseCode());
365    assertEquals("Fantastic", httpUrlConnection.getResponseMessage());
366    assertEquals(responseBody.contentLength(), httpUrlConnection.getContentLength());
367
368    // Check retrieval by string key.
369    assertEquals("HTTP/1.1 200 Fantastic", httpUrlConnection.getHeaderField(null));
370    assertEquals("e", httpUrlConnection.getHeaderField("A"));
371    // The RI and OkHttp supports case-insensitive matching for this method.
372    assertEquals("e", httpUrlConnection.getHeaderField("a"));
373
374    // Check retrieval using a Map.
375    Map<String, List<String>> responseHeaders = httpUrlConnection.getHeaderFields();
376    assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"), responseHeaders.get(null));
377    assertEquals(newSet("c", "e"), newSet(responseHeaders.get("A")));
378    // OkHttp supports case-insensitive matching here. The RI does not.
379    assertEquals(newSet("c", "e"), newSet(responseHeaders.get("a")));
380
381    // Check the Map iterator contains the expected mappings.
382    assertHeadersContainsMapping(responseHeaders, null, "HTTP/1.1 200 Fantastic");
383    assertHeadersContainsMapping(responseHeaders, "A", "c", "e");
384    assertHeadersContainsMapping(responseHeaders, "B", "d");
385
386    // Check immutability of the headers Map.
387    try {
388      responseHeaders.put("N", Arrays.asList("o"));
389      fail("Modified an unmodifiable view.");
390    } catch (UnsupportedOperationException expected) {
391    }
392    try {
393      responseHeaders.get("A").add("f");
394      fail("Modified an unmodifiable view.");
395    } catch (UnsupportedOperationException expected) {
396    }
397
398    // Check retrieval of headers by index.
399    assertEquals(null, httpUrlConnection.getHeaderFieldKey(0));
400    assertEquals("HTTP/1.1 200 Fantastic", httpUrlConnection.getHeaderField(0));
401    // After header zero there may be additional entries provided at the beginning or end by the
402    // implementation. It's probably important that the relative ordering of the headers is
403    // preserved, particularly if there are multiple value for the same key.
404    int i = 1;
405    while (!httpUrlConnection.getHeaderFieldKey(i).equals("A")) {
406      i++;
407    }
408    // Check the ordering of the headers set by app code.
409    assertResponseHeaderAtIndex(httpUrlConnection, i++, "A", "c");
410    assertResponseHeaderAtIndex(httpUrlConnection, i++, "B", "d");
411    assertResponseHeaderAtIndex(httpUrlConnection, i++, "A", "e");
412    // There may be some additional headers provided by the implementation.
413    while (httpUrlConnection.getHeaderField(i) != null) {
414      assertNotNull(httpUrlConnection.getHeaderFieldKey(i));
415      i++;
416    }
417    // Confirm the correct behavior when the index is out-of-range.
418    assertNull(httpUrlConnection.getHeaderFieldKey(i));
419  }
420
421  private static void assertResponseHeaderAtIndex(HttpURLConnection httpUrlConnection,
422      int headerIndex, String expectedKey, String expectedValue) {
423    assertEquals(expectedKey, httpUrlConnection.getHeaderFieldKey(headerIndex));
424    assertEquals(expectedValue, httpUrlConnection.getHeaderField(headerIndex));
425  }
426
427  private void assertHeadersContainsMapping(Map<String, List<String>> headers, String expectedKey,
428      String... expectedValues) {
429    assertTrue(headers.containsKey(expectedKey));
430    assertEquals(newSet(expectedValues), newSet(headers.get(expectedKey)));
431  }
432
433  @Test public void createJavaUrlConnection_accessibleRequestInfo_GET() throws Exception {
434    Request okRequest = createArbitraryOkRequest().newBuilder()
435        .get()
436        .build();
437    Response okResponse = createArbitraryOkResponse(okRequest);
438    HttpURLConnection httpUrlConnection =
439        JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
440
441    assertEquals("GET", httpUrlConnection.getRequestMethod());
442    assertTrue(httpUrlConnection.getDoInput());
443    assertFalse(httpUrlConnection.getDoOutput());
444  }
445
446  @Test public void createJavaUrlConnection_accessibleRequestInfo_POST() throws Exception {
447    Request okRequest = createArbitraryOkRequest().newBuilder()
448        .post(createRequestBody("PostBody"))
449        .build();
450    Response okResponse = createArbitraryOkResponse(okRequest);
451    HttpURLConnection httpUrlConnection =
452        JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
453
454    assertEquals("POST", httpUrlConnection.getRequestMethod());
455    assertTrue(httpUrlConnection.getDoInput());
456    assertTrue(httpUrlConnection.getDoOutput());
457  }
458
459  @Test public void createJavaUrlConnection_https_extraHttpsMethods() throws Exception {
460    Request okRequest = createArbitraryOkRequest().newBuilder()
461        .get()
462        .url("https://secure/request")
463        .build();
464    Handshake handshake = Handshake.get("SecureCipher", Arrays.<Certificate>asList(SERVER_CERT),
465        Arrays.<Certificate>asList(LOCAL_CERT));
466    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
467        .handshake(handshake)
468        .build();
469    HttpsURLConnection httpsUrlConnection =
470        (HttpsURLConnection) JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
471
472    assertEquals("SecureCipher", httpsUrlConnection.getCipherSuite());
473    assertEquals(SERVER_CERT.getSubjectX500Principal(), httpsUrlConnection.getPeerPrincipal());
474    assertArrayEquals(new Certificate[] { LOCAL_CERT }, httpsUrlConnection.getLocalCertificates());
475    assertArrayEquals(new Certificate[] { SERVER_CERT },
476        httpsUrlConnection.getServerCertificates());
477    assertEquals(LOCAL_CERT.getSubjectX500Principal(), httpsUrlConnection.getLocalPrincipal());
478  }
479
480  @Test public void createJavaUrlConnection_https_forbiddenFields() throws Exception {
481    Request okRequest = createArbitraryOkRequest().newBuilder()
482        .url("https://secure/request")
483        .build();
484    Response okResponse = createArbitraryOkResponse(okRequest);
485    HttpsURLConnection httpsUrlConnection =
486        (HttpsURLConnection) JavaApiConverter.createJavaUrlConnectionForCachePut(okResponse);
487
488    try {
489      httpsUrlConnection.getHostnameVerifier();
490      fail();
491    } catch (UnsupportedOperationException expected) {
492    }
493    try {
494      httpsUrlConnection.getSSLSocketFactory();
495      fail();
496    } catch (UnsupportedOperationException expected) {
497    }
498  }
499
500  @Test public void createJavaCacheResponse_httpGet() throws Exception {
501    Request okRequest =
502        createArbitraryOkRequest().newBuilder()
503            .url("http://insecure/request")
504            .get()
505            .build();
506    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
507        .protocol(Protocol.HTTP_1_1)
508        .code(200)
509        .message("Fantastic")
510        .addHeader("key1", "value1_1")
511        .addHeader("key2", "value2")
512        .addHeader("key1", "value1_2")
513        .body(null)
514        .build();
515    CacheResponse javaCacheResponse = JavaApiConverter.createJavaCacheResponse(okResponse);
516    assertFalse(javaCacheResponse instanceof SecureCacheResponse);
517    Map<String, List<String>> javaHeaders = javaCacheResponse.getHeaders();
518    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
519    assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"), javaHeaders.get(null));
520    assertNull(javaCacheResponse.getBody());
521  }
522
523  @Test public void createJavaCacheResponse_httpPost() throws Exception {
524    Request okRequest =
525        createArbitraryOkRequest().newBuilder()
526            .url("http://insecure/request")
527            .post(createRequestBody("RequestBody"))
528            .build();
529    ResponseBody responseBody = createResponseBody("ResponseBody");
530    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
531        .protocol(Protocol.HTTP_1_1)
532        .code(200)
533        .message("Fantastic")
534        .addHeader("key1", "value1_1")
535        .addHeader("key2", "value2")
536        .addHeader("key1", "value1_2")
537        .body(responseBody)
538        .build();
539    CacheResponse javaCacheResponse = JavaApiConverter.createJavaCacheResponse(okResponse);
540    assertFalse(javaCacheResponse instanceof SecureCacheResponse);
541    Map<String, List<String>> javaHeaders = javaCacheResponse.getHeaders();
542    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
543    assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"), javaHeaders.get(null));
544    assertEquals("ResponseBody", readAll(javaCacheResponse.getBody()));
545  }
546
547  @Test public void createJavaCacheResponse_httpsPost() throws Exception {
548    Request okRequest =
549        createArbitraryOkRequest().newBuilder()
550            .url("https://secure/request")
551            .post(createRequestBody("RequestBody") )
552            .build();
553    ResponseBody responseBody = createResponseBody("ResponseBody");
554    Handshake handshake = Handshake.get("SecureCipher", Arrays.<Certificate>asList(SERVER_CERT),
555        Arrays.<Certificate>asList(LOCAL_CERT));
556    Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
557        .protocol(Protocol.HTTP_1_1)
558        .code(200)
559        .message("Fantastic")
560        .addHeader("key1", "value1_1")
561        .addHeader("key2", "value2")
562        .addHeader("key1", "value1_2")
563        .body(responseBody)
564        .handshake(handshake)
565        .build();
566    SecureCacheResponse javaCacheResponse =
567        (SecureCacheResponse) JavaApiConverter.createJavaCacheResponse(okResponse);
568    Map<String, List<String>> javaHeaders = javaCacheResponse.getHeaders();
569    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
570    assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"), javaHeaders.get(null));
571    assertEquals("ResponseBody", readAll(javaCacheResponse.getBody()));
572    assertEquals(handshake.cipherSuite(), javaCacheResponse.getCipherSuite());
573    assertEquals(handshake.localCertificates(), javaCacheResponse.getLocalCertificateChain());
574    assertEquals(handshake.peerCertificates(), javaCacheResponse.getServerCertificateChain());
575    assertEquals(handshake.localPrincipal(), javaCacheResponse.getLocalPrincipal());
576    assertEquals(handshake.peerPrincipal(), javaCacheResponse.getPeerPrincipal());
577  }
578
579  @Test public void extractJavaHeaders() throws Exception {
580    Request okRequest = createArbitraryOkRequest().newBuilder()
581        .addHeader("key1", "value1_1")
582        .addHeader("key2", "value2")
583        .addHeader("key1", "value1_2")
584        .build();
585    Map<String, List<String>> javaHeaders = JavaApiConverter.extractJavaHeaders(okRequest);
586
587    assertEquals(Arrays.asList("value1_1", "value1_2"), javaHeaders.get("key1"));
588    assertEquals(Arrays.asList("value2"), javaHeaders.get("key2"));
589  }
590
591  @Test public void extractOkHeaders() {
592    Map<String, List<String>> javaResponseHeaders = new HashMap<>();
593    javaResponseHeaders.put(null, Arrays.asList("StatusLine"));
594    javaResponseHeaders.put("key1", Arrays.asList("value1_1", "value1_2"));
595    javaResponseHeaders.put("key2", Arrays.asList("value2"));
596
597    Headers okHeaders = JavaApiConverter.extractOkHeaders(javaResponseHeaders);
598    assertEquals(3, okHeaders.size()); // null entry should be stripped out
599    assertEquals(Arrays.asList("value1_1", "value1_2"), okHeaders.values("key1"));
600    assertEquals(Arrays.asList("value2"), okHeaders.values("key2"));
601  }
602
603  @Test public void extractStatusLine() throws Exception {
604    Map<String, List<String>> javaResponseHeaders = new HashMap<>();
605    javaResponseHeaders.put(null, Arrays.asList("StatusLine"));
606    javaResponseHeaders.put("key1", Arrays.asList("value1_1", "value1_2"));
607    javaResponseHeaders.put("key2", Arrays.asList("value2"));
608    assertEquals("StatusLine", JavaApiConverter.extractStatusLine(javaResponseHeaders));
609
610    try {
611      JavaApiConverter.extractStatusLine(Collections.<String, List<String>>emptyMap());
612      fail();
613    } catch (IOException expected) {
614    }
615  }
616
617  private static <T> void assertNotNullAndEquals(T expected, T actual) {
618    assertNotNull(actual);
619    assertEquals(expected, actual);
620  }
621
622  private static X509Certificate certificate(String certificate) {
623    try {
624      return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
625          new ByteArrayInputStream(certificate.getBytes(Util.UTF_8)));
626    } catch (CertificateException e) {
627      fail();
628      return null;
629    }
630  }
631
632  @SafeVarargs
633  private static <T> Set<T> newSet(T... elements) {
634    return newSet(Arrays.asList(elements));
635  }
636
637  private static <T> Set<T> newSet(List<T> elements) {
638    return new LinkedHashSet<>(elements);
639  }
640
641  private static Request createArbitraryOkRequest() {
642    return new Request.Builder().url("http://arbitrary/url").build();
643  }
644
645  private static Response createArbitraryOkResponse(Request request) {
646    return new Response.Builder()
647        .request(request)
648        .protocol(Protocol.HTTP_1_1)
649        .code(200)
650        .message("Arbitrary")
651        .build();
652  }
653
654  private static Response createArbitraryOkResponse() {
655    return createArbitraryOkResponse(createArbitraryOkRequest());
656  }
657
658  private static RequestBody createRequestBody(String bodyText) {
659    return RequestBody.create(MediaType.parse("text/plain"), bodyText);
660  }
661
662  private static ResponseBody createResponseBody(String bodyText) {
663    final Buffer source = new Buffer().writeUtf8(bodyText);
664    final long contentLength = source.size();
665    return new ResponseBody() {
666      @Override public MediaType contentType() {
667        return MediaType.parse("text/plain; charset=utf-8");
668      }
669
670      @Override public long contentLength() {
671        return contentLength;
672      }
673
674      @Override public BufferedSource source() {
675        return source;
676      }
677    };
678  }
679
680  private String readAll(InputStream in) throws IOException {
681    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
682    int value;
683    while ((value = in.read()) != -1) {
684      buffer.write(value);
685    }
686    in.close();
687    return buffer.toString("UTF-8");
688  }
689}
690