1/*
2 * Copyright (C) 2010 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.OkHttpClient;
20import com.squareup.okhttp.OkUrlFactory;
21import com.squareup.okhttp.mockwebserver.MockResponse;
22import com.squareup.okhttp.mockwebserver.MockWebServer;
23import com.squareup.okhttp.mockwebserver.RecordedRequest;
24import java.io.IOException;
25import java.net.CookieHandler;
26import java.net.CookieManager;
27import java.net.HttpCookie;
28import java.net.HttpURLConnection;
29import java.net.URI;
30import java.net.URLConnection;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.HashMap;
34import java.util.List;
35import java.util.Map;
36import org.junit.After;
37import org.junit.Before;
38import org.junit.Test;
39
40import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
41import static org.junit.Assert.assertEquals;
42import static org.junit.Assert.assertFalse;
43import static org.junit.Assert.assertNull;
44import static org.junit.Assert.assertTrue;
45import static org.junit.Assert.fail;
46
47/** Android's CookiesTest. */
48public class CookiesTest {
49
50  private OkHttpClient client;
51
52  @Before
53  public void setUp() throws Exception {
54    client = new OkHttpClient();
55  }
56
57  @After
58  public void tearDown() throws Exception {
59    CookieHandler.setDefault(null);
60  }
61
62  @Test
63  public void testNetscapeResponse() throws Exception {
64    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
65    CookieHandler.setDefault(cookieManager);
66    MockWebServer server = new MockWebServer();
67    server.start();
68
69    server.enqueue(new MockResponse().addHeader("Set-Cookie: a=android; "
70        + "expires=Fri, 31-Dec-9999 23:59:59 GMT; "
71        + "path=/path; "
72        + "domain=" + server.getCookieDomain() + "; "
73        + "secure"));
74    get(server, "/path/foo");
75
76    List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies();
77    assertEquals(1, cookies.size());
78    HttpCookie cookie = cookies.get(0);
79    assertEquals("a", cookie.getName());
80    assertEquals("android", cookie.getValue());
81    assertEquals(null, cookie.getComment());
82    assertEquals(null, cookie.getCommentURL());
83    assertEquals(false, cookie.getDiscard());
84    assertTrue(server.getCookieDomain().equalsIgnoreCase(cookie.getDomain()));
85    assertTrue(cookie.getMaxAge() > 100000000000L);
86    assertEquals("/path", cookie.getPath());
87    assertEquals(true, cookie.getSecure());
88    assertEquals(0, cookie.getVersion());
89  }
90
91  @Test public void testRfc2109Response() throws Exception {
92    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
93    CookieHandler.setDefault(cookieManager);
94    MockWebServer server = new MockWebServer();
95    server.start();
96
97    server.enqueue(new MockResponse().addHeader("Set-Cookie: a=android; "
98        + "Comment=this cookie is delicious; "
99        + "Domain=" + server.getCookieDomain() + "; "
100        + "Max-Age=60; "
101        + "Path=/path; "
102        + "Secure; "
103        + "Version=1"));
104    get(server, "/path/foo");
105
106    List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies();
107    assertEquals(1, cookies.size());
108    HttpCookie cookie = cookies.get(0);
109    assertEquals("a", cookie.getName());
110    assertEquals("android", cookie.getValue());
111    assertEquals("this cookie is delicious", cookie.getComment());
112    assertEquals(null, cookie.getCommentURL());
113    assertEquals(false, cookie.getDiscard());
114    assertTrue(server.getCookieDomain().equalsIgnoreCase(cookie.getDomain()));
115    assertEquals(60, cookie.getMaxAge());
116    assertEquals("/path", cookie.getPath());
117    assertEquals(true, cookie.getSecure());
118    assertEquals(1, cookie.getVersion());
119  }
120
121  @Test public void testRfc2965Response() throws Exception {
122    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
123    CookieHandler.setDefault(cookieManager);
124    MockWebServer server = new MockWebServer();
125    server.start();
126
127    server.enqueue(new MockResponse().addHeader("Set-Cookie2: a=android; "
128        + "Comment=this cookie is delicious; "
129        + "CommentURL=http://google.com/; "
130        + "Discard; "
131        + "Domain=" + server.getCookieDomain() + "; "
132        + "Max-Age=60; "
133        + "Path=/path; "
134        + "Port=\"80,443," + server.getPort() + "\"; "
135        + "Secure; "
136        + "Version=1"));
137    get(server, "/path/foo");
138
139    List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies();
140    assertEquals(1, cookies.size());
141    HttpCookie cookie = cookies.get(0);
142    assertEquals("a", cookie.getName());
143    assertEquals("android", cookie.getValue());
144    assertEquals("this cookie is delicious", cookie.getComment());
145    assertEquals("http://google.com/", cookie.getCommentURL());
146    assertEquals(true, cookie.getDiscard());
147    assertTrue(server.getCookieDomain().equalsIgnoreCase(cookie.getDomain()));
148    assertEquals(60, cookie.getMaxAge());
149    assertEquals("/path", cookie.getPath());
150    assertEquals("80,443," + server.getPort(), cookie.getPortlist());
151    assertEquals(true, cookie.getSecure());
152    assertEquals(1, cookie.getVersion());
153  }
154
155  @Test public void testQuotedAttributeValues() throws Exception {
156    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
157    CookieHandler.setDefault(cookieManager);
158    MockWebServer server = new MockWebServer();
159    server.start();
160
161    server.enqueue(new MockResponse().addHeader("Set-Cookie2: a=\"android\"; "
162        + "Comment=\"this cookie is delicious\"; "
163        + "CommentURL=\"http://google.com/\"; "
164        + "Discard; "
165        + "Domain=\"" + server.getCookieDomain() + "\"; "
166        + "Max-Age=\"60\"; "
167        + "Path=\"/path\"; "
168        + "Port=\"80,443," + server.getPort() + "\"; "
169        + "Secure; "
170        + "Version=\"1\""));
171    get(server, "/path/foo");
172
173    List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies();
174    assertEquals(1, cookies.size());
175    HttpCookie cookie = cookies.get(0);
176    assertEquals("a", cookie.getName());
177    assertEquals("android", cookie.getValue());
178    assertEquals("this cookie is delicious", cookie.getComment());
179    assertEquals("http://google.com/", cookie.getCommentURL());
180    assertEquals(true, cookie.getDiscard());
181    assertTrue(server.getCookieDomain().equalsIgnoreCase(cookie.getDomain()));
182    assertEquals(60, cookie.getMaxAge());
183    assertEquals("/path", cookie.getPath());
184    assertEquals("80,443," + server.getPort(), cookie.getPortlist());
185    assertEquals(true, cookie.getSecure());
186    assertEquals(1, cookie.getVersion());
187  }
188
189  @Test public void testSendingCookiesFromStore() throws Exception {
190    MockWebServer server = new MockWebServer();
191    server.enqueue(new MockResponse());
192    server.start();
193
194    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
195    HttpCookie cookieA = new HttpCookie("a", "android");
196    cookieA.setDomain(server.getCookieDomain());
197    cookieA.setPath("/");
198    cookieManager.getCookieStore().add(server.getUrl("/").toURI(), cookieA);
199    HttpCookie cookieB = new HttpCookie("b", "banana");
200    cookieB.setDomain(server.getCookieDomain());
201    cookieB.setPath("/");
202    cookieManager.getCookieStore().add(server.getUrl("/").toURI(), cookieB);
203    CookieHandler.setDefault(cookieManager);
204
205    get(server, "/");
206    RecordedRequest request = server.takeRequest();
207
208    assertEquals("$Version=\"1\"; "
209            + "a=\"android\";$Path=\"/\";$Domain=\""
210            + server.getCookieDomain()
211            + "\"; "
212            + "b=\"banana\";$Path=\"/\";$Domain=\""
213            + server.getCookieDomain()
214            + "\"", request.getHeader("Cookie"));
215  }
216
217  @Test public void testRedirectsDoNotIncludeTooManyCookies() throws Exception {
218    MockWebServer redirectTarget = new MockWebServer();
219    redirectTarget.enqueue(new MockResponse().setBody("A"));
220    redirectTarget.start();
221
222    MockWebServer redirectSource = new MockWebServer();
223    redirectSource.enqueue(new MockResponse()
224        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
225        .addHeader("Location: " + redirectTarget.getUrl("/")));
226    redirectSource.start();
227
228    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
229    HttpCookie cookie = new HttpCookie("c", "cookie");
230    cookie.setDomain(redirectSource.getCookieDomain());
231    cookie.setPath("/");
232    String portList = Integer.toString(redirectSource.getPort());
233    cookie.setPortlist(portList);
234    cookieManager.getCookieStore().add(redirectSource.getUrl("/").toURI(), cookie);
235    CookieHandler.setDefault(cookieManager);
236
237    get(redirectSource, "/");
238    RecordedRequest request = redirectSource.takeRequest();
239
240    assertEquals("$Version=\"1\"; "
241            + "c=\"cookie\";$Path=\"/\";$Domain=\""
242            + redirectSource.getCookieDomain()
243            + "\";$Port=\""
244            + portList
245            + "\"", request.getHeader("Cookie"));
246
247    for (String header : redirectTarget.takeRequest().getHeaders().names()) {
248      if (header.startsWith("Cookie")) {
249        fail(header);
250      }
251    }
252  }
253
254  /**
255   * Test which headers show up where. The cookie manager should be notified
256   * of both user-specified and derived headers like {@code Host}. Headers
257   * named {@code Cookie} or {@code Cookie2} that are returned by the cookie
258   * manager should show up in the request and in {@code
259   * getRequestProperties}.
260   */
261  @Test public void testHeadersSentToCookieHandler() throws IOException, InterruptedException {
262    final Map<String, List<String>> cookieHandlerHeaders = new HashMap<>();
263    CookieHandler.setDefault(new CookieManager() {
264      @Override
265      public Map<String, List<String>> get(URI uri,
266          Map<String, List<String>> requestHeaders) throws IOException {
267        cookieHandlerHeaders.putAll(requestHeaders);
268        Map<String, List<String>> result = new HashMap<>();
269        result.put("Cookie", Collections.singletonList("Bar=bar"));
270        result.put("Cookie2", Collections.singletonList("Baz=baz"));
271        result.put("Quux", Collections.singletonList("quux"));
272        return result;
273      }
274    });
275    MockWebServer server = new MockWebServer();
276    server.enqueue(new MockResponse());
277    server.start();
278
279    HttpURLConnection connection = new OkUrlFactory(client).open(server.getUrl("/"));
280    assertEquals(Collections.<String, List<String>>emptyMap(),
281        connection.getRequestProperties());
282
283    connection.setRequestProperty("Foo", "foo");
284    connection.setDoOutput(true);
285    connection.getOutputStream().write(5);
286    connection.getOutputStream().close();
287    connection.getInputStream().close();
288
289    RecordedRequest request = server.takeRequest();
290
291    assertContainsAll(cookieHandlerHeaders.keySet(), "Foo");
292    assertContainsAll(cookieHandlerHeaders.keySet(),
293        "Content-type", "User-Agent", "Connection", "Host");
294    assertFalse(cookieHandlerHeaders.containsKey("Cookie"));
295
296    /*
297     * The API specifies that calling getRequestProperties() on a connected instance should fail
298     * with an IllegalStateException, but the RI violates the spec and returns a valid map.
299     * http://www.mail-archive.com/net-dev@openjdk.java.net/msg01768.html
300     */
301    try {
302      assertContainsAll(connection.getRequestProperties().keySet(), "Foo");
303      assertContainsAll(connection.getRequestProperties().keySet(),
304          "Content-type", "Content-Length", "User-Agent", "Connection", "Host");
305      assertContainsAll(connection.getRequestProperties().keySet(), "Cookie", "Cookie2");
306      assertFalse(connection.getRequestProperties().containsKey("Quux"));
307    } catch (IllegalStateException expected) {
308    }
309
310    assertEquals("foo", request.getHeader("Foo"));
311    assertEquals("Bar=bar", request.getHeader("Cookie"));
312    assertEquals("Baz=baz", request.getHeader("Cookie2"));
313    assertNull(request.getHeader("Quux"));
314  }
315
316  @Test public void testCookiesSentIgnoresCase() throws Exception {
317    CookieHandler.setDefault(new CookieManager() {
318      @Override public Map<String, List<String>> get(URI uri,
319          Map<String, List<String>> requestHeaders) throws IOException {
320        Map<String, List<String>> result = new HashMap<>();
321        result.put("COOKIE", Collections.singletonList("Bar=bar"));
322        result.put("cooKIE2", Collections.singletonList("Baz=baz"));
323        return result;
324      }
325    });
326    MockWebServer server = new MockWebServer();
327    server. enqueue(new MockResponse());
328    server.start();
329
330    get(server, "/");
331
332    RecordedRequest request = server.takeRequest();
333    assertEquals("Bar=bar", request.getHeader("Cookie"));
334    assertEquals("Baz=baz", request.getHeader("Cookie2"));
335    assertNull(request.getHeader("Quux"));
336  }
337
338  private void assertContains(Collection<String> collection, String element) {
339    for (String c : collection) {
340      if (c != null && c.equalsIgnoreCase(element)) {
341        return;
342      }
343    }
344    fail("No " + element + " in " + collection);
345  }
346
347  private void assertContainsAll(Collection<String> collection, String... toFind) {
348    for (String s : toFind) {
349      assertContains(collection, s);
350    }
351  }
352
353  private Map<String,List<String>> get(MockWebServer server, String path) throws Exception {
354    URLConnection connection = new OkUrlFactory(client).open(server.getUrl(path));
355    Map<String, List<String>> headers = connection.getHeaderFields();
356    connection.getInputStream().close();
357    return headers;
358  }
359
360}
361