You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ol...@apache.org on 2005/08/27 12:15:28 UTC

svn commit: r240400 - in /jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src: java/org/apache/commons/httpclient/ java/org/apache/commons/httpclient/cookie/ test/org/apache/commons/httpclient/cookie/

Author: olegk
Date: Sat Aug 27 03:15:11 2005
New Revision: 240400

URL: http://svn.apache.org/viewcvs?rev=240400&view=rev
Log:
Initial check-in of the Cookie2 patch by Samit Jain

Added:
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java   (with props)
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java   (with props)
Modified:
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Cookie.java
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Header.java
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/HttpMethodBase.java
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java
    jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Cookie.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Cookie.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Cookie.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Cookie.java Sat Aug 27 03:15:11 2005
@@ -63,6 +63,17 @@
  */
 public class Cookie extends NameValuePair implements Serializable, Comparator {
 
+    // string constants for cookie attributes
+    public static final String DOMAIN = "domain";
+    public static final String PATH = "path";
+    public static final String PORT = "port";
+    public static final String VERSION = "version";
+    public static final String SECURE = "secure";
+    public static final String MAXAGE = "max-age";
+    public static final String COMMENT = "comment";
+    public static final String COMMENTURL = "commenturl";
+    public static final String DISCARD = "discard";
+
     // ----------------------------------------------------------- Constructors
 
     /**
@@ -145,6 +156,32 @@
     }
 
     /**
+     * Creates a cookie with the given name, value, domain attribute,
+     * path attribute, expiration attribute, secure attribute, and ports
+     * attribute.
+     *
+     * @param name    the cookie name
+     * @param value   the cookie value
+     * @param domain  the domain this cookie can be sent to
+     * @param path    the path prefix for which this cookie can be sent
+     * @param expires the {@link Date} at which this cookie expires,
+     *                or <tt>null</tt> if the cookie expires at the end
+     *                of the session
+     * @param secure if true this cookie can only be sent over secure
+     * connections
+     * @param ports   the ports for which this cookie can be sent
+     * @throws IllegalArgumentException If cookie name is null or blank,
+     *   cookie name contains a blank, or cookie name starts with character $
+     *
+     */
+    public Cookie(String domain, String name, String value,
+                  String path, Date expires, boolean secure, int[] ports) {
+
+        this(domain, name, value, path, expires, secure);
+        this.setPorts(ports);
+    }
+
+    /**
      * Returns the comment describing the purpose of this cookie, or
      * <tt>null</tt> if no such comment has been defined.
      * 
@@ -168,6 +205,50 @@
         cookieComment = comment;
     }
 
+   /**
+     * If a user agent (web browser) presents this cookie to a user, the
+     * cookie's purpose will be described by the information at this URL.
+     *
+     * @see #setCommentURL(String)
+     */
+    public String getCommentURL() {
+        return cookieCommentURL;
+    }
+
+   /**
+     * If a user agent (web browser) presents this cookie to a user, the
+     * cookie's purpose will be described by the information at this URL.
+     *
+     * @param commentURL
+     *
+     * @see #getCommentURL()
+     */
+    public void setCommentURL(String commentURL) {
+        this.cookieCommentURL = commentURL;
+    }
+
+   /**
+     * Get the Port attribute. It restricts the ports to which a cookie
+     * may be returned in a Cookie request header.
+     *
+     * @see #setPorts(int[])
+     */
+    public int[] getPorts() {
+        return cookiePorts;
+    }
+
+   /**
+     * Set the Port attribute. It restricts the ports to which a cookie
+     * may be returned in a Cookie request header.
+     *
+     * @param ports
+     *
+     * @see #getPorts()
+     */
+    public void setPorts(int[] ports) {
+        this.cookiePorts = ports;
+    }
+
     /**
      * Returns the expiration {@link Date} of the cookie, or <tt>null</tt>
      * if none exists.
@@ -198,6 +279,15 @@
         cookieExpiryDate = expiryDate;
     }
 
+    /**
+     * Set the Discard attribute.
+     *
+     * @see #isPersistent()
+     *
+     */
+    public void setDiscard(boolean toDiscard) {
+        discard = toDiscard;
+    }
 
     /**
      * Returns <tt>false</tt> if the cookie should be discarded at the end
@@ -207,7 +297,7 @@
      *         of the "session"; <tt>true</tt> otherwise
      */
     public boolean isPersistent() {
-        return (null != cookieExpiryDate);
+        return (null != cookieExpiryDate) && !discard;
     }
 
 
@@ -313,13 +403,15 @@
     }
 
     /**
-     * Returns true if this cookie has expired.
+     * Returns true if this cookie has expired. Also returns
+     * true for a session cookie (discard=true).
      * 
      * @return <tt>true</tt> if the cookie has expired.
      */
     public boolean isExpired() {
-        return (cookieExpiryDate != null  
-            && cookieExpiryDate.getTime() <= System.currentTimeMillis());
+        return (cookieExpiryDate != null &&
+                cookieExpiryDate.getTime() <= System.currentTimeMillis())
+               || discard;
     }
 
     /**
@@ -337,11 +429,18 @@
 
     /**
      * Indicates whether the cookie had a path specified in a 
-     * path attribute of the <tt>Set-Cookie</tt> header. This value
-     * is important for generating the <tt>Cookie</tt> header because 
-     * some cookie specifications require that the <tt>Cookie</tt> header 
-     * should only include a path attribute if the cookie's path 
-     * was specified in the <tt>Set-Cookie</tt> header.
+     * path attribute of the <tt>Set-Cookie</tt> header. This property
+     * has two uses:
+     * <ul>
+     * <li>This value is important for generating the <tt>Cookie</tt>
+     * header because some cookie specifications require that the
+     * <tt>Cookie</tt> header should only include a path attribute if
+     * the cookie's path was specified in the <tt>Set-Cookie</tt> header.
+     * </li>
+     * <li>This also ensures that if an attribute appears more than once in
+     * a cookie response header, the first appearance of the attribute is used.
+     * </li>
+     * </ul>
      *
      * @param value <tt>true</tt> if the cookie's path was explicitly 
      * set, <tt>false</tt> otherwise.
@@ -367,12 +466,18 @@
 
     /**
      * Indicates whether the cookie had a domain specified in a 
-     * domain attribute of the <tt>Set-Cookie</tt> header. This value
-     * is important for generating the <tt>Cookie</tt> header because 
-     * some cookie specifications require that the <tt>Cookie</tt> header 
+     * domain attribute of the <tt>Set-Cookie</tt> header. This property
+     * has two uses:
+     * <ul>
+     * <li>This value is important for generating the <tt>Cookie</tt> header
+     * because some cookie specifications require that the <tt>Cookie</tt> header
      * should only include a domain attribute if the cookie's domain 
      * was specified in the <tt>Set-Cookie</tt> header.
-     *
+     * </li>
+     * <li>This also ensures that if an attribute appears more than once in
+     * a cookie response header, the first appearance of the attribute is used.
+     * </li>
+     * </ul>
      * @param value <tt>true</tt> if the cookie's domain was explicitly 
      * set, <tt>false</tt> otherwise.
      *
@@ -396,6 +501,82 @@
     }
 
     /**
+     * Indicates whether the cookie had a ports list specified in the
+     * Port attribute of the <tt>Set-Cookie2</tt> header. This ensures
+     * that if an attribute appears more than once in a cookie response
+     * header, the first appearance of the attribute is used.
+     *
+     * @param value <tt>true</tt> if the cookie's ports were explicitly
+     * set, <tt>false</tt> otherwise.
+     *
+     * @see #isPortAttributeSpecified
+     */
+    public void setPortAttributeSpecified(boolean value) {
+        hasPortAttribute = value;
+    }
+
+    /**
+     * Returns <tt>true</tt> if cookie's ports were set via the Port
+     * attribute in the <tt>Set-Cookie2</tt> header.
+     *
+     * @return <tt>true</tt> if the cookie's ports were explicitly
+     * set, <tt>false</tt> otherwise.
+     *
+     * @see #setPortAttributeSpecified
+     */
+    public boolean isPortAttributeSpecified() {
+        return hasPortAttribute;
+    }
+
+    /**
+     * Indicates whether the Port attribute in <tt>Set-Cookie2</tt> header
+     * is blank.
+     *
+     * @param value
+     *
+     * @see #isPortAttributeBlank
+     */
+    public void setPortAttributeBlank(boolean value) {
+        isPortAttributeBlank = value;
+    }
+
+    /**
+     * Returns <tt>true</tt> if Port attribute in <tt>Set-Cookie2</tt> header
+     * did not have any value.
+     *
+     * @return <tt>true</tt> if Port attribute in <tt>Set-Cookie2</tt> header
+     * did not have any value.
+     *
+     * @see #setPortAttributeBlank
+     */
+    public boolean isPortAttributeBlank() {
+        return isPortAttributeBlank;
+    }
+
+    /**
+     * Returns the response header name this cookie was obtained from.
+     * Currently must be either 'set-cookie' or set-cookie2'.
+     *
+     * @return response header name
+     * @see #setHeaderName(String)
+     */
+    public String getHeaderName() {
+        return headerName;
+    }
+
+    /**
+     * Sets the header name of this cookie.
+     *
+     * @param headerName Currently must be either 'set-cookie' or
+     * set-cookie2'.
+     * @see #getHeaderName()
+     */
+    public void setHeaderName(String headerName) {
+        this.headerName = headerName;
+    }
+
+
+    /**
      * Returns a hash code in keeping with the
      * {@link Object#hashCode} general hashCode contract.
      * @return A hash code
@@ -498,37 +679,72 @@
 
    // ----------------------------------------------------- Instance Variables
 
-   /** Comment attribute. */
-   private String  cookieComment;
+    /** Comment attribute. */
+    private String  cookieComment;
+
+    /**
+     * Comment URL attribute
+     * Note: Required for RFC 2965 cookie specification.
+     */
+    private String cookieCommentURL;
+
+    /** Domain attribute. */
+    private String  cookieDomain;
 
-   /** Domain attribute. */
-   private String  cookieDomain;
+    /**
+     * Port attribute.
+     * Note: Required for RFC 2965 cookie specification.
+     */
+    private int[] cookiePorts;
 
-   /** Expiration {@link Date}. */
-   private Date    cookieExpiryDate;
+    /** Expiration {@link Date}. */
+    private Date    cookieExpiryDate;
 
-   /** Path attribute. */
-   private String  cookiePath;
+    /**
+     * Discard attribute.
+     * Note: Required for RFC 2965 cookie specification.
+     */
+    private boolean discard = false;
 
-   /** My secure flag. */
-   private boolean isSecure;
+    /** Path attribute. */
+    private String  cookiePath;
 
-   /**
-    * Specifies if the set-cookie header included a Path attribute for this
-    * cookie
-    */
-   private boolean hasPathAttribute = false;
+    /** My secure flag. */
+    private boolean isSecure;
 
-   /**
-    * Specifies if the set-cookie header included a Domain attribute for this
-    * cookie
-    */
-   private boolean hasDomainAttribute = false;
+    /**
+     * the name of the response header this cookie was obtained from.
+     */
+    private String headerName;
+
+    /**
+     * Specifies if the set-cookie header included a Path attribute for this
+     * cookie
+     */
+    private boolean hasPathAttribute = false;
+
+    /**
+     * Specifies if the set-cookie header included a Domain attribute for this
+     * cookie
+     */
+    private boolean hasDomainAttribute = false;
+
+    /**
+     * Specifies if the set-cookie2 header included a Port attribute for this
+     * cookie
+     */
+    private boolean hasPortAttribute = false;
+
+    /**
+     * Specifies if the set-cookie2 header's Port attribute did not have
+     * any value.
+     */
+    private boolean isPortAttributeBlank = false;
 
-   /** The version of the cookie specification I was created from. */
-   private int     cookieVersion = 0;
+    /** The version of the cookie specification I was created from. */
+    private int     cookieVersion = -1;
 
-   // -------------------------------------------------------------- Constants
+    // -------------------------------------------------------------- Constants
 
    /** 
     * Collator for Cookie comparisons.  Could be replaced with references to

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Header.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Header.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Header.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/Header.java Sat Aug 27 03:15:11 2005
@@ -45,6 +45,10 @@
      * Autogenerated header flag.
      */
     private boolean isAutogenerated = false;
+
+    public static String SET_COOKIE_KEY = "set-cookie";
+
+    public static String SET_COOKIE2_KEY = "set-cookie2";
     
     /**
      * Default constructor.

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/HttpMethodBase.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/HttpMethodBase.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/HttpMethodBase.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/HttpMethodBase.java Sat Aug 27 03:15:11 2005
@@ -44,6 +44,7 @@
 import org.apache.commons.httpclient.protocol.Protocol;
 import org.apache.commons.httpclient.util.EncodingUtil;
 import org.apache.commons.httpclient.util.ExceptionUtil;
+import org.apache.commons.httpclient.util.ParameterFormatter;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -1190,9 +1191,23 @@
                     getRequestHeaderGroup().addHeader(new Header("Cookie", s, true));
                 }
             }
+            // add a Cookie2 request header specifying the highest cookie version
+            // this client understands. There is no harm in always sending it.
+            // TODO (jain): need to refactor this into spec class somehow
+            getRequestHeaderGroup().addHeader(buildCookie2RequestHeader(matcher));
         }
     }
 
+    Header buildCookie2RequestHeader(CookieSpec spec) {
+        ParameterFormatter formatter = new ParameterFormatter();
+        StringBuffer buffer = new StringBuffer();
+        formatter.format(buffer,
+                         new NameValuePair("$Version",
+                                           Integer.toString(spec.getCookieVersion())));
+        Header header = new Header("Cookie2", buffer.toString(), true);
+        return header;
+    }
+
     /**
      * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
      * header already exists.
@@ -1460,13 +1475,34 @@
         LOG.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
             + "HttpConnection)");
 
-        Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie2");
-        //Only process old style set-cookie headers if new style headres
-        //are not present
-        if (headers.length == 0) { 
-            headers = getResponseHeaderGroup().getHeaders("set-cookie");
-        }
-        
+        // process cookie headers
+
+        // process set-cookie headers
+        Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie");
+        processCookieHeaders(headers, state, conn);
+
+        // process set-cookie2 headers. Older cookie specs will ignore this
+        // header. For newer specs (like RFC 2965), set-cookie2 header will
+        // replace set-cookie header (obtained in the same response) for the
+        // same cookie.
+        headers = getResponseHeaderGroup().getHeaders("set-cookie2");
+        processCookieHeaders(headers, state, conn);
+    }
+
+    /**
+     * This method processes the specified cookie headers. It is invoked from
+     * within {@link #processResponseHeaders(HttpState,HttpConnection)}
+     *
+     * @param headers cookie {@link Header}s to be processed
+     * @param state the {@link HttpState state} information associated with
+     *        this HTTP method
+     * @param conn the {@link HttpConnection connection} used to execute
+     *        this HTTP method
+     */
+    protected void processCookieHeaders(Header[] headers, HttpState state, HttpConnection conn) {
+        LOG.trace("enter HttpMethodBase.processCookieHeaders(Header[], HttpState, "
+                  + "HttpConnection)");
+
         CookieSpec parser = getCookieSpec(state);
         String host = this.params.getVirtualHost();
         if (host == null) {
@@ -1476,6 +1512,7 @@
             Header header = headers[i];
             Cookie[] cookies = null;
             try {
+                System.out.println("(jain) Header[" + i + "]: " + headers[i]);
                 cookies = parser.parse(
                   host,
                   conn.getPort(),
@@ -1493,6 +1530,9 @@
                 for (int j = 0; j < cookies.length; j++) {
                     Cookie cookie = cookies[j];
                     try {
+                        System.out.println("(jain) Cookie [" + j + "]: " +
+                                           cookies[j].getName() + " = " +
+                                           cookies[j].getValue() + " has version " + cookies[j].getVersion());
                         parser.validate(
                           host,
                           conn.getPort(),

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java Sat Aug 27 03:15:11 2005
@@ -79,8 +79,15 @@
      * @since 3.0
      */
     public static final String RFC_2109 = "rfc2109";
-    
-    /** 
+
+    /**
+     * The RFC 2965 compliant policy.
+     *
+     * @since 3.0
+     */
+    public static final String RFC_2965 = "rfc2965";
+
+    /**
      * The policy that ignores cookies. 
      * 
      * @since 3.0
@@ -97,6 +104,7 @@
     static {
         CookiePolicy.registerCookieSpec(DEFAULT, RFC2109Spec.class);
         CookiePolicy.registerCookieSpec(RFC_2109, RFC2109Spec.class);
+        CookiePolicy.registerCookieSpec(RFC_2965, RFC2965Spec.class);
         CookiePolicy.registerCookieSpec(BROWSER_COMPATIBILITY, CookieSpecBase.class);
         CookiePolicy.registerCookieSpec(NETSCAPE, NetscapeDraftSpec.class);
         CookiePolicy.registerCookieSpec(IGNORE_COOKIES, IgnoreCookiesSpec.class);
@@ -124,7 +132,14 @@
      */
     public static final int RFC2109 = 2;
 
-    /** 
+    /**
+     * The <tt>RFC2965</tt> RFC 2965 compliant policy.
+     *
+     * @deprecated Use {@link #RFC_2965}
+     */
+    public static final int RFC2965 = 3;
+
+    /**
      * The default cookie policy.
      *  
      * @deprecated Use {@link #DEFAULT} 
@@ -242,6 +257,8 @@
                 return new NetscapeDraftSpec(); 
             case RFC2109:
                 return new RFC2109Spec();
+            case RFC2965:
+                return new RFC2965Spec();
             default:
                 return getDefaultSpec(); 
         }

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java Sat Aug 27 03:15:11 2005
@@ -224,4 +224,11 @@
      */
     Header formatCookieHeader(Cookie cookie) throws IllegalArgumentException;
 
+    /**
+     * Gets the highest cookie version supported by this cookie specification.
+     *
+     * @return highest cookie version supported.
+     */
+    int getCookieVersion();
+
 }

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java Sat Aug 27 03:15:11 2005
@@ -204,7 +204,8 @@
 
     /**
       * Parse the <tt>"Set-Cookie"</tt> {@link Header} into an array of {@link
-      * Cookie}s.
+      * Cookie}s. Subclasses that need to support other cookie headers can
+      * override this method.
       *
       * <P>The syntax for the Set-Cookie response header is:
       *
@@ -244,7 +245,24 @@
         if (header == null) {
             throw new IllegalArgumentException("Header may not be null.");
         }
-        return parse(host, port, path, secure, header.getValue());
+
+        Cookie[] cookies;
+        // the default implementation only accepts Set-Cookie header
+        if (header.getName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+
+            cookies = parse(host, port, path, secure, header.getValue());
+            // set header name of cookies so we can identify later whether cookie
+            // came from set-cookie header
+            for (int i = 0; i < cookies.length; i++) {
+                cookies[i].setHeaderName(Header.SET_COOKIE_KEY);
+            }
+        }
+        else {
+            throw new MalformedCookieException("Header name is not valid. " +
+                                               "Default implementation supports " +
+                                               "only \"set-cookie\" header.");
+        }
+        return cookies;
     }
 
 
@@ -340,15 +358,15 @@
     }
 
     
-	public Collection getValidDateFormats() {
-		return this.datepatterns;
-	}
-
-	public void setValidDateFormats(final Collection datepatterns) {
-		this.datepatterns = datepatterns;
-	}
+    public Collection getValidDateFormats() {
+        return this.datepatterns;
+    }
+
+    public void setValidDateFormats(final Collection datepatterns) {
+        this.datepatterns = datepatterns;
+    }
 
-	/**
+    /**
       * Performs most common {@link Cookie} validation
       *
       * @param host the host from which the {@link Cookie} was received
@@ -572,7 +590,7 @@
      */
     private static void addInPathOrder(List list, Cookie addCookie) {
         int i = 0;
-
+        // TODO (jain): better implementation of sorting cookies
         for (i = 0; i < list.size(); i++) {
             Cookie c = (Cookie) list.get(i);
             if (addCookie.compare(addCookie, c) > 0) {
@@ -653,6 +671,11 @@
     public Header formatCookieHeader(Cookie cookie) {
         LOG.trace("enter CookieSpecBase.formatCookieHeader(Cookie)");
         return new Header("Cookie", formatCookie(cookie));
+    }
+
+    public int getCookieVersion() {
+        // default cookie version is 0
+        return 0;
     }
 
 }

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java?rev=240400&r1=240399&r2=240400&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/IgnoreCookiesSpec.java Sat Aug 27 03:15:11 2005
@@ -61,16 +61,16 @@
     /**
      * @return <code>null</code>
      */
-	public Collection getValidDateFormats() {
-		return null;
-	}
-	
+    public Collection getValidDateFormats() {
+        return null;
+    }
+    
     /**
      * Does nothing.
      */
-	public void setValidDateFormats(Collection datepatterns) {
-	}
-	
+    public void setValidDateFormats(Collection datepatterns) {
+    }
+    
     /**
      * @return <code>null</code>
      */
@@ -147,6 +147,10 @@
      */
     public boolean pathMatch(final String path, final String topmostPath) {
         return false;
+    }
+
+    public int getCookieVersion() {
+        return -1;
     }
 
 }

Added: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java?rev=240400&view=auto
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java (added)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java Sat Aug 27 03:15:11 2005
@@ -0,0 +1,794 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ * 
+ * ====================================================================
+ *
+ *  Copyright 2002-2004 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.httpclient.cookie;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.Cookie;
+import org.apache.commons.httpclient.HeaderElement;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.util.ParameterFormatter;
+
+import java.util.*;
+
+//TODO: make sure that the syntaz confirms to java 1.2/1.3 spec and does not include 1.4/1.4 features.
+//TODO: refacortinf and naming for easy readabale code
+//TODO: exception handling control flow problem in many parts of code. At many places we dont want to stop processing
+// when an exception occirs. we want to catch the exception and continie processing
+/**
+ * <p>RFC 2965 specific cookie management functions
+ *
+ * @author <a href="mailto:jain.samit@gmail.com">Samit Jain</a>
+ *
+ * @since 3.0
+ */
+
+// TODO: revise all comments and documentation
+
+// TODO: refactoring
+
+public class RFC2965Spec extends CookieSpecBase {
+
+    private final ParameterFormatter formatter;
+
+    /** Default constructor */
+    public RFC2965Spec() {
+        super();
+        this.formatter = new ParameterFormatter();
+        this.formatter.setAlwaysUseQuotes(true);
+    }
+
+    /**
+     * Parses the Set-Cookie2 value into an array of <tt>Cookie</tt>s.
+     *
+     * <P>The syntax for the Set-Cookie2 response header is:
+     *
+     * <PRE>
+     * set-cookie      =    "Set-Cookie:" cookies
+     * cookies         =    1#cookie
+     * cookie          =    NAME "=" VALUE * (";" cookie-av)
+     * NAME            =    attr
+     * VALUE           =    value
+     * cookie-av       =    "Comment" "=" value
+     *                 |    "CommentURL" "=" <"> http_URL <">
+     *                 |    "Discard"
+     *                 |    "Domain" "=" value
+     *                 |    "Max-Age" "=" value
+     *                 |    "Path" "=" value
+     *                 |    "Port" [ "=" <"> portlist <"> ]
+     *                 |    "Secure"
+     *                 |    "Version" "=" 1*DIGIT
+     * portlist        =       1#portnum
+     * portnum         =       1*DIGIT
+     * </PRE>
+     *
+     * @param host the host from which the <tt>Set-Cookie2</tt> value was
+     * received
+     * @param port the port from which the <tt>Set-Cookie2</tt> value was
+     * received
+     * @param path the path from which the <tt>Set-Cookie2</tt> value was
+     * received
+     * @param secure <tt>true</tt> when the <tt>Set-Cookie2</tt> value was
+     * received over secure conection
+     * @param header the <tt>Set-Cookie2</tt> <tt>Header</tt> received from the server
+     * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie2 value
+     * @throws MalformedCookieException if an exception occurs during parsing
+     */
+    public Cookie[] parse(
+            String host, int port, String path, boolean secure, final Header header)
+            throws MalformedCookieException {
+            //TODO (jain): should not throw MalformedCookieException since that is for a cookie
+            // should throw a more general exception -- like MalformedHeaderException or something ...
+        LOG.trace("enter RFC2965.parse("
+                  + "String, port, path, boolean, String)");
+
+        if (header == null) {
+            throw new IllegalArgumentException("Header may not be null.");
+        }
+        Cookie[] cookies = null;
+        String headerName;
+
+        if (header.getName().equalsIgnoreCase(Header.SET_COOKIE2_KEY)) {
+            cookies = parse(host, port, path, secure, header.getValue());
+            headerName = Header.SET_COOKIE2_KEY;
+        }
+        else if (header.getName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            cookies = rfc2109Spec.parse(host, port, path, secure, header.getValue());
+            headerName = Header.SET_COOKIE_KEY;
+        }
+        else {
+            throw new MalformedCookieException("Header name is not valid. " +
+                                               "RFC 2965 supports \"set-cookie\" " +
+                                               "and \"set-cookie2\" headers.");
+        }
+
+        // set header name of cookies so we can identify later whether cookie
+        // came from set-cookie header or set-cookie2 header or ...
+        for (int i = 0; i < cookies.length; i++) {
+            cookies[i].setHeaderName(headerName);
+        }
+        return cookies;
+    }
+
+    /**
+     * Parses the Set-Cookie2 value into an array of <tt>Cookie</tt>s.
+     *
+     * <P>The syntax for the Set-Cookie2 response header is:
+     *
+     * <PRE>
+     * set-cookie      =    "Set-Cookie:" cookies
+     * cookies         =    1#cookie
+     * cookie          =    NAME "=" VALUE * (";" cookie-av)
+     * NAME            =    attr
+     * VALUE           =    value
+     * cookie-av       =    "Comment" "=" value
+     *                 |    "CommentURL" "=" <"> http_URL <">
+     *                 |    "Discard"
+     *                 |    "Domain" "=" value
+     *                 |    "Max-Age" "=" value
+     *                 |    "Path" "=" value
+     *                 |    "Port" [ "=" <"> portlist <"> ]
+     *                 |    "Secure"
+     *                 |    "Version" "=" 1*DIGIT
+     * portlist        =       1#portnum
+     * portnum         =       1*DIGIT
+     * </PRE>
+     *
+     * @param host the host from which the <tt>Set-Cookie2</tt> value was
+     * received
+     * @param port the port from which the <tt>Set-Cookie2</tt> value was
+     * received
+     * @param path the path from which the <tt>Set-Cookie2</tt> value was
+     * received
+     * @param secure <tt>true</tt> when the <tt>Set-Cookie2</tt> value was
+     * received over secure conection
+     * @param header the <tt>Set-Cookie2</tt> header string received from the server
+     * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie2 value
+     * @throws MalformedCookieException if an exception occurs during parsing
+     */
+    public Cookie[] parse(String host, int port, String path,
+                          boolean secure, final String header)
+            throws MalformedCookieException {
+
+        LOG.trace("enter RFC2965Spec.parse("
+                  + "String, port, path, boolean, Header)");
+
+        checkCommonArguments(host, port, path);
+
+        if (header == null) {
+            throw new IllegalArgumentException("Header may not be null.");
+        }
+
+        if (path.trim().equals("")) {
+            path = PATH_DELIM;
+        }
+        host = host.toLowerCase();
+
+        HeaderElement[] headerElements =
+                HeaderElement.parseElements(header.toCharArray());
+
+        List cookies = new ArrayList();
+
+        for (int i = 0; i < headerElements.length; i++) {
+            HeaderElement headerelement = headerElements[i];
+            Cookie cookie = null;
+            try {
+                cookie = new Cookie(host,
+                                    headerelement.getName(),
+                                    headerelement.getValue(),
+                                    path,
+                                    null,
+                                    false,
+                                    new int[] {port});
+
+                // cycle through the parameters
+                NameValuePair[] parameters = headerelement.getParameters();
+                // could be null. In case only a header element and no parameters.
+                if (parameters != null) {
+                    for (int j = 0; j < parameters.length; j++) {
+                        parseAttribute(parameters[j], cookie);
+                    }
+                }
+                cookies.add(cookie);
+            }
+            catch (Exception e) {
+                // TODO (jain): Not sure what to do here? Oleg suggested stop processing
+                // when a cookie is malformed. However continue processing when cookie is well formed but there
+                // was a problem in parsing an attribute. How to implement this?
+
+                // log the error and continue processing other cookies in header
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Error occured while parsing cookie: \"" +
+                              headerelement + "\". " + e);
+            }
+        }
+        return (Cookie[]) cookies.toArray(new Cookie[0]);
+    }
+
+    private void checkCommonArguments(String host, int port, String path) {
+        if (host == null) {
+            throw new IllegalArgumentException(
+                    "Host of origin may not be null");
+        }
+        if (host.trim().equals("")) {
+            throw new IllegalArgumentException(
+                    "Host of origin may not be blank");
+        }
+        if (port < 0) {
+            throw new IllegalArgumentException("Invalid port: " + port);
+        }
+        if (path == null) {
+            throw new IllegalArgumentException(
+                    "Path of origin may not be null.");
+        }
+    }
+
+    /**
+     * Parse RFC 2965 specific cookie attribute and update the corresponsing
+     * {@link org.apache.commons.httpclient.Cookie} properties.
+     *
+     * @param attribute {@link org.apache.commons.httpclient.NameValuePair} cookie attribute from the
+     * <tt>Set-Cookie2</tt> header or <tt>Set-Cookie</tt> header (in case no Set-Cookie2 header was
+     * received).
+     * @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated
+     * @throws MalformedCookieException if an exception occurs during parsing
+     */
+    public void parseAttribute(
+            final NameValuePair attribute, final Cookie cookie)
+            throws MalformedCookieException {
+
+        if (attribute == null) {
+            throw new IllegalArgumentException("Attribute may not be null.");
+        }
+        if (cookie == null) {
+            throw new IllegalArgumentException("Cookie may not be null.");
+        }
+        final String paramName = attribute.getName().toLowerCase();
+        final String paramValue = attribute.getValue();
+
+        if (paramName.equals("path")) {
+            //TODO: if Path attribute is specified in header without any value, what to do?
+            if (!cookie.isPathAttributeSpecified()) {
+                if (paramValue == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for path attribute");
+                }
+                if (paramValue.trim().equals("")) {
+                    throw new MalformedCookieException(
+                            "Blank value for path attribute");
+                }
+                cookie.setPath(paramValue);
+                cookie.setPathAttributeSpecified(true);
+            }
+        }
+        else if (paramName.equals("domain")) {
+            //TODO: if Domain attribute is specified in header without any value, what to do?
+            if (!cookie.isDomainAttributeSpecified()) {
+                if (paramValue == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for domain attribute");
+                }
+                if (paramValue.trim().equals("")) {
+                    throw new MalformedCookieException(
+                            "Blank value for domain attribute");
+                }
+                // domain is lowercased before storing in cookie since
+                // domain matching is case-insensitive
+                String domain = paramValue.toLowerCase();
+                if (!domain.startsWith("."))
+                    domain = "." + domain;
+                cookie.setDomain(domain);
+                cookie.setDomainAttributeSpecified(true);
+            }
+
+        }
+        else if (paramName.equals("max-age")) {
+
+            if (cookie.getExpiryDate() == null) {
+                if (paramValue == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for max-age attribute");
+                }
+                int age;
+                try {
+                    age = Integer.parseInt(paramValue);
+                } catch (NumberFormatException e) {
+                    throw new MalformedCookieException ("Invalid max-age "
+                                                        + "attribute: " + e.getMessage());
+                }
+                cookie.setExpiryDate(
+                        new Date(System.currentTimeMillis() + age * 1000L));
+            }
+        }
+        else if (paramName.equals("comment")) {
+
+            if (cookie.getComment() == null)
+                cookie.setComment(paramValue);
+
+        }
+        else if (paramName.equals("secure")) {
+
+            cookie.setSecure(true);
+
+        }
+        else if (paramName.equals("version")) {
+            if (cookie.getVersion() == -1) {
+                if (paramValue == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for version attribute");
+                }
+                try {
+                    cookie.setVersion(Integer.parseInt(paramValue));
+                } catch (NumberFormatException e) {
+                    throw new MalformedCookieException("Invalid version: "
+                                                       + e.getMessage());
+                }
+            }
+        }
+        else if (paramName.equals("port")) {
+
+            if (!cookie.isPortAttributeSpecified()) {
+                if ((paramValue == null) || (paramValue.trim().equals(""))) {
+                    // If the Port attribute is present but has no value, the
+                    // cookie MUST only be sent to the request-port it was received from.
+                    // Since the default port list only contains request-port, we don't
+                    // need to do anything here.
+                    cookie.setPortAttributeBlank(true);
+                }
+                else {
+                    int[] ports = parsePortAttribute(paramValue);
+                    cookie.setPorts(ports);
+                }
+                cookie.setPortAttributeSpecified(true);
+            }
+        }
+        else if (paramName.equals("commenturl")) {
+
+            if (cookie.getCommentURL() == null)
+                cookie.setCommentURL(paramValue);
+
+        }
+        else if (paramName.equals("Discard")) {
+            cookie.setDiscard(true);
+
+        }
+        else {
+            //TODO: should we throw an exception here?
+            //should the header be considered malformed
+            if (LOG.isDebugEnabled())
+                LOG.debug("Unrecognized cookie attribute: " +
+                          attribute.toString());
+        }
+    }
+
+    private int[] parsePortAttribute(final String paramValue)
+            throws MalformedCookieException {
+        StringTokenizer st = new StringTokenizer(paramValue, ",");
+        int[] ports = new int[st.countTokens()];
+
+        try {
+            int i = 0;
+            while(st.hasMoreTokens()) {
+                ports[i] = Integer.parseInt(st.nextToken().trim());
+                ++i;
+            }
+        } catch (NumberFormatException e) {
+            throw new MalformedCookieException ("Invalid Port "
+                                                + "attribute: " + e.getMessage());
+        }
+        return ports;
+    }
+
+    /**
+     * Performs RFC 2965 compliant {@link org.apache.commons.httpclient.Cookie} validation
+     *
+     * @param host the host from which the {@link org.apache.commons.httpclient.Cookie} was received
+     * @param port the port from which the {@link org.apache.commons.httpclient.Cookie} was received
+     * @param path the path from which the {@link org.apache.commons.httpclient.Cookie} was received
+     * @param secure <tt>true</tt> when the {@link org.apache.commons.httpclient.Cookie} was received using a
+     * secure connection
+     * @param cookie The cookie to validate
+     * @throws MalformedCookieException if an exception occurs during
+     * validation
+     */
+    public void validate(String host, int port, String path,
+                         boolean secure, final Cookie cookie) throws MalformedCookieException {
+
+        LOG.trace("enter RFC2965Spec.validate(String, int, String, "
+                  + "boolean, Cookie)");
+
+        if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+            // old-style cookies are validated according to the old rules
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            rfc2109Spec.validate(host, port, path, secure, cookie);
+        }
+        else if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE2_KEY)) {
+            // check validity of arguments
+            checkCommonArguments(host, port, path);
+
+            if (path.trim().equals("")) {
+                path = PATH_DELIM;
+            }
+            host = host.toLowerCase();
+
+            // validate cookie name
+            if (cookie.getName().indexOf(' ') != -1) {
+                throw new MalformedCookieException("Cookie name may not contain blanks");
+            }
+            if (cookie.getName().startsWith("$")) {
+                throw new MalformedCookieException("Cookie name may not start with $");
+            }
+
+            // validate cookie Version attribute. A user agent rejects
+            // cookie if the Version attribute is missing.
+            if (cookie.getVersion() == -1) {
+                throw new MalformedCookieException(
+                        "Missing value for Version attribute. Violates RFC 2965.");
+            }
+
+            // validate cookie Path attribute. The value for the Path
+            // attribute must be a prefix of the request-URI (case-sensitive).
+            // TODO (jain): refactor into pathMatch method for clarity
+            if (!pathMatch(path, cookie.getPath())) {
+                throw new MalformedCookieException(
+                        "Illegal path attribute \"" + cookie.getPath()
+                        + "\". Path of origin: \"" + path + "\"");
+            }
+
+            // validate cookie Domain attribute.
+            final String cookieDomain = cookie.getDomain().toLowerCase();
+
+            if (cookie.isDomainAttributeSpecified()) {
+                // Domain attribute must start with a dot
+                if (!cookieDomain.startsWith(".")) {
+                    throw new MalformedCookieException("Domain attribute \""
+                                                       + cookie.getDomain()
+                                                       + "\" violates RFC 2109: domain must start with a dot");
+                }
+
+                // Domain attribute must contain atleast one embedded dot,
+                // or the value must be equal to .local.
+                int dotIndex = cookieDomain.indexOf('.', 1);
+                if ((dotIndex < 0 || dotIndex == cookieDomain.length() - 1)
+                    && (!cookieDomain.equals(".local"))) {
+
+                    throw new MalformedCookieException(
+                            "Domain attribute \"" + cookie.getDomain()
+                            + "\" violates RFC 2965: the value contains no embedded dots "
+                            + "and the value is not .local");
+                }
+
+                // The effective host name must domain-match domain attribute.
+                String effectiveHost = getEffectiveHost(host, cookieDomain);
+                if (!domainMatch(effectiveHost, cookieDomain)) {
+                    throw new MalformedCookieException(
+                            "Domain attribute \"" + cookie.getDomain()
+                            + "\" violates RFC 2965: effective host name does not "
+                            + "domain-match domain attribute.");
+                }
+
+                // effective host name minus domain must not contain any dots
+                String effectiveHostWithoutDomain =
+                        effectiveHost.substring(0, effectiveHost.length()
+                                                   - cookieDomain.length());
+                if (effectiveHostWithoutDomain.indexOf('.') != -1) {
+                    throw new MalformedCookieException("Domain attribute \""
+                                                       + cookie.getDomain() + "\" violates RFC 2965: "
+                                                       + "effective host minus domain may not contain any dots");
+                }
+            }
+            else {
+                // Domain was not specified in header. In this case, domain must
+                // string match request host (case-insensitive).
+                if (!cookie.getDomain().equals(host)) {
+                    throw new MalformedCookieException("Illegal domain attribute: \""
+                                                       + cookie.getDomain() + "\"."
+                                                       + "Domain of origin: \""
+                                                       + host + "\"");
+                }
+            }
+
+            // validate cookie Port attribute.
+            // If the Port attribute is not specified in header, the
+            // cookie can be sent to any port. Otherwise, the request port
+            // must be in the cookie's port list.
+            if (cookie.isPortAttributeSpecified()) {
+                if (!portMatch(port, cookie.getPorts())) {
+                    throw new MalformedCookieException(
+                            "Port attribute violates RFC 2965: "
+                            + "Request port not found in cookie's port list.");
+                }
+            }
+        }
+        else {
+            throw new MalformedCookieException("RFC 2956 violation. Header name of cookie must be either " +
+                                               "\"set-cookie\" or \"set-cookie2\".");
+        }
+    }
+
+    private String getEffectiveHost(String host, final String cookieDomain) {
+        String effectiveHost = host;
+        if (cookieDomain.equals(".local") && (host.indexOf('.') < 0)) {
+            effectiveHost += cookieDomain;
+        }
+        return effectiveHost;
+    }
+
+    /**
+     * Returns <tt>true</tt> if the given port exists in the given
+     * ports list. Used to validate cookie.
+     *
+     * @param port
+     * @param ports
+     * @return true returns <tt>true</tt> if the given port exists in
+     *         the given ports list; <tt>false</tt> otherwise.
+     */
+    private boolean portMatch(int port, int[] ports) {
+        boolean portInList = false;
+        for (int i = 0, len = ports.length; i < len; i++) {
+            if (port == ports[i]) {
+                portInList = true;
+                break;
+            }
+        }
+        return portInList;
+    }
+
+    /**
+     * Return <tt>true</tt> if the cookie should be submitted with a request
+     * with given attributes, <tt>false</tt> otherwise.
+     * @param host the host to which the request is being submitted
+     * @param port the port to which the request is being submitted (ignored)
+     * @param path the path to which the request is being submitted
+     * @param secure <tt>true</tt> if the request is using a secure connection
+     * @param cookie {@link Cookie} to be matched
+     * @return true if the cookie matches the criterium
+     */
+    public boolean match(String host, int port, String path,
+                         boolean secure, final Cookie cookie) {
+
+        LOG.trace("enter RFC2965.match("
+                  + "String, int, String, boolean, Cookie");
+
+        if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+
+            // old-style cookies are matched according to the old rules
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            return rfc2109Spec.match(host, port, path, secure, cookie);
+        }
+        else if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE2_KEY)) {
+            checkCommonArguments(host, port, path);
+
+            if (cookie == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            if (path.trim().equals("")) {
+                path = PATH_DELIM;
+            }
+            if (cookie.getDomain() == null) {
+                LOG.warn("Invalid cookie state: domain not specified");
+                return false;
+            }
+            if (cookie.getPath() == null) {
+                LOG.warn("Invalid cookie state: path not specified");
+                return false;
+            }
+            host = host.toLowerCase();
+
+            // match cookie Domain attribute.
+            String cookieDomain = cookie.getDomain();
+            String effectiveHost = getEffectiveHost(host, cookieDomain);
+
+            // The effective host name MUST domain-match the Domain
+            // attribute of the cookie.
+            if (!domainMatch(effectiveHost, cookieDomain)) {
+                return false;
+            }
+
+            // match cookie Port attribute.
+            if (cookie.isPortAttributeSpecified()) {
+                // If Port attribute is specified, the port must be in
+                // the cookie port list.
+                if (!portMatch(port, cookie.getPorts())) {
+                    return false;
+                }
+            }
+
+            // match cookie Path attribute. The request-URI MUST path-match
+            // the Path attribute of the cookie.
+            if (!pathMatch(path, cookie.getPath())) {
+                return false;
+            }
+
+            // validate cookie's age
+            if (cookie.getExpiryDate() != null
+                && !cookie.getExpiryDate().after(new Date())) {
+                return false;
+            }
+
+            // finally make sure that if cookie Secure attribute is set, then this
+            // request is using a secure connection
+            if (cookie.getSecure()) {
+                return secure;
+            }
+
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * Compares the given cookies and returns <tt>true</tt> if they match
+     * according to the rules specified in RFC 2965 (section 3.3.3); otherwise
+     * returns false.
+     *
+     * @param cookie1
+     * @param cookie2
+     * @return <tt>true</tt> if cookies match; otherwise false.
+     */
+    public boolean cookieMatch(Cookie cookie1, Cookie cookie2) {
+        //TODO (jain): how is the cookie name compared
+        return cookie1.getName().equals(cookie2.getName()) &&
+               cookie1.getDomain().equalsIgnoreCase(cookie2.getDomain()) &&
+               cookie1.getPath().equals(cookie2.getPath());
+    }
+
+    /**
+     * Return a string suitable for sending in a <tt>"Cookie"</tt> header 
+     * as defined in RFC 2965.
+     * @param buffer The string buffer to use for output
+     * @param cookie The {@link org.apache.commons.httpclient.Cookie} to be formatted as string
+     */
+    private void formatCookieAttributes(final StringBuffer buffer, final Cookie cookie) {
+        String value = cookie.getValue();
+        if (value == null) {
+            value = "";
+        }
+        this.formatter.format(buffer, new NameValuePair(cookie.getName(), value));
+
+        if (cookie.getDomain() != null
+            && cookie.isDomainAttributeSpecified()) {
+            buffer.append("; ");
+            this.formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain()));
+        }
+        if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
+            buffer.append("; ");
+            this.formatter.format(buffer, new NameValuePair("$Path", cookie.getPath()));
+        }
+
+        if (cookie.isPortAttributeSpecified()) {
+            buffer.append("; ");
+            String portValue = "";
+            if (!cookie.isPortAttributeBlank()) {
+                portValue = createPortAttributeValue(cookie.getPorts());
+            }
+            this.formatter.format(buffer, new NameValuePair("$Port", portValue));
+        }
+    }
+
+    private String createPortAttributeValue(int[] ports) {
+        StringBuffer portValue = new StringBuffer();
+        for (int i = 0, len = ports.length; i < len; i++) {
+            if (i > 0) {
+                portValue.append(",");
+            }
+            portValue.append(ports[i]);
+        }
+        return portValue.toString();
+    }
+
+    /**
+     * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
+     * defined in RFC 2965
+     * @param cookie a {@link org.apache.commons.httpclient.Cookie} to be formatted as string
+     * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
+     */
+    public String formatCookie(Cookie cookie) {
+        LOG.trace("enter RFC2965Spec.formatCookie(Cookie)");
+
+        if (cookie == null) {
+            throw new IllegalArgumentException("Cookie may not be null");
+        }
+        int version = cookie.getVersion();
+        if (version < 1 ||
+            cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+            // delegate cookie formatting to rfc2109Spec
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            return rfc2109Spec.formatCookie(cookie);
+        }
+
+        StringBuffer buffer = new StringBuffer();
+        this.formatter.format(buffer,
+                              new NameValuePair("$Version", Integer.toString(version)));
+        buffer.append("; ");
+        formatCookieAttributes(buffer, cookie);
+
+        return buffer.toString();
+    }
+
+    /**
+     * Create a RFC 2965 compliant <tt>"Cookie"</tt> header value containing all
+     * {@link org.apache.commons.httpclient.Cookie}s in <i>cookies</i> suitable for sending in a <tt>"Cookie"
+     * </tt> header
+     * @param cookies an array of {@link org.apache.commons.httpclient.Cookie}s to be formatted
+     * @return a string suitable for sending in a Cookie header.
+     */
+    public String formatCookies(Cookie[] cookies) {
+        LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])");
+
+        if (cookies == null) {
+            throw new IllegalArgumentException("Cookies may not be null");
+        }
+        int lowestVersion = Integer.MAX_VALUE;
+        // pick the lowest version
+        for (int i = 0; i < cookies.length; i++) {
+            Cookie cookie = cookies[i];
+            if (cookie.getVersion() < lowestVersion) {
+                lowestVersion = cookie.getVersion();
+            }
+        }
+
+        boolean hasOldStyleCookie = false;
+        // check if cookies array contains set-cookie (old style) cookie
+        for (int i = 0; i < cookies.length; i++) {
+            if (cookies[i].getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+                hasOldStyleCookie = true;
+                break;
+            }
+        }
+
+        if ((lowestVersion < 1) || hasOldStyleCookie) {
+            // delegate cookie formatting to rfc2109Spec
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            return rfc2109Spec.formatCookies(cookies);
+        }
+
+        final StringBuffer buffer = new StringBuffer();
+        this.formatter.format(buffer,
+            new NameValuePair("$Version", Integer.toString(lowestVersion)));
+
+        for (int i = 0; i < cookies.length; i++) {
+            buffer.append("; ");
+            formatCookieAttributes(buffer, cookies[i]);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Gets the highest cookie version supported by this cookie specification.
+     *
+     * @return highest cookie version supported.
+     */
+    public int getCookieVersion() {
+        return 1;
+    }
+}

Propchange: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java?rev=240400&view=auto
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java (added)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java Sat Aug 27 03:15:11 2005
@@ -0,0 +1,537 @@
+/*
+ * $Header: $
+ * $Revision$
+ * $Date$
+ * ====================================================================
+ *
+ *  Copyright 1999-2004 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.httpclient.cookie;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.commons.httpclient.Cookie;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.NameValuePair;
+
+/**
+ * Test cases for RFC2965 cookie spec
+ *
+ * @author jain.samit@gmail.com (Samit Jain)
+ */
+public class TestCookieRFC2965Spec extends TestCookieBase {
+
+
+    // ------------------------------------------------------------ Constructor
+
+    public TestCookieRFC2965Spec(String name) {
+        super(name);
+    }
+
+    // ------------------------------------------------------- TestCase Methods
+
+    public static Test suite() {
+        return new TestSuite(TestCookieRFC2965Spec.class);
+    }
+
+    public void testParseAttributeInvalidParams() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie cookie = new Cookie();
+        NameValuePair attribute = new NameValuePair("name", "value");
+        try {
+            cookiespec.parseAttribute(null, cookie);
+            fail("IllegalArgumentException must have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            cookiespec.parseAttribute(attribute, null);
+            fail("IllegalArgumentException must have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testParseAttributePath() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie cookie = new Cookie();
+        try {
+            cookiespec.parseAttribute(new NameValuePair("path", null), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+        try {
+            cookiespec.parseAttribute(new NameValuePair("path", "   "), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+        cookiespec.parseAttribute(new NameValuePair("path", "/"), cookie);
+        assertEquals(cookie.getPath(), "/");
+        // this will be ignored since path is already specified for this cookie
+        cookiespec.parseAttribute(new NameValuePair("path", null), cookie);
+
+    }
+
+    public void testParseAttributeDomain() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie cookie = new Cookie();
+        try {
+            cookiespec.parseAttribute(new NameValuePair("domain", null), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+        try {
+            cookiespec.parseAttribute(new NameValuePair("domain", "   "), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+        cookiespec.parseAttribute(new NameValuePair("domain", ".domain.com"), cookie);
+        assertEquals(cookie.getDomain(), ".domain.com");
+        // this will be ignored since domain is already specified for this cookie
+        cookiespec.parseAttribute(new NameValuePair("domain", null), cookie);
+
+        // puts a dot in front of domain if missing
+        cookie = new Cookie();
+        cookiespec.parseAttribute(new NameValuePair("domain", "domain.com"), cookie);
+        assertEquals(cookie.getDomain(), ".domain.com");
+    }
+
+    public void testParseAttributePort() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+
+        Cookie cookie = new Cookie();
+        cookie.setPorts(new int[] {80}); //default to request port
+        cookiespec.parseAttribute(new NameValuePair("port", null), cookie);
+        // null port defaults to request port
+        assertTrue(equalArray(cookie.getPorts(), new int[] {80}));
+
+        cookie = new Cookie();
+        cookie.setPorts(new int[] {80}); //default to request port
+        cookiespec.parseAttribute(new NameValuePair("port", "  "), cookie);
+        // blank port defaults to request port
+        assertTrue(equalArray(cookie.getPorts(), new int[] {80}));
+
+        try {
+            cookie = new Cookie();
+            cookiespec.parseAttribute(new NameValuePair("port", "nonsense"), cookie);
+            // anything else not an 'int' must fail
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+
+        cookie = new Cookie();
+        cookiespec.parseAttribute(new NameValuePair("port", "80,8080"), cookie);
+        assertTrue(equalArray(cookie.getPorts(), new int[] {80,8080}));
+        // this will be ignored since port already specified for this cookie
+        cookiespec.parseAttribute(new NameValuePair("port", "nonsense"), cookie);
+    }
+
+    public void testParseAttributeVersion() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie cookie = new Cookie();
+        try {
+            cookiespec.parseAttribute(new NameValuePair("version", null), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+
+        try {
+            cookiespec.parseAttribute(new NameValuePair("version", "nonsense"), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+        cookiespec.parseAttribute(new NameValuePair("version", "1"), cookie);
+        assertEquals(cookie.getVersion(), 1);
+    }
+
+    public void testParseAttributeMaxage() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie cookie = new Cookie();
+        try {
+            cookiespec.parseAttribute(new NameValuePair("max-age", null), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+
+        try {
+            cookiespec.parseAttribute(new NameValuePair("max-age", "nonsense"), cookie);
+            fail("MalformedCookieException must have been thrown");
+        } catch (MalformedCookieException expected) {
+        }
+        cookiespec.parseAttribute(new NameValuePair("max-age", "3600"), cookie);
+        assertNotNull(cookie.getExpiryDate());
+    }
+
+    public void testParseAttributeOthers() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie cookie = new Cookie();
+        // test comment
+        String comment = "test cookie";
+        cookiespec.parseAttribute(new NameValuePair("comment", comment), cookie);
+        assertEquals(cookie.getComment(), comment);
+        // test comment URL
+        String commentURL = "www.sourceforge.com/cookiedesc";
+        cookiespec.parseAttribute(new NameValuePair("commenturl", commentURL), cookie);
+        assertEquals(cookie.getCommentURL(), commentURL);
+        // test secure
+        cookiespec.parseAttribute(new NameValuePair("secure", null), cookie);
+        assertTrue(cookie.getSecure());
+        // test discard
+        cookiespec.parseAttribute(new NameValuePair("discard", null), cookie);
+        assertFalse(cookie.isPersistent());
+    }
+
+    /**
+     * Test domain equals host
+     */
+    public void testParseValidateDomainEqualsHost() throws Exception {
+        Header header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=www.b.com; version=1");
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie[] parsed = cookieParse(cookiespec, "www.b.com", 80, "/", false, header);
+        assertNotNull(parsed);
+        assertEquals(1, parsed.length);
+        assertEquals("www.b.com", parsed[0].getDomain());
+
+        // test backward compatibility with Set-Cookie header
+        header = new Header("Set-Cookie",
+            "cookie-name=cookie-value; domain=www.b.com; version=1");
+        parsed = cookieParse(cookiespec, "www.b.com", 80, "/", false, header);
+        assertNotNull(parsed);
+        assertEquals(1, parsed.length);
+        assertEquals("www.b.com", parsed[0].getDomain());
+    }
+
+    /**
+     * Test Domain with no leading dot in domain name
+     */
+    public void testParseValidateDomainLeadingDot() throws Exception {
+        // set-cookie2 header works without a leading dot
+        Header header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=a.b.com; version=1");
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie[] parsed = cookieParse(cookiespec, "www.a.b.com", 80, "/", false, header);
+        assertNotNull(parsed);
+        assertEquals(1, parsed.length);
+        assertEquals(".a.b.com", parsed[0].getDomain());
+
+        // test backward compatibility; set-cookie header must fail
+        // without a leading dot in domain name
+        header = new Header("Set-Cookie",
+             "cookie-name=cookie-value; domain=a.b.com; version=1");
+        try {
+            parsed = cookieParse(cookiespec, "www.a.b.com", 80, "/", false, header);
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Domain must have atleast least one embedded dot
+     */
+    public void testParseValidateDomainEmbeddedDot() throws Exception {
+        Header header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=.com; version=1");
+
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "b.com", 80, "/", false, header);
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+
+        // test backward compatibility with Set-Cookie header
+        header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=.com; version=1");
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "b.com", 80, "/", false, header);
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+    }
+
+    /**
+     * test .local domain
+     */
+    public void testParseValidateDomainLocal() throws Exception {
+        Header header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=.local; version=1");
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie[] parsed = cookieParse(cookiespec, "host", 80, "/", false, header);
+        assertNotNull(parsed);
+        assertEquals(1, parsed.length);
+        assertEquals(".local", parsed[0].getDomain());
+        //TODO: however simple host name like 'host' should not be accepted if domain name is not .local
+    }
+
+    /**
+     * effective host name minus domain must not contain any dots.
+     */
+    public void testParseValidateDomainIllegal() throws Exception {
+        Header header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=.c.com; version=1");
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "a.b.c.com", 80, "/", false, header);
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+
+        // test backward compatibility with Set-Cookie header
+        header = new Header("Set-Cookie",
+            "cookie-name=cookie-value; domain=.c.com; version=1");
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "a.b.c.com", 80, "/", false, header);
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+    }
+
+    /**
+     * test version.
+     */
+    public void testParseValidateVersion() throws Exception {
+        Header header = new Header("Set-Cookie2", "cookie-name=cookie-value");
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "c.a.com", 80, "/", false, header);
+            // version attribute is REQUIRED
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+
+        // test backward compatibility with Set-Cookie header
+        header = new Header("Set-Cookie", "cookie-name=cookie-value");
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "c.a.com", 80, "/", false, header);
+            // version attribute is REQUIRED
+            fail("MalformedCookieException should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+    }
+
+    /**
+     * test path.
+     */
+    public void testParseValidatePath() throws Exception {
+        Header header = new Header("Set-Cookie2",
+            "cookie-name=cookie-value; domain=127.0.0.1; path=/path");
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header);
+            fail("HttpException exception should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+        Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/path/path1", false, header);
+        assertNotNull(parsed);
+        assertEquals(1, parsed.length);
+        assertEquals("/path", parsed[0].getDomain());
+
+        // test backward compatibility with Set-Cookie header
+        header = new Header("Set-Cookie",
+            "cookie-name=cookie-value; domain=127.0.0.1; path=/path");
+        try {
+            parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header);
+            fail("HttpException exception should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+        parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/path/path1", false, header);
+        assertNotNull(parsed);
+        assertEquals(1, parsed.length);
+        assertEquals("/path", parsed[0].getDomain());
+    }
+
+    /**
+     * Tests if cookie constructor rejects cookie name containing blanks.
+     */
+    public void testCookieNameWithBlanks() throws Exception {
+        Header header = new Header("Set-Cookie2", "invalid name=");
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header);
+            fail("MalformedCookieException exception should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+
+        // backward compatibility with set-cookie
+        header = new Header("Set-Cookie", "invalid name=");
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header);
+            fail("MalformedCookieException exception should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests if cookie constructor rejects cookie name starting with $.
+     */
+    public void testCookieNameStartingWithDollarSign() throws Exception {
+        Header header = new Header("Set-Cookie2", "$invalid_name=");
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header);
+            fail("MalformedCookieException exception should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+
+        // backward compatibility with set-cookie
+        header = new Header("Set-Cookie", "$invalid_name=");
+        try {
+            Cookie[] parsed = cookieParse(cookiespec, "127.0.0.1", 80, "/", false, header);
+            fail("MalformedCookieException exception should have been thrown");
+        } catch (MalformedCookieException e) {
+            // expected
+        }
+    }
+
+    //TODO: test for cookie matching
+    //TODO: test for cookie formatting
+    /**
+     * Tests if cookie values with embedded comma are handled correctly.
+     */
+    public void testCookieWithComma() throws Exception {
+        Header header = new Header("Set-Cookie", "a=b,c");
+
+        CookieSpec cookiespec = new RFC2965Spec();
+        Cookie[] cookies = cookiespec.parse("localhost", 80, "/", false, header);
+        assertEquals("number of cookies", 2, cookies.length);
+        assertEquals("a", cookies[0].getName());
+        assertEquals("b", cookies[0].getValue());
+        assertEquals("c", cookies[1].getName());
+        assertEquals(null, cookies[1].getValue());
+    }
+
+    public void testFormatInvalidCookies() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        try {
+            String s = cookiespec.formatCookie(null);
+            fail("IllegalArgumentException nust have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Tests RFC 2965 compiant cookie formatting.
+     */
+    public void testRFC2965CookieFormatting() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Header header = new Header("Set-Cookie",
+            "name=\"value\"; version=\"1\"; path=\"/\"; domain=\".mydomain.com\"");
+        Cookie[] cookies  = cookiespec.parse("myhost.mydomain.com", 80, "/", false, header );
+        cookiespec.validate("myhost.mydomain.com", 80, "/", false, cookies[0]);
+        String s1 = cookiespec.formatCookie(cookies[0]);
+        assertEquals(s1, "$Version=\"1\"; name=\"value\"; $Domain=\".mydomain.com\"; $Path=\"/\"");
+
+        header = new Header( "Set-Cookie",
+            "name=value; path=/; domain=.mydomain.com");
+        cookies = cookiespec.parse("myhost.mydomain.com", 80, "/", false, header );
+        cookiespec.validate("myhost.mydomain.com", 80, "/", false, cookies[0]);
+        String s2 = cookiespec.formatCookie(cookies[0]);
+        assertEquals(s2, "$Version=0; name=value; $Domain=.mydomain.com; $Path=/");
+    }
+
+    public void testRFC2965CookiesFormatting() throws Exception {
+        CookieSpec cookiespec = new RFC2965Spec();
+        Header header = new Header("Set-Cookie",
+            "name1=value1; path=/; domain=.mydomain.com, " +
+            "name2=\"value2\"; version=\"1\"; path=\"/\"; domain=\".mydomain.com\"");
+        Cookie[] cookies = cookieParse(cookiespec, "myhost.mydomain.com", 80, "/", false, header);
+        assertNotNull(cookies);
+        assertEquals(2, cookies.length);
+        String s1 = cookiespec.formatCookies(cookies);
+        assertEquals(s1,
+            "$Version=0; name1=value1; $Domain=.mydomain.com; $Path=/; " +
+            "name2=value2; $Domain=.mydomain.com; $Path=/");
+
+        header = new Header("Set-Cookie",
+            "name1=value1; version=1; path=/; domain=.mydomain.com, " +
+            "name2=\"value2\"; version=\"1\"; path=\"/\"; domain=\".mydomain.com\"");
+        cookies = cookieParse(cookiespec, "myhost.mydomain.com", 80, "/", false, header);
+        assertNotNull(cookies);
+        assertEquals(2, cookies.length);
+        String s2 = cookiespec.formatCookies(cookies);
+        assertEquals(s2,
+            "$Version=\"1\"; name1=\"value1\"; $Domain=\".mydomain.com\"; $Path=\"/\"; " +
+            "name2=\"value2\"; $Domain=\".mydomain.com\"; $Path=\"/\"");
+    }
+
+    /**
+     * Tests if null cookie values are handled correctly.
+     */
+    public void testNullCookieValueFormatting() {
+        Cookie cookie = new Cookie(".whatever.com", "name", null, "/", null, false);
+        cookie.setDomainAttributeSpecified(true);
+        cookie.setPathAttributeSpecified(true);
+
+        CookieSpec cookiespec = new RFC2965Spec();
+        String s = cookiespec.formatCookie(cookie);
+        assertEquals("$Version=0; name=; $Domain=.whatever.com; $Path=/", s);
+
+        cookie.setVersion(1);
+        s = cookiespec.formatCookie(cookie);
+        assertEquals("$Version=\"1\"; name=\"\"; $Domain=\".whatever.com\"; $Path=\"/\"", s);
+    }
+
+    public void testCookieNullDomainNullPathFormatting() {
+        Cookie cookie = new Cookie(null, "name", null, "/", null, false);
+        cookie.setDomainAttributeSpecified(true);
+        cookie.setPathAttributeSpecified(true);
+
+        CookieSpec cookiespec = new RFC2965Spec();
+        String s = cookiespec.formatCookie(cookie);
+        assertEquals("$Version=0; name=; $Path=/", s);
+
+        cookie.setDomainAttributeSpecified(false);
+        cookie.setPathAttributeSpecified(false);
+        s = cookiespec.formatCookie(cookie);
+        assertEquals("$Version=0; name=", s);
+    }
+
+    private boolean equalArray(int[] a1, int[] a2) {
+        if (a1 == null && a2 == null)
+            return true;
+        if (a1 == null || a2 == null)
+            return false;
+        if (a1.length != a2.length)
+            return false;
+        for (int i = 0; i < a1.length; i++) {
+            if (a1[i] != a2[i])
+                return false;
+        }
+        return true;
+    }
+
+}
+

Propchange: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieRFC2965Spec.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org