View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software 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    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import java.net.URL;
18  import java.nio.charset.StandardCharsets;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.http.client.utils.DateUtils;
27  import org.htmlunit.junit.BrowserRunner;
28  import org.htmlunit.junit.annotation.Alerts;
29  import org.htmlunit.util.MimeType;
30  import org.htmlunit.util.NameValuePair;
31  import org.junit.Test;
32  import org.junit.runner.RunWith;
33  import org.openqa.selenium.By;
34  import org.openqa.selenium.WebDriver;
35  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
36  
37  /**
38   * Unit tests for {@link CookieManager}.
39   *
40   * @author Daniel Gredler
41   * @author Ahmed Ashour
42   * @author Marc Guillemot
43   * @author Frank Danek
44   * @author Ronald Brill
45   */
46  @RunWith(BrowserRunner.class)
47  public class CookieManagerTest extends WebDriverTestCase {
48  
49      /** HTML code with JS code <code>alert(document.cookie)</code>. */
50      public static final String HTML_ALERT_COOKIE = DOCTYPE_HTML
51          + "<html><head>\n"
52          + "<script>\n"
53          + LOG_TITLE_FUNCTION
54          + "  function test() {\n"
55          // there is no fixed order, sort for stable testing
56          + "    var c = document.cookie;\n"
57          + "    c = c.split('; ').sort().join('; ');\n"
58          + "    log(c);\n"
59          + "  }\n"
60          + "</script>\n"
61          + "</head>\n"
62          + "<body onload='test()'>\n"
63          + "</body></html>";
64  
65      /**
66       * @throws Exception if the test fails
67       */
68      @Test
69      @Alerts("my_key=\"Hello, big, big, world\"; yet_another_key=Hi")
70      public void comma() throws Exception {
71          final List<NameValuePair> responseHeader = new ArrayList<>();
72          responseHeader.add(new NameValuePair("Set-Cookie", "my_key=\"Hello, big, big, world\""));
73          responseHeader.add(new NameValuePair("Set-Cookie", "yet_another_key=Hi"));
74          getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
75  
76          loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
77          verifyTitle2(getWebDriver(), getExpectedAlerts());
78      }
79  
80      /**
81       * @throws Exception if the test fails
82       */
83      @Test
84      @Alerts("a_key=helloA; b_key=helloB; c_key=helloC")
85      public void orderFromServer() throws Exception {
86          final List<NameValuePair> responseHeader = new ArrayList<>();
87          responseHeader.add(new NameValuePair("Set-Cookie", "c_key=helloC"));
88          responseHeader.add(new NameValuePair("Set-Cookie", "a_key=helloA"));
89          responseHeader.add(new NameValuePair("Set-Cookie", "b_key=helloB"));
90          getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
91  
92          loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
93          verifyTitle2(getWebDriver(), getExpectedAlerts());
94      }
95  
96      /**
97       * @throws Exception if the test fails
98       */
99      @Test
100     public void orderCookiesByPath_fromJs() throws Exception {
101         final String html = DOCTYPE_HTML
102             + "<html><body><script>\n"
103             + "document.cookie = 'exampleCookie=rootPath;path=/';\n"
104             + "document.cookie = 'exampleCookie=currentPath;path=/testpages/';\n"
105             + "</script>\n"
106             + "<a href='/testpages/next.html'>next page</a>\n"
107             + "</body></html>";
108 
109         getMockWebConnection().setDefaultResponse("");
110 
111         final WebDriver webDriver = getWebDriver();
112         webDriver.manage().deleteAllCookies();
113 
114         loadPage2(html);
115         webDriver.findElement(By.linkText("next page")).click();
116 
117         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
118         assertEquals("exampleCookie=currentPath; exampleCookie=rootPath",
119             lastRequest.getAdditionalHeaders().get(HttpHeader.COOKIE));
120     }
121 
122     /**
123      * @throws Exception if the test fails
124      */
125     @Test
126     @Alerts("key1=; key2=")
127     public void emptyCookie() throws Exception {
128         final List<NameValuePair> responseHeader = new ArrayList<>();
129         responseHeader.add(new NameValuePair("Set-Cookie", "key1="));
130         responseHeader.add(new NameValuePair("Set-Cookie", "key2="));
131         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
132 
133         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
134         verifyTitle2(getWebDriver(), getExpectedAlerts());
135     }
136 
137     /**
138      * @throws Exception if the test fails
139      */
140     @Test
141     @Alerts("value1")
142     public void emptyCookieName() throws Exception {
143         final List<NameValuePair> responseHeader = new ArrayList<>();
144         responseHeader.add(new NameValuePair("Set-Cookie", "=value1"));
145         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
146 
147         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
148         verifyTitle2(getWebDriver(), getExpectedAlerts());
149     }
150 
151     /**
152      * Test that " are not discarded.
153      * Regression test for issue 2973040.
154      * When a cookie is set with value within quotes, this value should be sent within quotes
155      * in the following requests. This is a bug in HttpClient which is not fixed in HttpClient-4.0.1.
156      * @see <a href="https://issues.apache.org/jira/browse/HTTPCLIENT-1006">HttpClient bug 1006</a>
157      * @throws Exception if the test fails
158      */
159     @Test
160     public void valueQuoted() throws Exception {
161         final List<NameValuePair> responseHeader = new ArrayList<>();
162         responseHeader.add(new NameValuePair("Set-Cookie", "key=value"));
163         responseHeader.add(new NameValuePair("Set-Cookie", "quoted=\"hello world\""));
164         responseHeader.add(new NameValuePair("Set-Cookie", "quotedEquals=\"aa= xx==\""));
165         getMockWebConnection().setResponse(URL_FIRST, "", 200, "OK", MimeType.TEXT_HTML, responseHeader);
166         getMockWebConnection().setDefaultResponse("");
167 
168         final WebDriver driver = loadPageWithAlerts2(URL_FIRST);
169 
170         driver.get(URL_SECOND.toExternalForm());
171 
172         final String lastCookies = getMockWebConnection().getLastAdditionalHeaders().get(HttpHeader.COOKIE);
173         final List<String> cookies = Arrays.asList(lastCookies.split(";\\s"));
174         Collections.sort(cookies);
175         assertEquals(new String[] {"key=value", "quoted=\"hello world\"", "quotedEquals=\"aa= xx==\""}, cookies);
176     }
177 
178     /**
179      * Regression test for issue 1735.
180      * @throws Exception if the test fails
181      */
182     @Test
183     public void valueEmpty() throws Exception {
184         ensureCookieValueIsSentBackUnquoted("SID=");
185     }
186 
187     /**
188      * @throws Exception if the test fails
189      */
190     @Test
191     public void valueUnsafe() throws Exception {
192         // fails with jetty 9.4.6.v20170531 but was working before
193         // ensureCookieValueIsSentBackUnquoted("SID=\"");
194 
195         ensureCookieValueIsSentBackUnquoted("SID=\"\"");
196         ensureCookieValueIsSentBackUnquoted("SID=ab\"cd");
197 
198         ensureCookieValueIsSentBackUnquoted("SID=\\");
199         ensureCookieValueIsSentBackUnquoted("SID=ab\\cd");
200     }
201 
202     /**
203      * If a Version 1 cookie is set with a value that requires quoting,
204      * but wasn't quoted by the server, then this value should be
205      * sent back unquoted as well.
206      * @throws Exception if the test fails
207      */
208     @Test
209     public void unquotedCookieValueIsSentBackUnquotedAsWell() throws Exception {
210         ensureCookieValueIsSentBackUnquoted("SID=1234");
211 
212         // even if there are special chars
213         ensureCookieValueIsSentBackUnquoted("SID=1234=");
214         ensureCookieValueIsSentBackUnquoted("SID=1234:");
215         ensureCookieValueIsSentBackUnquoted("SID=1234<");
216     }
217 
218     private void ensureCookieValueIsSentBackUnquoted(final String cookie) throws Exception {
219         final List<NameValuePair> responseHeaders = new ArrayList<>();
220         responseHeaders.add(new NameValuePair("Set-Cookie", cookie + "; Path=/; Version=1"));
221         getMockWebConnection().setResponse(URL_FIRST, "", 200, "OK", MimeType.TEXT_HTML, responseHeaders);
222         getMockWebConnection().setDefaultResponse("");
223 
224         final WebDriver driver = loadPageWithAlerts2(URL_FIRST);
225         driver.get(URL_SECOND.toExternalForm());
226 
227         final String lastCookie = getMockWebConnection().getLastAdditionalHeaders().get(HttpHeader.COOKIE);
228         assertEquals(cookie, lastCookie);
229     }
230 
231     /**
232      * @throws Exception if the test fails
233      */
234     @Test
235     @Alerts("nbCalls=1")
236     public void serverModifiesCookieValue() throws Exception {
237         final List<NameValuePair> responseHeader = new ArrayList<>();
238         responseHeader.add(new NameValuePair("Set-Cookie", "nbCalls=1"));
239         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
240 
241         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
242         verifyTitle2(getWebDriver(), getExpectedAlerts());
243 
244         final List<NameValuePair> responseHeader2 = new ArrayList<>();
245         responseHeader2.add(new NameValuePair("Set-Cookie", "nbCalls=2"));
246         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
247                 responseHeader2);
248 
249         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
250         verifyTitle2(getWebDriver(), "nbCalls=2");
251     }
252 
253     /**
254      * @throws Exception if the test fails
255      */
256     @Test
257     @Alerts("first=1")
258     public void cookie2() throws Exception {
259         final List<NameValuePair> responseHeader1 = new ArrayList<>();
260         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1"));
261         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
262                 responseHeader1);
263 
264         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
265         verifyTitle2(getWebDriver(), getExpectedAlerts());
266 
267         final List<NameValuePair> responseHeader2 = new ArrayList<>();
268         responseHeader2.add(new NameValuePair("Set-Cookie", "second=2"));
269         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
270                 responseHeader2);
271 
272         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
273         verifyTitle2(getWebDriver(), "first=1; second=2");
274     }
275 
276     /**
277      * Tests for expiration date.
278      * Tests as well for bug 3421201 (with expiration date enclosed with quotes).
279      * @throws Exception if the test fails
280      */
281     @Test
282     @Alerts("second=2; visitor=f2")
283     public void setCookieExpired() throws Exception {
284         final Date aBitLater = new Date(new Date().getTime() + 60 * 60 * 1000); // one hour later
285         final List<NameValuePair> responseHeader1 = new ArrayList<>();
286         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;expires=Fri, 02-Jan-1970 00:00:00 GMT"));
287         responseHeader1.add(new NameValuePair("Set-Cookie",
288             "second=2;expires=" + DateUtils.formatDate(aBitLater)));
289         responseHeader1.add(new NameValuePair("Set-Cookie", "visit=fo; expires=\"Sat, 07-Apr-2002 13:11:33 GMT\";"));
290         responseHeader1.add(new NameValuePair("Set-Cookie", "visitor=f2; expires=\"Sat, 07-Apr-2092 13:11:33 GMT\";"));
291         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
292                 responseHeader1);
293 
294         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
295         verifyTitle2(getWebDriver(), getExpectedAlerts());
296     }
297 
298     /**
299      * Test for document.cookie for cookies expired after the page was loaded.
300      * @throws Exception if the test fails
301      */
302     @Test
303     @Alerts({"cookies: first=1", "cookies: "})
304     public void setCookieTimeout() throws Exception {
305         final String html = DOCTYPE_HTML
306                 + "<html><head>\n"
307                 + "<script>\n"
308                 + LOG_TITLE_FUNCTION
309                 + "  function alertCookies() {\n"
310                 + "    log('cookies: ' + document.cookie);\n"
311                 + "  }\n"
312 
313                 + "  function test() {\n"
314                 + "    alertCookies();\n"
315                 + "    window.setTimeout(alertCookies, 2500);\n"
316                 + "  }\n"
317                 + "</script></head><body onload='test()'>\n"
318                 + "</body></html>";
319 
320         final List<NameValuePair> responseHeader1 = new ArrayList<>();
321         final String expires = DateUtils.formatDate(new Date(System.currentTimeMillis() + 2_000));
322         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1; expires=" + expires + ";"));
323         getMockWebConnection().setResponse(URL_FIRST, html, 200, "OK", MimeType.TEXT_HTML, responseHeader1);
324 
325         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
326         verifyTitle2(DEFAULT_WAIT_TIME.multipliedBy(4), getWebDriver(), getExpectedAlerts());
327     }
328 
329     /**
330      * Regression test for bug 3081652: it seems that expiration date should be ignored if format is incorrect.
331      * @throws Exception if the test fails
332      */
333     @Test
334     @Alerts(DEFAULT = "fourth=4; third=3",
335             FF = "first=1; second=2; third=3",
336             FF_ESR = "first=1; second=2; third=3")
337     public void setCookieExpired_badDateFormat() throws Exception {
338         final List<NameValuePair> responseHeader1 = new ArrayList<>();
339         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;expires=Dec-1-94 16:00:00"));
340         responseHeader1.add(new NameValuePair("Set-Cookie", "second=2;expires=Dec-1-1994 16:00:00"));
341         responseHeader1.add(new NameValuePair("Set-Cookie", "third=3;expires=Dec-1-2094 16:00:00"));
342         responseHeader1.add(new NameValuePair("Set-Cookie", "fourth=4;expires=1/1/2000; path=/"));
343         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
344                 responseHeader1);
345 
346         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
347         verifyTitle2(getWebDriver(), getExpectedAlerts());
348     }
349 
350     /**
351      * Two digits years should be interpreted as 20xx if before 1970 and as 19xx otherwise.
352      * @throws Exception if the test fails
353      */
354     @Test
355     @Alerts("cookie1=1; cookie2=2; cookie3=3")
356     public void setCookieExpires_twoDigits() throws Exception {
357         final List<NameValuePair> responseHeader1 = new ArrayList<>();
358         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie1=1;expires=Sun 01-Dec-68 16:00:00 GMT"));
359         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie2=2;expires=Thu 01-Dec-69 16:00:00 GMT"));
360         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie3=3;expires=Mon 31-Dec-69 16:00:00 GMT"));
361         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie4=4;expires=Thu 01-Jan-70 16:00:00 GMT"));
362         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie5=5;expires=Tue 01-Dec-70 16:00:00 GMT"));
363         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie6=6;expires=Wed 01-Dec-71 16:00:00 GMT"));
364         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
365                 responseHeader1);
366 
367         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
368         verifyTitle2(getWebDriver(), getExpectedAlerts());
369     }
370 
371     /**
372      * Two digits years should be interpreted as 20xx if before 1970 and as 19xx otherwise.
373      * Same as the test before, only different formating was used.
374      * @throws Exception if the test fails
375      */
376     @Test
377     @Alerts("cookie1=1; cookie2=2; cookie3=3")
378     public void setCookieExpires_twoDigits2() throws Exception {
379         final List<NameValuePair> responseHeader1 = new ArrayList<>();
380         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie1=1;expires=Sun,01 Dec 68 16:00:00 GMT"));
381         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie2=2;expires=Thu,01 Dec 69 16:00:00 GMT"));
382         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie3=3;expires=Mon,31 Dec 69 16:00:00 GMT"));
383         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie4=4;expires=Thu,01 Jan 70 16:00:00 GMT"));
384         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie5=5;expires=Tue,01 Dec 70 16:00:00 GMT"));
385         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie6=6;expires=Wed,01 Dec 71 16:00:00 GMT"));
386         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
387                 responseHeader1);
388 
389         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
390         verifyTitle2(getWebDriver(), getExpectedAlerts());
391     }
392 
393     /**
394      * Two digits years should be interpreted as 20xx if before 1970 and as 19xx otherwise,
395      * even with a quite strange date format.
396      * @throws Exception if the test fails
397      */
398     @Test
399     @Alerts("cookie1=1")
400     public void cookieExpires_TwoDigits3() throws Exception {
401         final List<NameValuePair> responseHeader1 = new ArrayList<>();
402         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie1=1;expires=Sun-01 Dec 68 16:00:00 GMT"));
403         responseHeader1.add(new NameValuePair("Set-Cookie", "cookie6=6;expires=Wed-01 Dec 71 16:00:00 GMT"));
404         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
405                 responseHeader1);
406 
407         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
408         verifyTitle2(getWebDriver(), getExpectedAlerts());
409     }
410 
411     /**
412      * @throws Exception if the test fails
413      */
414     @Test
415     @Alerts("cookie1=1")
416     public void cookieExpires_GMT() throws Exception {
417         final List<NameValuePair> responseHeader = new ArrayList<>();
418         responseHeader.add(new NameValuePair("Set-Cookie",
419                                                 "cookie1=1;expires=Sun Jan 20 2042 17:45:00 GMT+0800 (CST)"));
420         responseHeader.add(new NameValuePair("Set-Cookie",
421                                                 "cookie2=2;expires=Sun Jan 20 2004 17:45:00 GMT+0800 (CST)"));
422         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
423 
424         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
425         verifyTitle2(getWebDriver(), getExpectedAlerts());
426     }
427 
428     /**
429      * Test some formating errors.
430      * @throws Exception if the test fails
431      */
432     @Test
433     @Alerts("fifth=5; first=1; fourth=4; second=2; sixth=6; third=3")
434     public void cookieExpires_badDateFormat() throws Exception {
435         final List<NameValuePair> responseHeader1 = new ArrayList<>();
436         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;expires=Thu 01-Dec-42 16:00:00 GMT"));
437         responseHeader1.add(new NameValuePair("Set-Cookie", "second=2;expires=Thu 01 Dec 42 16:00:00 GMT"));
438         responseHeader1.add(new NameValuePair("Set-Cookie", "third=3;expires=Thu, 01-Dec-42 16:00:00 GMT"));
439         responseHeader1.add(new NameValuePair("Set-Cookie", "fourth=4;expires=Thu, 01 Dec 42 16:00:00 GMT"));
440         responseHeader1.add(new NameValuePair("Set-Cookie", "fifth=5;expires=Thu,01-Dec-42 16:00:00 GMT"));
441         responseHeader1.add(new NameValuePair("Set-Cookie", "sixth=6;expires=Thu,01 Dec 42 16:00:00 GMT"));
442         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
443                 responseHeader1);
444 
445         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
446         verifyTitle2(getWebDriver(), getExpectedAlerts());
447     }
448 
449     /**
450      * @throws Exception if the test fails
451      */
452     @Test
453     @Alerts("first=1")
454     public void cookieMaxAge() throws Exception {
455         final List<NameValuePair> responseHeader1 = new ArrayList<>();
456         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;max-age=1000"));
457         responseHeader1.add(new NameValuePair("Set-Cookie", "second=2;max-age=-1"));
458         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
459                 responseHeader1);
460 
461         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
462         verifyTitle2(getWebDriver(), getExpectedAlerts());
463     }
464 
465     /**
466      * @throws Exception if the test fails
467      */
468     @Test
469     @Alerts({"first=1", ""})
470     public void cookieMaxAge_zeroDeletes() throws Exception {
471         List<NameValuePair> responseHeader1 = new ArrayList<>();
472         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;max-age=1000"));
473         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
474                 responseHeader1);
475 
476         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
477         verifyTitle2(getWebDriver(), getExpectedAlerts()[0]);
478 
479         responseHeader1 = new ArrayList<>();
480         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;max-age=0"));
481         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
482                 responseHeader1);
483 
484         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
485         verifyTitle2(getWebDriver(), getExpectedAlerts()[1]);
486     }
487 
488     /**
489      * @throws Exception if the test fails
490      */
491     @Test
492     @Alerts({"first=1", ""})
493     public void cookieMaxAge_negativeDeletes() throws Exception {
494         List<NameValuePair> responseHeader1 = new ArrayList<>();
495         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;max-age=1000"));
496         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
497                 responseHeader1);
498 
499         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
500         verifyTitle2(getWebDriver(), getExpectedAlerts()[0]);
501 
502         responseHeader1 = new ArrayList<>();
503         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;max-age=-1"));
504         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML,
505                 responseHeader1);
506 
507         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
508         verifyTitle2(getWebDriver(), getExpectedAlerts()[1]);
509     }
510 
511     /**
512      * @throws Exception if the test fails
513      */
514     @Test
515     @Alerts({"", "dog=dalmation"})
516     public void trailing_slash() throws Exception {
517         final String[] expectedAlerts = getExpectedAlerts();
518         final WebDriver driver = getWebDriver();
519 
520         setExpectedAlerts(expectedAlerts[0]);
521         loadPage2(HTML_ALERT_COOKIE, URL_SECOND);
522         verifyTitle2(getWebDriver(), getExpectedAlerts());
523 
524         setExpectedAlerts(expectedAlerts[1]);
525         driver.manage().addCookie(new org.openqa.selenium.Cookie("dog", "dalmation", "/second/"));
526         loadPage2(HTML_ALERT_COOKIE, URL_SECOND);
527         verifyTitle2(getWebDriver(), getExpectedAlerts());
528     }
529 
530     /**
531      * Regression test for bug 3032380: expired cookies shouldn't be returned.
532      * @throws Exception if the test fails
533      */
534     @Test
535     @Alerts({"Cookies: cookie1=value1; cookie2=value2", "Cookies: cookie2=value2"})
536     public void cookieExpiresAfterBeingSet() throws Exception {
537         final String html = DOCTYPE_HTML
538             + "<html><head><script>\n"
539             + LOG_TITLE_FUNCTION
540             + "  function f() {\n"
541             + "    log('Cookies: ' + document.cookie);\n"
542             + "  }\n"
543 
544             + "  function test() {\n"
545             + "    var date1 = new Date();\n"
546             + "    date1.setTime(date1.getTime() + 1000);\n"
547             + "    document.cookie = 'cookie1=value1; expires=' + date1.toGMTString() + '; path=/';\n"
548             + "    var date2 = new Date();\n"
549             + "    date2.setTime(date2.getTime() + 60 * 1000);\n"
550             + "    document.cookie = 'cookie2=value2; expires=' + date2.toGMTString() + '; path=/';\n"
551             + "    f();\n"
552             + "    setTimeout(f, 1500);\n"
553             + "  }\n"
554             + "</script></head><body onload='test()'>\n"
555             + "</body></html>";
556 
557         loadPage2(html);
558         verifyTitle2(DEFAULT_WAIT_TIME.multipliedBy(4), getWebDriver(), getExpectedAlerts());
559     }
560 
561     /**
562      * Regression test for bug 3181695 (introduced during 2.9-SNAPSHOT).
563      * @throws Exception if the test fails
564      */
565     @Test
566     public void removeExpiredCookies() throws Exception {
567         final List<NameValuePair> responseHeader = new ArrayList<>();
568         responseHeader.add(new NameValuePair("Set-Cookie", "key1=value1"));
569         responseHeader.add(new NameValuePair("Set-Cookie", "key2=value2"));
570         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
571 
572         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
573         verifyTitle2(getWebDriver(), "key1=value1; key2=value2");
574 
575         responseHeader.clear();
576         responseHeader.add(new NameValuePair("Set-Cookie", "key1=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"));
577         responseHeader.add(new NameValuePair("Set-Cookie", "key2=value2"));
578         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
579 
580         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
581         verifyTitle2(getWebDriver(), "key2=value2");
582     }
583 
584     /**
585      * Response to / sets cookie to /foo/blah.
586      * @throws Exception if the test fails
587      */
588     @Test
589     @Alerts("first=1; second=2")
590     public void setCookieSubPath() throws Exception {
591         final List<NameValuePair> responseHeader1 = new ArrayList<>();
592         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1;path=/foo/blah"));
593         responseHeader1.add(new NameValuePair("Set-Cookie", "second=2;path=/foo/blah/test"));
594         responseHeader1.add(new NameValuePair("Location", "/foo/blah/test"));
595 
596         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE);
597         getMockWebConnection().setResponse(URL_FIRST, "", 302, "Moved", MimeType.TEXT_HTML, responseHeader1);
598 
599         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
600         verifyTitle2(getWebDriver(), getExpectedAlerts());
601     }
602 
603     /**
604      * Response to /a/b sets cookie to /foo/blah.
605      * @throws Exception if the test fails
606      */
607     @Test
608     @Alerts("first=1; second=2")
609     public void setCookieDifferentPath() throws Exception {
610         final List<NameValuePair> responseHeader1 = new ArrayList<>();
611         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1; path=/foo/blah"));
612         responseHeader1.add(new NameValuePair("Set-Cookie", "second=2; path=/foo/blah/test"));
613         responseHeader1.add(new NameValuePair("Set-Cookie", "third=3; path=/foo/other"));
614         responseHeader1.add(new NameValuePair("Set-Cookie", "fourth=4; path=/other/path"));
615         responseHeader1.add(new NameValuePair("Location", "/foo/blah/test"));
616 
617         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE);
618         final URL firstUrl = new URL(URL_FIRST, "/a/b");
619         getMockWebConnection().setResponse(firstUrl, "", 302, "Moved", MimeType.TEXT_HTML, responseHeader1);
620 
621         loadPage2(firstUrl, StandardCharsets.ISO_8859_1);
622         verifyTitle2(getWebDriver(), getExpectedAlerts());
623     }
624 
625     /**
626      * Response to /a/b sets cookie to /foo/blah.
627      * @throws Exception if the test fails
628      */
629     @Test
630     @Alerts("first=1; second=2")
631     public void setCookieDifferentPathSlashAtEnd() throws Exception {
632         final List<NameValuePair> responseHeader1 = new ArrayList<>();
633         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1; path=/foo"));
634         responseHeader1.add(new NameValuePair("Set-Cookie", "second=2; path=/foo/"));
635         responseHeader1.add(new NameValuePair("Set-Cookie", "third=3; path=/foo/other"));
636         responseHeader1.add(new NameValuePair("Set-Cookie", "fourth=4; path=/other/path"));
637         responseHeader1.add(new NameValuePair("Location", "/foo/"));
638 
639         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE);
640         final URL firstUrl = new URL(URL_FIRST, "/a/b");
641         getMockWebConnection().setResponse(firstUrl, "", 302, "Moved", MimeType.TEXT_HTML, responseHeader1);
642 
643         loadPage2(firstUrl, StandardCharsets.ISO_8859_1);
644         verifyTitle2(getWebDriver(), getExpectedAlerts());
645     }
646 
647     /**
648      * Test for document.cookie for cookies expired after the page was loaded.
649      * @throws Exception if the test fails
650      */
651     @Test
652     @Alerts({"cookies: first=1", "cookies: "})
653     public void setCookieDuring302() throws Exception {
654         final String html = DOCTYPE_HTML
655                 + "<html><head>\n"
656                 + "<script>\n"
657                 + LOG_TITLE_FUNCTION
658                 + "  function alertCookies() {\n"
659                 + "    log('cookies: ' + document.cookie);\n"
660                 + "  }\n"
661 
662                 + "  function test() {\n"
663                 + "    alertCookies();\n"
664                 + "    window.setTimeout(alertCookies, 2500);\n"
665                 + "  }\n"
666                 + "</script></head><body onload='test()'>\n"
667                 + "</body></html>";
668 
669         getMockWebConnection().setDefaultResponse(html);
670         final URL firstUrl = new URL(URL_FIRST, "/foo/test.html");
671 
672         final List<NameValuePair> responseHeader1 = new ArrayList<>();
673         final String expires = DateUtils.formatDate(new Date(System.currentTimeMillis() + 2_000));
674         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1; expires=" + expires + "; path=/foo"));
675         responseHeader1.add(new NameValuePair("Location", "/foo/content.html"));
676         getMockWebConnection().setResponse(firstUrl, "", 302, "Moved", MimeType.TEXT_HTML, responseHeader1);
677 
678         loadPage2(firstUrl, StandardCharsets.ISO_8859_1);
679         verifyTitle2(DEFAULT_WAIT_TIME.multipliedBy(4), getWebDriver(), getExpectedAlerts());
680     }
681 
682     /**
683      * HttpOnly cookies should not be available from JS.
684      * @throws Exception if the test fails
685      */
686     @Test
687     @Alerts("second=2")
688     public void httpOnly() throws Exception {
689         final List<NameValuePair> responseHeader = new ArrayList<>();
690         responseHeader.add(new NameValuePair("Set-Cookie", "first=1; path=/; HttpOnly"));
691         responseHeader.add(new NameValuePair("Set-Cookie", "second=2; path=/;"));
692 
693         getMockWebConnection().setDefaultResponse("");
694         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
695 
696         final WebDriver driver = loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
697         verifyTitle2(getWebDriver(), getExpectedAlerts());
698 
699         loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
700         verifyTitle2(getWebDriver(), getExpectedAlerts());
701         driver.get(URL_FIRST + "foo");
702 
703         final Map<String, String> lastHeaders = getMockWebConnection().getLastAdditionalHeaders();
704 
705         // strange check, but there is no order
706         final String lastCookies = lastHeaders.get(HttpHeader.COOKIE);
707         assertEquals(17, lastCookies.length());
708         assertTrue("lastCookies: " + lastCookies, lastCookies.contains("first=1")
709                     && lastCookies.contains("second=2")
710                     && lastCookies.contains("; "));
711 
712         if (driver instanceof HtmlUnitDriver) {
713             final CookieManager mgr = getWebClient().getCookieManager();
714             assertEquals(2, mgr.getCookies().size());
715             assertTrue(mgr.getCookie("first").isHttpOnly());
716             assertFalse(mgr.getCookie("second").isHttpOnly());
717         }
718     }
719 
720     /**
721      * @throws Exception if the test fails
722      */
723     @Test
724     @Alerts({"first=1; second=2; third=3", "26"})
725     public void sameSite() throws Exception {
726         final List<NameValuePair> responseHeader = new ArrayList<>();
727         responseHeader.add(new NameValuePair("Set-Cookie", "first=1; path=/; SameSite=None; Secure"));
728         responseHeader.add(new NameValuePair("Set-Cookie", "second=2; path=/; SameSite=Lax"));
729         responseHeader.add(new NameValuePair("Set-Cookie", "third=3; path=/; SameSite=Strict"));
730 
731         getMockWebConnection().setDefaultResponse("");
732         getMockWebConnection().setResponse(URL_FIRST, HTML_ALERT_COOKIE, 200, "OK", MimeType.TEXT_HTML, responseHeader);
733 
734         final WebDriver driver = loadPage2(URL_FIRST, StandardCharsets.ISO_8859_1);
735         verifyTitle2(driver, getExpectedAlerts()[0]);
736         driver.get(URL_FIRST + "foo");
737 
738         final Map<String, String> lastHeaders = getMockWebConnection().getLastAdditionalHeaders();
739 
740         // strange check, but there is no order
741         final String lastCookies = lastHeaders.get(HttpHeader.COOKIE);
742         assertEquals(Integer.parseInt(getExpectedAlerts()[1]), lastCookies.length());
743         assertTrue("lastCookies: " + lastCookies,
744                     /* lastCookies.contains("first=1")
745                     && */ lastCookies.contains("second=2")
746                     && lastCookies.contains("third=3")
747                     && lastCookies.contains("; "));
748 
749         if (driver instanceof HtmlUnitDriver) {
750             final CookieManager mgr = getWebClient().getCookieManager();
751             assertEquals(3, mgr.getCookies().size());
752             assertEquals("none", mgr.getCookie("first").getSameSite());
753             assertEquals("lax", mgr.getCookie("second").getSameSite());
754             assertEquals("strict", mgr.getCookie("third").getSameSite());
755         }
756     }
757 
758     /**
759      * A cookie set with document.cookie without path applies only for the current path
760      * and overrides cookie previously set for this path.
761      * @throws Exception if the test fails
762      */
763     @Test
764     @Alerts("first=new")
765     public void cookieSetFromJSWithoutPathUsesCurrentLocation() throws Exception {
766         final List<NameValuePair> responseHeader1 = new ArrayList<>();
767         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1"));
768 
769         final String html = DOCTYPE_HTML
770             + "<html>\n"
771             + "<head></head>\n"
772             + "<body><script>\n"
773             + "  document.cookie = 'first=new';\n"
774             + "  location.replace('/a/b/-');\n"
775             + "</script></body>\n"
776             + "</html>";
777 
778         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE);
779         final URL firstUrl = new URL(URL_FIRST, "/a/b");
780         getMockWebConnection().setResponse(firstUrl, html, 200, "Ok", MimeType.TEXT_HTML, responseHeader1);
781 
782         loadPage2(firstUrl, StandardCharsets.ISO_8859_1);
783         verifyTitle2(getWebDriver(), getExpectedAlerts());
784     }
785 
786     /**
787      * A cookie set with document.cookie without path applies only for the current path.
788      * @throws Exception if the test fails
789      */
790     @Test
791     @Alerts("first=1")
792     public void cookieSetFromJSWithoutPathUsesCurrentLocation2() throws Exception {
793         final List<NameValuePair> responseHeader1 = new ArrayList<>();
794         responseHeader1.add(new NameValuePair("Set-Cookie", "first=1; path=/c"));
795 
796         final String html = DOCTYPE_HTML
797             + "<html>\n"
798             + "<head></head>\n"
799             + "<body><script>\n"
800             + "  document.cookie = 'first=new';\n"
801             + "  location.replace('/c/test.html');\n"
802             + "</script></body>\n"
803             + "</html>";
804 
805         getMockWebConnection().setDefaultResponse(HTML_ALERT_COOKIE);
806         final URL firstUrl = new URL(URL_FIRST, "/a/b");
807         getMockWebConnection().setResponse(firstUrl, html, 200, "Ok", MimeType.TEXT_HTML, responseHeader1);
808 
809         loadPage2(firstUrl, StandardCharsets.ISO_8859_1);
810         verifyTitle2(getWebDriver(), getExpectedAlerts());
811     }
812 }