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/29 16:28:43 UTC

svn commit: r264149 [2/3] - 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/

Modified: 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=264149&r1=264148&r2=264149&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java Mon Aug 29 07:28:21 2005
@@ -37,40 +37,64 @@
 
 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>
+ * <p>RFC 2965 specific cookie management functions. *
+ * @author @author jain.samit@gmail.com (Samit Jain)
  *
  * @since 3.0
  */
 
-// TODO: revise all comments and documentation
-
-// TODO: refactoring
-
 public class RFC2965Spec extends CookieSpecBase {
 
+    /**
+    * Cookie Response Header  name for cookies processed
+    * by this spec.
+    */
+    public static String SET_COOKIE2_KEY = "set-cookie2";
+    
+    /**
+    * used for formatting RFC 2956 style cookies
+    */
     private final ParameterFormatter formatter;
-
-    /** Default constructor */
+     
+    /**
+    * Stores attribute name -> attribute handler mappings
+    */
+    private static Map attributeHandlerMap = null;
+    
+    /** 
+     * Default constructor 
+     * */
     public RFC2965Spec() {
         super();
         this.formatter = new ParameterFormatter();
         this.formatter.setAlwaysUseQuotes(true);
+        initializeAttributeHandlerMap();        
     }
 
     /**
+    * initializes attribute name -> attribute handler mappings.
+    * Called from constructor.
+    */
+    private void initializeAttributeHandlerMap() {
+        if (attributeHandlerMap == null) {
+            attributeHandlerMap = new HashMap();
+            attributeHandlerMap.put(Cookie2.COOKIE_NAME_KEY, new Cookie2NameAttributeHandler());
+            attributeHandlerMap.put(Cookie2.PATH, new Cookie2PathAttributeHandler());
+            attributeHandlerMap.put(Cookie2.DOMAIN, new Cookie2DomainAttributeHandler());
+            attributeHandlerMap.put(Cookie2.PORT, new Cookie2PortAttributeHandler());
+            attributeHandlerMap.put(Cookie2.VERSION, new Cookie2VersionAttributeHandler());
+            attributeHandlerMap.put(Cookie2.MAXAGE, new Cookie2MaxageAttributeHandler());
+        }
+    }
+    
+    /**
      * 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
+     * set-cookie      =    "Set-Cookie2:" cookies
      * cookies         =    1#cookie
      * cookie          =    NAME "=" VALUE * (";" cookie-av)
      * NAME            =    attr
@@ -103,85 +127,41 @@
     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)");
+                  + "String, int, String, boolean, Header)");
 
         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;
+        if (header.getName() == null) {
+            throw new IllegalArgumentException("Header name may not be null.");
         }
-        else if (header.getName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+
+        if (header.getName().equalsIgnoreCase(SET_COOKIE2_KEY)) {
+            // parse cookie2 cookies
+            return parse(host, port, path, secure, header.getValue());
+        } else if (header.getName().equalsIgnoreCase(RFC2109Spec.SET_COOKIE_KEY)) {
+            // delegate parsing of old-style cookies to rfc2109Spec
             CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
-            cookies = rfc2109Spec.parse(host, port, path, secure, header.getValue());
-            headerName = Header.SET_COOKIE_KEY;
-        }
-        else {
+            return rfc2109Spec.parse(host, port, path, secure, header.getValue());
+        } 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
+     * @see #parse(String, int, String, boolean, org.apache.commons.httpclient.Header)
      */
     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);
+                  + "String, int, String, boolean, String)");
 
+        // before we do anything, lets check validity of arguments
+        validateArgs(host, port, path);
         if (header == null) {
             throw new IllegalArgumentException("Header may not be null.");
         }
@@ -194,13 +174,12 @@
         HeaderElement[] headerElements =
                 HeaderElement.parseElements(header.toCharArray());
 
-        List cookies = new ArrayList();
-
+        List cookies = new LinkedList();
         for (int i = 0; i < headerElements.length; i++) {
             HeaderElement headerelement = headerElements[i];
-            Cookie cookie = null;
+            Cookie2 cookie = null;
             try {
-                cookie = new Cookie(host,
+                cookie = new Cookie2(host,
                                     headerelement.getName(),
                                     headerelement.getValue(),
                                     path,
@@ -217,37 +196,17 @@
                     }
                 }
                 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
+            } catch (Exception e) {
+                //TODO(jain): when do we consider the header malformed and stop processing it?
+                // throw this cookie, 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.");
-        }
+        if (cookies.isEmpty())
+            return null;
+        return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
     }
 
     /**
@@ -255,157 +214,55 @@
      * {@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
+     * <tt>Set-Cookie2</tt> header.
+     * @param cookieParam {@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)
+            final NameValuePair attribute, final Cookie cookieParam)
             throws MalformedCookieException {
-
         if (attribute == null) {
             throw new IllegalArgumentException("Attribute may not be null.");
         }
-        if (cookie == null) {
+        if (attribute.getName() == null) {
+            throw new IllegalArgumentException("Attribute Name may not be null.");
+        }
+        if (cookieParam == null) {
             throw new IllegalArgumentException("Cookie may not be null.");
         }
+        if (!(cookieParam instanceof Cookie2)) {
+            throw new IllegalArgumentException("Expected Cookie2 cookies.");
+        }
+        Cookie2 cookie = (Cookie2) cookieParam;
         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));
-            }
+        try {
+            CookieAttributeHandler handler = getAttributeHandler(paramName);
+            handler.parse(cookie, paramValue);
+        } catch (IllegalStateException canIgnore) {
+            // handler not registered for this paramName
         }
-        else if (paramName.equals("comment")) {
 
+        // handle other cookie attributes
+        if (paramName.equals(Cookie2.COMMENT)) {
             if (cookie.getComment() == null)
                 cookie.setComment(paramValue);
-
-        }
-        else if (paramName.equals("secure")) {
-
+        } else if (paramName.equals(Cookie2.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")) {
-
+        } else if (paramName.equals(Cookie2.COMMENTURL)) {
             if (cookie.getCommentURL() == null)
                 cookie.setCommentURL(paramValue);
-
-        }
-        else if (paramName.equals("Discard")) {
+        } else if (paramName.equals(Cookie2.DISCARD)) {
             cookie.setDiscard(true);
-
-        }
-        else {
-            //TODO: should we throw an exception here?
-            //should the header be considered malformed
+        } else {
+            // ignore unknown attribute-value pairs
             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
      *
@@ -414,151 +271,44 @@
      * @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
+     * @param cookieParam 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 {
+                         boolean secure, final Cookie cookieParam)
+            throws MalformedCookieException {
 
         LOG.trace("enter RFC2965Spec.validate(String, int, String, "
                   + "boolean, Cookie)");
 
-        if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+        // before we do anything, lets check validity of arguments
+        validateArgs(host, port, path);
+
+        if (!(cookieParam instanceof Cookie2)) {
             // 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;
+            rfc2109Spec.validate(host, port, path, secure, cookieParam);
+            return;
         }
-        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;
+        /* validate cookie2 cookies */
+        Cookie2 cookie = (Cookie2) cookieParam;
+        // validate cookie name
+        CookieAttributeHandler handler = getAttributeHandler(Cookie2.COOKIE_NAME_KEY);
+        handler.validate(cookie, null);
+        // validate cookie path attribute
+        handler = getAttributeHandler(Cookie2.PATH);
+        handler.validate(cookie, path);
+        // validate cookie domain attribute
+        handler = getAttributeHandler(Cookie2.DOMAIN);
+        handler.validate(cookie, host);
+        // validate cookie port attribute
+        handler = getAttributeHandler(Cookie2.PORT);
+        handler.validate(cookie, String.valueOf(port));
+        // validate cookie version attribute
+        handler = getAttributeHandler(Cookie2.VERSION);
+        handler.validate(cookie, null);
     }
 
     /**
@@ -568,227 +318,879 @@
      * @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
+     * @param cookieParam {@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) {
+                         boolean secure, final Cookie cookieParam) {
 
         LOG.trace("enter RFC2965.match("
                   + "String, int, String, boolean, Cookie");
 
-        if (cookie.getHeaderName().equalsIgnoreCase(Header.SET_COOKIE_KEY)) {
+        // before we do anything, lets check validity of arguments
+        validateArgs(host, port, path);
+        if (cookieParam == null) {
+            throw new IllegalArgumentException("Cookie may not be null");
+        }
 
+        if (!(cookieParam instanceof Cookie2)) {
             // 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);
+            return rfc2109Spec.match(host, port, path, secure, cookieParam);
         }
-        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 cookie2 cookies */
+        Cookie2 cookie = (Cookie2) cookieParam;
+        // match cookie path attribute
+        CookieAttributeHandler handler = getAttributeHandler(Cookie2.PATH);
+        if (!handler.match(cookie, path))
+            return false;
+        // match cookie domain attribute
+        handler = getAttributeHandler(Cookie2.DOMAIN);
+        if (!handler.match(cookie, host))
+            return false;
+        // match cookie port attribute
+        handler = getAttributeHandler(Cookie2.PORT);
+        if (!handler.match(cookie, String.valueOf(port)))
+            return false;
+        // check if cookie has expired
+        if (cookie.isPersistent() && cookie.isExpired())
+            return false;
+        // finally make sure that if cookie Secure attribute is set, then this
+        // request is made using a secure connection
+        if (cookie.getSecure())
+            return secure;
+        // if we get to this stage, we have a match
+        return true;
+    }
 
-            // match cookie Domain attribute.
-            String cookieDomain = cookie.getDomain();
-            String effectiveHost = getEffectiveHost(host, cookieDomain);
+    /**
+     * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
+     * defined in RFC 2965
+     * @param cookieParam 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 cookieParam) {
+        LOG.trace("enter RFC2965Spec.formatCookie(Cookie)");
 
-            // The effective host name MUST domain-match the Domain
-            // attribute of the cookie.
-            if (!domainMatch(effectiveHost, cookieDomain)) {
-                return false;
-            }
+        if (cookieParam == null) {
+            throw new IllegalArgumentException("Cookie may not be null");
+        }
+        if (!(cookieParam instanceof Cookie2)) {
+            // old-style cookies are formatted according to the old rules
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            return rfc2109Spec.formatCookie(cookieParam);
+        }
 
-            // 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;
-                }
-            }
+        /* format cookie2 cookie */
+        Cookie2 cookie = (Cookie2) cookieParam;
+        final StringBuffer buffer = new StringBuffer();
+        // format cookie version
+        CookieAttributeHandler handler = getAttributeHandler(Cookie2.VERSION);
+        handler.format(buffer, cookie);
+        // format cookie attributes
+        formatCookieAttributes(buffer, cookie);
+        return buffer.toString();
+    }
 
-            // match cookie Path attribute. The request-URI MUST path-match
-            // the Path attribute of the cookie.
-            if (!pathMatch(path, cookie.getPath())) {
-                return false;
-            }
+    /**
+     * Create a RFC 2965 compliant <tt>"Cookie"</tt> header value containing all
+     * {@link org.apache.commons.httpclient.Cookie}s 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[])");
 
-            // validate cookie's age
-            if (cookie.getExpiryDate() != null
-                && !cookie.getExpiryDate().after(new Date())) {
-                return false;
+        if (cookies == null) {
+            throw new IllegalArgumentException("Cookies may not be null");
+        }
+        // check if cookies array contains a set-cookie (old style) cookie
+        boolean hasOldStyleCookie = false;
+        for (int i = 0; i < cookies.length; i++) {
+            if (!(cookies[i] instanceof Cookie2)) {
+                hasOldStyleCookie = true;
+                break;
             }
+        }
+        // TODO(jain): check this logic?
+        if (hasOldStyleCookie) {
+            // delegate old-style cookie formatting to rfc2109Spec
+            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
+            return rfc2109Spec.formatCookies(cookies);
+        }
 
-            // finally make sure that if cookie Secure attribute is set, then this
-            // request is using a secure connection
-            if (cookie.getSecure()) {
-                return secure;
-            }
+        /* format cookie2 cookies */
+        final StringBuffer buffer = new StringBuffer();
+        // format cookie version
+        CookieAttributeHandler handler = getAttributeHandler(Cookie2.VERSION);
+        handler.format(buffer, null);
 
-            return true;
-        }
-        else {
-            return false;
+        for (int i = 0; i < cookies.length; i++) {
+            Cookie2 cookie = (Cookie2) cookies[i];
+            // format cookie attributes
+            formatCookieAttributes(buffer, cookie);
         }
+        return buffer.toString();
     }
 
     /**
-     * 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.
+     * 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 Cookie2} to be formatted as string
+     */
+    private void formatCookieAttributes(final StringBuffer buffer, final Cookie2 cookie) {
+        // format cookie name and value
+        CookieAttributeHandler handler = getAttributeHandler(Cookie2.COOKIE_NAME_KEY);
+        handler.format(buffer, cookie);
+        // format domain attribute
+        handler = getAttributeHandler(Cookie2.DOMAIN);
+        handler.format(buffer, cookie);
+        // format path attribute
+        handler = getAttributeHandler(Cookie2.PATH);
+        handler.format(buffer, cookie);
+        // format port attribute
+        handler = getAttributeHandler(Cookie2.PORT);
+        handler.format(buffer, cookie);
+    }
+
+    /**
+     * Retrieves valid Port attribute value for the given ports array.
+     * e.g. "8000,8001,8002"
      *
-     * @param cookie1
-     * @param cookie2
-     * @return <tt>true</tt> if cookies match; otherwise false.
+     * @param ports int array of ports
      */
-    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());
+    private String createPortAttribute(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 buffer The string buffer to use for output
-     * @param cookie The {@link org.apache.commons.httpclient.Cookie} to be formatted as string
+     * Parses the given Port attribute value (e.g. "8000,8001,8002")
+     * into an array of ports.
+     *
+     * @param portValue port attribute value
+     * @return parsed array of ports
+     * @throws MalformedCookieException if there is a problem in
+     *          parsing due to invalid portValue.
      */
-    private void formatCookieAttributes(final StringBuffer buffer, final Cookie cookie) {
-        String value = cookie.getValue();
-        if (value == null) {
-            value = "";
+    private int[] parsePortAttribute(final String portValue)
+            throws MalformedCookieException {
+        StringTokenizer st = new StringTokenizer(portValue, ",");
+        int[] ports = new int[st.countTokens()];
+        try {
+            int i = 0;
+            while(st.hasMoreTokens()) {
+                ports[i] = Integer.parseInt(st.nextToken().trim());
+                if (ports[i] < 0) {
+                  throw new MalformedCookieException ("Invalid Port attribute.");
+                }
+                ++i;
+            }
+        } catch (NumberFormatException e) {
+            throw new MalformedCookieException ("Invalid Port "
+                                                + "attribute: " + e.getMessage());
         }
-        this.formatter.format(buffer, new NameValuePair(cookie.getName(), value));
+        return ports;
+    }
 
-        if (cookie.getDomain() != null
-            && cookie.isDomainAttributeSpecified()) {
-            buffer.append("; ");
-            this.formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain()));
+    /**
+     * Validates host, port, path parameters. Refactored out since it is reqd by
+     * many methods. Validation rules are:
+     * <ul>
+     *   <li>Host name must not be null or blank.</li>
+     *   <li>Path must not be null.</li>
+     *   <li>Port must be &gt;0.</li>
+     * </ul>
+     *
+     * @param host host name where cookie was received from or being sent to.
+     * @param port port of host where cookie was received from or being sent to.
+     * @param path path on host where cookie was received from or being sent to.
+     */
+    private void validateArgs(String host, int port, String path) {
+        if (host == null) {
+            throw new IllegalArgumentException(
+                    "Host of origin may not be null");
         }
-        if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
-            buffer.append("; ");
-            this.formatter.format(buffer, new NameValuePair("$Path", cookie.getPath()));
+        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.");
         }
+    }
 
-        if (cookie.isPortAttributeSpecified()) {
-            buffer.append("; ");
-            String portValue = "";
-            if (!cookie.isPortAttributeBlank()) {
-                portValue = createPortAttributeValue(cookie.getPorts());
-            }
-            this.formatter.format(buffer, new NameValuePair("$Port", portValue));
+    /**
+     * Gets attribute handler {@link CookieAttributeHandler} for the
+     * given attribute.
+     *
+     * @param name attribute name. e.g. Domain, Path, etc.
+     * @throws IllegalStateException if handler not found for the
+     *          specified attribute.
+     */
+    private CookieAttributeHandler getAttributeHandler(final String name)
+            throws IllegalStateException {
+        CookieAttributeHandler handler =
+                (CookieAttributeHandler)(attributeHandlerMap.get(name));
+        if (handler == null) {
+            throw new IllegalStateException("Handler not registered for " +
+                                            name + " attribute.");
+        } else {
+            return handler;
         }
     }
 
-    private String createPortAttributeValue(int[] ports) {
-        StringBuffer portValue = new StringBuffer();
+    /**
+     * Gets 'effective host name' as defined in RFC 2965.
+     * <p>
+     * If a host name contains no dots, the effective host name is
+     * that name with the string .local appended to it.  Otherwise
+     * the effective host name is the same as the host name.  Note
+     * that all effective host names contain at least one dot.
+     *
+     * @param host host name where cookie is received from or being sent to.
+     * @return
+     */
+    private String getEffectiveHost(String host) {
+        String effectiveHost = host;
+        if (host.indexOf('.') < 0) {
+            effectiveHost += ".local";
+        }
+        return effectiveHost;
+    }
+
+    /**
+     * Performs domain-match as defined by the RFC2965.
+     * <p>
+     * Host A's name domain-matches host B's if
+     * <ol>
+     *   <ul>their host name strings string-compare equal; or</ul>
+     *   <ul>A is a HDN string and has the form NB, where N is a non-empty
+     *       name string, B has the form .B', and B' is a HDN string.  (So,
+     *       x.y.com domain-matches .Y.com but not Y.com.)</ul>
+     * </ol>
+     *
+     * @param host host name where cookie is received from or being sent to.
+     * @param domain The cookie domain attribute.
+     * @return true if the specified host matches the given domain.
+     */
+    public boolean domainMatch(String host, String domain) {
+        boolean match = host.equals(domain)
+                        || (domain.startsWith(".") && host.endsWith(domain));
+
+        return match;
+    }
+
+    /**
+     * Returns <tt>true</tt> if the given port exists in the given
+     * ports list.
+     *
+     * @param port port of host where cookie was received from or being sent to.
+     * @param ports port list
+     * @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 (i > 0) {
-                portValue.append(",");
+            if (port == ports[i]) {
+                portInList = true;
+                break;
             }
-            portValue.append(ports[i]);
         }
-        return portValue.toString();
+        return portInList;
     }
 
     /**
-     * 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.
+     * Casts the given {@link Cookie} cookie to {@link Cookie2} cookie.
+     * @param cookieParam {@link Cookie}
+     * @return {@link Cookie2}
      */
-    public String formatCookie(Cookie cookie) {
-        LOG.trace("enter RFC2965Spec.formatCookie(Cookie)");
+    private Cookie2 getCookie2Cookie(Cookie cookieParam) {
+        if (!(cookieParam instanceof Cookie2)) {
+            throw new IllegalArgumentException("Expected Cookie2 cookie.");
+        }
+        return ((Cookie2) cookieParam);
+    }
 
-        if (cookie == null) {
-            throw new IllegalArgumentException("Cookie may not be null");
+    /**
+     * <tt>"Path"</tt> attribute handler for RFC 2965 cookie spec.
+     */
+    private class Cookie2PathAttributeHandler
+            implements CookieAttributeHandler {
+
+        /**
+         * Parse cookie path attribute.
+         * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void parse(Cookie cookieParam, String path)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (!cookie.isPathAttributeSpecified()) {
+                if (path == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for path attribute");
+                }
+                if (path.trim().equals("")) {
+                    throw new MalformedCookieException(
+                            "Blank value for path attribute");
+                }
+                cookie.setPath(path);
+                cookie.setPathAttributeSpecified(true);
+            }
         }
-        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);
+
+        /**
+         * Validate cookie path attribute. The value for the Path attribute must be a
+         * prefix of the request-URI (case-sensitive matching).
+         * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void validate(Cookie cookieParam, String path)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (path == null) {
+                throw new IllegalArgumentException(
+                        "Path of origin host may not be null.");
+            }
+            if (cookie.getPath() == null) {
+                throw new MalformedCookieException("Invalid cookie state: " +
+                                                   "path attribute is null.");
+            }
+            if (path.trim().equals("")) {
+                path = PATH_DELIM;
+            }
+
+            if (!pathMatch(path, cookie.getPath())) {
+                throw new MalformedCookieException(
+                        "Illegal path attribute \"" + cookie.getPath()
+                        + "\". Path of origin: \"" + path + "\"");
+            }
+
         }
 
-        StringBuffer buffer = new StringBuffer();
-        this.formatter.format(buffer,
-                              new NameValuePair("$Version", Integer.toString(version)));
-        buffer.append("; ");
-        formatCookieAttributes(buffer, cookie);
+        /**
+         * Match cookie path attribute. The value for the Path attribute must be a
+         * prefix of the request-URI (case-sensitive matching).
+         * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
+         */
+        public boolean match(Cookie cookieParam, String path) {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (path == null) {
+                throw new IllegalArgumentException(
+                        "Path of destination host may not be null.");
+            }
+            if (cookie.getPath() == null) {
+                LOG.warn("Invalid cookie state: path attribute is null.");
+                return false;
+            }
+            if (path.trim().equals("")) {
+                path = PATH_DELIM;
+            }
 
-        return buffer.toString();
+            if (!pathMatch(path, cookie.getPath())) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Format cookie Path attribute.
+         * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie)
+         */
+        public void format(StringBuffer buffer, Cookie cookieParam) {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if ((cookie.getPath() != null) && (cookie.isPathAttributeSpecified())) {
+                buffer.append("; ");
+                formatter.format(buffer, new NameValuePair("$Path", cookie.getPath()));
+            }
+        }
     }
 
     /**
-     * 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.
+     * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec.
      */
-    public String formatCookies(Cookie[] cookies) {
-        LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])");
+    private class Cookie2DomainAttributeHandler
+            implements CookieAttributeHandler {
 
-        if (cookies == null) {
-            throw new IllegalArgumentException("Cookies may not be null");
+        /**
+         * Parse cookie domain attribute.
+         * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void parse(Cookie cookieParam, String domain)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (!cookie.isDomainAttributeSpecified()) {
+                //TODO (jain): how do we handle the case when domain is specified and equals host?
+                if (domain == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for domain attribute");
+                }
+                if (domain.trim().equals("")) {
+                    throw new MalformedCookieException(
+                            "Blank value for domain attribute");
+                }
+                domain = domain.toLowerCase();
+                // put a leading dot if domain does not start with a dot
+                if (!domain.startsWith("."))
+                    domain = "." + domain;
+                cookie.setDomain(domain);
+                cookie.setDomainAttributeSpecified(true);
+            }
         }
-        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();
+
+        /**
+         * Validate cookie domain attribute.
+         * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void validate(Cookie cookieParam, String host)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            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 (cookie.getDomain() == null) {
+                throw new MalformedCookieException("Invalid cookie state: " +
+                                                   "domain not specified");
+            }
+            host = host.toLowerCase();
+            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);
+                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 + "\"");
+                }
             }
         }
 
-        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;
+        /**
+         * Match cookie domain attribute.
+         * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
+         */
+        public boolean match(Cookie cookieParam, String host) {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (host == null) {
+                throw new IllegalArgumentException(
+                        "Destination Host may not be null");
+            }
+            if (host.trim().equals("")) {
+                throw new IllegalArgumentException(
+                        "Destination Host may not be blank");
+            }
+            if (cookie.getDomain() == null) {
+                LOG.warn("Invalid cookie state: domain not specified");
+                return false;
             }
+            host = host.toLowerCase();
+            String cookieDomain = cookie.getDomain();
+            String effectiveHost = getEffectiveHost(host);
+
+            // The effective host name MUST domain-match the Domain
+            // attribute of the cookie.
+            if (!domainMatch(effectiveHost, cookieDomain)) {
+                return false;
+            }
+            // effective host name minus domain must not contain any dots
+            String effectiveHostWithoutDomain =
+                    effectiveHost.substring(0, effectiveHost.length()
+                                               - cookieDomain.length());
+            if (effectiveHostWithoutDomain.indexOf('.') != -1) {
+                return false;
+            }
+            return true;
         }
 
-        if ((lowestVersion < 1) || hasOldStyleCookie) {
-            // delegate cookie formatting to rfc2109Spec
-            CookieSpec rfc2109Spec = CookiePolicy.getCookieSpec(CookiePolicy.RFC_2109);
-            return rfc2109Spec.formatCookies(cookies);
+        /**
+         * Format cookie domain attribute.
+         * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie)
+         */
+        public void format(StringBuffer buffer, Cookie cookieParam) {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (cookie.getDomain() != null
+                && cookie.isDomainAttributeSpecified()) {
+                buffer.append("; ");
+                formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain()));
+            }
         }
+    }
 
-        final StringBuffer buffer = new StringBuffer();
-        this.formatter.format(buffer,
-            new NameValuePair("$Version", Integer.toString(lowestVersion)));
+    /**
+     * <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec.
+     */
+    private class Cookie2PortAttributeHandler
+            implements CookieAttributeHandler {
 
-        for (int i = 0; i < cookies.length; i++) {
-            buffer.append("; ");
-            formatCookieAttributes(buffer, cookies[i]);
+        /**
+         * Parse cookie port attribute.
+         * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void parse(Cookie cookieParam, String portValue)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (!cookie.isPortAttributeSpecified()) {
+                if ((portValue == null) || (portValue.trim().equals(""))) {
+                    // If the Port attribute is present but has no value, the
+                    // cookie can only be sent to the request-port.
+                    // Since the default port list contains only request-port, we don't
+                    // need to do anything here.
+                    cookie.setPortAttributeBlank(true);
+                } else {
+                    int[] ports = parsePortAttribute(portValue);
+                    cookie.setPorts(ports);
+                }
+                cookie.setPortAttributeSpecified(true);
+            }
+        }
+
+        /**
+         * Validate cookie port attribute. If the Port attribute was specified
+         * in header, the request port must be in cookie's port list.
+         * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void validate(Cookie cookieParam, String value)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            int port = -1;
+            try {
+                port = Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+                port = -1;
+            }
+            if (port < 0) {
+                throw new IllegalArgumentException("Invalid port of host.");
+            }
+
+            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.");
+                }
+            }
+        }
+
+        /**
+         * Match 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.
+         * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
+         */
+        public boolean match(Cookie cookieParam, String value) {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            int port = -1;
+            try {
+                port = Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+                port = -1;
+            }
+            if (port < 0) {
+                throw new IllegalArgumentException("Invalid port of destination: " + value);
+            }
+
+            if (cookie.isPortAttributeSpecified()) {
+                if (cookie.getPorts() == null) {
+                    LOG.warn("Invalid cookie state: port not specified");
+                    return false;
+                }
+                if (!portMatch(port, cookie.getPorts())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie)
+         */
+        public void format(StringBuffer buffer, Cookie cookieParam) {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (cookie.isPortAttributeSpecified()) {
+                String portValue = "";
+                if (!cookie.isPortAttributeBlank()) {
+                    portValue = createPortAttribute(cookie.getPorts());
+                }
+                buffer.append("; ");
+                formatter.format(buffer, new NameValuePair("$Port", portValue));
+            }
         }
-        return buffer.toString();
     }
 
+  /**
+   * <tt>"Name"</tt> cookie attribute handler for RFC 2965 cookie spec.
+   */
+  private class Cookie2NameAttributeHandler
+          implements CookieAttributeHandler {
+
+      /**
+       * Parse cookie name.
+       * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String)
+       */
+      public void parse(Cookie cookie, String value)
+              throws MalformedCookieException {
+      }
+
+      /**
+       * validate cookie name.
+       * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String)
+       */
+      public void validate(Cookie cookieParam, String value)
+              throws MalformedCookieException {
+          if (cookieParam == null) {
+              throw new IllegalArgumentException("Cookie may not be null");
+          }
+          Cookie2 cookie = getCookie2Cookie(cookieParam);
+          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 $");
+            }
+      }
+
+      /**
+       * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
+       */
+      public boolean match(Cookie cookie, String value) {
+          return true;
+      }
+
+      /**
+       * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie)
+       */
+      public void format(StringBuffer buffer, Cookie cookieParam) {
+          if (cookieParam == null) {
+              throw new IllegalArgumentException("Cookie may not be null");
+          }
+          Cookie2 cookie = getCookie2Cookie(cookieParam);
+          // format cookie name and value
+          String value = cookie.getValue();
+          if (value == null) {
+              value = "";
+          }
+          buffer.append("; ");
+          formatter.format(buffer, new NameValuePair(cookie.getName(), value));
+      }
+  }
+
+  /**
+   * <tt>"Max-age"</tt> cookie attribute handler for RFC 2965 cookie spec.
+   */
+  private class Cookie2MaxageAttributeHandler
+          implements CookieAttributeHandler {
+
+      /**
+       * Parse cookie max-age attribute.
+       * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String)
+       */
+      public void parse(Cookie cookieParam, String value)
+              throws MalformedCookieException {
+          if (cookieParam == null) {
+              throw new IllegalArgumentException("Cookie may not be null");
+          }
+          Cookie2 cookie = getCookie2Cookie(cookieParam);
+          if (cookie.getExpiryDate() == null) {
+                if (value == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for max-age attribute");
+                }
+                int age = -1;
+                try {
+                    age = Integer.parseInt(value);
+                } catch (NumberFormatException e) {
+                    age = -1;
+                }
+                if (age < 0) {
+                    throw new MalformedCookieException ("Invalid max-age attribute.");
+                }
+                cookie.setExpiryDate(
+                        new Date(System.currentTimeMillis() + age * 1000L));
+            }
+      }
+
+      /**
+       * validate cookie max-age attribute.
+       * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String)
+       */
+      public void validate(Cookie cookie, String value) {
+      }
+
+      /**
+       * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
+       */
+      public boolean match(Cookie cookie, String value) {
+          return true;
+      }
+
+      /**
+       * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie)
+       */
+      public void format(StringBuffer buffer, Cookie cookie) {
+      }
+  }
+
     /**
-     * Gets the highest cookie version supported by this cookie specification.
-     *
-     * @return highest cookie version supported.
+     * <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec.
      */
-    public int getCookieVersion() {
-        return 1;
+    private class Cookie2VersionAttributeHandler
+            implements CookieAttributeHandler {
+
+        /**
+         * Parse cookie version attribute.
+         * @see CookieAttributeHandler#parse(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void parse(Cookie cookieParam, String value)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (!cookie.isVersionAttributeSpecified()) {
+                if (value == null) {
+                    throw new MalformedCookieException(
+                            "Missing value for version attribute");
+                }
+                int version = -1;
+                try {
+                    version = Integer.parseInt(value);
+                } catch (NumberFormatException e) {
+                    version = -1;
+                }
+                if (version < 0) {
+                    throw new MalformedCookieException("Invalid cookie version.");
+                }
+                cookie.setVersion(Integer.parseInt(value));
+                cookie.setVersionAttributeSpecified(true);
+            }
+        }
+
+        /**
+         * validate cookie version attribute. Version attribute is REQUIRED.
+         * @see CookieAttributeHandler#validate(org.apache.commons.httpclient.Cookie, String)
+         */
+        public void validate(Cookie cookieParam, String value)
+                throws MalformedCookieException {
+            if (cookieParam == null) {
+                        throw new IllegalArgumentException("Cookie may not be null");
+            }
+            Cookie2 cookie = getCookie2Cookie(cookieParam);
+            if (!cookie.isVersionAttributeSpecified()) {
+                throw new MalformedCookieException(
+                        "Violates RFC 2965. Version attribute is required.");
+            }
+            //TODO (jain): other versions for set-cookie2 ?
+            if (cookie.getVersion() != 1) {
+                throw new MalformedCookieException(
+                        "Violates RFC 2965. Invalid value for Version attribute." +
+                        "Must be \"1\".");
+            }
+        }
+
+        /**
+         * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
+         */
+        public boolean match(Cookie cookie, String value) {
+            return true;
+        }
+
+        /**
+         * @see CookieAttributeHandler#format(StringBuffer, org.apache.commons.httpclient.Cookie)
+         */
+        public void format(StringBuffer buffer, Cookie cookie) {
+            formatter.format(buffer, new NameValuePair("$Version", "1"));
+        }
     }
 }

Added: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookie2.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookie2.java?rev=264149&view=auto
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookie2.java (added)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookie2.java Mon Aug 29 07:28:21 2005
@@ -0,0 +1,127 @@
+/*
+ * $Header: /cvsroot/httpc-cookie2/httpc-cookie2/httpcookie2SVN-patch.082805-2100.diff,v 1.1 2005/08/29 05:01:58 sjain700 Exp $
+ * $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 java.util.*;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.commons.httpclient.Cookie;
+import org.apache.commons.httpclient.Header;
+
+
+/**
+ * Test cases for {@link Cookie2}.
+ *
+ * @author Samit Jain (jain.samit@gmail.com)
+ */
+public class TestCookie2 extends TestCookieBase {
+
+
+    // ------------------------------------------------------------ Constructor
+
+    public TestCookie2(String name) {
+        super(name);
+    }
+
+    // ------------------------------------------------------- TestCase Methods
+
+    public static Test suite() {
+        return new TestSuite(TestCookie2.class);
+    }
+
+    /**
+     * Tests default constructor.
+     */
+    public void testDefaultConstuctor() {
+        Cookie2 dummy = new Cookie2();
+        // check cookie properties (default values)
+        assertNull(dummy.getPorts());
+        assertFalse(dummy.getSecure());
+        assertFalse(dummy.isExpired());
+        assertFalse(dummy.isDomainAttributeSpecified());
+        assertFalse(dummy.isPathAttributeSpecified());
+        assertFalse(dummy.isPortAttributeSpecified());
+        assertFalse(dummy.isVersionAttributeSpecified());
+        assertFalse(dummy.isPersistent());
+
+        Cookie2 dummy2 = new Cookie2();
+        assertEquals(dummy, dummy2);
+    }
+
+    public void testComparator() throws Exception {
+        Header setCookie2 = null;
+        Cookie[] parsed = null;
+        List cookies = new LinkedList();
+        CookieSpec cookiespec = new RFC2965Spec();
+        // Cookie 0
+        setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie0; Version=1");
+        parsed = cookieParse(cookiespec, "domain.com", 80,
+                             "/path/path1", true, setCookie2);
+        cookies.add(parsed[0]);
+        // Cookie 1
+        setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie1; Version=1");
+        parsed = cookieParse(cookiespec, "domain.com", 80, "/path", true, setCookie2);
+        cookies.add(parsed[0]);
+        // Cookie 2
+        setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie2; Version=1");
+        parsed = cookieParse(cookiespec, "domain.com", 80, "/", true, setCookie2);
+        cookies.add(parsed[0]);
+        // Cookie 3
+        setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie3; Version=1");
+        parsed = cookieParse(cookiespec, "domain.com", 80,
+                             "/path/path1/path2", true, setCookie2);
+        cookies.add(parsed[0]);
+        // Cookie 4
+        setCookie2 = new Header("Set-Cookie2","cookie-name=Cookie4; Version=1");
+        parsed = cookieParse(cookiespec, "domain.com", 80,
+                             "/path/path1/path2/path3", true, setCookie2);
+        cookies.add(parsed[0]);
+
+        // The ascending order should be:
+        // 2, 1, 0, 3, 4
+        int[] expectedOrder = new int[] {2, 1, 0, 3, 4};
+        Set sortedCookies = new TreeSet(parsed[0]);
+        sortedCookies.addAll(cookies);
+
+        int pass = 0;
+        for (Iterator itr = sortedCookies.iterator(); itr.hasNext(); ++pass) {
+            Cookie2 cookie = (Cookie2) itr.next();
+            assertTrue("sortedCookies[" + pass + "] should be cookies[" + expectedOrder[pass] + "]",
+                       cookie == cookies.get(expectedOrder[pass]));
+        }
+
+        try {
+            parsed[0].compare(parsed[0], "foo");
+            fail("Should have thrown an exception trying to compare non-cookies");
+        } catch (ClassCastException expected) {}
+    }
+}
+

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

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

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

Modified: jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java?rev=264149&r1=264148&r2=264149&view=diff
==============================================================================
--- jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java (original)
+++ jakarta/commons/proper/httpclient/branches/COOKIE_2_BRANCH/src/test/org/apache/commons/httpclient/cookie/TestCookieAll.java Mon Aug 29 07:28:21 2005
@@ -43,8 +43,10 @@
     public static Test suite() {
         TestSuite suite = new TestSuite();
         suite.addTest(TestCookie.suite());
+        suite.addTest(TestCookie2.suite());
         suite.addTest(TestCookieCompatibilitySpec.suite());
         suite.addTest(TestCookieRFC2109Spec.suite());
+        suite.addTest(TestCookieRFC2965Spec.suite());
         suite.addTest(TestCookieNetscapeDraft.suite());
         suite.addTest(TestCookieIgnoreSpec.suite());
         suite.addTest(TestCookiePolicy.suite());



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