View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.cookie;
28  
29  import java.util.ArrayList;
30  import java.util.Date;
31  import java.util.List;
32  
33  import org.apache.http.Header;
34  import org.apache.http.cookie.ClientCookie;
35  import org.apache.http.cookie.Cookie;
36  import org.apache.http.cookie.CookieOrigin;
37  import org.apache.http.cookie.CookieSpec;
38  import org.apache.http.cookie.MalformedCookieException;
39  import org.apache.http.message.BasicHeader;
40  import org.junit.Assert;
41  import org.junit.Test;
42  
43  /**
44   * Test cases for RFC2965 cookie spec
45   */
46  public class TestCookieRFC2965Spec {
47  
48      /**
49       * Test parsing cookie {@code "Path"} attribute.
50       */
51      @Test
52      public void testParsePath() throws Exception {
53          final CookieSpec cookiespec = new RFC2965Spec();
54          final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
55          final Header header = new BasicHeader("Set-Cookie2", "name=value;Path=/;Version=1;Path=");
56          final List<Cookie> cookies = cookiespec.parse(header, origin);
57          Assert .assertNotNull(cookies);
58          Assert.assertEquals(1, cookies.size());
59          // only the first occurrence of path attribute is considered, others ignored
60          final ClientCookie cookie = (ClientCookie) cookies.get(0);
61          Assert.assertEquals("/", cookie.getPath());
62          Assert.assertTrue(cookie.containsAttribute(ClientCookie.PATH_ATTR));
63      }
64  
65      @Test
66      public void testParsePathDefault() throws Exception {
67          final CookieSpec cookiespec = new RFC2965Spec();
68          final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/path/", false);
69          // Path is OPTIONAL, defaults to the request path
70          final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1");
71          final List<Cookie> cookies = cookiespec.parse(header, origin);
72          Assert.assertNotNull(cookies);
73          Assert.assertEquals(1, cookies.size());
74          final ClientCookie cookie = (ClientCookie) cookies.get(0);
75          Assert.assertEquals("/path", cookie.getPath());
76          Assert.assertFalse(cookie.containsAttribute(ClientCookie.PATH_ATTR));
77      }
78  
79      @Test
80      public void testParseNullPath() throws Exception {
81          final CookieSpec cookiespec = new RFC2965Spec();
82          final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
83          final Header header = new BasicHeader("Set-Cookie2", "name=value;Path=;Version=1");
84          final List<Cookie> cookies = cookiespec.parse(header, origin);
85          Assert.assertNotNull(cookies);
86          Assert.assertEquals(1, cookies.size());
87          final ClientCookie cookie = (ClientCookie) cookies.get(0);
88          Assert.assertEquals("/", cookie.getPath());
89          Assert.assertTrue(cookie.containsAttribute(ClientCookie.PATH_ATTR));
90      }
91  
92      @Test
93      public void testParseBlankPath() throws Exception {
94          final CookieSpec cookiespec = new RFC2965Spec();
95          final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
96          final Header header = new BasicHeader("Set-Cookie2", "name=value;Path=\"   \";Version=1");
97          final List<Cookie> cookies = cookiespec.parse(header, origin);
98          Assert.assertNotNull(cookies);
99          Assert.assertEquals(1, cookies.size());
100         final ClientCookie cookie = (ClientCookie) cookies.get(0);
101         Assert.assertEquals("/", cookie.getPath());
102         Assert.assertTrue(cookie.containsAttribute(ClientCookie.PATH_ATTR));
103     }
104 
105     /**
106      * Test parsing cookie {@code "Domain"} attribute.
107      */
108     @Test
109     public void testParseDomain() throws Exception {
110         final CookieSpec cookiespec = new RFC2965Spec();
111         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
112         Header header = new BasicHeader("Set-Cookie2", "name=value;Domain=.domain.com;Version=1;Domain=");
113         List<Cookie> cookies = cookiespec.parse(header, origin);
114         Assert.assertNotNull(cookies);
115         Assert.assertEquals(1, cookies.size());
116         // only the first occurrence of domain attribute is considered, others ignored
117         ClientCookie cookie = (ClientCookie) cookies.get(0);
118         Assert.assertEquals(".domain.com", cookie.getDomain());
119         Assert.assertTrue(cookie.containsAttribute(ClientCookie.DOMAIN_ATTR));
120 
121         // should put a leading dot if there is no dot in front of domain
122         header = new BasicHeader("Set-Cookie2", "name=value;Domain=domain.com;Version=1");
123         cookies = cookiespec.parse(header, origin);
124         Assert.assertNotNull(cookies);
125         Assert.assertEquals(1, cookies.size());
126         cookie = (ClientCookie) cookies.get(0);
127         Assert.assertEquals(".domain.com", cookie.getDomain());
128     }
129 
130     @Test
131     public void testParseDomainDefaultValue() throws Exception {
132         final CookieSpec cookiespec = new RFC2965Spec();
133         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
134         // Domain is OPTIONAL, defaults to the request host
135         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1");
136         final List<Cookie> cookies = cookiespec.parse(header, origin);
137         Assert.assertNotNull(cookies);
138         Assert.assertEquals(1, cookies.size());
139         final ClientCookie cookie = (ClientCookie) cookies.get(0);
140         Assert.assertEquals("www.domain.com", cookie.getDomain());
141         Assert.assertFalse(cookie.containsAttribute(ClientCookie.DOMAIN_ATTR));
142     }
143 
144     @Test
145     public void testParseNullDomain() throws Exception {
146         final CookieSpec cookiespec = new RFC2965Spec();
147         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
148         // domain cannot be null
149         final Header header = new BasicHeader("Set-Cookie2", "name=value;Domain=;Version=1");
150         try {
151             cookiespec.parse(header, origin);
152             Assert.fail("MalformedCookieException should have been thrown");
153         } catch (final MalformedCookieException ex) {
154             // expected
155         }
156     }
157 
158     @Test
159     public void testParseBlankDomain() throws Exception {
160         final CookieSpec cookiespec = new RFC2965Spec();
161         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
162         final Header header = new BasicHeader("Set-Cookie2", "name=value;Domain=\"   \";Version=1");
163         try {
164             cookiespec.parse(header, origin);
165             Assert.fail("MalformedCookieException should have been thrown");
166         } catch (final MalformedCookieException ex) {
167             // expected
168         }
169     }
170 
171     /**
172      * Test parsing cookie {@code "Port"} attribute.
173      */
174     @Test
175     public void testParsePort() throws Exception {
176         final CookieSpec cookiespec = new RFC2965Spec();
177         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
178         final Header header = new BasicHeader("Set-Cookie2", "name=value;Port=\"80,800,8000\";Version=1;Port=nonsense");
179         final List<Cookie> cookies = cookiespec.parse(header, origin);
180         Assert.assertNotNull(cookies);
181         Assert.assertEquals(1, cookies.size());
182         // only the first occurrence of port attribute is considered, others ignored
183         final ClientCookie cookie = (ClientCookie) cookies.get(0);
184         final int[] ports = cookie.getPorts();
185         Assert.assertNotNull(ports);
186         Assert.assertEquals(3, ports.length);
187         Assert.assertEquals(80, ports[0]);
188         Assert.assertEquals(800, ports[1]);
189         Assert.assertEquals(8000, ports[2]);
190         Assert.assertTrue(cookie.containsAttribute(ClientCookie.PORT_ATTR));
191     }
192 
193     @Test
194     public void testParsePortDefault() throws Exception {
195         final CookieSpec cookiespec = new RFC2965Spec();
196         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
197         // Port is OPTIONAL, cookie can be accepted from any port
198         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1");
199         final List<Cookie> cookies = cookiespec.parse(header, origin);
200         Assert.assertNotNull(cookies);
201         Assert.assertEquals(1, cookies.size());
202         final ClientCookie cookie = (ClientCookie) cookies.get(0);
203         Assert.assertFalse(cookie.containsAttribute(ClientCookie.PORT_ATTR));
204     }
205 
206     @Test
207     public void testParseNullPort() throws Exception {
208         final CookieSpec cookiespec = new RFC2965Spec();
209         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
210         // null port defaults to request port
211         final Header header = new BasicHeader("Set-Cookie2", "name=value;Port=;Version=1");
212         final List<Cookie> cookies = cookiespec.parse(header, origin);
213         Assert.assertNotNull(cookies);
214         Assert.assertEquals(1, cookies.size());
215         final ClientCookie cookie = (ClientCookie) cookies.get(0);
216         final int[] ports = cookie.getPorts();
217         Assert.assertNotNull(ports);
218         Assert.assertEquals(1, ports.length);
219         Assert.assertEquals(80, ports[0]);
220         Assert.assertEquals("", cookie.getAttribute(ClientCookie.PORT_ATTR));
221     }
222 
223     @Test
224     public void testParseBlankPort() throws Exception {
225         final CookieSpec cookiespec = new RFC2965Spec();
226         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
227         // blank port defaults to request port
228         final Header header = new BasicHeader("Set-Cookie2", "name=value;Port=\"  \";Version=1");
229         final List<Cookie> cookies = cookiespec.parse(header, origin);
230         Assert.assertNotNull(cookies);
231         Assert.assertEquals(1, cookies.size());
232         final ClientCookie cookie = (ClientCookie) cookies.get(0);
233         final int[] ports = cookie.getPorts();
234         Assert.assertNotNull(ports);
235         Assert.assertEquals(1, ports.length);
236         Assert.assertEquals(80, ports[0]);
237         Assert.assertEquals("  ", cookie.getAttribute(ClientCookie.PORT_ATTR));
238     }
239 
240     @Test
241     public void testParseInvalidPort() throws Exception {
242         final CookieSpec cookiespec = new RFC2965Spec();
243         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
244         final Header header = new BasicHeader("Set-Cookie2", "name=value;Port=nonsense;Version=1");
245         try {
246             cookiespec.parse(header, origin);
247             Assert.fail("MalformedCookieException should have been thrown");
248         } catch (final MalformedCookieException ex) {
249             // expected
250         }
251     }
252 
253     @Test
254     public void testParseNegativePort() throws Exception {
255         final CookieSpec cookiespec = new RFC2965Spec();
256         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
257         final Header header = new BasicHeader("Set-Cookie2", "name=value;Port=\"80,-800,8000\";Version=1");
258         try {
259             cookiespec.parse(header, origin);
260             Assert.fail("MalformedCookieException should have been thrown");
261         } catch (final MalformedCookieException ex) {
262             // expected
263         }
264     }
265 
266     /**
267      * test parsing cookie name/value.
268      */
269     @Test
270     public void testParseNameValue() throws Exception {
271         final CookieSpec cookiespec = new RFC2965Spec();
272         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
273         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1;");
274         final List<Cookie> cookies = cookiespec.parse(header, origin);
275         Assert.assertNotNull(cookies);
276         Assert.assertEquals(1, cookies.size());
277         final ClientCookie cookie = (ClientCookie) cookies.get(0);
278         Assert.assertEquals("name", cookie.getName());
279         Assert.assertEquals("value", cookie.getValue());
280     }
281 
282     /**
283      * test parsing cookie {@code "Version"} attribute.
284      */
285     @Test
286     public void testParseVersion() throws Exception {
287         final CookieSpec cookiespec = new RFC2965Spec();
288         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
289         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1;");
290         final List<Cookie> cookies = cookiespec.parse(header, origin);
291         Assert.assertNotNull(cookies);
292         Assert.assertEquals(1, cookies.size());
293         final ClientCookie cookie = (ClientCookie) cookies.get(0);
294         Assert.assertEquals(1, cookie.getVersion());
295         Assert.assertTrue(cookie.containsAttribute(ClientCookie.VERSION_ATTR));
296     }
297 
298     @Test
299     public void testParseNullVersion() throws Exception {
300         final CookieSpec cookiespec = new RFC2965Spec();
301         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
302         // version cannot be null
303         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=;");
304         try {
305             cookiespec.parse(header, origin);
306             Assert.fail("MalformedCookieException should have been thrown");
307         } catch (final MalformedCookieException ex) {
308             // expected
309         }
310     }
311 
312     @Test
313     public void testParseNegativeVersion() throws Exception {
314         final CookieSpec cookiespec = new RFC2965Spec();
315         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
316         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=-1;");
317         try {
318             cookiespec.parse(header, origin);
319             Assert.fail("MalformedCookieException should have been thrown");
320         } catch (final MalformedCookieException ex) {
321             // expected
322         }
323     }
324     /**
325      * test parsing cookie {@code "Max-age"} attribute.
326      */
327     @Test
328     public void testParseMaxage() throws Exception {
329         final CookieSpec cookiespec = new RFC2965Spec();
330         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
331         final Header header = new BasicHeader("Set-Cookie2", "name=value;Max-age=3600;Version=1;Max-age=nonsense");
332         final List<Cookie> cookies = cookiespec.parse(header, origin);
333         Assert.assertNotNull(cookies);
334         Assert.assertEquals(1, cookies.size());
335         // only the first occurence of max-age attribute is considered, others ignored
336         final ClientCookie cookie = (ClientCookie) cookies.get(0);
337         Assert.assertFalse(cookie.isExpired(new Date()));
338     }
339 
340     @Test
341     public void testParseMaxageDefault() throws Exception {
342         final CookieSpec cookiespec = new RFC2965Spec();
343         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
344         // Max-age is OPTIONAL, defaults to session cookie
345         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1");
346         final List<Cookie> cookies = cookiespec.parse(header, origin);
347         Assert.assertNotNull(cookies);
348         Assert.assertEquals(1, cookies.size());
349         final ClientCookie cookie = (ClientCookie) cookies.get(0);
350         Assert.assertFalse(cookie.isPersistent());
351     }
352 
353     @Test
354     public void testParseNullMaxage() throws Exception {
355         final CookieSpec cookiespec = new RFC2965Spec();
356         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
357         final Header header = new BasicHeader("Set-Cookie2", "name=value;Max-age=;Version=1");
358         try {
359             cookiespec.parse(header, origin);
360             Assert.fail("MalformedCookieException should have been thrown");
361         } catch (final MalformedCookieException ex) {
362             // expected
363         }
364     }
365 
366     @Test
367     public void testParseNegativeMaxage() throws Exception {
368         final CookieSpec cookiespec = new RFC2965Spec();
369         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
370         final Header header = new BasicHeader("Set-Cookie2", "name=value;Max-age=-3600;Version=1;");
371         try {
372             cookiespec.parse(header, origin);
373             Assert.fail("MalformedCookieException should have been thrown");
374         } catch (final MalformedCookieException ex) {
375             // expected
376         }
377     }
378 
379     /**
380      * test parsing {@code "Secure"} attribute.
381      */
382     @Test
383     public void testParseSecure() throws Exception {
384         final CookieSpec cookiespec = new RFC2965Spec();
385         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
386         final Header header = new BasicHeader("Set-Cookie2", "name=value;Secure;Version=1");
387         final List<Cookie> cookies = cookiespec.parse(header, origin);
388         Assert.assertNotNull(cookies);
389         Assert.assertEquals(1, cookies.size());
390         final ClientCookie cookie = (ClientCookie) cookies.get(0);
391         Assert.assertTrue(cookie.isSecure());
392     }
393 
394     /**
395      * test parsing {@code "Discard"} attribute.
396      */
397     @Test
398     public void testParseDiscard() throws Exception {
399         final CookieSpec cookiespec = new RFC2965Spec();
400         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
401         Header header = new BasicHeader("Set-Cookie2", "name=value;Discard;Max-age=36000;Version=1");
402         List<Cookie> cookies = cookiespec.parse(header, origin);
403         Assert.assertNotNull(cookies);
404         Assert.assertEquals(1, cookies.size());
405         ClientCookie cookie = (ClientCookie) cookies.get(0);
406         // discard overrides max-age
407         Assert.assertFalse(cookie.isPersistent());
408 
409         // Discard is OPTIONAL, default behavior is dictated by max-age
410         header = new BasicHeader("Set-Cookie2", "name=value;Max-age=36000;Version=1");
411         cookies = cookiespec.parse(header, origin);
412         Assert.assertNotNull(cookies);
413         Assert.assertEquals(1, cookies.size());
414         cookie = (ClientCookie) cookies.get(0);
415         Assert.assertTrue(cookie.isPersistent());
416     }
417 
418     /**
419      * test parsing {@code "Comment"}, {@code "CommentURL"} and
420      * {@code "Secure"} attributes.
421      */
422     @Test
423     public void testParseOtherAttributes() throws Exception {
424         final CookieSpec cookiespec = new RFC2965Spec();
425         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
426         Header header = new BasicHeader("Set-Cookie2", "name=value;Comment=\"good cookie\";" +
427                 "CommentURL=\"www.domain.com/goodcookie/\";Secure;Version=1");
428         List<Cookie> cookies = cookiespec.parse(header, origin);
429         Assert.assertNotNull(cookies);
430         Assert.assertEquals(1, cookies.size());
431         ClientCookie cookie = (ClientCookie) cookies.get(0);
432         Assert.assertEquals("good cookie", cookie.getComment());
433         Assert.assertEquals("www.domain.com/goodcookie/", cookie.getCommentURL());
434         Assert.assertTrue(cookie.isSecure());
435 
436         // Comment, CommentURL, Secure are OPTIONAL
437         header = new BasicHeader("Set-Cookie2", "name=value;Version=1");
438         cookies = cookiespec.parse(header, origin);
439         Assert.assertNotNull(cookies);
440         Assert.assertEquals(1, cookies.size());
441         cookie = (ClientCookie) cookies.get(0);
442         Assert.assertFalse(cookie.isSecure());
443     }
444 
445     /**
446      * Test parsing header with 2 cookies (separated by comma)
447      */
448     @Test
449     public void testCookiesWithComma() throws Exception {
450         final CookieSpec cookiespec = new RFC2965Spec();
451         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
452         final Header header = new BasicHeader("Set-Cookie2", "a=b,c");
453         final List<Cookie> cookies = cookiespec.parse(header, origin);
454         Assert.assertNotNull(cookies);
455         Assert.assertEquals(2, cookies.size());
456         Assert.assertEquals("a", cookies.get(0).getName());
457         Assert.assertEquals("b", cookies.get(0).getValue());
458         Assert.assertEquals("c", cookies.get(1).getName());
459         Assert.assertEquals(null, cookies.get(1).getValue());
460     }
461 
462     // ------------------------------------------------------- Test Cookie Validation
463 
464     /**
465      * Test {@code Domain} validation when domain is not specified
466      * in {@code Set-Cookie2} header.
467      */
468     @Test
469     public void testValidateNoDomain() throws Exception {
470         final CookieSpec cookiespec = new RFC2965Spec();
471         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
472         final Header header = new BasicHeader("Set-Cookie2", "name=value;Version=1");
473         final List<Cookie> cookies = cookiespec.parse(header, origin);
474         for (int i = 0; i < cookies.size(); i++) {
475             cookiespec.validate(cookies.get(i), origin);
476         }
477         Assert.assertNotNull(cookies);
478         Assert.assertEquals(1, cookies.size());
479         final ClientCookie cookie = (ClientCookie) cookies.get(0);
480         // cookie domain must string match request host
481         Assert.assertEquals("www.domain.com", cookie.getDomain());
482     }
483 
484     /**
485      * Test {@code Domain} validation. Cookie domain attribute must have a
486      * leading dot.
487      */
488     @Test
489     public void testValidateDomainLeadingDot() throws Exception {
490         final CookieSpec cookiespec = new RFC2965Spec();
491         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
492         final Header header = new BasicHeader("Set-Cookie2", "name=value;Domain=domain.com;Version=1");
493         final List<Cookie> cookies = cookiespec.parse(header, origin);
494         for (int i = 0; i < cookies.size(); i++) {
495             cookiespec.validate(cookies.get(i), origin);
496         }
497         Assert.assertNotNull(cookies);
498         Assert.assertEquals(1, cookies.size());
499         final ClientCookie cookie = (ClientCookie) cookies.get(0);
500         Assert.assertEquals(".domain.com", cookie.getDomain());
501     }
502 
503     /**
504      * Test {@code Domain} validation. Domain must have at least one embedded dot.
505      */
506     @Test
507     public void testValidateDomainEmbeddedDot() throws Exception {
508         final CookieSpec cookiespec = new RFC2965Spec();
509         CookieOrigin origin = new CookieOrigin("b.com", 80, "/", false);
510         Header header = new BasicHeader("Set-Cookie2", "name=value; domain=.com; version=1");
511         try {
512             final List<Cookie> cookies = cookiespec.parse(header, origin);
513             for (int i = 0; i < cookies.size(); i++) {
514                 cookiespec.validate(cookies.get(i), origin);
515             }
516             Assert.fail("MalformedCookieException should have been thrown");
517         } catch (final MalformedCookieException expected) {}
518 
519         origin = new CookieOrigin("www.domain.com", 80, "/", false);
520         header = new BasicHeader("Set-Cookie2", "name=value;Domain=domain.com;Version=1");
521         final List<Cookie> cookies = cookiespec.parse(header, origin);
522         for (int i = 0; i < cookies.size(); i++) {
523             cookiespec.validate(cookies.get(i), origin);
524         }
525         Assert.assertNotNull(cookies);
526         Assert.assertEquals(1, cookies.size());
527     }
528 
529     /**
530      * Test local {@code Domain} validation. Simple host names
531      * (without any dots) are valid only when cookie domain is specified
532      * as ".local".
533      */
534     @Test
535     public void testValidateDomainLocal() throws Exception {
536         final CookieSpec cookiespec = new RFC2965Spec();
537         final CookieOrigin origin = new CookieOrigin("simplehost", 80, "/", false);
538         // when domain is specified as .local, simple host names are valid
539         Header header = new BasicHeader("Set-Cookie2", "name=value; domain=.local; version=1");
540         List<Cookie> cookies = cookiespec.parse(header, origin);
541         for (int i = 0; i < cookies.size(); i++) {
542             cookiespec.validate(cookies.get(i), origin);
543         }
544         Assert.assertNotNull(cookies);
545         Assert.assertEquals(1, cookies.size());
546         final ClientCookie cookie = (ClientCookie) cookies.get(0);
547         Assert.assertEquals(".local", cookie.getDomain());
548 
549         // when domain is NOT specified as .local, simple host names are invalid
550         header = new BasicHeader("Set-Cookie2", "name=value; domain=domain.com; version=1");
551         try {
552             // since domain is not .local, this must Assert.fail
553             cookies = cookiespec.parse(header, origin);
554             for (int i = 0; i < cookies.size(); i++) {
555                 cookiespec.validate(cookies.get(i), origin);
556             }
557             Assert.fail("MalformedCookieException should have been thrown");
558         } catch (final MalformedCookieException expected) {}
559     }
560 
561     @Test
562     public void testValidateDomainLocalhost() throws Exception {
563         final CookieSpec cookiespec = new RFC2965Spec();
564         final CookieOrigin origin = new CookieOrigin("localhost", 80, "/", false);
565         final Header header = new BasicHeader("Set-Cookie2", "name=value; version=1");
566         final List<Cookie> cookies = cookiespec.parse(header, origin);
567         for (int i = 0; i < cookies.size(); i++) {
568             cookiespec.validate(cookies.get(i), origin);
569         }
570         Assert.assertNotNull(cookies);
571         Assert.assertEquals(1, cookies.size());
572         final ClientCookie cookie = (ClientCookie) cookies.get(0);
573         Assert.assertEquals("localhost.local", cookie.getDomain());
574     }
575 
576     /**
577      * Test {@code Domain} validation. Effective host name
578      * must domain-match domain attribute.
579      */
580     @Test
581     public void testValidateDomainEffectiveHost() throws Exception {
582         final CookieSpec cookiespec = new RFC2965Spec();
583 
584         // cookie domain does not domain-match request host
585         Header header = new BasicHeader("Set-Cookie2", "name=value; domain=.domain.com; version=1");
586         try {
587             final CookieOrigin origin = new CookieOrigin("www.domain.org", 80, "/", false);
588             final List<Cookie> cookies = cookiespec.parse(header, origin);
589             for (int i = 0; i < cookies.size(); i++) {
590                 cookiespec.validate(cookies.get(i), origin);
591             }
592             Assert.fail("MalformedCookieException should have been thrown");
593         } catch (final MalformedCookieException expected) {}
594 
595         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
596         // cookie domain domain-matches request host
597         header = new BasicHeader("Set-Cookie2", "name=value; domain=.domain.com; version=1");
598         final List<Cookie> cookies = cookiespec.parse(header, origin);
599         for (int i = 0; i < cookies.size(); i++) {
600             cookiespec.validate(cookies.get(i), origin);
601         }
602         Assert.assertNotNull(cookies);
603         Assert.assertEquals(1, cookies.size());
604     }
605 
606     /**
607      * Test local {@code Domain} validation.
608      * Effective host name minus domain must not contain any dots.
609      */
610     @Test
611     public void testValidateDomainIllegal() throws Exception {
612         final CookieSpec cookiespec = new RFC2965Spec();
613         final CookieOrigin origin = new CookieOrigin("a.b.domain.com", 80, "/", false);
614         final Header header = new BasicHeader("Set-Cookie2", "name=value; domain=.domain.com; version=1");
615         try {
616             final List<Cookie> cookies = cookiespec.parse(header, origin);
617             for (int i = 0; i < cookies.size(); i++) {
618                 cookiespec.validate(cookies.get(i), origin);
619             }
620             Assert.fail("MalformedCookieException should have been thrown");
621         } catch (final MalformedCookieException expected) {}
622     }
623 
624     /**
625      * Test cookie {@code Path} validation. Cookie path attribute must path-match
626      * request path.
627      */
628     @Test
629     public void testValidatePath() throws Exception {
630         final CookieSpec cookiespec = new RFC2965Spec();
631         Header header = new BasicHeader("Set-Cookie2", "name=value;path=/path;version=1");
632         try {
633             final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
634             final List<Cookie> cookies = cookiespec.parse(header, origin);
635             for (int i = 0; i < cookies.size(); i++) {
636                 cookiespec.validate(cookies.get(i), origin);
637             }
638             Assert.fail("MalformedCookieException exception should have been thrown");
639         } catch (final MalformedCookieException expected) {}
640 
641         // path-matching is case-sensitive
642         header = new BasicHeader("Set-Cookie2", "name=value;path=/Path;version=1");
643         try {
644             final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/path", false);
645             final List<Cookie> cookies = cookiespec.parse(header, origin);
646             for (int i = 0; i < cookies.size(); i++) {
647                 cookiespec.validate(cookies.get(i), origin);
648             }
649             Assert.fail("MalformedCookieException exception should have been thrown");
650         } catch (final MalformedCookieException expected) {}
651 
652         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/path/path1", false);
653         header = new BasicHeader("Set-Cookie2", "name=value;path=/path;version=1");
654         final List<Cookie> cookies = cookiespec.parse(header, origin);
655         for (int i = 0; i < cookies.size(); i++) {
656             cookiespec.validate(cookies.get(i), origin);
657         }
658         Assert.assertNotNull(cookies);
659         Assert.assertEquals(1, cookies.size());
660         Assert.assertEquals("/path", cookies.get(0).getPath());
661     }
662 
663     /**
664      * Test cookie name validation.
665      */
666     @Test
667     public void testValidateCookieName() throws Exception {
668         final CookieSpec cookiespec = new RFC2965Spec();
669         final CookieOrigin origin = new CookieOrigin("127.0.0.1", 80, "/", false);
670         // cookie name must not contain blanks
671         Header header = new BasicHeader("Set-Cookie2", "invalid name=value; version=1");
672         try {
673             final List<Cookie> cookies = cookiespec.parse(header, origin);
674             for (int i = 0; i < cookies.size(); i++) {
675                 cookiespec.validate(cookies.get(i), origin);
676             }
677             Assert.fail("MalformedCookieException exception should have been thrown");
678         } catch (final MalformedCookieException expected) {}
679 
680         // cookie name must not start with '$'.
681         header = new BasicHeader("Set-Cookie2", "$invalid_name=value; version=1");
682         try {
683             final List<Cookie> cookies = cookiespec.parse(header, origin);
684             for (int i = 0; i < cookies.size(); i++) {
685                 cookiespec.validate(cookies.get(i), origin);
686             }
687             Assert.fail("MalformedCookieException exception should have been thrown");
688         } catch (final MalformedCookieException expected) {}
689 
690         // valid name
691         header = new BasicHeader("Set-Cookie2", "name=value; version=1");
692         final List<Cookie> cookies = cookiespec.parse(header, origin);
693         Assert.assertNotNull(cookies);
694         Assert.assertEquals(1, cookies.size());
695         final ClientCookie cookie = (ClientCookie) cookies.get(0);
696         Assert.assertEquals("name", cookie.getName());
697         Assert.assertEquals("value", cookie.getValue());
698     }
699 
700     /**
701      * Test cookie {@code Port} validation. Request port must be in the
702      * port attribute list.
703      */
704     @Test
705     public void testValidatePort() throws Exception {
706         final Header header = new BasicHeader("Set-Cookie2", "name=value; Port=\"80,800\"; version=1");
707         final CookieSpec cookiespec = new RFC2965Spec();
708         try {
709             final CookieOrigin origin = new CookieOrigin("www.domain.com", 8000, "/", false);
710             final List<Cookie> cookies = cookiespec.parse(header, origin);
711             for (int i = 0; i < cookies.size(); i++) {
712                 cookiespec.validate(cookies.get(i), origin);
713             }
714             Assert.fail("MalformedCookieException should have been thrown");
715         } catch (final MalformedCookieException e) {}
716 
717         // valid port list
718         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
719         final List<Cookie> cookies = cookiespec.parse(header, origin);
720         for (int i = 0; i < cookies.size(); i++) {
721             cookiespec.validate(cookies.get(i), origin);
722         }
723         Assert.assertNotNull(cookies);
724         Assert.assertEquals(1, cookies.size());
725         final ClientCookie cookie = (ClientCookie) cookies.get(0);
726         final int[] ports = cookie.getPorts();
727         Assert.assertNotNull(ports);
728         Assert.assertEquals(2, ports.length);
729         Assert.assertEquals(80, ports[0]);
730         Assert.assertEquals(800, ports[1]);
731     }
732 
733     /**
734      * Test cookie {@code Version} validation.
735      */
736     @Test
737     public void testValidateVersion() throws Exception {
738         final CookieSpec cookiespec = new RFC2965Spec();
739         // version attribute is REQUIRED
740         final Header header = new BasicHeader("Set-Cookie2", "name=value");
741         try {
742             final CookieOrigin origin = new CookieOrigin("www.domain.com", 8000, "/", false);
743             final List<Cookie> cookies = cookiespec.parse(header, origin);
744             for (int i = 0; i < cookies.size(); i++) {
745                 cookiespec.validate(cookies.get(i), origin);
746             }
747             Assert.fail("MalformedCookieException should have been thrown");
748         } catch (final MalformedCookieException e) {}
749     }
750 
751     // ------------------------------------------------------- Test Cookie Matching
752 
753     /**
754      * test cookie {@code Path} matching. Cookie path attribute must path-match
755      * path of the request URI.
756      */
757     @Test
758     public void testMatchPath() throws Exception {
759         final BasicClientCookie2 cookie = new BasicClientCookie2("name", "value");
760         cookie.setDomain(".domain.com");
761         cookie.setPath("/path");
762         cookie.setPorts(new int[] {80});
763         final CookieSpec cookiespec = new RFC2965Spec();
764         final CookieOrigin origin1 = new CookieOrigin("www.domain.com", 80, "/", false);
765         Assert.assertFalse(cookiespec.match(cookie, origin1));
766         final CookieOrigin origin2 = new CookieOrigin("www.domain.com", 80, "/path/path1", false);
767         Assert.assertTrue(cookiespec.match(cookie, origin2));
768     }
769 
770     /**
771      * test cookie {@code Domain} matching.
772      */
773     @Test
774     public void testMatchDomain() throws Exception {
775         final BasicClientCookie2 cookie = new BasicClientCookie2("name", "value");
776         cookie.setDomain(".domain.com");
777         cookie.setPath("/");
778         cookie.setPorts(new int[] {80});
779         final CookieSpec cookiespec = new RFC2965Spec();
780         // effective host name minus domain must not contain any dots
781         final CookieOrigin origin1 = new CookieOrigin("a.b.domain.com" /* request host */, 80, "/", false);
782         Assert.assertFalse(cookiespec.match(cookie, origin1));
783         // The effective host name MUST domain-match the Domain
784         // attribute of the cookie.
785         final CookieOrigin origin2 = new CookieOrigin("www.domain.org" /* request host */, 80, "/", false);
786         Assert.assertFalse(cookiespec.match(cookie, origin2));
787         final CookieOrigin origin3 = new CookieOrigin("www.domain.com" /* request host */, 80, "/", false);
788         Assert.assertTrue(cookiespec.match(cookie, origin3));
789     }
790 
791     /**
792      * test cookie local {@code Domain} matching.
793      */
794     @Test
795     public void testMatchDomainLocal() throws Exception {
796         final BasicClientCookie2 cookie = new BasicClientCookie2("name", "value");
797         cookie.setDomain(".local");
798         cookie.setPath("/");
799         cookie.setPorts(new int[] {80});
800         final CookieSpec cookiespec = new RFC2965Spec();
801         final CookieOrigin origin1 = new CookieOrigin("host" /* request host */, 80, "/", false);
802         Assert.assertTrue(cookiespec.match(cookie, origin1));
803         final CookieOrigin origin2 = new CookieOrigin("host.com" /* request host */, 80, "/", false);
804         Assert.assertFalse(cookiespec.match(cookie, origin2));
805     }
806 
807     /**
808      * test cookie {@code Port} matching.
809      */
810     @Test
811     public void testMatchPort() throws Exception {
812         // cookie can be sent to any port if port attribute not specified
813         BasicClientCookie2 cookie = new BasicClientCookie2("name", "value");
814         cookie.setDomain(".domain.com");
815         cookie.setPath("/");
816         cookie.setPorts(null);
817 
818         final CookieSpec cookiespec = new RFC2965Spec();
819         final CookieOrigin origin1 = new CookieOrigin("www.domain.com", 8080 /* request port */, "/", false);
820         Assert.assertTrue(cookiespec.match(cookie, origin1));
821         final CookieOrigin origin2 = new CookieOrigin("www.domain.com", 323 /* request port */, "/", false);
822         Assert.assertTrue(cookiespec.match(cookie, origin2));
823 
824         // otherwise, request port must be in cookie's port list
825         cookie = new BasicClientCookie2("name", "value");
826         cookie.setDomain(".domain.com");
827         cookie.setPath("/");
828         cookie.setPorts(new int[] {80, 8080});
829         cookie.setAttribute(ClientCookie.PORT_ATTR, "80, 8080");
830         final CookieOrigin origin3 = new CookieOrigin("www.domain.com", 434 /* request port */, "/", false);
831         Assert.assertFalse(cookiespec.match(cookie, origin3));
832         final CookieOrigin origin4 = new CookieOrigin("www.domain.com", 8080 /* request port */, "/", false);
833         Assert.assertTrue(cookiespec.match(cookie, origin4));
834     }
835 
836     /**
837      * test cookie expiration.
838      */
839     @Test
840     public void testCookieExpiration() throws Exception {
841         final Date now = new Date();
842 
843         final Date beforeOneHour = new Date(now.getTime() - 3600 * 1000L);
844         BasicClientCookie2 cookie = new BasicClientCookie2("name", "value");
845         cookie.setDomain(".domain.com");
846         cookie.setPath("/");
847         cookie.setPorts(null);
848         cookie.setExpiryDate(beforeOneHour);
849 
850         Assert.assertTrue(cookie.isExpired(now));
851 
852         final Date afterOneHour = new Date(now.getTime() + 3600 * 1000L);
853         cookie = new BasicClientCookie2("name", "value");
854         cookie.setDomain(".domain.com");
855         cookie.setPath("/");
856         cookie.setPorts(null);
857         cookie.setExpiryDate(afterOneHour);
858 
859         Assert.assertFalse(cookie.isExpired(now));
860 
861         // discard attributes overrides cookie age, makes it a session cookie.
862         cookie.setDiscard(true);
863         Assert.assertFalse(cookie.isPersistent());
864         Assert.assertTrue(cookie.isExpired(now));
865     }
866 
867     /**
868      * test cookie {@code Secure} attribute.
869      */
870     @Test
871     public void testCookieSecure() throws Exception {
872         final CookieSpec cookiespec = new RFC2965Spec();
873         // secure cookie can only be sent over a secure connection
874         final BasicClientCookie2 cookie = new BasicClientCookie2("name", "value");
875         cookie.setDomain(".domain.com");
876         cookie.setPath("/");
877         cookie.setSecure(true);
878         final CookieOrigin origin1 = new CookieOrigin("www.domain.com", 80, "/", false);
879         Assert.assertFalse(cookiespec.match(cookie, origin1));
880         final CookieOrigin origin2 = new CookieOrigin("www.domain.com", 80, "/", true);
881         Assert.assertTrue(cookiespec.match(cookie, origin2));
882     }
883 
884     // ------------------------------------------------------- Test Cookie Formatting
885 
886     /**
887      * Tests RFC 2965 compliant cookie formatting.
888      */
889     @Test
890     public void testRFC2965CookieFormatting() throws Exception {
891         final CookieSpec cookiespec = new RFC2965Spec(null, true);
892         final BasicClientCookie2 cookie1 = new BasicClientCookie2("name1", "value");
893         cookie1.setDomain(".domain.com");
894         cookie1.setPath("/");
895         cookie1.setPorts(new int[] {80,8080});
896         cookie1.setVersion(1);
897         // domain, path, port specified
898         cookie1.setAttribute(ClientCookie.DOMAIN_ATTR, ".domain.com");
899         cookie1.setAttribute(ClientCookie.PATH_ATTR, "/");
900         cookie1.setAttribute(ClientCookie.PORT_ATTR, "80,8080");
901 
902         List<Cookie> cookies = new ArrayList<Cookie>();
903         cookies.add(cookie1);
904         List<Header> headers = cookiespec.formatCookies(cookies);
905         Assert.assertNotNull(headers);
906         Assert.assertEquals(1, headers.size());
907         Assert.assertEquals("$Version=1; name1=\"value\"; $Path=\"/\"; $Domain=\".domain.com\"; $Port=\"80,8080\"",
908                 headers.get(0).getValue());
909 
910 
911         final BasicClientCookie2 cookie2 = new BasicClientCookie2("name2", "value");
912         cookie2.setDomain(".domain.com");
913         cookie2.setPath("/a/");
914         cookie2.setPorts(new int[] {80,8080});
915         cookie2.setVersion(2);
916         // domain, path specified  but port unspecified
917         cookie2.setAttribute(ClientCookie.DOMAIN_ATTR, ".domain.com");
918         cookie2.setAttribute(ClientCookie.PATH_ATTR, "/a/");
919 
920         cookies = new ArrayList<Cookie>();
921         cookies.add(cookie2);
922         headers = cookiespec.formatCookies(cookies);
923         Assert.assertNotNull(headers);
924         Assert.assertEquals(1, headers.size());
925         Assert.assertEquals("$Version=2; name2=\"value\"; $Path=\"/a/\"; $Domain=\".domain.com\"",
926                 headers.get(0).getValue());
927 
928         final BasicClientCookie2 cookie3 = new BasicClientCookie2("name3", "value");
929         cookie3.setDomain(".domain.com");
930         cookie3.setPath("/a/b/");
931         cookie3.setPorts(new int[] {80,8080});
932         cookie3.setVersion(1);
933         // path specified, port specified but blank, domain unspecified
934         cookie3.setAttribute(ClientCookie.PATH_ATTR, "/a/b/");
935         cookie3.setAttribute(ClientCookie.PORT_ATTR, "  ");
936 
937         cookies = new ArrayList<Cookie>();
938         cookies.add(cookie3);
939         headers = cookiespec.formatCookies(cookies);
940         Assert.assertNotNull(headers);
941         Assert.assertEquals(1, headers.size());
942         Assert.assertEquals("$Version=1; name3=\"value\"; $Path=\"/a/b/\"; $Port=\"\"",
943                 headers.get(0).getValue());
944 
945         cookies = new ArrayList<Cookie>();
946         cookies.add(cookie3);
947         cookies.add(cookie2);
948         cookies.add(cookie1);
949         headers = cookiespec.formatCookies(cookies);
950         Assert.assertNotNull(headers);
951         Assert.assertEquals(1, headers.size());
952         Assert.assertEquals("$Version=1; " +
953                 "name3=\"value\"; $Path=\"/a/b/\"; $Port=\"\"; " +
954                 "name2=\"value\"; $Path=\"/a/\"; $Domain=\".domain.com\"; " +
955                 "name1=\"value\"; $Path=\"/\"; $Domain=\".domain.com\"; $Port=\"80,8080\"",
956                 headers.get(0).getValue());
957     }
958 
959     /**
960      * Tests RFC 2965 compliant cookies formatting.
961      */
962     @Test
963     public void testRFC2965CookiesFormatting() throws Exception {
964         final CookieSpec cookiespec = new RFC2965Spec(null, true);
965         final BasicClientCookie2 cookie1 = new BasicClientCookie2("name1", "value1");
966         cookie1.setDomain(".domain.com");
967         cookie1.setPath("/");
968         cookie1.setPorts(new int[] {80,8080});
969         cookie1.setVersion(1);
970         // domain, path, port specified
971         cookie1.setAttribute(ClientCookie.DOMAIN_ATTR, ".domain.com");
972         cookie1.setAttribute(ClientCookie.PATH_ATTR, "/");
973         cookie1.setAttribute(ClientCookie.PORT_ATTR, "80,8080");
974 
975         final BasicClientCookie2 cookie2 = new BasicClientCookie2("name2", "");
976         cookie2.setDomain(".domain.com");
977         cookie2.setPath("/");
978         cookie2.setPorts(new int[] {80,8080});
979         cookie2.setVersion(1);
980         // value null, domain, path specified
981         cookie2.setAttribute(ClientCookie.DOMAIN_ATTR, ".domain.com");
982         cookie2.setAttribute(ClientCookie.PATH_ATTR, "/");
983 
984         final List<Cookie> cookies = new ArrayList<Cookie>();
985         cookies.add(cookie1);
986         cookies.add(cookie2);
987         final List<Header> headers = cookiespec.formatCookies(cookies);
988         Assert.assertNotNull(headers);
989         Assert.assertEquals(1, headers.size());
990 
991         Assert.assertEquals("$Version=1; name1=\"value1\"; $Path=\"/\"; $Domain=\".domain.com\"; $Port=\"80,8080\"; " +
992             "name2=\"\"; $Path=\"/\"; $Domain=\".domain.com\"",
993             headers.get(0).getValue());
994     }
995 
996     // ------------------------------------------------------- Backward compatibility tests
997 
998     /**
999      * Test rejection of {@code Set-Cookie} header.
1000      */
1001     @Test
1002     public void testRejectSetCookie() throws Exception {
1003         final CookieSpec cookiespec = new RFC2965Spec();
1004         final CookieOrigin origin = new CookieOrigin("www.domain.com", 80, "/", false);
1005         final Header header = new BasicHeader("Set-Cookie", "name=value; domain=.domain.com; version=1");
1006         try {
1007             cookiespec.parse(header, origin);
1008         } catch (final MalformedCookieException ex) {
1009             // expected
1010         }
1011     }
1012 
1013 }
1014