View Javadoc

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