// HTMLParser Library $Name: v1_6 $ - A java-based parser for HTML
// http://sourceforge.org/projects/htmlparser
// Copyright (C) 2004 Derrick Oswald
//
// Revision Control Information
//
// $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/http/Cookie.java,v $
// $Author: derrickoswald $
// $Date: 2006/03/19 20:14:58 $
// $Revision: 1.4 $
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package org.htmlparser.http;

import java.io.Serializable;
import java.util.Date;

/**
 * A HTTP cookie.
 * This class represents a "Cookie", as used for session management with HTTP
 * and HTTPS protocols. Cookies are used to get user agents (web browsers etc)
 * to hold small amounts of state associated with a user's web browsing. Common
 * applications for cookies include storing user preferences, automating low
 * security user signon facilities, and helping collect data used for "shopping
 * cart" style applications.
 * <P>
 * Cookies are named, and have a single value. They may have optional
 * attributes, including a comment presented to the user, path and domain
 * qualifiers for which hosts see the cookie, a maximum age, and a version.
 * Current web browsers often have bugs in how they treat those attributes, so
 * interoperability can be improved by not relying on them heavily.
 * <P>
 * Cookies are assigned by servers, using fields added to HTTP response headers.
 * Cookies are passed back to those servers using fields added to HTTP request
 * headers. Several cookies with the same name can be returned;
 * they have different path attributes, but those attributes
 * will not be visible when using "old format" cookies.
 * <P>
 * Cookies affect the caching of the web pages used to set their values. At this
 * time, none of the sophisticated HTTP/1.1 cache control models are supported.
 * Standard HTTP/1.0 caches will not cache pages which contain
 * cookies created by this class.
 * <P>
 * Cookies are being standardized by the IETF. This class supports the original
 * Cookie specification (from Netscape Communications Corp.) as well as the
 * updated <a href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a>
 * specification.
 */
public class Cookie
    implements
        Cloneable,
        Serializable
{
    /**
     * Special characters to watch out for.
     * From RFC 2068, token special case characters.
     */
    private static final String SPECIALS = "()<>@,;:\\\"/[]?={} \t";

    /**
     * The name of the cookie.
     */
    protected String mName;

    /**
     * The cookie value.
     */
    protected String mValue; // value of NAME

    /**
     * Describes the cookie's use.
     */
    protected String mComment;

    /**
     * Domain that sees cookie.
     */
    protected String mDomain;

    /**
     * Cookie expires after this date.
     */
    protected Date mExpiry;

    /**
     * URLs that see the cookie.
     */
    protected String mPath;

    /**
     * Use SSL.
     */
    protected boolean mSecure;

    /**
     * If Version=1 it means RFC 2109++ style cookies.
     */
    protected int mVersion;

    /**
     * Defines a cookie with an initial name/value pair. The name must be an
     * HTTP/1.1 "token" value; alphanumeric ASCII strings work. Names starting
     * with a "$" character are reserved by RFC 2109.
     * The path for the cookie is set to the root ("/") and there is no
     * expiry time set.
     * @param name  The name of the cookie.
     * @param value The value of the cookie.
     * @exception IllegalArgumentException
     *             if the cookie name is not an HTTP/1.1 "token", or if it is
     *             one of the tokens reserved for use by the cookie protocol
     */
    public Cookie (String name, String value)
        throws
            IllegalArgumentException
    {
        if (!isToken (name) || name.equalsIgnoreCase ("Comment") // rfc2019
                || name.equalsIgnoreCase ("Discard") // 2019++
                || name.equalsIgnoreCase ("Domain")
                || name.equalsIgnoreCase ("Expires") // (old cookies)
                || name.equalsIgnoreCase ("Max-Age") // rfc2019
                || name.equalsIgnoreCase ("Path")
                || name.equalsIgnoreCase ("Secure")
                || name.equalsIgnoreCase ("Version"))
            throw new IllegalArgumentException ("invalid cookie name: " + name);
        mName = name;
        mValue = value;
        mComment = null;
        mDomain = null;
        mExpiry = null; // not persisted
        mPath = "/";
        mSecure = false;
        mVersion = 0;
    }

    /**
     * If a user agent (web browser) presents this cookie to a user, the
     * cookie's purpose will be described using this comment. This is not
     * supported by version zero cookies.
     * @param purpose The cookie comment.
     * @see #getComment
     */
    public void setComment (String purpose)
    {
        mComment = purpose;
    }

    /**
     * Returns the comment describing the purpose of this cookie, or null if no
     * such comment has been defined.
     * @see #setComment
     * @return The cookie comment, or <code>null</code> if none.
     */
    public String getComment ()
    {
        return (mComment);
    }

    /**
     * This cookie should be presented only to hosts satisfying this domain name
     * pattern. Read RFC 2109 for specific details of the syntax. Briefly, a
     * domain name name begins with a dot (".foo.com") and means that hosts in
     * that DNS zone ("www.foo.com", but not "a.b.foo.com") should see the
     * cookie. By default, cookies are only returned to the host which saved
     * them.
     * @see #getDomain
     * @param pattern The domain name pattern. The pattern is converted to
     * lower case to accommodate less capable browsers.
     */
    public void setDomain (String pattern)
    {
        mDomain = pattern.toLowerCase (); // IE allegedly needs this
    }

    /**
     * Returns the domain of this cookie.
     * @return The cookie domain (the base URL name it applies to).
     * @see #setDomain
     */
    public String getDomain ()
    {
        return (mDomain);
    }

    /**
     * Sets the expiry date of the cookie. The cookie will expire after the
     * date specified. A null value indicates the default behaviour:
     * the cookie is not stored persistently, and will be deleted when the user
     * agent (web browser) exits.
     * @param expiry The expiry date for this cookie, or <code>null</code> if
     * the cookie is persistent.
     * @see #getExpiryDate
     */
    public void setExpiryDate (Date expiry)
    {
        mExpiry = expiry;
    }

    /**
     * Returns the expiry date of the cookie. If none was specified,
     * null is returned, indicating the default behaviour described
     * with <em>setExpiryDate</em>.
     * @return The cookie expiry date, or <code>null</code> if it is persistent.
     * @see #setExpiryDate
     */
    public Date getExpiryDate ()
    {
        return (mExpiry);
    }

    /**
     * This cookie should be presented only with requests beginning with this
     * URL. Read RFC 2109 for a specification of the default behaviour.
     * Basically, URLs in the same "directory" as the one which set the cookie,
     * and in subdirectories, can all see the cookie unless a different path is
     * set.
     * @param uri The exclusion prefix for the cookie.
     * @see #getPath
     */
    public void setPath (String uri)
    {
        mPath = uri;
    }

    /**
     * Returns the prefix of all URLs for which this cookie is targetted.
     * @return The cookie path (or "/" if no specific path is specified).
     * @see #setPath
     */
    public String getPath ()
    {
        return (mPath);
    }

    /**
     * Indicates to the user agent that the cookie should only be sent using a
     * secure protocol (https). This should only be set when the cookie's
     * originating server used a secure protocol to set the cookie's value.
     * @see #getSecure
     * @param flag Use <code>true</code> if the cookie is to be sent using
     * secure protocols, <code>false</code> otherwise.
     */
    public void setSecure (boolean flag)
    {
        mSecure = flag;
    }

    /**
     * Returns the value of the 'secure' flag.
     * @return The <code>true</code> if this cookie should only be sent using
     * a secure protocol, <code>false</code> otherwise.
     * @see #setSecure
     */
    public boolean getSecure ()
    {
        return (mSecure);
    }

    /**
     * Returns the name of the cookie. This name may not be changed after the
     * cookie is created.
     * @return The name of the cookie.
     */
    public String getName ()
    {
        return (mName);
    }

    /**
     * Sets the value of the cookie. BASE64 encoding is suggested for use with
     * binary values.
     * <p>With version zero cookies, you need to be careful about the kinds of
     * values you use. Values with various special characters (whitespace,
     * brackets and parentheses, the equals sign, comma, double quote, slashes,
     * question marks, the "at" sign, colon, and semicolon) should be avoided.
     * Empty values may not behave the same way on all browsers.</p>
     * @param newValue The new value for the cookie.
     * @see #getValue
     */
    public void setValue (String newValue)
    {
        mValue = newValue;
    }

    /**
     * Returns the value of the cookie.
     * @return The cookie value.
     * @see #setValue
     */
    public String getValue ()
    {
        return (mValue);
    }

    /**
     * Returns the version of the cookie. Version 1 complies with RFC 2109,
     * version 0 indicates the original version, as specified by Netscape. Newly
     * constructed cookies use version 0 by default, to maximize
     * interoperability. Cookies provided by a user agent will identify the
     * cookie version used by the browser.
     * @see #setVersion
     * @return The cookie version.
     */
    public int getVersion ()
    {
        return (mVersion);
    }

    /**
     * Sets the version of the cookie protocol used when this cookie saves
     * itself. Since the IETF standards are still being finalized, consider
     * version 1 as experimental; do not use it (yet) on production sites.
     * @param version The version of the cookie, either 0 or 1.
     * @see #getVersion
     */
    public void setVersion (int version)
    {
        mVersion = version;
    }

    /**
     * Return true iff the string counts as an HTTP/1.1 "token".
     * Valid tokens cannot have characters outside the ASCII range 0x20-0x7e,
     * and cannot contain any of these characters: "()<>@,;:\\\"/[]?={} \t".
     * @return The <code>true</code> if the provided string is a valid
     * token, <code>false</code> otherwise.
     */
    private boolean isToken (String value)
    {
        int length;
        char c;
        boolean ret;

        ret = true;

        length = value.length ();
        for (int i = 0; i < length && ret; i++)
        {
            c = value.charAt (i);
            if (c < ' ' || c > '~' || SPECIALS.indexOf (c) != -1)
                ret = false;
        }

        return (ret);
    }

    /**
     * Returns a copy of this object.
     * @return The clone of this cookie.
     */
    public Object clone ()
    {
        try
        {
            return (super.clone ());
        }
        catch (CloneNotSupportedException e)
        {
            throw new RuntimeException (e.getMessage ());
        }
    }

    /**
     * Convert this cookie into a user friendly string.
     * @return A short form string representing this cookie.
     */
    public String toString ()
    {
        StringBuffer ret;

        ret = new StringBuffer (50);
        if (getSecure ())
            ret.append ("secure ");
        if (0 != getVersion ())
        {
            ret.append ("version ");
            ret.append (getVersion ());
            ret.append (" ");
        }
        ret.append ("cookie");
        if (null != getDomain ())
        {
            ret.append (" for ");
            ret.append (getDomain ());

            if (null != getPath ())
                ret.append (getPath ());
        }
        else
        {
            if (null != getPath ())
            {
                ret.append (" (path ");
                ret.append (getPath ());
                ret.append (")");
            }
        }
        ret.append (": ");
        ret.append (getName ());
        ret.append (getName ().equals ("") ? "" : "=");
        if (getValue ().length () > 40)
        {
            ret.append (getValue ().substring (1, 40));
            ret.append ("...");
        }
        else
            ret.append (getValue ());
        if (null != getComment ())
        {
            ret.append (" // ");
            ret.append (getComment ());
        }

        return (ret.toString ());
    }
}
