    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