View Javadoc

1   /*
2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/Cookie.java,v 1.44 2004/06/05 16:49:20 olegk Exp $
3    * $Revision: 5562 $
4    * $Date: 2007-11-16 00:53:10 +0000 (Fri, 16 Nov 2007) $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   */
29  
30  package org.apache.commons.httpclient;
31  
32  import java.io.Serializable;
33  import java.text.RuleBasedCollator;
34  import java.util.Comparator;
35  import java.util.Date;
36  import java.util.Locale;
37  
38  import org.apache.commons.httpclient.cookie.CookiePolicy;
39  import org.apache.commons.httpclient.cookie.CookieSpec;
40  import org.apache.commons.httpclient.util.LangUtils;
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  
44  /***
45   * <p>
46   * HTTP "magic-cookie" represents a piece of state information
47   * that the HTTP agent and the target server can exchange to maintain 
48   * a session.
49   * </p>
50   * 
51   * @author B.C. Holmes
52   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
53   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
54   * @author Rod Waldhoff
55   * @author dIon Gillard
56   * @author Sean C. Sullivan
57   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
58   * @author Marc A. Saegesser
59   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
60   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
61   * 
62   * @version $Revision: 5562 $ $Date: 2007-11-16 00:53:10 +0000 (Fri, 16 Nov 2007) $
63   */
64  @SuppressWarnings("serial") // <- HERITRIX CHANGE
65  public class Cookie extends NameValuePair implements Serializable, Comparator {
66  
67      // ----------------------------------------------------------- Constructors
68  
69      /***
70       * Default constructor. Creates a blank cookie 
71       */
72  
73      public Cookie() {
74          this(null, "noname", null, null, null, false);
75      }
76  
77      /***
78       * Creates a cookie with the given name, value and domain attribute.
79       *
80       * @param name    the cookie name
81       * @param value   the cookie value
82       * @param domain  the domain this cookie can be sent to
83       */
84      public Cookie(String domain, String name, String value) {
85          this(domain, name, value, null, null, false);
86      }
87  
88      /***
89       * Creates a cookie with the given name, value, domain attribute,
90       * path attribute, expiration attribute, and secure attribute 
91       *
92       * @param name    the cookie name
93       * @param value   the cookie value
94       * @param domain  the domain this cookie can be sent to
95       * @param path    the path prefix for which this cookie can be sent
96       * @param expires the {@link Date} at which this cookie expires,
97       *                or <tt>null</tt> if the cookie expires at the end
98       *                of the session
99       * @param secure if true this cookie can only be sent over secure
100      * connections
101      * @throws IllegalArgumentException If cookie name is null or blank,
102      *   cookie name contains a blank, or cookie name starts with character $
103      *   
104      */
105     public Cookie(String domain, String name, String value, 
106         String path, Date expires, boolean secure) {
107             
108         super(name, value);
109         LOG.trace("enter Cookie(String, String, String, String, Date, boolean)");
110         if (name == null) {
111             throw new IllegalArgumentException("Cookie name may not be null");
112         }
113         if (name.trim().equals("")) {
114             throw new IllegalArgumentException("Cookie name may not be blank");
115         }
116         this.setPath(path);
117         this.setDomain(domain);
118         this.setExpiryDate(expires);
119         this.setSecure(secure);
120     }
121 
122     /***
123      * Creates a cookie with the given name, value, domain attribute,
124      * path attribute, maximum age attribute, and secure attribute 
125      *
126      * @param name   the cookie name
127      * @param value  the cookie value
128      * @param domain the domain this cookie can be sent to
129      * @param path   the path prefix for which this cookie can be sent
130      * @param maxAge the number of seconds for which this cookie is valid.
131      *               maxAge is expected to be a non-negative number. 
132      *               <tt>-1</tt> signifies that the cookie should never expire.
133      * @param secure if <tt>true</tt> this cookie can only be sent over secure
134      * connections
135      */
136     public Cookie(String domain, String name, String value, String path, 
137         int maxAge, boolean secure) {
138             
139         this(domain, name, value, path, null, secure);
140         if (maxAge < -1) {
141             throw new IllegalArgumentException("Invalid max age:  " + Integer.toString(maxAge));
142         }            
143         if (maxAge >= 0) {
144             setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
145         }
146     }
147 
148     /***
149      * Returns the comment describing the purpose of this cookie, or
150      * <tt>null</tt> if no such comment has been defined.
151      * 
152      * @return comment 
153      *
154      * @see #setComment(String)
155      */
156     public String getComment() {
157         return cookieComment;
158     }
159 
160     /***
161      * If a user agent (web browser) presents this cookie to a user, the
162      * cookie's purpose will be described using this comment.
163      * 
164      * @param comment
165      *  
166      * @see #getComment()
167      */
168     public void setComment(String comment) {
169         cookieComment = comment;
170     }
171 
172     /***
173      * Returns the expiration {@link Date} of the cookie, or <tt>null</tt>
174      * if none exists.
175      * <p><strong>Note:</strong> the object returned by this method is 
176      * considered immutable. Changing it (e.g. using setTime()) could result
177      * in undefined behaviour. Do so at your peril. </p>
178      * @return Expiration {@link Date}, or <tt>null</tt>.
179      *
180      * @see #setExpiryDate(java.util.Date)
181      *
182      */
183     public Date getExpiryDate() {
184         return cookieExpiryDate;
185     }
186 
187     /***
188      * Sets expiration date.
189      * <p><strong>Note:</strong> the object returned by this method is considered
190      * immutable. Changing it (e.g. using setTime()) could result in undefined 
191      * behaviour. Do so at your peril.</p>
192      *
193      * @param expiryDate the {@link Date} after which this cookie is no longer valid.
194      *
195      * @see #getExpiryDate
196      *
197      */
198     public void setExpiryDate (Date expiryDate) {
199         cookieExpiryDate = expiryDate;
200     }
201 
202 
203     /***
204      * Returns <tt>false</tt> if the cookie should be discarded at the end
205      * of the "session"; <tt>true</tt> otherwise.
206      *
207      * @return <tt>false</tt> if the cookie should be discarded at the end
208      *         of the "session"; <tt>true</tt> otherwise
209      */
210     public boolean isPersistent() {
211         return (null != cookieExpiryDate);
212     }
213 
214 
215     /***
216      * Returns domain attribute of the cookie.
217      * 
218      * @return the value of the domain attribute
219      *
220      * @see #setDomain(java.lang.String)
221      */
222     public String getDomain() {
223         return cookieDomain;
224     }
225 
226     /***
227      * Sets the domain attribute.
228      * 
229      * @param domain The value of the domain attribute
230      *
231      * @see #getDomain
232      */
233     public void setDomain(String domain) {
234         if (domain != null) {
235             int ndx = domain.indexOf(":");
236             if (ndx != -1) {
237               domain = domain.substring(0, ndx);
238             }
239             cookieDomain = domain.toLowerCase();
240         }
241     }
242 
243 
244     /***
245      * Returns the path attribute of the cookie
246      * 
247      * @return The value of the path attribute.
248      * 
249      * @see #setPath(java.lang.String)
250      */
251     public String getPath() {
252         return cookiePath;
253     }
254 
255     /***
256      * Sets the path attribute.
257      *
258      * @param path The value of the path attribute
259      *
260      * @see #getPath
261      *
262      */
263     public void setPath(String path) {
264         cookiePath = path;
265     }
266 
267     /***
268      * @return <code>true</code> if this cookie should only be sent over secure connections.
269      * @see #setSecure(boolean)
270      */
271     public boolean getSecure() {
272         return isSecure;
273     }
274 
275     /***
276      * Sets the secure attribute of the cookie.
277      * <p>
278      * When <tt>true</tt> the cookie should only be sent
279      * using a secure protocol (https).  This should only be set when
280      * the cookie's originating server used a secure protocol to set the
281      * cookie's value.
282      *
283      * @param secure The value of the secure attribute
284      * 
285      * @see #getSecure()
286      */
287     public void setSecure (boolean secure) {
288         isSecure = secure;
289     }
290 
291     /***
292      * Returns the version of the cookie specification to which this
293      * cookie conforms.
294      *
295      * @return the version of the cookie.
296      * 
297      * @see #setVersion(int)
298      *
299      */
300     public int getVersion() {
301         return cookieVersion;
302     }
303 
304     /***
305      * Sets the version of the cookie specification to which this
306      * cookie conforms. 
307      *
308      * @param version the version of the cookie.
309      * 
310      * @see #getVersion
311      */
312     public void setVersion(int version) {
313         cookieVersion = version;
314     }
315 
316     /***
317      * Returns true if this cookie has expired.
318      * 
319      * @return <tt>true</tt> if the cookie has expired.
320      */
321     public boolean isExpired() {
322         return (cookieExpiryDate != null  
323             && cookieExpiryDate.getTime() <= System.currentTimeMillis());
324     }
325 
326     /***
327      * Returns true if this cookie has expired according to the time passed in.
328      * 
329      * @param now The current time.
330      * 
331      * @return <tt>true</tt> if the cookie expired.
332      */
333     public boolean isExpired(Date now) {
334         return (cookieExpiryDate != null  
335             && cookieExpiryDate.getTime() <= now.getTime());
336     }
337 
338 
339     /***
340      * Indicates whether the cookie had a path specified in a 
341      * path attribute of the <tt>Set-Cookie</tt> header. This value
342      * is important for generating the <tt>Cookie</tt> header because 
343      * some cookie specifications require that the <tt>Cookie</tt> header 
344      * should only include a path attribute if the cookie's path 
345      * was specified in the <tt>Set-Cookie</tt> header.
346      *
347      * @param value <tt>true</tt> if the cookie's path was explicitly 
348      * set, <tt>false</tt> otherwise.
349      * 
350      * @see #isPathAttributeSpecified
351      */
352     public void setPathAttributeSpecified(boolean value) {
353         hasPathAttribute = value;
354     }
355 
356     /***
357      * Returns <tt>true</tt> if cookie's path was set via a path attribute
358      * in the <tt>Set-Cookie</tt> header.
359      *
360      * @return value <tt>true</tt> if the cookie's path was explicitly 
361      * set, <tt>false</tt> otherwise.
362      * 
363      * @see #setPathAttributeSpecified
364      */
365     public boolean isPathAttributeSpecified() {
366         return hasPathAttribute;
367     }
368 
369     /***
370      * Indicates whether the cookie had a domain specified in a 
371      * domain attribute of the <tt>Set-Cookie</tt> header. This value
372      * is important for generating the <tt>Cookie</tt> header because 
373      * some cookie specifications require that the <tt>Cookie</tt> header 
374      * should only include a domain attribute if the cookie's domain 
375      * was specified in the <tt>Set-Cookie</tt> header.
376      *
377      * @param value <tt>true</tt> if the cookie's domain was explicitly 
378      * set, <tt>false</tt> otherwise.
379      *
380      * @see #isDomainAttributeSpecified
381      */
382     public void setDomainAttributeSpecified(boolean value) {
383         hasDomainAttribute = value;
384     }
385 
386     /***
387      * Returns <tt>true</tt> if cookie's domain was set via a domain 
388      * attribute in the <tt>Set-Cookie</tt> header.
389      *
390      * @return value <tt>true</tt> if the cookie's domain was explicitly 
391      * set, <tt>false</tt> otherwise.
392      *
393      * @see #setDomainAttributeSpecified
394      */
395     public boolean isDomainAttributeSpecified() {
396         return hasDomainAttribute;
397     }
398 
399     /***
400      * Returns a hash code in keeping with the
401      * {@link Object#hashCode} general hashCode contract.
402      * @return A hash code
403      */
404     public int hashCode() {
405         int hash = LangUtils.HASH_SEED;
406         hash = LangUtils.hashCode(hash, this.getName());
407         hash = LangUtils.hashCode(hash, this.cookieDomain);
408         hash = LangUtils.hashCode(hash, this.cookiePath);
409         return hash;
410     }
411 
412 
413     /***
414      * Two cookies are equal if the name, path and domain match.
415      * @param obj The object to compare against.
416      * @return true if the two objects are equal.
417      */
418     public boolean equals(Object obj) {
419         if (obj == null) return false;
420         if (this == obj) return true;
421         if (obj instanceof Cookie) {
422             Cookie that = (Cookie) obj;
423             return LangUtils.equals(this.getName(), that.getName())
424                   && LangUtils.equals(this.cookieDomain, that.cookieDomain)
425                   && LangUtils.equals(this.cookiePath, that.cookiePath);
426         } else {
427             return false;
428         }
429     }
430 
431 
432     /***
433      * Return a textual representation of the cookie.
434      * 
435      * @return string.
436      */
437     public String toExternalForm() {
438         CookieSpec spec = null;
439         if (getVersion() > 0) {
440             spec = CookiePolicy.getDefaultSpec(); 
441         } else {
442             spec = CookiePolicy.getCookieSpec(CookiePolicy.NETSCAPE); 
443         }
444         return spec.formatCookie(this); 
445     }
446 
447     /***
448      * <p>Compares two cookies to determine order for cookie header.</p>
449      * <p>Most specific should be first. </p>
450      * <p>This method is implemented so a cookie can be used as a comparator for
451      * a SortedSet of cookies. Specifically it's used above in the 
452      * createCookieHeader method.</p>
453      * @param o1 The first object to be compared
454      * @param o2 The second object to be compared
455      * @return See {@link java.util.Comparator#compare(Object,Object)}
456      */
457     public int compare(Object o1, Object o2) {
458         LOG.trace("enter Cookie.compare(Object, Object)");
459 
460         if (!(o1 instanceof Cookie)) {
461             throw new ClassCastException(o1.getClass().getName());
462         }
463         if (!(o2 instanceof Cookie)) {
464             throw new ClassCastException(o2.getClass().getName());
465         }
466         Cookie c1 = (Cookie) o1;
467         Cookie c2 = (Cookie) o2;
468         if (c1.getPath() == null && c2.getPath() == null) {
469             return 0;
470         } else if (c1.getPath() == null) {
471             // null is assumed to be "/"
472             if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
473                 return 0;
474             } else {
475                 return -1;
476             }
477         } else if (c2.getPath() == null) {
478             // null is assumed to be "/"
479             if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
480                 return 0;
481             } else {
482                 return 1;
483             }
484         } else {
485             return STRING_COLLATOR.compare(c1.getPath(), c2.getPath());
486         }
487     }
488 
489     /***
490      * Return a textual representation of the cookie.
491      * 
492      * @return string.
493      * 
494      * @see #toExternalForm
495      */
496     public String toString() {
497         return toExternalForm();
498     }
499 
500 // BEGIN IA/HERITRIX ADDITION
501     /***
502      * Create a 'sort key' for this Cookie that will cause it to sort 
503      * alongside other Cookies of the same domain (with or without leading
504      * '.'). This helps cookie-match checks consider only narrow set of
505      * possible matches, rather than all cookies. 
506      * 
507      * Only two cookies that are equals() (same domain, path, name) will have
508      * the same sort key. The '\1' separator character is important in 
509      * conjunction with Cookie.DOMAIN+OVERBOUNDS, allowing keys based on the
510      * domain plus an extension to define the relevant range in a SortedMap. 
511      * @return String sort key for this cookie
512      */
513     public String getSortKey() {
514         String domain = getDomain();
515         return (domain.startsWith(".")) 
516             ? domain.substring(1) + "\1.\1" + getPath() + "\1" + getName() 
517             : domain + "\1\1" + getPath() + "\1" + getName();
518     }
519 //  END IA/HERITRIX ADDITION   
520     
521    // ----------------------------------------------------- Instance Variables
522 
523    /*** Comment attribute. */
524    private String  cookieComment;
525 
526    /*** Domain attribute. */
527    private String  cookieDomain;
528 
529    /*** Expiration {@link Date}. */
530    private Date    cookieExpiryDate;
531 
532    /*** Path attribute. */
533    private String  cookiePath;
534 
535    /*** My secure flag. */
536    private boolean isSecure;
537 
538    /***
539     * Specifies if the set-cookie header included a Path attribute for this
540     * cookie
541     */
542    private boolean hasPathAttribute = false;
543 
544    /***
545     * Specifies if the set-cookie header included a Domain attribute for this
546     * cookie
547     */
548    private boolean hasDomainAttribute = false;
549 
550    /*** The version of the cookie specification I was created from. */
551    private int     cookieVersion = 0;
552 
553    // -------------------------------------------------------------- Constants
554 
555    /*** 
556     * Collator for Cookie comparisons.  Could be replaced with references to
557     * specific Locales.
558     */
559    private static final RuleBasedCollator STRING_COLLATOR =
560         (RuleBasedCollator) RuleBasedCollator.getInstance(
561                                                 new Locale("en", "US", ""));
562 
563    /*** Log object for this class */
564    private static final Log LOG = LogFactory.getLog(Cookie.class);
565 
566 // BEGIN IA/HERITRIX ADDITION
567    /***
568     * Character which, if appended to end of a domain, will give a 
569     * boundary key that sorts past all Cookie sortKeys for the same
570     * domain. 
571     */
572    public static final char DOMAIN_OVERBOUNDS = '\2';
573 // END IA/HERITRIX ADDITION
574 }
575