Cookie.java
  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: 531354 $
  4   * $Date: 2007-04-23 08:53:20 +0200 (Mon, 23 Apr 2007) $
  5   *
  6   * ====================================================================
  7   *
  8   *  Licensed to the Apache Software Foundation (ASF) under one or more
  9   *  contributor license agreements.  See the NOTICE file distributed with
 10   *  this work for additional information regarding copyright ownership.
 11   *  The ASF licenses this file to You under the Apache License, Version 2.0
 12   *  (the "License"); you may not use this file except in compliance with
 13   *  the License.  You may obtain a copy of the License at
 14   *
 15   *      http://www.apache.org/licenses/LICENSE-2.0
 16   *
 17   *  Unless required by applicable law or agreed to in writing, software
 18   *  distributed under the License is distributed on an "AS IS" BASIS,
 19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 20   *  See the License for the specific language governing permissions and
 21   *  limitations under the License.
 22   * ====================================================================
 23   *
 24   * This software consists of voluntary contributions made by many
 25   * individuals on behalf of the Apache Software Foundation.  For more
 26   * information on the Apache Software Foundation, please see
 27   * <http://www.apache.org/>.
 28   *
 29   */
 30  
 31  package org.apache.commons.httpclient;
 32  
 33  import java.io.Serializable;
 34  import java.util.Comparator;
 35  import java.util.Date;
 36  
 37  import org.apache.commons.httpclient.cookie.CookiePolicy;
 38  import org.apache.commons.httpclient.cookie.CookieSpec;
 39  import org.apache.commons.httpclient.util.LangUtils;
 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: 531354 $ $Date: 2007-04-23 08:53:20 +0200 (Mon, 23 Apr 2007) $
 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.trim().equals("")) {
112              throw new IllegalArgumentException("Cookie name may not be blank");
113          }
114          this.setPath(path);
115          this.setDomain(domain);
116          this.setExpiryDate(expires);
117          this.setSecure(secure);
118      }
119  
120      /**
121       * Creates a cookie with the given name, value, domain attribute,
122       * path attribute, maximum age attribute, and secure attribute 
123       *
124       * @param name   the cookie name
125       * @param value  the cookie value
126       * @param domain the domain this cookie can be sent to
127       * @param path   the path prefix for which this cookie can be sent
128       * @param maxAge the number of seconds for which this cookie is valid.
129       *               maxAge is expected to be a non-negative number. 
130       *               <tt>-1</tt> signifies that the cookie should never expire.
131       * @param secure if <tt>true</tt> this cookie can only be sent over secure
132       * connections
133       */
134      public Cookie(String domain, String name, String value, String path, 
135          int maxAge, boolean secure) {
136              
137          this(domain, name, value, path, null, secure);
138          if (maxAge < -1) {
139              throw new IllegalArgumentException("Invalid max age:  " + Integer.toString(maxAge));
140          }            
141          if (maxAge >= 0) {
142              setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
143          }
144      }
145  
146      /**
147       * Returns the comment describing the purpose of this cookie, or
148       * <tt>null</tt> if no such comment has been defined.
149       * 
150       * @return comment 
151       *
152       * @see #setComment(String)
153       */
154      public String getComment() {
155          return cookieComment;
156      }
157  
158      /**
159       * If a user agent (web browser) presents this cookie to a user, the
160       * cookie's purpose will be described using this comment.
161       * 
162       * @param comment
163       *  
164       * @see #getComment()
165       */
166      public void setComment(String comment) {
167          cookieComment = comment;
168      }
169  
170      /**
171       * Returns the expiration {@link Date} of the cookie, or <tt>null</tt>
172       * if none exists.
173       * <p><strong>Note:</strong> the object returned by this method is 
174       * considered immutable. Changing it (e.g. using setTime()) could result
175       * in undefined behaviour. Do so at your peril. </p>
176       * @return Expiration {@link Date}, or <tt>null</tt>.
177       *
178       * @see #setExpiryDate(java.util.Date)
179       *
180       */
181      public Date getExpiryDate() {
182          return cookieExpiryDate;
183      }
184  
185      /**
186       * Sets expiration date.
187       * <p><strong>Note:</strong> the object returned by this method is considered
188       * immutable. Changing it (e.g. using setTime()) could result in undefined 
189       * behaviour. Do so at your peril.</p>
190       *
191       * @param expiryDate the {@link Date} after which this cookie is no longer valid.
192       *
193       * @see #getExpiryDate
194       *
195       */
196      public void setExpiryDate (Date expiryDate) {
197          cookieExpiryDate = expiryDate;
198      }
199  
200  
201      /**
202       * Returns <tt>false</tt> if the cookie should be discarded at the end
203       * of the "session"; <tt>true</tt> otherwise.
204       *
205       * @return <tt>false</tt> if the cookie should be discarded at the end
206       *         of the "session"; <tt>true</tt> otherwise
207       */
208      public boolean isPersistent() {
209          return (null != cookieExpiryDate);
210      }
211  
212  
213      /**
214       * Returns domain attribute of the cookie.
215       * 
216       * @return the value of the domain attribute
217       *
218       * @see #setDomain(java.lang.String)
219       */
220      public String getDomain() {
221          return cookieDomain;
222      }
223  
224      /**
225       * Sets the domain attribute.
226       * 
227       * @param domain The value of the domain attribute
228       *
229       * @see #getDomain
230       */
231      public void setDomain(String domain) {
232          if (domain != null) {
233              int ndx = domain.indexOf(":");
234              if (ndx != -1) {
235                domain = domain.substring(0, ndx);
236              }
237              cookieDomain = domain.toLowerCase();
238          }
239      }
240  
241  
242      /**
243       * Returns the path attribute of the cookie
244       * 
245       * @return The value of the path attribute.
246       * 
247       * @see #setPath(java.lang.String)
248       */
249      public String getPath() {
250          return cookiePath;
251      }
252  
253      /**
254       * Sets the path attribute.
255       *
256       * @param path The value of the path attribute
257       *
258       * @see #getPath
259       *
260       */
261      public void setPath(String path) {
262          cookiePath = path;
263      }
264  
265      /**
266       * @return <code>true</code> if this cookie should only be sent over secure connections.
267       * @see #setSecure(boolean)
268       */
269      public boolean getSecure() {
270          return isSecure;
271      }
272  
273      /**
274       * Sets the secure attribute of the cookie.
275       * <p>
276       * When <tt>true</tt> the cookie should only be sent
277       * using a secure protocol (https).  This should only be set when
278       * the cookie's originating server used a secure protocol to set the
279       * cookie's value.
280       *
281       * @param secure The value of the secure attribute
282       * 
283       * @see #getSecure()
284       */
285      public void setSecure (boolean secure) {
286          isSecure = secure;
287      }
288  
289      /**
290       * Returns the version of the cookie specification to which this
291       * cookie conforms.
292       *
293       * @return the version of the cookie.
294       * 
295       * @see #setVersion(int)
296       *
297       */
298      public int getVersion() {
299          return cookieVersion;
300      }
301  
302      /**
303       * Sets the version of the cookie specification to which this
304       * cookie conforms. 
305       *
306       * @param version the version of the cookie.
307       * 
308       * @see #getVersion
309       */
310      public void setVersion(int version) {
311          cookieVersion = version;
312      }
313  
314      /**
315       * Returns true if this cookie has expired.
316       * 
317       * @return <tt>true</tt> if the cookie has expired.
318       */
319      public boolean isExpired() {
320          return (cookieExpiryDate != null  
321              && cookieExpiryDate.getTime() <= System.currentTimeMillis());
322      }
323  
324      /**
325       * Returns true if this cookie has expired according to the time passed in.
326       * 
327       * @param now The current time.
328       * 
329       * @return <tt>true</tt> if the cookie expired.
330       */
331      public boolean isExpired(Date now) {
332          return (cookieExpiryDate != null  
333              && cookieExpiryDate.getTime() <= now.getTime());
334      }
335  
336  
337      /**
338       * Indicates whether the cookie had a path specified in a 
339       * path attribute of the <tt>Set-Cookie</tt> header. This value
340       * is important for generating the <tt>Cookie</tt> header because 
341       * some cookie specifications require that the <tt>Cookie</tt> header 
342       * should only include a path attribute if the cookie's path 
343       * was specified in the <tt>Set-Cookie</tt> header.
344       *
345       * @param value <tt>true</tt> if the cookie's path was explicitly 
346       * set, <tt>false</tt> otherwise.
347       * 
348       * @see #isPathAttributeSpecified
349       */
350      public void setPathAttributeSpecified(boolean value) {
351          hasPathAttribute = value;
352      }
353  
354      /**
355       * Returns <tt>true</tt> if cookie's path was set via a path attribute
356       * in the <tt>Set-Cookie</tt> header.
357       *
358       * @return value <tt>true</tt> if the cookie's path was explicitly 
359       * set, <tt>false</tt> otherwise.
360       * 
361       * @see #setPathAttributeSpecified
362       */
363      public boolean isPathAttributeSpecified() {
364          return hasPathAttribute;
365      }
366  
367      /**
368       * Indicates whether the cookie had a domain specified in a 
369       * domain attribute of the <tt>Set-Cookie</tt> header. This value
370       * is important for generating the <tt>Cookie</tt> header because 
371       * some cookie specifications require that the <tt>Cookie</tt> header 
372       * should only include a domain attribute if the cookie's domain 
373       * was specified in the <tt>Set-Cookie</tt> header.
374       *
375       * @param value <tt>true</tt> if the cookie's domain was explicitly 
376       * set, <tt>false</tt> otherwise.
377       *
378       * @see #isDomainAttributeSpecified
379       */
380      public void setDomainAttributeSpecified(boolean value) {
381          hasDomainAttribute = value;
382      }
383  
384      /**
385       * Returns <tt>true</tt> if cookie's domain was set via a domain 
386       * attribute in the <tt>Set-Cookie</tt> header.
387       *
388       * @return value <tt>true</tt> if the cookie's domain was explicitly 
389       * set, <tt>false</tt> otherwise.
390       *
391       * @see #setDomainAttributeSpecified
392       */
393      public boolean isDomainAttributeSpecified() {
394          return hasDomainAttribute;
395      }
396  
397      /**
398       * Returns a hash code in keeping with the
399       * {@link Object#hashCode} general hashCode contract.
400       * @return A hash code
401       */
402      public int hashCode() {
403          int hash = LangUtils.HASH_SEED;
404          hash = LangUtils.hashCode(hash, this.getName());
405          hash = LangUtils.hashCode(hash, this.cookieDomain);
406          hash = LangUtils.hashCode(hash, this.cookiePath);
407          return hash;
408      }
409  
410  
411      /**
412       * Two cookies are equal if the name, path and domain match.
413       * @param obj The object to compare against.
414       * @return true if the two objects are equal.
415       */
416      public boolean equals(Object obj) {
417          if (obj == null) return false;
418          if (this == obj) return true;
419          if (obj instanceof Cookie) {
420              Cookie that = (Cookie) obj;
421              return LangUtils.equals(this.getName(), that.getName())
422                    && LangUtils.equals(this.cookieDomain, that.cookieDomain)
423                    && LangUtils.equals(this.cookiePath, that.cookiePath);
424          } else {
425              return false;
426          }
427      }
428  
429  
430      /**
431       * Return a textual representation of the cookie.
432       * 
433       * @return string.
434       */
435      public String toExternalForm() {
436          CookieSpec spec = null;
437          if (getVersion() > 0) {
438              spec = CookiePolicy.getDefaultSpec(); 
439          } else {
440              spec = CookiePolicy.getCookieSpec(CookiePolicy.NETSCAPE); 
441          }
442          return spec.formatCookie(this); 
443      }
444  
445      /**
446       * <p>Compares two cookies to determine order for cookie header.</p>
447       * <p>Most specific should be first. </p>
448       * <p>This method is implemented so a cookie can be used as a comparator for
449       * a SortedSet of cookies. Specifically it's used above in the 
450       * createCookieHeader method.</p>
451       * @param o1 The first object to be compared
452       * @param o2 The second object to be compared
453       * @return See {@link java.util.Comparator#compare(Object,Object)}
454       */
455      public int compare(Object o1, Object o2) {
456          LOG.trace("enter Cookie.compare(Object, Object)");
457  
458          if (!(o1 instanceof Cookie)) {
459              throw new ClassCastException(o1.getClass().getName());
460          }
461          if (!(o2 instanceof Cookie)) {
462              throw new ClassCastException(o2.getClass().getName());
463          }
464          Cookie c1 = (Cookie) o1;
465          Cookie c2 = (Cookie) o2;
466          if (c1.getPath() == null && c2.getPath() == null) {
467              return 0;
468          } else if (c1.getPath() == null) {
469              // null is assumed to be "/"
470              if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
471                  return 0;
472              } else {
473                  return -1;
474              }
475          } else if (c2.getPath() == null) {
476              // null is assumed to be "/"
477              if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
478                  return 0;
479              } else {
480                  return 1;
481              }
482          } else {
483              return c1.getPath().compareTo(c2.getPath());
484          }
485      }
486  
487      /**
488       * Return a textual representation of the cookie.
489       * 
490       * @return string.
491       * 
492       * @see #toExternalForm
493       */
494      public String toString() {
495          return toExternalForm();
496      }
497  
498     // ----------------------------------------------------- Instance Variables
499  
500     /** Comment attribute. */
501     private String  cookieComment;
502  
503     /** Domain attribute. */
504     private String  cookieDomain;
505  
506     /** Expiration {@link Date}. */
507     private Date    cookieExpiryDate;
508  
509     /** Path attribute. */
510     private String  cookiePath;
511  
512     /** My secure flag. */
513     private boolean isSecure;
514  
515     /**
516      * Specifies if the set-cookie header included a Path attribute for this
517      * cookie
518      */
519     private boolean hasPathAttribute = false;
520  
521     /**
522      * Specifies if the set-cookie header included a Domain attribute for this
523      * cookie
524      */
525     private boolean hasDomainAttribute = false;
526  
527     /** The version of the cookie specification I was created from. */
528     private int     cookieVersion = 0;
529  
530     // -------------------------------------------------------------- Constants
531  
532     /** Log object for this class */
533     private static final Log LOG = LogFactory.getLog(Cookie.class);
534  
535  }
536