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.mockwebserver.MockResponse;
21import com.squareup.okhttp.mockwebserver.MockWebServer;
22import com.squareup.okhttp.mockwebserver.RecordedRequest;
23
24import org.junit.After;
25import org.junit.Before;
26import org.junit.Test;
27
28import java.io.IOException;
29import java.net.CookieHandler;
30import java.net.CookieManager;
31import java.net.HttpCookie;
32import java.net.HttpURLConnection;
33import java.net.URI;
34import java.net.URLConnection;
35import java.util.Collection;
36import java.util.Collections;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40
41import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
42import static org.junit.Assert.assertEquals;
43import static org.junit.Assert.assertFalse;
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.play();
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    assertEquals(server.getCookieDomain(), 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.play();
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    assertEquals(server.getCookieDomain(), 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.play();
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    assertEquals(server.getCookieDomain(), 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.play();
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    assertEquals(server.getCookieDomain(), 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.play();
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    List<String> receivedHeaders = request.getHeaders();
209    assertContains(receivedHeaders, "Cookie: $Version=\"1\"; "
210        + "a=\"android\";$Path=\"/\";$Domain=\"" + server.getCookieDomain() + "\"; "
211        + "b=\"banana\";$Path=\"/\";$Domain=\"" + server.getCookieDomain() + "\"");
212  }
213
214  @Test public void testRedirectsDoNotIncludeTooManyCookies() throws Exception {
215    MockWebServer redirectTarget = new MockWebServer();
216    redirectTarget.enqueue(new MockResponse().setBody("A"));
217    redirectTarget.play();
218
219    MockWebServer redirectSource = new MockWebServer();
220    redirectSource.enqueue(new MockResponse()
221        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
222        .addHeader("Location: " + redirectTarget.getUrl("/")));
223    redirectSource.play();
224
225    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
226    HttpCookie cookie = new HttpCookie("c", "cookie");
227    cookie.setDomain(redirectSource.getCookieDomain());
228    cookie.setPath("/");
229    String portList = Integer.toString(redirectSource.getPort());
230    cookie.setPortlist(portList);
231    cookieManager.getCookieStore().add(redirectSource.getUrl("/").toURI(), cookie);
232    CookieHandler.setDefault(cookieManager);
233
234    get(redirectSource, "/");
235    RecordedRequest request = redirectSource.takeRequest();
236
237    assertContains(request.getHeaders(), "Cookie: $Version=\"1\"; "
238        + "c=\"cookie\";$Path=\"/\";$Domain=\"" + redirectSource.getCookieDomain()
239        + "\";$Port=\"" + portList + "\"");
240
241    for (String header : redirectTarget.takeRequest().getHeaders()) {
242      if (header.startsWith("Cookie")) {
243        fail(header);
244      }
245    }
246  }
247
248  /**
249   * Test which headers show up where. The cookie manager should be notified
250   * of both user-specified and derived headers like {@code Host}. Headers
251   * named {@code Cookie} or {@code Cookie2} that are returned by the cookie
252   * manager should show up in the request and in {@code
253   * getRequestProperties}.
254   */
255  @Test public void testHeadersSentToCookieHandler() throws IOException, InterruptedException {
256    final Map<String, List<String>> cookieHandlerHeaders = new HashMap<String, List<String>>();
257    CookieHandler.setDefault(new CookieManager() {
258      @Override
259      public Map<String, List<String>> get(URI uri,
260          Map<String, List<String>> requestHeaders) throws IOException {
261        cookieHandlerHeaders.putAll(requestHeaders);
262        Map<String, List<String>> result = new HashMap<String, List<String>>();
263        result.put("Cookie", Collections.singletonList("Bar=bar"));
264        result.put("Cookie2", Collections.singletonList("Baz=baz"));
265        result.put("Quux", Collections.singletonList("quux"));
266        return result;
267      }
268    });
269    MockWebServer server = new MockWebServer();
270    server.enqueue(new MockResponse());
271    server.play();
272
273    HttpURLConnection connection = client.open(server.getUrl("/"));
274    assertEquals(Collections.<String, List<String>>emptyMap(),
275        connection.getRequestProperties());
276
277    connection.setRequestProperty("Foo", "foo");
278    connection.setDoOutput(true);
279    connection.getOutputStream().write(5);
280    connection.getOutputStream().close();
281    connection.getInputStream().close();
282
283    RecordedRequest request = server.takeRequest();
284
285    assertContainsAll(cookieHandlerHeaders.keySet(), "Foo");
286    assertContainsAll(cookieHandlerHeaders.keySet(),
287        "Content-type", "User-Agent", "Connection", "Host");
288    assertFalse(cookieHandlerHeaders.containsKey("Cookie"));
289
290    /*
291     * The API specifies that calling getRequestProperties() on a connected instance should fail
292     * with an IllegalStateException, but the RI violates the spec and returns a valid map.
293     * http://www.mail-archive.com/net-dev@openjdk.java.net/msg01768.html
294     */
295    try {
296      assertContainsAll(connection.getRequestProperties().keySet(), "Foo");
297      assertContainsAll(connection.getRequestProperties().keySet(),
298          "Content-type", "Content-Length", "User-Agent", "Connection", "Host");
299      assertContainsAll(connection.getRequestProperties().keySet(), "Cookie", "Cookie2");
300      assertFalse(connection.getRequestProperties().containsKey("Quux"));
301    } catch (IllegalStateException expected) {
302    }
303
304    assertContainsAll(request.getHeaders(), "Foo: foo", "Cookie: Bar=bar", "Cookie2: Baz=baz");
305    assertFalse(request.getHeaders().contains("Quux: quux"));
306  }
307
308  @Test public void testCookiesSentIgnoresCase() throws Exception {
309    CookieHandler.setDefault(new CookieManager() {
310      @Override public Map<String, List<String>> get(URI uri,
311          Map<String, List<String>> requestHeaders) throws IOException {
312        Map<String, List<String>> result = new HashMap<String, List<String>>();
313        result.put("COOKIE", Collections.singletonList("Bar=bar"));
314        result.put("cooKIE2", Collections.singletonList("Baz=baz"));
315        return result;
316      }
317    });
318    MockWebServer server = new MockWebServer();
319    server. enqueue(new MockResponse());
320    server.play();
321
322    get(server, "/");
323
324    RecordedRequest request = server.takeRequest();
325    assertContainsAll(request.getHeaders(), "COOKIE: Bar=bar", "cooKIE2: Baz=baz");
326    assertFalse(request.getHeaders().contains("Quux: quux"));
327  }
328
329  private void assertContains(Collection<String> collection, String element) {
330    for (String c : collection) {
331      if (c != null && c.equalsIgnoreCase(element)) {
332        return;
333      }
334    }
335    fail("No " + element + " in " + collection);
336  }
337
338  private void assertContainsAll(Collection<String> collection, String... toFind) {
339    for (String s : toFind) {
340      assertContains(collection, s);
341    }
342  }
343
344  private Map<String,List<String>> get(MockWebServer server, String path) throws Exception {
345    URLConnection connection = client.open(server.getUrl(path));
346    Map<String, List<String>> headers = connection.getHeaderFields();
347    connection.getInputStream().close();
348    return headers;
349  }
350
351}
352