Return-Path: Delivered-To: apmail-jakarta-commons-dev-archive@www.apache.org Received: (qmail 15177 invoked from network); 29 Aug 2005 17:15:47 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 29 Aug 2005 17:15:47 -0000 Received: (qmail 81264 invoked by uid 500); 29 Aug 2005 17:15:36 -0000 Delivered-To: apmail-jakarta-commons-dev-archive@jakarta.apache.org Received: (qmail 81128 invoked by uid 500); 29 Aug 2005 17:15:36 -0000 Mailing-List: contact commons-dev-help@jakarta.apache.org; run by ezmlm Precedence: bulk List-Unsubscribe: List-Help: List-Post: List-Id: "Jakarta Commons Developers List" Reply-To: "Jakarta Commons Developers List" Delivered-To: mailing list commons-dev@jakarta.apache.org Received: (qmail 81062 invoked by uid 500); 29 Aug 2005 17:15:34 -0000 Received: (qmail 80880 invoked by uid 99); 29 Aug 2005 17:15:33 -0000 X-ASF-Spam-Status: No, hits=-9.8 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Mon, 29 Aug 2005 10:15:29 -0700 Received: (qmail 67397 invoked by uid 65534); 29 Aug 2005 14:28:48 -0000 Message-ID: <20050829142848.67395.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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/ Date: Mon, 29 Aug 2005 14:28:43 -0000 To: commons-cvs@jakarta.apache.org From: olegk@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N 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 /** - *

RFC 2965 specific cookie management functions - * - * @author Samit Jain + *

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 Cookies. * *

The syntax for the Set-Cookie2 response header is: * *

-     * 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 Cookies.
-     *
-     * 

The syntax for the Set-Cookie2 response header is: - * - *

-     * 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
-     * 
- * - * @param host the host from which the Set-Cookie2 value was - * received - * @param port the port from which the Set-Cookie2 value was - * received - * @param path the path from which the Set-Cookie2 value was - * received - * @param secure true when the Set-Cookie2 value was - * received over secure conection - * @param header the Set-Cookie2 header string received from the server - * @return an array of Cookies 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 - * Set-Cookie2 header or Set-Cookie header (in case no Set-Cookie2 header was - * received). - * @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated + * Set-Cookie2 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 true 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 true if the given port exists in the given - * ports list. Used to validate cookie. - * - * @param port - * @param ports - * @return true returns true if the given port exists in - * the given ports list; false 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 true 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 "Cookie" 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 "Cookie" 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 "Cookie" header value containing all + * {@link org.apache.commons.httpclient.Cookie}s suitable for + * sending in a "Cookie" 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 true 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 "Cookie" 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 true 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 "Cookie" 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: + *
    + *
  • Host name must not be null or blank.
  • + *
  • Path must not be null.
  • + *
  • Port must be >0.
  • + *
+ * + * @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. + *

+ * 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. + *

+ * Host A's name domain-matches host B's if + *

    + *
      their host name strings string-compare equal; or
    + *
      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.)
    + *
+ * + * @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 true 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 true if the given port exists in + * the given ports list; false 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 "Cookie" 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 "Cookie" 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"); + /** + * "Path" 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 "Cookie" header value containing all - * {@link org.apache.commons.httpclient.Cookie}s in cookies suitable for sending in a "Cookie" - * 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. + * "Domain" 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))); + /** + * "Port" 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(); } + /** + * "Name" 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)); + } + } + + /** + * "Max-age" 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. + * "Version" 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 + * . + * + */ + +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