You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by js...@apache.org on 2002/12/08 07:09:47 UTC

cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestCookie.java TestWebappCookie.java

jsdever     2002/12/07 22:09:46

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        Cookie.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestCookie.java TestWebappCookie.java
  Added:       httpclient/src/java/org/apache/commons/httpclient/cookie
                        CookiePolicy.java CookieSpec.java
                        CookieSpecBase.java HttpMethodBase.java
                        MalformedCookieException.java
                        NetscapeDraftSpec.java RFC2109Spec.java
  Log:
  Cookie refactoring into a new cookie package.
  
  Changes:
  
  - Policy driven cookie management
  - Improved Netscape draft compliance
  - Full RFC2109 compliance
  - Compatibility mode to simulate relaxed cookie management of popular
  web browsers
  - In strict mode all the cookies are still crammed into one Cookie
  header. In non-strict mode, however, each cookie is sent in a separate
  header
  
  Bug fixes:
  - Cookie constructor now rejects cookie name containing blanks
  - Cookie constructor now not rejects cookie name starting with $ (That's
          a bad one)
  - Netscape compliant spec now properly formats Cookie header value
  (domain and path attributes not included)
  - RFC2109 compliant spec now properly formats Cookie header value
  (cookie value and attribute values are quoted)
  
  Contributed by: Oleg Kalnichevski
  
  Revision  Changes    Path
  1.29      +35 -460   jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Cookie.java
  
  Index: Cookie.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Cookie.java,v
  retrieving revision 1.28
  retrieving revision 1.29
  diff -u -r1.28 -r1.29
  --- Cookie.java	2 Dec 2002 06:14:56 -0000	1.28
  +++ Cookie.java	8 Dec 2002 06:09:46 -0000	1.29
  @@ -66,18 +66,14 @@
   import org.apache.commons.logging.LogFactory;
   
   import java.io.Serializable;
  -import java.text.DateFormat;
  -import java.text.ParseException;
   import java.text.RuleBasedCollator;
  -import java.text.SimpleDateFormat;
   import java.util.Comparator;
   import java.util.Date;
   import java.util.Iterator;
   import java.util.LinkedList;
   import java.util.List;
   import java.util.Locale;
  -import java.util.StringTokenizer;
  -
  +import org.apache.commons.httpclient.cookie.*;
   
   
   /**
  @@ -141,6 +137,12 @@
           if (name.equals("")) {
               throw new IllegalArgumentException("Cookie name may not be blank");
           }
  +        if (name.indexOf(' ') != -1) {
  +            throw new IllegalArgumentException("Cookie name may contain blanks");
  +        }
  +        if (name.startsWith("$")) {
  +            throw new IllegalArgumentException("Cookie name may start with $");
  +        }
           this.setPath(path);
           this.setDomain(domain);
           this.setExpiryDate(expires);
  @@ -443,17 +445,7 @@
        * @return a string suitable for sending in a Cookie header.
        */
       public String toExternalForm() {
  -        StringBuffer buf = new StringBuffer();
  -        buf.append(getName()).append("=").append(getValue());
  -        if (_path != null && isPathAttributeSpecified()) {
  -            buf.append("; $Path=");
  -            buf.append(_path);
  -        }
  -        if (_domain != null && isDomainAttributeSpecified()) {
  -            buf.append("; $Domain=");
  -            buf.append(_domain);
  -        }
  -        return buf.toString();
  +        return CookiePolicy.getSpecByVersion(getVersion()).formatCookie(this);
       }
   
       /**
  @@ -465,49 +457,11 @@
        * @param secure <tt>true</tt> if the request is using the HTTPS protocol
        * @param date the time at which the request is submitted
        */
  -    public boolean matches(String domain, int port, String path, boolean secure, Date date) {
  +    public boolean matches(String domain, int port, String path, boolean secure, Date date)
  +    {
           log.trace("enter Cookie.matches(Strinng, int, String, boolean, Date");
  -        // FIXME: RFC2109 doesn't consider ports when filtering/matching
  -        //        cookies. Quoting from Section 2 - Terminology:
  -        //          The terms request-host and request-URI refer to
  -        //          the values the client would send to the server
  -        //          as, respectively, the host (but not port) and
  -        //          abs_path portions of the absoluteURI (http_URL)
  -        //          of the HTTP request line.
  -        //
  -        //        RFC2965 includes ports in cookie-sending
  -        //        determination, but only when the cookie is received
  -        //        via a 'Set-Cookie2' header.
  -        //
  -        //        The current implementation doesn't support RFC2965,
  -        //        and ignores ports when matching cookies.
  -        if (domain == null) {
  -            throw new IllegalArgumentException("domain parameter may not be null");
  -        }
  -        if (path == null) {
  -            throw new IllegalArgumentException("path parameter may not be null");
  -        }
  -        if (date == null) {
  -            throw new IllegalArgumentException("date parameter may not be null");
  -        }
  -        if (getDomain() == null) {
  -            log.warn("Invalid cookie state: domain not specified");
  -            return false;
  -        }
  -        if (getPath() == null) {
  -            log.warn("Invalid cookie state: path not specified");
  -            return false;
  -        }
  -        
  -        domain = domain.toLowerCase();
  -
  -        return (
  -                (getExpiryDate() == null || 
  -                 getExpiryDate().after(date)) &&      // only add the cookie if it hasn't yet expired
  -                (domainMatch(domain, getDomain())) && // and the domain pattern matches
  -                (pathMatch(path, getPath())) &&       // and the path is null or matching
  -                (getSecure() ? secure : true)         // and if the secure flag is set, only if the request is actually secure
  -               );
  +        CookieSpec matcher = CookiePolicy.getDefaultSpec();
  +        return matcher.match(domain, port, path, secure, this);
       }
   
       /**
  @@ -593,49 +547,20 @@
        * If no cookies match, returns null.
        * @exception java.lang.IllegalArgumentException if domain or path is null
        */
  +
       public static Header createCookieHeader(String domain, int port, String path, boolean secure, Date now, Cookie[] cookies) 
       throws IllegalArgumentException {
  -	 log.trace("enter Cookie.createCookieHeader(String, int, String, boolean, Date, Cookie[])");
  -
  -        // Validate the domain and path
  -        if(domain == null){
  -            throw new IllegalArgumentException("null domain in createCookieHeader.");
  -        }
  -        if(path == null){
  -            throw new IllegalArgumentException("null path in createCookieHeader.");
  -        }
  -
  -        boolean added = false;
  -        StringBuffer value = new StringBuffer();
  -
  -        if (cookies.length <= 0) {
  -            return null;
  -        }
  -        List addedCookies = new LinkedList();
  -        for(int i=0;i<cookies.length;i++) {
  -            if(cookies[i].matches(domain,port,path,secure,now)) {
  -                addInPathOrder(addedCookies, cookies[i]);
  -                added = true;
  -            }
  +        log.trace("enter Cookie.createCookieHeader(String, int, String, boolean, Date, Cookie[])");
  +        CookieSpec matcher = CookiePolicy.getDefaultSpec();
  +        cookies = matcher.match(domain, port, path, secure, cookies);
  +        if ((cookies != null) && (cookies.length > 0))
  +        {
  +            return matcher.formatCookieHeader(cookies);
           }
  -        if (added) {
  -            /* FIXME.  We assume here that all the cookies we're sending back
  -             * are the same version.  A better solution might be to separate
  -             * cookies of different versions and send them on separate cookie
  -             * headers.
  -             */
  -            value.append("$Version=");
  -            value.append(((Cookie)addedCookies.get(0)).getVersion());
  -            for (Iterator itr = addedCookies.iterator(); itr.hasNext(); ) {
  -                Cookie cookie = (Cookie)itr.next();
  -                value.append("; ");
  -                value.append(cookie.toExternalForm());
  -            }
  -
  -            return new Header("Cookie", value.toString());
  -        } else {
  +        else
  +        {
               return null;
  -        }
  +        } 
       }
   
       /**
  @@ -662,14 +587,14 @@
               return 0;
           } else if (c1.getPath() == null) {
               // null is assumed to be "/"
  -            if (c2.getPath().equals(PATH_DELIM)) {
  +            if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
                   return 0;
               } else {
                   return -1;
               }
           } else if (c2.getPath() == null) {
               // null is assumed to be "/"
  -            if (c1.getPath().equals(PATH_DELIM)) {
  +            if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
                   return 0;
               } else {
                   return 1;
  @@ -774,349 +699,18 @@
           throws HttpException, IllegalArgumentException {
   	log.trace("enter Cookie.parse(String, int, String, boolean, Header)");
   
  -        // Validate domain and path
  -        if(domain == null){
  -            throw new IllegalArgumentException("domain may not be null.");
  -        }
  -        if(path == null){
  -            throw new IllegalArgumentException("path may not be null.");
  -        }
  -        if(setCookie == null){
  -            throw new IllegalArgumentException("set-cookie header may not be null.");
  -        }
  -
  -        // Normalize domain name
  -        domain = domain.toLowerCase();
  -
  -        /* Build the default path.  Per RFC 2109/4.3.1 this is the 
  -         * request path up to, but not including, the right-most / charater.
  -         */
  -        if (path.length() == 0) {
  -            log.debug("Fixing up empty request path.");
  -            path = PATH_DELIM;
  -        }
  -        String defaultPath = null;
  -        int lastSlashIndex = path.lastIndexOf(PATH_DELIM);
  -        if(lastSlashIndex == 0){
  -            defaultPath = PATH_DELIM;
  -        }
  -        else if(lastSlashIndex > 0) {
  -            defaultPath = path.substring(0, lastSlashIndex);
  -        }
  -        else {
  -            defaultPath = path;
  -        }
  -        
  -        HeaderElement[] headerElements =
  -            HeaderElement.parse(setCookie.getValue());
  -
  -        Cookie[] cookies = new Cookie[headerElements.length];
  -
  -        int index = 0;
  -        for (int i = 0; i < headerElements.length; i++) {
  -
  -            HeaderElement headerelement = headerElements[i];
  -            Cookie cookie = new Cookie(domain,
  -                                       headerelement.getName(),
  -                                       headerelement.getValue(),
  -                                       defaultPath, 
  -                                       null,
  -                                       false);
  -
  -            // cycle through the parameters
  -            NameValuePair[] parameters = headerelement.getParameters();
  -            // could be null. In case only a header element and no parameters.
  -            if (parameters != null) {
  -
  -                for (int j = 0; j < parameters.length; j++) {
  -
  -                    String param_name = parameters[j].getName().toLowerCase();
  -                    String param_value = parameters[j].getValue();
  -
  -                    if (param_name.equals("version")) {
  -
  -                        if (param_value == null) {
  -                            throw new HttpException("Missing value for version attribute");
  -                        }
  -                        try {
  -                           cookie.setVersion(Integer.parseInt(param_value));
  -                        } catch (NumberFormatException e) {
  -                            throw new HttpException( "Invalid version attribute: " + e.getMessage());
  -                        }
  -
  -                    } 
  -                    else if (param_name.equals("path")) {
  -
  -                        if (param_value == null) {
  -                            throw new HttpException("Missing value for path attribute");
  -                        }
  -                        cookie.setPath(param_value);
  -                        cookie.setPathAttributeSpecified(true);
  -
  -                    } 
  -                    else if (param_name.equals("domain")) {
  -
  -                        if (param_value == null) {
  -                            throw new HttpException("Missing value for domain attribute");
  -                        }
  -                        cookie.setDomain(param_value);
  -                        cookie.setDomainAttributeSpecified(true);
  -
  -                    } 
  -                    else if (param_name.equals("max-age")) {
  -
  -                        if (param_value == null) {
  -                            throw new HttpException("Missing value for max-age attribute");
  -                        }
  -                        int age;
  -                        try {
  -                            age = Integer.parseInt(param_value);
  -                        } catch (NumberFormatException e) {
  -                            throw new HttpException( "Invalid max-age attribute: " + e.getMessage());
  -                        }
  -                        cookie.setExpiryDate(new Date(System.currentTimeMillis() +
  -                                age * 1000L));
  -
  -                    } 
  -                    else if (param_name.equals("secure")) {
  -
  -                        cookie.setSecure(true);
  -
  -                    } 
  -                    else if (param_name.equals("comment")) {
  -
  -                        cookie.setComment(param_value);
  -
  -                    } 
  -                    else if (param_name.equals("expires")) {
  -
  -                        if (param_value == null) {
  -                            throw new HttpException("Missing value for expires attribute");
  -                        }
  -                        boolean set = false;
  -                        // trim single quotes around expiry if present
  -                        // see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279
  -                        if(param_value.length() > 1 &&
  -                                param_value.startsWith("'") &&
  -                                param_value.endsWith("'")) {
  -                            param_value = param_value.substring(1,param_value.length()-1);
  -                        }
  -
  -                        for(int k=0;k<expiryFormats.length;k++) {
  -
  -                            try {
  -                                Date date = expiryFormats[k].parse(param_value);
  -                                cookie.setExpiryDate(date);
  -                                set = true;
  -                                break;
  -                            } catch (ParseException e) {
  -                                //Ignore and move on
  -                            }
  -                        }
  -                        if(!set) {
  -                            throw new HttpException("Unable to parse expiration date parameter: " + param_value);
  -                        }
  -                    }
  -                }
  -            }
  -            
  -            // Check if the cookie has all required attributes. If not, apply defaults
  -            if (cookie.getDomain() == null) {
  -                cookie.setDomain(domain);
  -                cookie.setDomainAttributeSpecified(false);
  -                if (log.isDebugEnabled()) {
  -                    log.debug("Domain attribute not explicitly set. Default applied: \"" + domain + "\"");
  -                }
  -            }
  -            if (cookie.getPath() == null) {
  -                cookie.setPath(defaultPath);
  -                cookie.setPathAttributeSpecified(false);
  -                if (log.isDebugEnabled()) {
  -                    log.debug("Path attribute not explicitly set. Default applied: \"" + defaultPath + "\"");
  -                }
  -            }
  +        CookieSpec parser = CookiePolicy.getDefaultSpec();
  +        Cookie[] cookies = parser.parse(domain, port, path, secure, setCookie);
   
  -            validate(cookie, domain, port, path, secure);
  -            
  -            if(log.isDebugEnabled()){
  -                log.debug("Cookie accepted: \"" + cookie.toString() +"\"");
  -            }
  -            cookies[index++] = cookie;
  +        for (int i = 0; i < cookies.length; i++)
  +        {
  +            Cookie cookie = cookies[i];
  +            CookieSpec validator = CookiePolicy.getSpecByVersion(cookie.getVersion());
  +            validator.validate(domain, port, path, secure, cookie);
           }
           return cookies;
       }
   
  -
  -    private static void validate(Cookie cookie, String domain, int port, String path, boolean secure)
  -        throws HttpException {
  -        log.trace("enter Cookie.validate(String, int, String, boolean)");
  -        // This private method does not validate any input parameters under assumption
  -        // that parameters are properly invalidated by the caller
  -
  -        // check version
  -        if (cookie.getVersion() < 0) {
  -            throw new HttpException( "Illegal version number " + cookie.getValue());
  -        }
  -
  -        // security check... we musn't allow the server to give us an
  -        // invalid domain scope
  -
  -        // Validate the cookies domain attribute.  NOTE:  Domains without any dots are
  -        // allowed to support hosts on private LANs that don't have DNS names.  Since
  -        // they have no dots, to domain-match the request-host and domain must be identical
  -        // for the cookie to sent back to the origin-server.
  -        if (!cookie.getDomain().equals("localhost") && domain.indexOf(".") >= 0) {
  -
  -            // Not required to have at least two dots.  RFC 2965.
  -            // A Set-Cookie2 with Domain=ajax.com will be accepted.
  -
  -            // domain must domain match host
  -            if (!domain.endsWith(cookie.getDomain())){
  -         
  -                throw new HttpException(
  -                    "Illegal domain attribute \"" + cookie.getDomain() + "\". Domain of origin: \"" + domain + "\"");
  -         
  -            }
  -
  -            switch(cookie.getVersion()) {
  -                case 0:
  -                    // Validate domain using Netscape cookie specification
  -                    validateDomainAttribVer0(cookie, domain);
  -                    break;
  -                default:
  -                    validateDomainAttribVer1(cookie, domain);
  -                    break;
  -            }
  -        }
  -
  -        // another security check... we musn't allow the server to give us a
  -        // cookie that doesn't match this path
  -
  -        if(!path.startsWith(cookie.getPath())) {
  -
  -                throw new HttpException(
  -                    "Illegal path attribute \"" + cookie.getPath() + "\". Path of origin: \"" + path + "\"");
  -        }
  -    }
  -
  -    /**
  -     * Validate domain attribute using Netscape cookie specification draft
  -     */
  -
  -    private static void validateDomainAttribVer0(final Cookie cookie, final String domain)
  -        throws HttpException {
  -        log.trace("enter Cookie.validateDomainAttribVer0()");
  -
  -        int domainParts = new StringTokenizer(cookie.getDomain(), ".").countTokens();
  -
  -        if (isSpecialDomain(cookie.getDomain())) {
  -            
  -            if(domainParts < 2) {
  -
  -                throw new HttpException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification for special domains");
  -            }
  -        }
  -        else {
  -
  -            if(domainParts < 3) {
  -
  -                throw new HttpException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification");
  -            }            
  -        }
  -    }
  -
  -    /**
  -     * Validate domain using RFC 2109
  -     */
  -
  -    private static void validateDomainAttribVer1(final Cookie cookie, final String domain)
  -        throws HttpException {
  -        log.trace("enter Cookie.validateDomainAttribVer1()");
  -
  -        // domain must have at least one embedded dot
  -        int dotIndex = cookie.getDomain().indexOf('.', 1);
  -        if(dotIndex < 0 || dotIndex == cookie.getDomain().length()-1){
  -
  -                throw new HttpException("Illegal domain attribute \"" + cookie.getDomain() + "\"");
  -
  -        }
  -        // host minus domain may not contain any dots
  -        if (domain.substring(0,
  -                domain.length() -
  -                cookie.getDomain().length()).indexOf('.') != -1) {
  -
  -                throw new HttpException("Illegal domain attribute \"" + cookie.getDomain() + "\"");
  -        }
  -    }
  -
  -    /**
  -     * Checks if the given domain is in one of the seven special
  -     * top level domains defined by the Netscape cookie specification.
  -     */
  -    private static boolean isSpecialDomain(String domain)
  -    {
  -        String ucDomain = domain.toUpperCase();
  -        if(ucDomain.endsWith(".COM") ||
  -           ucDomain.endsWith(".EDU") ||
  -           ucDomain.endsWith(".NET") ||
  -           ucDomain.endsWith(".GOV") ||
  -           ucDomain.endsWith(".MIL") ||
  -           ucDomain.endsWith(".ORG") ||
  -           ucDomain.endsWith(".INT")){
  -            return true;
  -        }
  -
  -        return false;
  -
  -    }
  -
  -    /**
  -     * Performs a domain-match as described in RFC2109.
  -     */
  -    private static boolean domainMatch(String host, String domain)
  -    {
  -        boolean match = host.equals(domain) ||
  -                        (domain.startsWith(".") && host.endsWith(domain));
  -
  -        return match;
  -    }
  -
  -    /**
  -     * Performs a path-match slightly smarter than a straight-forward startsWith check.
  -     */
  -    private static boolean pathMatch(final String path, final String topmostPath)
  -    {
  -        boolean match = path.startsWith( topmostPath );
  -        if (match) {
  -            if (!topmostPath.endsWith(PATH_DELIM)) {
  -                match = (path.charAt(topmostPath.length()) == PATH_DELIM_CHAR);
  -            }
  -        }
  -        return match;
  -    }
  -
  -    /**
  -     * Adds the given cookie into the given list in descending path order.  That is, 
  -     * more specific path to least specific paths.  This may not be the fastest
  -     * algorythm, but it'll work OK for the small number of cookies we're 
  -     * generally dealing with.
  -     *
  -     * @param list - the list to add the cookie to
  -     * @param addCookie - the Cookie to add to list
  -     */
  -    private static void addInPathOrder(List list, Cookie addCookie)
  -    {
  -        int i = 0;
  -
  -        for(i=0;i<list.size();i++){
  -            Cookie c = (Cookie)list.get(i);
  -            if(addCookie.compare(addCookie, c) > 0){
  -                break;
  -            }
  -        }
  -        list.add(i, addCookie);
  -    }
  -
      // ----------------------------------------------------- Instance Variables
   
      /** My comment. */
  @@ -1145,29 +739,10 @@
   
      // -------------------------------------------------------------- Constants
   
  -   /** List of valid date formats for the "expires" cookie attribute. */
  -   private static final DateFormat[] expiryFormats = {
  -       // RFC 1123, 822, Date and time specification is English.
  -       new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE dd-MMM-yy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE dd-MMM-yyyy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE dd MMM yy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE dd MMM yyyy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE, dd MMM yy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US),
  -       new SimpleDateFormat("EEE, dd-MMM-yyyy HH-mm-ss z", Locale.US),
  -       new SimpleDateFormat("EEE dd-MMM-yyyy HH-mm-ss z", Locale.US)
  -   };
  -
      /** Collator for Cookie comparisons.  Could be replaced with references to specific Locales. */
      private static final RuleBasedCollator stringCollator =
           (RuleBasedCollator)RuleBasedCollator.getInstance(
                                                   new Locale("en", "US", ""));
  -
  -   /** Path delimiter */
  -   private static final String PATH_DELIM = "/";
  -   private static final char   PATH_DELIM_CHAR = PATH_DELIM.charAt(0);
   
      /** Log object for this class */
      private static final Log log = LogFactory.getLog(Cookie.class);
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java
  
  Index: CookiePolicy.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient.cookie;
  
  /**
   * <p>
   *  Cookie management policy class. The cookie policy provides corresponding cookie 
   *  management interfrace for a given type or version of cookie. 
   * </p>
   * <p>
   *  RFC 2109 specification is used per default. Other supported specification can be 
   *  chosen when appropriate or set default when desired
   * </p>
   * <p>
   *  The following specifications are provided:
   *  <tt>COMPATIBILITY</tt>: compatible with the common cookie management practices 
   *  (even if they are not 100% standards compliant
   *  <tt>NETSCAPE_DRAFT</tt>: Netscape cookie draft compliant
   *  <tt>RFC2109</tt>: RFC2109 compliant (default)
   * </p>
   * <p>
   *  Default policy can be set on JVM start-up through the system property 
   *  "apache.commons.httpclient.cookiespec". Recognized values: 
   *  COMPATIBILITY, NETSCAPE_DRAFT, RFC2109.
   * </p>
   * 
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   *
   * @since 2.0
   */
  
  public abstract class CookiePolicy
  {
      private static final String SYSTEM_PROPERTY = "apache.commons.httpclient.cookiespec";
  
      /**
       * The <tt>COMPATIBILITY</tt> policy provides high compatibilty 
       * with common cookie management of popular HTTP agents
       */
      public static final int COMPATIBILITY  = 0;
      /**
       * The <tt>NETSCAPE_DRAFT</tt> Netscape draft compliant policy
       */
      public static final int NETSCAPE_DRAFT = 1;
      /**
       * The <tt>RFC2109</tt> RFC 2109 compliant policy (default)
       */
      public static final int RFC2109        = 2;
  
      private static int DefaultPolicy = RFC2109;
  
      static {
          String s = System.getProperty(SYSTEM_PROPERTY);
          if (s != null)
          {
              if (s.equalsIgnoreCase("COMPATIBILITY"))
              {
                  setDefaultPolicy(COMPATIBILITY);
              }else
              if (s.equalsIgnoreCase("NETSCAPE_DRAFT"))
              {
                  setDefaultPolicy(NETSCAPE_DRAFT);
              }else
              if (s.equalsIgnoreCase("RFC2109"));
              {
                  setDefaultPolicy(RFC2109);
              }
          }
      }
  
      /**
       * @return default cookie policy (COMPATIBILITY | NETSCAPE_DRAFT | RFC2109)
       */
      
      public static int getDefaultPolicy()
      {
          return DefaultPolicy;
      }
      
  
      /**
       * @param new default cookie policy (COMPATIBILITY | NETSCAPE_DRAFT | RFC2109)
       */
      
      public static void setDefaultPolicy(int policy)
      {
          DefaultPolicy = policy;
      }
      
  
      /**
       * @return cookie specification interface for the given policy
       * (COMPATIBILITY | NETSCAPE_DRAFT | RFC2109)
       */
      
      public static CookieSpec getSpecByPolicy(int policy)
      {
          switch(policy)
          {
              case COMPATIBILITY: 
                  return new CookieSpecBase(); 
              case NETSCAPE_DRAFT: 
                  return new NetscapeDraftSpec(); 
              case RFC2109:
                  return new RFC2109Spec();
              default:
                  return new RFC2109Spec(); 
          }
      }
  
  
      /**
       * @return default cookie specification interface
       */
      
      public static CookieSpec getDefaultSpec()
      {
          return getSpecByPolicy(DefaultPolicy);
      }
      
  
      /**
       * @return cookie specification interface intended for processing 
       * cookies with the given version 
       * 
       * Supported versions:
       * <tt>version 0</tt> corresponds to the NETSCAPE_DRAFT
       * <tt>version 1</tt> corresponds to the RFC2109
       * Any unsupported cookie version number corresponds to 
       * the current default cookie specification
       */
      
      public static CookieSpec getSpecByVersion(int ver)
      {
          switch(ver)
          {
              case 0: 
                  return new NetscapeDraftSpec(); 
              case 1:
                  return new RFC2109Spec();
              default:
                  return getDefaultSpec(); 
          }
      }
  
      /**
       * @return cookie specification interface that provides high compatibilty 
       * with common cookie management of popular HTTP agents
       */
      
      public static CookieSpec getCompatibilitySpec()
      {
          return getSpecByPolicy(COMPATIBILITY);
      }
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpec.java
  
  Index: CookieSpec.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient.cookie;
  
  import org.apache.commons.httpclient.Header;
  import org.apache.commons.httpclient.Cookie;
  
  /**
   * <p>
   * Cookie management specification must define
   * - rules of parsing "Set-Cookie" header
   * - rules of validation of parsed cookies
   * - formatting of "Cookie" header 
   * for a given host, port and path of origin
   * </p>
   * 
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   *
   * @since 2.0
   */
  
  public interface CookieSpec
  {    
      /** Path delimiter */
      public static final String PATH_DELIM = "/";
      /** Path delimiting charachter */
      public static final char   PATH_DELIM_CHAR = PATH_DELIM.charAt(0);
  
      /**
        * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s {@link Cookie}.
        *
        * @param host the host from which the <tt>Set-Cookie</tt> value was received
        * @param port the port from which the <tt>Set-Cookie</tt> value was received
        * @param path the path from which the <tt>Set-Cookie</tt> value was received
        * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was received over secure conection
        * @param header the <tt>Set-Cookie</tt> received from the server
        * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
        * @throws MalformedCookieException if an exception occurs during parsing
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
  
      public Cookie[] parse(String host, int port, String path, boolean secure, final String header)
        throws MalformedCookieException;
  
      /**
        * Parses the Set-Cookie header into an array of <tt>Cookie</tt>s {@link Cookie}.
        *
        * @param host the host from which the <tt>Set-Cookie</tt> header was received
        * @param port the port from which the <tt>Set-Cookie</tt> header was received
        * @param path the path from which the <tt>Set-Cookie</tt> header was received
        * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> header was received over secure conection
        * @param header the <tt>Set-Cookie</tt> received from the server
        * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie header
        * @throws MalformedCookieException if an exception occurs during parsing
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
  
      public Cookie[] parse(String host, int port, String path, boolean secure, final Header header)
        throws MalformedCookieException;
  
      /**
        * Performs cookie {@link Cookie} validation accoding to the cookie specification
        *
        * @param host the host from which the {@link Cookie} was received
        * @param port the port from which the {@link Cookie} was received
        * @param path the path from which the {@link Cookie} was received
        * @param secure <tt>true</tt> when the {@link Cookie} was received over HTTPS
        * @throws MalformedCookieException if an exception occurs during validation
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
  
      public void validate(String host, int port, String path, boolean secure, final Cookie cookie) 
        throws MalformedCookieException;
      
      /**
       * Return <tt>true</tt> if the cookie should be submitted with a request with
       * given attributes, <tt>false</tt> otherwise.
       * @param host the host to which the request is being submitted
       * @param port the port to which the request is being submitted (currenlty 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 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);
  
      /**
       * Returns an array of <tt>Cookie</tt>s that should be submitted with a request with
       * given attributes, <tt>false</tt> otherwise.
       * @param host the host to which the request is being submitted
       * @param port the port to which the request is being submitted (currenlty ignored)
       * @param path the path to which the request is being submitted
       * @param secure <tt>true</tt> if the request is using a secure protocol
       * @param an array of <tt>Cookie</tt>s to be matched
       * @return an array of <tt>Cookie</tt>s matching the criterium
       */
  
      public Cookie[] match(String host, int port, String path, boolean secure, final Cookie cookies[]);
  
      /**
       * Return a string suitable for sending in a Cookie header
       * @param cookie {@link Cookie} to be formatted as string
       * @return a string suitable for sending in a Cookie header.
       */
  
      public String formatCookie(Cookie cookie);
  
      /**
       * Creates a <tt>Cookie</tt> header value containing all cookies in <i>cookies</i>
       * suitable for sending in a Cookie header
       * @param an array of <tt>Cookie</tt>s to be formatted
       * @return a string suitable for sending in a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public String formatCookies(Cookie[] cookies);
      
      /**
       * Creates a <tt>Cookie</tt> header containing all cookies in <i>cookies</i>,
       * @param an array of <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
       * @return a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public Header formatCookieHeader(Cookie[] cookies);
  
      /**
       * Creates a <tt>Cookie</tt> header containing the <i>cookie</i>,
       * @param <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
       * @return a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public Header formatCookieHeader(Cookie cookie);
  
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java
  
  Index: CookieSpecBase.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  
  package org.apache.commons.httpclient.cookie;
  
  import java.util.Iterator;
  import java.util.List;
  import java.util.LinkedList;
  import java.util.Date;
  import java.util.Locale;   
  import java.text.DateFormat; 
  import java.text.ParseException; 
  import java.text.SimpleDateFormat;  
  import org.apache.commons.httpclient.NameValuePair;
  import org.apache.commons.httpclient.HttpException;
  import org.apache.commons.httpclient.Header;
  import org.apache.commons.httpclient.HeaderElement;
  import org.apache.commons.httpclient.Cookie;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * <p>
   * Cookie management functions shared by all specifications
   * </p>
   *
   * @author  B.C. Holmes
   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
   * @author Rod Waldhoff
   * @author dIon Gillard
   * @author Sean C. Sullivan
   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
   * @author Marc A. Saegesser
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   * 
   * @since 2.0 
   */
  
  public class CookieSpecBase implements CookieSpec
  {
      /** Log object */
      protected static final Log log = LogFactory.getLog(CookieSpec.class);
  
      /** List of valid date formats for the "expires" cookie attribute. */
      private static final DateFormat[] expiryFormats = {
         // RFC 1123, 822, Date and time specification is English.
         new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE dd-MMM-yy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE dd-MMM-yyyy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE dd MMM yy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE dd MMM yyyy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE, dd MMM yy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US),
         new SimpleDateFormat("EEE, dd-MMM-yyyy HH-mm-ss z", Locale.US),
         new SimpleDateFormat("EEE dd-MMM-yyyy HH-mm-ss z", Locale.US)
      };
  
      /** Default constructor */
  
      public CookieSpecBase()
      {
          super();
      }
  
  
      /**
        * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s.
        *
        * <P>The syntax for the Set-Cookie 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
        *                 |    "Domain" "=" value
        *                 |    "Max-Age" "=" value
        *                 |    "Path" "=" value
        *                 |    "Secure"
        *                 |    "Version" "=" 1*DIGIT
        * </PRE>
        *
        * @param host the host from which the <tt>Set-Cookie</tt> value was received
        * @param port the port from which the <tt>Set-Cookie</tt> value was received
        * @param path the path from which the <tt>Set-Cookie</tt> value was received
        * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was received over secure conection
        * @param header the <tt>Set-Cookie</tt> received from the server
        * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
        * @throws MalformedCookieException if an exception occurs during parsing
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
  
      public Cookie[] parse(String host, int port, String path, boolean secure, final String header) 
        throws MalformedCookieException
      {
          log.trace("enter CookieSpecBase.parse(String, port, path, boolean, Header)");
  
          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(header == null)
          {
              throw new IllegalArgumentException("Header may not be null.");
          }
  
          if (path.trim().equals("")) {
              path = PATH_DELIM;
          }
          host = host.toLowerCase();
      
          HeaderElement[] headerElements = null;
          try
          {
              headerElements = HeaderElement.parse(header);
          }
          catch(HttpException e)
          {
              throw new MalformedCookieException(e.getMessage());
          } 
      
          String defaultPath = path;    
          int lastSlashIndex = defaultPath.lastIndexOf(PATH_DELIM);
          if(lastSlashIndex > 0)
          {
              defaultPath = defaultPath.substring(0, lastSlashIndex);
          }
          
          Cookie[] cookies = new Cookie[headerElements.length];
  
          for (int i = 0; i < headerElements.length; i++) {
  
              HeaderElement headerelement = headerElements[i];
              Cookie cookie = new Cookie(host,
                                         headerelement.getName(),
                                         headerelement.getValue(),
                                         defaultPath, 
                                         null,
                                         false);
  
              // cycle through the parameters
              NameValuePair[] parameters = headerelement.getParameters();
              // could be null. In case only a header element and no parameters.
              if (parameters != null) {
  
                  for (int j = 0; j < parameters.length; j++) {
  
                      String param_name = parameters[j].getName().toLowerCase();
                      String param_value = parameters[j].getValue();
  
                      if (param_name.equals("version")) {
  
                          if (param_value == null) {
                              throw new MalformedCookieException("Missing value for version attribute");
                          }
                          try {
                             cookie.setVersion(Integer.parseInt(param_value));
                          } catch (NumberFormatException e) {
                              throw new MalformedCookieException( "Invalid version attribute: " + e.getMessage());
                          }
  
                      } 
                      else if (param_name.equals("path")) {
  
                          if (param_value == null) {
                              throw new MalformedCookieException("Missing value for path attribute");
                          }
                          if (param_value.trim().equals("")) {
                              throw new MalformedCookieException("Blank value for path attribute");
                          }
                          cookie.setPath(param_value);
                          cookie.setPathAttributeSpecified(true);
  
                      } 
                      else if (param_name.equals("domain")) {
  
                          if (param_value == null) {
                              throw new MalformedCookieException("Missing value for domain attribute");
                          }
                          if (param_value.trim().equals("")) {
                              throw new MalformedCookieException("Blank value for domain attribute");
                          }
                          cookie.setDomain(param_value);
                          cookie.setDomainAttributeSpecified(true);
  
                      } 
                      else if (param_name.equals("max-age")) {
  
                          if (param_value == null) {
                              throw new MalformedCookieException("Missing value for max-age attribute");
                          }
                          int age;
                          try {
                              age = Integer.parseInt(param_value);
                          } catch (NumberFormatException e) {
                              throw new MalformedCookieException( "Invalid max-age attribute: " + e.getMessage());
                          }
                          cookie.setExpiryDate(new Date(System.currentTimeMillis() +
                                  age * 1000L));
  
                      } 
                      else if (param_name.equals("secure")) {
  
                          cookie.setSecure(true);
  
                      } 
                      else if (param_name.equals("comment")) {
  
                          cookie.setComment(param_value);
  
                      } 
                      else if (param_name.equals("expires")) {
  
                          if (param_value == null) {
                              throw new MalformedCookieException("Missing value for expires attribute");
                          }
                          boolean set = false;
                          // trim single quotes around expiry if present
                          // see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279
                          if(param_value.length() > 1 &&
                                  param_value.startsWith("'") &&
                                  param_value.endsWith("'")) {
                              param_value = param_value.substring(1,param_value.length()-1);
                          }
  
                          for(int k=0;k<expiryFormats.length;k++) {
  
                              try {
                                  Date date = expiryFormats[k].parse(param_value);
                                  cookie.setExpiryDate(date);
                                  set = true;
                                  break;
                              } catch (ParseException e) {
                                  //Ignore and move on
                              }
                          }
                          if(!set) {
                              throw new MalformedCookieException("Unable to parse expiration date parameter: " + param_value);
                          }
                      }
                  }
              }
              cookies[i] = cookie;
          }
          return cookies;
      }
  
  
      /**
        * Parses the Set-Cookie {@link Header} into an array of
        * <tt>Cookie</tt>s.
        *
        * <P>The syntax for the Set-Cookie 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
        *                 |    "Domain" "=" value
        *                 |    "Max-Age" "=" value
        *                 |    "Path" "=" value
        *                 |    "Secure"
        *                 |    "Version" "=" 1*DIGIT
        * </PRE>
        *
        * @param host the host from which the <tt>Set-Cookie</tt> header was received
        * @param port the port from which the <tt>Set-Cookie</tt> header was received
        * @param path the path from which the <tt>Set-Cookie</tt> header was received
        * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> header was received over secure conection
        * @param header the <tt>Set-Cookie</tt> received from the server
        * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie header
        * @throws MalformedCookieException if an exception occurs during parsing
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
  
      public Cookie[] parse(String host, int port, String path, boolean secure, final Header header)
        throws MalformedCookieException
      {
          if(header == null)
          {
              throw new IllegalArgumentException("Header may not be null.");
          }
          return parse(host, port, path, secure, header.getValue());
      }
  
      
      /**
        * Performs most fundamental cookie {@link Cookie} validation
        *
        * @param host the host from which the {@link Cookie} was received
        * @param port the port from which the {@link Cookie} was received
        * @param path the path from which the {@link Cookie} was received
        * @param secure <tt>true</tt> when the {@link Cookie} was received over HTTPS
        * @throws MalformedCookieException if an exception occurs during validation
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
      
      public void validate(String host, int port, String path, boolean secure, final Cookie cookie) throws MalformedCookieException
      {
          log.trace("enter CookieSpecBase.validate(String, port, path, boolean, Cookie)");
          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 (path.trim().equals("")) {
              path = PATH_DELIM;
          }
          host = host.toLowerCase();
          // check version
          if (cookie.getVersion() < 0) 
          {
              throw new MalformedCookieException( "Illegal version number " + cookie.getValue());
          }
  
          // security check... we musn't allow the server to give us an
          // invalid domain scope
  
          // Validate the cookies domain attribute.  NOTE:  Domains without any dots are
          // allowed to support hosts on private LANs that don't have DNS names.  Since
          // they have no dots, to domain-match the request-host and domain must be identical
          // for the cookie to sent back to the origin-server.
          if (host.indexOf(".") >= 0)
          {
              // Not required to have at least two dots.  RFC 2965.
              // A Set-Cookie2 with Domain=ajax.com will be accepted.
  
              // domain must match host
              if (!host.endsWith(cookie.getDomain()))
              {
                  throw new MalformedCookieException(
                      "Illegal domain attribute \"" + cookie.getDomain() + "\". Domain of origin: \"" + host + "\"");
              }
          }
  
          // another security check... we musn't allow the server to give us a
          // cookie that doesn't match this path
  
          if(!path.startsWith(cookie.getPath()))
          {
              throw new MalformedCookieException(
                      "Illegal path attribute \"" + cookie.getPath() + "\". Path of origin: \"" + path + "\"");
          }
      }
  
  
      /**
       * Return <tt>true</tt> if the cookie should be submitted with a request with
       * given attributes, <tt>false</tt> otherwise.
       * @param host the host to which the request is being submitted
       * @param port the port to which the request is being submitted (currenlty ignored)
       * @param path the path to which the request is being submitted
       * @param secure <tt>true</tt> if the request is using a secure protocol
       */
  
      public boolean match(String host, int port, String path, boolean secure, final Cookie cookie) 
      {
          log.trace("enter CookieSpecBase.match(String, int, String, boolean, Cookie");
          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(cookie == null)
          {
              throw new IllegalArgumentException("Cookie may not be null");
          }
          if (path.trim().equals("")) {
              path = PATH_DELIM;
          }
          host = host.toLowerCase();
          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;
          }
          
          return (
                  (cookie.getExpiryDate() == null || 
                   cookie.getExpiryDate().after(new Date())) && // only add the cookie if it hasn't yet expired
                  (domainMatch(host, cookie.getDomain())) &&    // and the domain pattern matches
                  (pathMatch(path, cookie.getPath())) &&        // and the path is null or matching
                  (cookie.getSecure() ? secure : true)          // and if the secure flag is set, only if the request is actually secure
                 );
      }
  
      /**
       * Performs a domain-match as described in RFC2109.
       */
      private static boolean domainMatch(String host, String domain)
      {
          boolean match = host.equals(domain) ||
                          (domain.startsWith(".") && host.endsWith(domain));
  
          return match;
      }
  
      /**
       * Performs a path-match slightly smarter than a straight-forward startsWith check.
       */
      private static boolean pathMatch(final String path, final String topmostPath)
      {
          boolean match = path.startsWith( topmostPath );
          if (match) {
              if (!topmostPath.endsWith(PATH_DELIM)) {
                  match = (path.charAt(topmostPath.length()) == PATH_DELIM_CHAR);
              }
          }
          return match;
      }
  
  
      /**
       * Returns an array of <tt>Cookie</tt>s that should be submitted with a request with
       * given attributes, <tt>false</tt> otherwise.
       * @param host the host to which the request is being submitted
       * @param port the port to which the request is being submitted (currenlty ignored)
       * @param path the path to which the request is being submitted
       * @param secure <tt>true</tt> if the request is using a secure protocol
       * @param an array of <tt>Cookie</tt>s to be matched
       * @return an array of <tt>Cookie</tt>s matching the criterium
       */
  
      public Cookie[] match(String host, int port, String path, boolean secure, final Cookie cookies[])
      {
          log.trace("enter CookieSpecBase.match(String, int, String, boolean, Cookie[])");
  
          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 == null)
          {
              throw new IllegalArgumentException("Cookie array may not be null");
          }
          if (path.trim().equals("")) {
              path = PATH_DELIM;
          }
          host = host.toLowerCase();
  
          StringBuffer value = new StringBuffer();
  
          if (cookies.length <= 0) {
              return null;
          }
          List matching = new LinkedList();
          for(int i=0;i<cookies.length;i++)
          {
              if(cookies[i].matches(host, port, path, secure))
              {
                  addInPathOrder(matching, cookies[i]);
              }
          }
          return (Cookie[])matching.toArray(new Cookie[matching.size()]);
      }
  
  
      /**
       * Adds the given cookie into the given list in descending path order.  That is, 
       * more specific path to least specific paths.  This may not be the fastest
       * algorythm, but it'll work OK for the small number of cookies we're 
       * generally dealing with.
       *
       * @param list - the list to add the cookie to
       * @param addCookie - the Cookie to add to list
       */
      private static void addInPathOrder(List list, Cookie addCookie)
      {
          int i = 0;
  
          for(i=0;i<list.size();i++){
              Cookie c = (Cookie)list.get(i);
              if(addCookie.compare(addCookie, c) > 0){
                  break;
              }
          }
          list.add(i, addCookie);
      }
  
      /**
       * Return a string suitable for sending in a Cookie header
       * @param cookie {@link Cookie} to be formatted as string
       * @return a string suitable for sending in a Cookie header.
       */
  
      public String formatCookie(Cookie cookie)
      {
          log.trace("enter CookieSpecBase.toExternalForm(Cookie)");
          if(cookie == null)
          {
              throw new IllegalArgumentException("Cookie may not be null");
          }
          StringBuffer buf = new StringBuffer();
          buf.append(cookie.getName()).append("=").append(cookie.getValue());
          return buf.toString();
      }
  
      /**
       * Creates a <tt>Cookie</tt> header value containing all cookies in <i>cookies</i>
       * suitable for sending in a Cookie header
       * @param an array of <tt>Cookie</tt>s to be formatted
       * @return a string suitable for sending in a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public String formatCookies(Cookie[] cookies)
        throws IllegalArgumentException
      {
          log.trace("enter CookieSpecBase.formatCookies(Cookie[])");
          if(cookies == null)
          {
              throw new IllegalArgumentException("Cookie array may not be null");
          }
          if(cookies.length == 0)
          {
              throw new IllegalArgumentException("Cookie array may not be empty");
          }
  
          StringBuffer buffer = new StringBuffer();
          for (int i = 0; i < cookies.length; i++)
          {
              if (i > 0)
              {
                  buffer.append("; ");
              }
              buffer.append(formatCookie(cookies[i]));
          }
          return buffer.toString();
      }
  
  
      /**
       * Creates a <tt>Cookie</tt> header containing all cookies in <i>cookies</i>,
       * @param an array of <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
       * @return a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public Header formatCookieHeader(Cookie[] cookies)
      {
          log.trace("enter CookieSpecBase.formatCookieHeader(Cookie[])");
          return new Header("Cookie", formatCookies(cookies));
      }
  
  
      /**
       * Creates a <tt>Cookie</tt> header containing the <i>cookie</i>,
       * @param <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
       * @return a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public Header formatCookieHeader(Cookie cookie)
      {
          log.trace("enter CookieSpecBase.formatCookieHeader(Cookie)");
          return new Header("Cookie", formatCookie(cookie));
      }
  
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/HttpMethodBase.java
  
  Index: HttpMethodBase.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/HttpMethodBase.java,v 1.1 2002/12/08 06:09:46 jsdever Exp $
   * $Revision: 1.1 $
   * $Date: 2002/12/08 06:09:46 $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  package org.apache.commons.httpclient;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.UnsupportedEncodingException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Set;
  import java.util.StringTokenizer;
  
  import org.apache.commons.httpclient.cookie.CookiePolicy;
  import org.apache.commons.httpclient.cookie.CookieSpec;
  import org.apache.commons.httpclient.util.URIUtil;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * <p>
   * An abstract base implementation of {@link HttpMethod}.
   * </p>
   *
   * <p>
   * At minimum, subclasses will need to override
   * </p>
   *
   * <dl>
   * <dt>
   * {@link #getName}
   * </dt>
   * <dd>
   * to return the approriate name for this method
   * </dd>
   * </dl>
   *
   * <p>
   * When a method's request may contain a body, subclasses will typically want
   * to override:
   * </p>
   *
   * <dl>
   * <dt>
   * {@link #getRequestContentLength}
   * </dt>
   * <dd>
   * to indicate the length (in bytes) of that body
   * </dd>
   * <dt>
   * {@link #writeRequestBody writeRequestBody(HttpState,HttpConnection)}
   * </dt>
   * <dd>
   * to write the body
   * </dd>
   * </dl>
   *
   * <p>
   * When a method requires additional request headers, subclasses will typically
   * want to override:
   * </p>
   *
   * <dl>
   * <dt>
   * {@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
   * </dt>
   * <dd>
   * to write those headers
   * </dd>
   * </dl>
   *
   * <p>
   * When a method expects specific response headers, subclasses may want to
   * override:
   * </p>
   *
   * <dl>
   * <dt>
   * {@link #processResponseHeaders
   * processResponseHeaders(HttpState,HttpConnection)}
   * </dt>
   * <dd>
   * to handle those headers
   * </dd>
   * </dl>
   *
   *
   * @version $Revision: 1.1 $ $Date: 2002/12/08 06:09:46 $
   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
   * @author Rodney Waldhoff
   * @author Sean C. Sullivan
   * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
   * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
   * @author Ortwin Gl�ck
   */
  public abstract class HttpMethodBase implements HttpMethod {
      //~ Static variables/initializers ������������������������������������������
  
      /** Maximum number of redirects and authentications that will be followed */
      private static int maxForwards = 100;
      // -------------------------------------------------------------- Constants
  
      /** Log object for this class. */
      private static final Log log = LogFactory.getLog(HttpMethod.class);
  
      /** Log for any wire messages. */
      private static final Log wireLog = LogFactory.getLog("httpclient.wire");
  
      /** The User-Agent header sent on every request. */
      protected static final Header USER_AGENT;
  
      static {
          String agent = System.getProperties()
                               .getProperty("httpclient.useragent",
                                            "Jakarta Commons-HttpClient/2.0M1");
          USER_AGENT = new Header("User-Agent", agent);
      }
  
      //~ Instance variables �����������������������������������������������������
  
      /** My request headers, if any. */
      private Map requestHeaders = new HashMap();
  
      /** The Status-Line from the response. */
      private StatusLine statusLine = null;
  
      /** My response headers, if any. */
      private Map responseHeaders = new HashMap();
  
      /** My response footers, if any. */
      private Map responseFooters = null;
  
      /** Realms that we tried to authenticate to */
      private Set realms = null;
  
      /** Proxy Realms that we tried to authenticate to */
      private Set proxyRealms = null;
  
      /** My request path. */
      private String path = null;
  
      /** My query string, if any. */
      private String queryString = null;
  
      /** The response body, assuming it has not be intercepted by a sub-class. */
      private InputStream responseStream = null;
  
      /** The connection that the response stream was read from. */
      private HttpConnection responseConnection = null;
  
      /** Buffer for the response */
      private byte[] responseBody = null;
  
      /** Whether or not the request body has been sent. */
      private boolean bodySent = false;
  
      /** Whether or not I should automatically follow redirects. */
      private boolean followRedirects = false;
  
      /** Whether or not I should automatically processs authentication. */
      private boolean doAuthentication = true;
  
      /** Whether or not I should use the HTTP/1.1 protocol. */
      private boolean http11 = true;
  
      /** True if we're in strict mode. */
      private boolean strictMode = false;
  
      /** Whether or not I have been executed. */
      private boolean used = false;
  
      /**
       * The maximum number of attempts to attempt recovery from an
       * HttpRecoverableException.
       */
      private int maxRetries = 3;
  
      /** Default content encoding chatset */
      protected static final String DEFAULT_CHARSET = "ISO-8859-1";
  
  
      //~ Constructors �����������������������������������������������������������
  
      // ----------------------------------------------------------- Constructors
  
      /**
       * No-arg constructor.
       */
      public HttpMethodBase() {
      }
  
      /**
       * Path-specifying constructor.
       *
       * @param path my path which can include a query
       */
      public HttpMethodBase(String path) {
          int pa = path.indexOf("?");
          if (pa < 0) { //its just a path
              setPath(path);
          } else { //its a path with a query
              setPath(path.substring(0, pa));
              setQueryString(path.substring(pa+1, path.length()));
          }
      }
  
      //~ Methods ����������������������������������������������������������������
  
      // ------------------------------------------- Property Setters and Getters
  
      /**
       * Obtain the name of this method, suitable for use in the "request line",
       * for example <tt>GET</tt> or <tt>POST</tt>.
       *
       * @return the name of this method
       */
      public abstract String getName();
  
      /**
       * Set whether or not I should automatically follow HTTP redirects (status
       * code 302, etc.)
       *
       * @param followRedirects true to follow redirects, false otherwise
       */
      public void setFollowRedirects(boolean followRedirects) {
          this.followRedirects = followRedirects;
      }
  
      /**
       * Whether or not I should automatically follow HTTP redirects (status code
       * 302, etc.)
       *
       * @return <tt>true</tt> if I will automatically follow HTTP redirects
       */
      public boolean getFollowRedirects() {
          return this.followRedirects;
      }
  
      /**
       * Set whether or not I should use the HTTP/1.1 protocol.
       *
       * @param http11 true to use HTTP/1.1, false to use 1.0
       */
      public void setHttp11(boolean http11) {
          this.http11 = http11;
      }
  
          /**
       * Whether or not I should automatically process responses where
       * authentication is required (status code 401, etc.)
       *
       * @return <tt>true</tt> if authentications will be processed automatically
       * @since 2.0
       */
      public boolean getDoAuthentication() {
          return doAuthentication;
      }
  
      /**
       * Set whether or not I should automatically process responses where
       * authentication is required (status code 401, etc.)
       *
       * @param doAuthentication <tt>true</tt> to process authentications
       * @since 2.0
       */
      public void setDoAuthentication(boolean doAuthentication) {
          this.doAuthentication = doAuthentication;
      }
  
      // ---------------------------------------------- Protected Utility Methods
  
      /**
       * Access to flag to determine if client should use
       * HTTP/1.1 protocol.
       *
       * @return <tt>true</tt> if I should use the HTTP/1.1 protocol
       */
      public boolean isHttp11() {
          return http11;
      }
  
      /**
       * Set the path part of my request.
       *
       * @param path the path to request
       */
      public void setPath(String path) {
          this.path = path;
      }
  
      /**
       * Add the specified request header.
       *
       * If a header of the same name already exists, the new value will be
       * appended onto the the existing value list.
       * A <i>header</i> value of <code>null</code> will be ignored.
       * Note that header-name matching is case insensitive.
       *
       * @param header the header to add to the request
       */
      public void addRequestHeader(Header header) {
          log.trace("HttpMethodBase.addRequestHeader(Header)");
  
          if (header == null) {
            log.debug("null header value ignored");
          } else {
              addRequestHeader(header.getName(), header.getValue());
          }
      }
  
      /**
       * adds a response footer to the internal list
       */
      public void addResponseFooter(Header footer) {
          if (responseFooters == null) responseFooters = new HashMap();
          responseFooters.put(footer.getName().toLowerCase(), footer);
      }
  
      /**
       * Get the path part of my request.
       *
       * @return the path to request or "/" if the path is blank.
       */
      public String getPath() {
          return (path == null || path.equals("")) ? "/" : path;
      }
  
      /**
       * Sets the query string.
       * The user must ensure that the string is properly URL encoded.
       * URIUtil.encodeAll, URIUtil.encodeWithinQuery or URIUtil.encodeQuery can
       * be used to encode parameter names and values.
       * The query string should not start with the question mark character.
       *
       * @param queryString the query string
       */
      public void setQueryString(String queryString) {
          this.queryString = queryString;
      }
  
      /**
       * Set my query string.
       *
       * @param params an array of {@link NameValuePair}s to add as query string
       *        parameterss
       */
      public void setQueryString(NameValuePair[] params) {
          log.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
          StringBuffer buf = new StringBuffer();
          boolean needAmp = false;
          for (int i = 0; i < params.length; i++) {
              if (params[i].getName() != null) {
                  if (needAmp) {
                      buf.append("&");
                  } else {
                      needAmp = true;
                  }
                  String queryName = null;
                  try {
                      queryName = URIUtil.encodeWithinQuery(params[i].getName());
                  } catch (URIException urie) {
                      log.error("encoding error within query name", urie);
                      queryName = params[i].getName();
                  }
                  buf.append(queryName).append("=");
                  if (params[i].getValue() != null) {
                      String queryValue = null;
                      try {
                          queryValue =
                              URIUtil.encodeWithinQuery(params[i].getValue());
                      } catch (URIException urie) {
                          log.error("encoding error within query value", urie);
                          queryValue = params[i].getValue();
                      }
                      buf.append(queryValue);
                  }
              }
          }
          queryString = buf.toString();
      }
  
      /**
       * Get my query string.
       *
       * @return The query string portion of the request
       */
      public String getQueryString() {
          return queryString;
      }
  
      /**
       * Set the specified request header, overwriting any previous value. Note
       * that header-name matching is case-insensitive.
       *
       * @param headerName the header's name
       * @param headerValue the header's value
       */
      public void setRequestHeader(String headerName, String headerValue) {
          Header header = new Header(headerName, headerValue);
          setRequestHeader(header);
      }
  
      /**
       * Set the specified request header, overwriting any previous value. Note
       * that header-name matching is case insensitive.
       *
       * @param header the header
       */
      public void setRequestHeader(Header header) {
          requestHeaders.put(header.getName().toLowerCase(), header);
      }
  
      /**
       * Get the request header associated with the given name. Header name
       * matching is case insensitive. <tt>null</tt> will be returned if either
       * <i>headerName</i> is <tt>null</tt> or there is no matching header for
       * <i>headerName</i>.
       *
       * @param headerName the header name to match
       *
       * @return the matching header
       */
      public Header getRequestHeader(String headerName) {
          return (headerName == null)
                 ? null : (Header) (requestHeaders.get(headerName.toLowerCase()));
      }
  
      /**
       * Provides access to the request headers.
       *
       * @return an array of my request headers.
       */
      public Header[] getRequestHeaders() {
          return (Header[]) (requestHeaders.values().toArray(
              new Header[requestHeaders.size()]));
      }
  
      /**
       * Convenience method top provide access to the status code.
       *
       * @return the status code associated with the latest response.
       */
      public int getStatusCode() {
          return statusLine.getStatusCode();
      }
  
      /**
       * Provide access to the status line.
       *
       * @return the status line object from the latest response.
       * @since 2.0
       */
      public StatusLine getStatusLine() {
          return statusLine;
      }
  
      /**
       * Checks if response data is available.
       * @return true if response data is available, false otherwise.
       */
      private boolean responseAvailable() {
          return (responseBody != null) || (responseStream != null);
      }
  
      /**
       * Provide access to the response headers
       *
       * @return an array of my response headers.
       */
      public Header[] getResponseHeaders() {
          return (Header[]) (responseHeaders.values().toArray(
              new Header[responseHeaders.size()]));
      }
  
      /**
       * Get the response header associated with the given name. Header name
       * matching is case insensitive. <tt>null</tt> will be returned if either
       * <i>headerName</i> is <tt>null</tt> or there is no matching header for
       * <i>headerName</i>.
       *
       * @param headerName the header name to match
       *
       * @return the matching header
       */
      public Header getResponseHeader(String headerName) {
          return (headerName == null)
                 ? null
                 : (Header) (responseHeaders.get(headerName.toLowerCase()));
      }
  
      /**
       * Return my response body, if any, as a byte array.
       * Otherwise return <tt>null</tt>.
       */
      public byte[] getResponseBody() {
          if (responseBody == null) {
              try {
                  ByteArrayOutputStream os = new ByteArrayOutputStream();
                  InputStream is = getResponseBodyAsStream();
                  byte[] buffer = new byte[10000];
                  int len;
                  while ((len = is.read(buffer)) > 0) {
                      os.write(buffer, 0, len);
                  }
                  responseBody = os.toByteArray();
                  setResponseStream(null);
                  log.debug("buffering response body");
              } catch(IOException e) {
                  log.error("getResponseBody failed", e);
                  responseBody = null;
              }
          }
          return responseBody;
      }
  
      /**
       * Return my response body, if any, as an {@link InputStream}. Otherwise
       * return <tt>null</tt>.
       *
       * @return the response body as an {@link InputStream}
       *
       * @throws IOException when there are errors obtaining the response
       */
      public InputStream getResponseBodyAsStream() throws IOException {
          if (responseStream != null) {
              return responseStream;
          }
          if (responseBody != null) {
              responseStream = new ByteArrayInputStream(responseBody);
              log.debug("re-creating response stream from byte array");
              return responseStream;
          }
          return null;
      }
  
      /**
       * Gets the response body as a string.
       *
       * <b>Note:</b> The string conversion done on the data is done with the
       * default character encoding.  The use of this method may be non-portable.
       *
       * @return my response body, if any, as a {@link String}. Otherwise return
       *         <tt>null</tt>.
       */
      public String getResponseBodyAsString() {
          if (!responseAvailable()) {
              return null;
          }
          String body;
          try {
              body = new String(getResponseBody(), getResponseCharSet());
          }
          catch(UnsupportedEncodingException e) {
              if (log.isWarnEnabled()) {
                  log.warn("Unsupported request body charset: " + e.getMessage());
              }
              body = new String(getResponseBody());
          }
  
          return body;
      }
  
  
      /**
       * Return an array of response footers.
       * @return <tt>null</tt> if no footers are available
       */
      public Header[] getResponseFooters() {
          if (responseFooters == null) {
              return null;
          }
          return (Header[])(responseFooters.values().toArray(
              new Header[responseFooters.size()]));
      }
  
      /**
       * Get the response footer associated with the given name.
       * Footer name matching is case insensitive.
       * <tt>null</tt> will be returned if either <i>footerName</i> is
       * <tt>null</tt> or there is no matching header for <i>footerName</i>
       * or there are no footers available.
       * @param footerName the footer name to match
       * @return the matching footer
       */
      public Header getResponseFooter(String footerName) {
          if (responseFooters == null) {
              return null;
          }
          return (footerName == null) ? null :
              (Header)(responseFooters.get(footerName.toLowerCase()));
      }
  
  
      protected void setResponseStream(InputStream responseStream) {
          this.responseStream = responseStream;
      }
  
      /**
       * Provide access to the status text
       *
       * @return the status text (or "reason phrase") associated with the latest
       *         response.
       */
      public String getStatusText() {
          return statusLine.getReasonPhrase();
      }
  
      /**
       * Turns strict mode on or off.  In strict mode (the default) we following
       * the letter of RFC 2616, the Http 1.1 specification. If strict mode is
       * turned off we attempt to violate the specification in the same way that
       * most Http user agent's do (and many HTTP servers expect. NOTE:
       * StrictMode is currently experimental and its functionlaity may change
       * in the future.
       *
       * @param strictMode true for strict mode, false otherwise
       */
      public void setStrictMode(boolean strictMode) {
          this.strictMode = strictMode;
      }
  
      /**
       * Returns the value of strictMode. NOTE:  StrictMode is currently
       * experimental and its functionlaity may  change in the future.
       *
       * @return true if strict mode is enabled.
       */
      public boolean isStrictMode() {
          return strictMode;
      }
  
      /**
       * Add the specified request header, NOT overwriting any previous value.
       * Note that header-name matching is case insensitive.
       *
       * @param headerName the header's name
       * @param headerValue the header's value
       */
      public void addRequestHeader(String headerName, String headerValue) {
          // "It must be possible to combine the multiple header fields into
          // one "field-name: field-value" pair, without changing the
          // semantics of the message, by appending each subsequent field-value
          // to the first, each separated by a comma."
          //   - HTTP/1.0 (4.3)
          Header header = getRequestHeader(headerName);
          if (null == header) {
              // header doesn't exist already, simply create with name and value
              header = new Header(headerName, headerValue);
          } else {
              // header exists, add this value to the comma separated list
              header.setValue(getNewHeaderValue(header, headerValue));
          }
          setRequestHeader(header);
      }
  
  
      /**
       * Close the provided HTTP connection, if:
       *  http 1.0 and not using the 'connect' method, or
       *  http 1.1 and the Connection: close header is sent
       *
       * @param connection the HTTP connection to process
       * Add the specified request header. If a header of the same name already
       * exists, the new value will be appended onto the the existing value
       * list.  A <i>header</i> value of <code>null</code> will be ignored. Note
       * that header-name matching is case insensitive.
       */
      private void closeConnection(HttpConnection connection) {
          if (shouldCloseConnection()) {
              connection.close();
          }
      }
  
      private boolean shouldCloseConnection() {
          if (!http11) {
              if (getName().equals(ConnectMethod.NAME) &&
                      (statusLine.getStatusCode() == HttpStatus.SC_OK)) {
                  log.debug("Will leave connection open for tunneling");
                  return false;
              } else {
                  log.debug("Should close connection since using HTTP/1.0, " +
                          "ConnectMethod and status is OK");
                  return true;
              }
          } else {
              Header connectionHeader = getResponseHeader("connection");
              if (null != connectionHeader
                  && "close".equalsIgnoreCase(connectionHeader.getValue())) {
                  log.debug("Should close connection since \"Connection: close\" header found.");
                  return true;
              }
          }
          return false;
      }
  
      private void wrapResponseStream( HttpConnection connection ) {
  
          if ( responseStream != null ) {
              this.responseConnection = connection;
              this.responseStream = new ResponseAutoReleaseInputStream(responseStream);
          }
  
      }
  
      /**
       * Execute this method. Note that we cannot currently support redirects
       * that change  the connection parameters (host, port, protocol) because
       * we  don't yet have a good way to get the new connection.  For  the time
       * being, we just return the redirect response code,  and allow the user
       * agent to resubmit if desired.
       *
       * @param state {@link HttpState} information to associate with this
       *        request. Must be non-null.
       * @param conn the{@link HttpConnection} to write to/read from. Must be
       *        non-null. Note that we cannot currently support redirects that
       *        change the HttpConnection parameters (host, port, protocol)
       *        because we don't yet have a good way to get the new connection.
       *        For the time being, we just return the 302 response, and allow
       *        the user agent to resubmit if desired.
       *
       * @return the integer status code if one was obtained, or <tt>-1</tt>
       *
       * @throws HttpException  if an protocol exception occurs
       * @throws HttpRecoverableException if too many redirects occur.
       * @throws IOException if an I/O error occurs
       * @throws NullPointerException if the state is null
       */
      public int execute(HttpState state, HttpConnection conn)
      throws HttpException, IOException, NullPointerException {
          log.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
  
          //TODO: This method is too large
          //check some error conditions
          if (null == state) {
              throw new NullPointerException("HttpState parameter");
          }
          if (null == conn) {
              throw new NullPointerException("HttpConnection parameter");
          }
          if (hasBeenUsed()) {
              throw new HttpException("Already used, but not recycled.");
          }
          if (!validate()) {
              throw new HttpException("Not valid");
          }
  
          //pre-emptively add the authorization header, if required.
          Authenticator.authenticate(this, state);
          if (conn.isProxied()) {
              Authenticator.authenticateProxy(this, state);
          }
  
          //Set visited = new HashSet();
          realms = new HashSet();
          proxyRealms = new HashSet();
          int forwardCount = 0; //protect from an infinite loop
  
          while (forwardCount++ < maxForwards) {
              if (log.isDebugEnabled()) {
                  log.debug("Execute loop try " + forwardCount);
              }
  
              //write the request and read the response, will retry
              processRequest(state, conn);
  
              //if SC_CONTINUE write the request body
              writeRemainingRequestBody(state, conn);
  
              int statusCode = statusLine.getStatusCode();
              switch (statusCode) {
                  case HttpStatus.SC_UNAUTHORIZED:
                  case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
                      log.debug("Authorization required");
                      if (doAuthentication) { //process authentication response
                          //if the authentication is successful, return the statusCode
                          //otherwise, drop through the switch and try again.
                          if (processAuthenticationResponse(state, conn)) {
                              wrapResponseStream(conn);
                              return statusCode;
                          }
                      } else { //let the client handle the authenticaiton
                          wrapResponseStream(conn);
                          return statusCode;
                      }
                      break;
  
                  case HttpStatus.SC_MOVED_TEMPORARILY:
                  case HttpStatus.SC_MOVED_PERMANENTLY:
                  case HttpStatus.SC_TEMPORARY_REDIRECT:
                      log.debug("Redirect required");
  
                      if (! processRedirectResponse(state, conn)) {
                          wrapResponseStream(conn);
                          return statusCode;
                      }
                      break;
  
                  default:
                      // neither an unauthorized nor a redirect response
                      wrapResponseStream(conn);
                      return statusCode;
              } //end of switch
  
  /*
      Revisiting may be desired. We do not know about the server's internal state.
  
              //check to see if we have visited this url before
              if (visited.contains(generateVisitedKey(conn))) {
                  log.error("Link " + generateVisitedKey(conn) + "' revisited");
                  return statusCode;
              }
              visited.add(generateVisitedKey(conn));
  */
  
              //close connection if required
              closeConnection(conn);
              if (conn.isOpen()) {
                  //throw away body to position the stream after the response
                  getResponseBodyAsString();
              }
          } //end of loop
  
          wrapResponseStream(conn);
  
          log.error("Narrowly avoided an infinite loop in execute");
          throw new HttpRecoverableException("Maximum redirects ("+ maxForwards +") exceeded");
      }
  
      private boolean processRedirectResponse(HttpState state, HttpConnection conn) {
  
          if (!getFollowRedirects()) {
              log.info("Redirect requested but followRedirects is "
                      + "disabled");
              return false;
          }
  
          //get the location header to find out where to redirect to
          Header locationHeader = getResponseHeader("location");
          if (locationHeader == null) {
              // got a redirect response, but no location header
              log.error("Received redirect response " + getStatusCode()
                      + " but no location header");
              return false;
          }
          String location = locationHeader.getValue();
          if (log.isDebugEnabled()) {
              log.debug("Redirect requested to location '" + location
                      + "'");
          }
  
          //rfc2616 demands the location value be a complete URI
          //Location       = "Location" ":" absoluteURI
          URL redirectUrl = null;
          URL currentUrl = null;
  
          try {
              currentUrl = new URL(conn.getProtocol().toLowerCase(),
                  conn.getHost(), conn.getPort(), "");
              redirectUrl = new URL(location);
          } catch (MalformedURLException e) {
              if (isStrictMode()) {
                  log.warn("Redirected location '" + location +
                          "' is not acceptable in strict mode");
                  return false;
              } else { //location is incomplete, use current values for defaults
                  try {
                      log.debug("Redirect URL is not absolute - parsing as relative");
                      redirectUrl = new URL(currentUrl, location);
                  } catch (MalformedURLException ex) {
                      log.warn("Redirected location '" + location
                              + "' is malformed");
                      return false;
                  }
              }
          }
  
          //check for redirect to a different protocol, host or port
          try{
              checkValidRedirect(currentUrl, redirectUrl);
          } catch (HttpException ex) {
              //log the error and let the client handle the redirect
              log.warn(ex.getMessage());
              return false;
          }
  
          //update the current location with the redirect location
          setPath(redirectUrl.getPath());
          setQueryString(redirectUrl.getQuery());
  
          if (log.isDebugEnabled()) {
              log.debug("Redirecting from '" + currentUrl.toExternalForm()
                      + "' to '" + redirectUrl.toExternalForm());
          }
  
          return true;
  
      }
  
  
      /**
       * Check for a valid redirect given the current conn and new url.
       * Redirect to a different protocol, host or port are checked for validity.
       *
       * @param conn The existing HttpConnection
       * @param url The new URL to redirect to
       * @throws HttpException if the redirect is invalid
       * @since 2.0
       */
      private static void checkValidRedirect(URL currentUrl, URL redirectUrl)
      throws HttpException {
          log.trace("enter HttpMethodBase.checkValidRedirect(HttpConnection, URL)");
  
          String oldProtocol = currentUrl.getProtocol();
          String newProtocol = redirectUrl.getProtocol();
          if (! oldProtocol.equals(newProtocol)) {
              throw new HttpException("Redirect from protocol " + oldProtocol
                      + " to " + newProtocol + " is not supported");
          }
  
          String oldHost = currentUrl.getHost();
          String newHost = redirectUrl.getHost();
          if (! oldHost.equalsIgnoreCase(newHost)) {
              throw new HttpException("Redirect from host " + oldHost
                      + " to " + newHost + " is not supported");
          }
  
          int oldPort = currentUrl.getPort();
          if (oldPort < 0) {
              oldPort = getDefaultPort(oldProtocol);
          }
          int newPort = redirectUrl.getPort();
          if (newPort < 0) {
              newPort = getDefaultPort(newProtocol);
          }
          if (oldPort != newPort) {
              throw new HttpException("Redirect from port " + oldPort
                      + " to " + newPort + " is not supported");
          }
      }
  
  
      /**
       * Returns the default port for the given protocol.
       *
       * @param protocol currently only http and https are recognized
       * @return the default port of the given protocol or -1 if the
       * protocol is not recognized.
       *
       * @since 2.0
       *
       */
      private static int getDefaultPort(String protocol) {
          String proto = protocol.toLowerCase().trim();
          if (proto.equals("http")){
              return 80;
          }
          else if (proto.equals("https")){
              return 443;
          }
          return -1;
      }
  
      /**
       * Whether the object has been used and not recycled.
       *
       * @return <tt>true</tt> if I have been {@link #execute executed} but not
       *         recycled.
       */
      public boolean hasBeenUsed() {
          return used;
      }
  
      /**
       * Recycle this method so that it can be used again. All of my instances
       * variables will be reset once this method has been called.
       */
      public void recycle() {
          log.trace("enter HttpMethodBase.recycle()");
  
          releaseConnection();
  
          path = null;
          followRedirects = false;
          doAuthentication = true;
          queryString = null;
          requestHeaders.clear();
          responseHeaders.clear();
          statusLine = null;
          used = false;
          http11 = true;
          bodySent = false;
          responseBody = null;
      }
  
      /**
       * @see org.apache.commons.httpclient.HttpMethod#releaseConnection()
       *
       * @since 2.0
       */
      public void releaseConnection() {
  
          if ( responseConnection != null ) {
              responseConnection.releaseConnection();
              this.responseConnection = null;
              this.responseStream = null;
          }
  
      }
  
      /**
       * Remove the request header associated with the given name. Note that
       * header-name matching is case insensitive.
       *
       * @param headerName the header name
       */
      public void removeRequestHeader(String headerName) {
          requestHeaders.remove(headerName.toLowerCase());
      }
  
      // ---------------------------------------------------------------- Queries
  
      /**
       * Confirm that I am ready to execute.
       *
       * <p>
       * This implementation always returns <tt>true</tt>.
       * </p>
       *
       * @return <tt>true</tt>
       */
      public boolean validate() {
          return true;
      }
  
      /**
       * Return the length (in bytes) of my request body, suitable for use in a
       * <tt>Content-Length</tt> header.
       *
       * <p>
       * Return <tt>-1</tt> when the content-length is unknown.
       * </p>
       *
       * <p>
       * This implementation returns <tt>0</tt>, indicating that the request has
       * no body.
       * </p>
       *
       * @return <tt>0</tt>, indicating that the request has no body.
       */
      protected int getRequestContentLength() {
          return 0;
      }
  
      /**
       * Adds an <tt>Authorization</tt> request if needed, as long as no
       * <tt>Authorization</tt> request header already exists.
       *
       * @param state current state of http requests
       * @param conn the connection to use for I/O
       *
       * @throws IOException when errors occur reading or writing to/from the
       *         connection
       * @throws HttpException when a recoverable error occurs
       */
      protected void addAuthorizationRequestHeader(HttpState state,
                                                   HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addAuthorizationRequestHeader("
                    + "HttpState, HttpConnection)");
  
          // add authorization header, if needed
          if (getRequestHeader(Authenticator.WWW_AUTH_RESP) == null) {
              Header wwwAuthenticateHeader = getResponseHeader(
                                                 Authenticator.WWW_AUTH);
              if (null != wwwAuthenticateHeader) {
                  try {
                      Authenticator.authenticate(this, state);
                  } catch (HttpException e) {
                      // ignored
                  }
              }
          }
      }
  
      /**
       * Adds a <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
       * request header, as long as no <tt>Content-Length</tt> request header
       * already exists.
       *
       * @param state current state of http requests
       * @param conn the connection to use for I/O
       *
       * @throws IOException when errors occur reading or writing to/from the
       *         connection
       * @throws HttpException when a recoverable error occurs
       */
      protected void addContentLengthRequestHeader(HttpState state,
                                                   HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addContentLengthRequestHeader("
                    + "HttpState, HttpConnection)");
  
          // add content length or chunking
          int len = getRequestContentLength();
          if (getRequestHeader("content-length") == null) {
              if (0 < len) {
                  setRequestHeader("Content-Length", String.valueOf(len));
              } else if (http11 && (len < 0)) {
                  setRequestHeader("Transfer-Encoding", "chunked");
              }
          }
      }
  
      /**
       * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s.
       *
       * @param state current state of http requests
       * @param conn the connection to use for I/O
       *
       * @throws IOException when errors occur reading or writing to/from the
       *         connection
       * @throws HttpException when a recoverable error occurs
       */
      protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
                    + "HttpConnection)");
  
          CookieSpec matcher = CookiePolicy.getDefaultSpec();
          Cookie[] cookies = matcher.match(
            conn.getHost(),
            conn.getPort(),
            getPath(),
            conn.isSecure(),
            state.getCookies());
          if ((cookies != null) && (cookies.length > 0))
          {
              if (this.strictMode)
              {
                  // If in strict mode put all cookie in one header
                  setRequestHeader(matcher.formatCookieHeader(cookies));
              }
              else
              {
                  // If not in strict mode put each cookie in a separate header
                  for(int i = 0; i < cookies.length; i++)
                  {
                      setRequestHeader(matcher.formatCookieHeader(cookies[i]));
                  }
              }
          }
      }
  
      /**
       * Adds a <tt>Host</tt> request header, as long as no <tt>Host</tt> request
       * header already exists.
       *
       * @param state current state of http requests
       * @param conn the connection to use for I/O
       *
       * @throws IOException when errors occur reading or writing to/from the
       *         connection
       * @throws HttpException when a recoverable error occurs
       */
      protected void addHostRequestHeader(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
                    + "HttpConnection)");
  
          // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
          // applications to send the Host request-header.
          // TODO: Add the ability to disable the sending of this header for
          //       HTTP/1.0 requests.
          String host = conn.getHost();
          int port = conn.getPort();
  
          if (getRequestHeader("host") != null) {
              log.debug(
                  "Request to add Host header ignored: header already added");
              return;
          }
  
          if (isIpAddress(host)) {
              log.debug("Adding empty Host request header: host is an ipaddress");
              setRequestHeader("Host", "");
              return;
          }
          if (log.isDebugEnabled()) {
              log.debug("Adding Host request header");
          }
  
          //appends the port only if not using the default port for the protocol
          if (conn.isSecure()) {
              setRequestHeader("Host", (port == 443) ? host : host + ':' + port);
          } else {
              setRequestHeader("Host", (port == 80) ? host : host + ':' + port);
          }
      }
  
      /**
       * Adds a <tt>Proxy-Authorization</tt> request if needed, as long as no
       * <tt>Proxy-Authorization</tt> request header already exists.
       *
       * @param state current state of http requests
       * @param conn the connection to use for I/O
       *
       * @throws IOException when errors occur reading or writing to/from the
       *         connection
       * @throws HttpException when a recoverable error occurs
       */
      protected void addProxyAuthorizationRequestHeader(HttpState state,
                                                        HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader("
                    + "HttpState, HttpConnection)");
  
          // add proxy authorization header, if needed
          if (getRequestHeader(Authenticator.PROXY_AUTH_RESP) == null) {
              Header wwwAuthenticateHeader = getResponseHeader(
                                                 Authenticator.PROXY_AUTH);
              if (null != wwwAuthenticateHeader) {
                  try {
                      Authenticator.authenticateProxy(this, state);
                  } catch (HttpException e) {
                      // ignored
                  }
              }
          }
      }
  
      /**
       * Populates the request headers map to with additional {@link Header
       * headers} to be submitted to the given {@link HttpConnection}.
       *
       * <p>
       * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
       * <tt>Cookie</tt>, <tt>Content-Length</tt>, <tt>Transfer-Encoding</tt>,
       * and <tt>Authorization</tt> headers, when appropriate.
       * </p>
       *
       * <p>
       * Subclasses may want to override this method to to add additional
       * headers, and may choose to invoke this implementation (via
       * <tt>super</tt>) to add the "standard" headers.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} the headers will eventually be
       *        written to
       * @throws IOException when an error occurs writing the request
       * @throws HttpException when a HTTP protocol error occurs
       *
       * @see #writeRequestHeaders
       */
      protected void addRequestHeaders(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
              + "HttpConnection)");
  
          addUserAgentRequestHeader(state, conn);
          addHostRequestHeader(state, conn);
          addCookieRequestHeader(state, conn);
          addAuthorizationRequestHeader(state, conn);
          addProxyAuthorizationRequestHeader(state, conn);
          addContentLengthRequestHeader(state, conn);
      }
  
      /**
       * Adds a default <tt>User-Agent</tt> request header, as long as no
       * <tt>User-Agent</tt> request header already exists.
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} the headers will eventually be
       *        written to
       * @throws IOException when an error occurs writing the request
       * @throws HttpException when a HTTP protocol error occurs
       */
      protected void addUserAgentRequestHeader(HttpState state,
                                               HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
              + "HttpConnection)");
  
          if (getRequestHeader("user-agent") == null) {
              setRequestHeader(HttpMethodBase.USER_AGENT);
          }
      }
  
      /**
       * Throws an {@link IllegalStateException} if used but not recycled.
       *
       * @throws IllegalStateException if the method has been used and not
       *      recycled
       */
      protected void checkNotUsed() throws IllegalStateException {
          if (used) {
              throw new IllegalStateException("Already used.");
          }
      }
  
      /**
       * Throws an {@link IllegalStateException} if not used since last recycle.
       *
       * @throws IllegalStateException if not used
       */
      protected void checkUsed()  throws IllegalStateException {
          if (!used) {
              throw new IllegalStateException("Not Used.");
          }
      }
  
      // ------------------------------------------------- Static Utility Methods
  
      /**
       * Generate an HTTP/S request line according to the specified attributes.
       *
       * @param connection the connection the request will be sent to
       * @param name the method name generate a request for
       * @param requestPath the path string for the request
       * @param query the query string for the request
       * @param protocol the protocol to use (e.g. HTTP/1.0)
       *
       * @return a line to send to the server that will fulfil the request
       */
      protected static String generateRequestLine(HttpConnection connection,
          String name, String requestPath, String query, String protocol) {
          log.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
              + "String, String, String, String)");
  
          StringBuffer buf = new StringBuffer();
          String path = null;
          try {
              path = (requestPath == null) ? "/" : URIUtil.encodePath(requestPath);
          } catch (URIException urie) {
              log.error("URI path encoding error");
              path = requestPath;
          }
          buf.append(path);
          if (query != null) {
              if (query.indexOf("?") != 0) {
                  buf.append("?");
              }
              String queryString = null;
              queryString = (query == null) ? "/" : query;
              buf.append(queryString);
          }
  
          if (!connection.isProxied() || connection.isTransparent()) {
              return (name + " " + buf.toString() + " " + protocol + "\r\n");
          } else {
              if (connection.isSecure()) {
                  return (name + " https://" + connection.getHost()
                         + ((443 == connection.getPort()
                                 || -1 == connection.getPort())
                            ? "" : (":" + connection.getPort())) + buf.toString()
                         + " " + protocol + "\r\n");
              } else {
                  return (name + " http://" + connection.getHost()
                         + ((80 == connection.getPort()
                                 || -1 == connection.getPort())
                            ? "" : (":" + connection.getPort())) + buf.toString()
                         + " " + protocol + "\r\n");
              }
          }
      }
  
      /**
       * When this method is invoked, {@link #readResponseBody
       * readResponseBody(HttpState,HttpConnection)} will have been invoked.
       *
       * <p>
       * This implementation does nothing.
       * </p>
       *
       * <p>
       * Subclasses may want to override this method.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       *
       * @see #readResponse
       * @see #readResponseBody
       */
      protected void processResponseBody(HttpState state, HttpConnection conn) {
      }
  
      /**
       * When this method is invoked, the response headers map will have been
       * populated with the response headers (in other words, {@link
       * #readResponseHeaders readResponseHeaders(HttpState,HttpConnection)}
       * will have been invoked).
       *
       * <p>
       * This implementation will handle the <tt>Set-Cookie</tt> and
       * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
       * the given {@link HttpState}.
       * </p>
       *
       * <p>
       * Subclasses may want to override this method to specially process
       * additional headers, and/or invoke this method (via <tt>super</tt>) to
       * process the <tt>Set-Cookie</tt> and <tt>Set-Cookie2</tt> headers.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       *
       * @see #readResponse
       * @see #readResponseHeaders
       */
      protected void processResponseHeaders(HttpState state,
          HttpConnection conn) {
          log.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
              + "HttpConnection)");
  
          // add cookies, if any
          // should we set cookies?
          Header setCookieHeader = getResponseHeader("set-cookie2");
          if (null == setCookieHeader) { //ignore old-style if new is supported
              setCookieHeader = getResponseHeader("set-cookie");
          }
  
          if (setCookieHeader == null) return;
          try {
  
              CookieSpec parser = CookiePolicy.getDefaultSpec();
              Cookie[] cookies = parser.parse(
                conn.getHost(), 
                conn.getPort(),
                getPath(), 
                conn.isSecure(),
                setCookieHeader);
              for (int i = 0; i < cookies.length; i++)
              {
                  Cookie cookie = cookies[i];
                  CookieSpec validator = CookiePolicy.getSpecByVersion(cookie.getVersion());
                  validator.validate(
                    conn.getHost(), 
                    conn.getPort(),
                    getPath(), 
                    conn.isSecure(),
                    cookie);
                  if (log.isDebugEnabled()) {
                      log.debug("Cookie accepted: \"" + validator.formatCookie(cookie) + "\"");
                  }
                  state.addCookie(cookie);
              }
                
          } catch (HttpException e) {
              if (log.isWarnEnabled()) {
                  log.warn("Cookie rejected: \"" + setCookieHeader.getValue() + "\". " + e.getMessage());
              }
          }
      }
  
      /**
       * When this method is invoked, the {@link #getStatusCode status code} and
       * {@link #getStatusText status text} values will have been set (in other
       * words, {@link #readStatusLine readStatusLine(HttpState,HttpConnection}
       * will have been invoked).
       *
       * <p>
       * Subclasses may want to override this method to respond to these value.
       * This implementation does nothing.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       *
       * @see #readResponse
       * @see #readStatusLine
       */
      protected void processStatusLine(HttpState state, HttpConnection conn) {
      }
  
      /**
       * Reads the response from the given {@link HttpConnection}.
       *
       * <p>
       * The response is written according to the following logic:
       *
       * <ol>
       * <li>
       * {@link #readStatusLine readStatusLine(HttpState,HttpConnection)} is
       * invoked to read the request line.
       * </li>
       * <li>
       * {@link #processStatusLine processStatusLine(HttpState,HttpConnection)}
       * is invoked, allowing the method to respond to the status line if
       * desired.
       * </li>
       * <li>
       * {@link #readResponseHeaders
       * readResponseHeaders(HttpState,HttpConnection} is invoked to read the
       * associated headers.
       * </li>
       * <li>
       * {@link #processResponseHeaders
       * processResponseHeaders(HttpState,HttpConnection} is invoked, allowing
       * the method to respond to the headers if desired.
       * </li>
       * <li>
       * {@link #readResponseBody readResponseBody(HttpState,HttpConnection)} is
       * invoked to read the associated body (if any).
       * </li>
       * <li>
       * {@link #processResponseBody
       * processResponseBody(HttpState,HttpConnection} is invoked, allowing the
       * method to respond to the body if desired.
       * </li>
       * </ol>
       *
       * Subclasses may want to override one or more of the above methods to to
       * customize the processing. (Or they may choose to override this method
       * if dramatically different processing is required.)
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       */
      protected void readResponse(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace(
              "enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
          readStatusLine(state, conn);
          processStatusLine(state, conn);
          readResponseHeaders(state, conn);
          processResponseHeaders(state, conn);
          readResponseBody(state, conn);
          processResponseBody(state, conn);
      }
  
      /**
       * Read the response body from the given {@link HttpConnection}.
       *
       * <p>
       * The current implementation simply consumes the expected response body
       * (according to the values of the <tt>Content-Length</tt> and
       * <tt>Transfer-Encoding</tt> headers, if any).
       * </p>
       *
       * <p>
       * Subclasses may want to override this method to to customize the
       * processing.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       *
       * @see #readResponse
       * @see #processResponseBody
       */
      protected void readResponseBody(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace(
              "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
  
          setResponseStream(_readResponseBody(state, conn));
      }
  
      /**
       * Read the response body from the given {@link HttpConnection}.
       * <p>
       * The current implementation returns an appropriate stream
       * (according to the values of the
       * <tt>Content-Length</tt> and <tt>Transfer-Encoding</tt>
       * headers, if any).
       * <p>
       *
       * @see #readResponse
       * @see #processResponseBody
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       * @return InputStream to read the response body from
       */
      private InputStream _readResponseBody(HttpState state, HttpConnection conn)
      throws IOException {
          log.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
  
          responseBody = null; // is this desired?
          Header lengthHeader = getResponseHeader("Content-Length");
          Header transferEncodingHeader = getResponseHeader("Transfer-Encoding");
          InputStream is = conn.getResponseInputStream();
          if (wireLog.isDebugEnabled()) {
              is = new WireLogInputStream(is);
          }
          InputStream result = null;
          // We use Transfer-Encoding if present and ignore Content-Length.
          // RFC2616, 4.4 item number 3
          if (null != transferEncodingHeader) {
              if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
                  result = new ChunkedInputStream(is, this);
              }
          } else if (null != lengthHeader) {
  
              // we're using this just in case the content length is duplicated
              // i.e. '57, 57'
              HeaderElement[] lengthElements = lengthHeader.getValues();
              String lengthValue = null;
  
              if ( lengthElements.length > 1 ) {
                  // looks like the content length header was duplicated. if so
                  // they won't be key=value pairs so we just want to get
                  // the name not the value (which should be null)
                  // take the first value and ignore any others
                  lengthValue = lengthElements[0].getName();
              } else {
                  lengthValue = lengthHeader.getValue();
              }
  
              try {
                  int expectedLength = Integer.parseInt(lengthValue);
                  // FIXME: what if the content length is 0, perhaps we should
                  // just return an empty stream in that case
                  result = new ContentLengthInputStream(is, expectedLength);
              } catch(NumberFormatException e) {
                  throw new HttpException(
                      "Unable to parse server response content length: '"
                      + lengthValue + "'"
                  );
  
              }
  
          } else if(canResponseHaveBody(statusLine.getStatusCode())
                  && !getName().equals(ConnectMethod.NAME)){
              result = is;
          }
          if (result == null) {
              return null;
          }
  
          if (shouldCloseConnection()) {
              result = new AutoCloseInputStream(result, conn);
          }
  
          return result;
      }
  
      /**
       * Read response headers from the given {@link HttpConnection}, populating
       * the response headers map.
       *
       * <p>
       * Subclasses may want to override this method to to customize the
       * processing.
       * </p>
       *
       * <p>
       * "It must be possible to combine the multiple header fields into one
       * "field-name: field-value" pair, without changing the semantics of the
       * message, by appending each subsequent field-value to the first, each
       * separated by a comma." - HTTP/1.0 (4.3)
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       *
       * @see #readResponse
       * @see #processResponseHeaders
       */
      protected void readResponseHeaders(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
              + "HttpConnection)");
  
          responseHeaders.clear();
  
          String name = null;
          String value = null;
          for (; ;) {
              String line = conn.readLine();
              if ((line == null) || (line.length() < 1)) {
                  break;
              }
  
              // Parse the header name and value
              // Check for folded headers first
              // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
              // discussion on folded headers
              boolean isFolded = false;
              if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) {
                  // we have continuation folded header
                  // so append value
                  isFolded = true;
                  value = line.substring(1).trim();
              } else {
                  // Otherwise we should have normal HTTP header line
                  // Parse the header name and value
                  int colon = line.indexOf(":");
                  if (colon < 0) {
                      throw new HttpException("Unable to parse header: " + line);
                  }
                  name = line.substring(0, colon).trim();
                  value = line.substring(colon + 1).trim();
              }
              Header header = getResponseHeader(name);
              if (null == header) {
                  header = new Header(name, value);
              } else {
                  String oldvalue = header.getValue();
                  if (null != oldvalue) {
                      if (isFolded) {
                          // LWS becomes space plus extended value
                          header = new Header(name, oldvalue + " " + value);
                      } else {
                          // Append additional header value
                          header = new Header(name, oldvalue + ", " + value);
                      }
                  } else {
                      header = new Header(name, value);
                  }
              }
              setResponseHeader(header);
          }
      }
  
      /**
       * Read the status line from the given {@link HttpConnection}, setting my
       * {@link #getStatusCode status code} and {@link #getStatusText status
       * text}.
       *
       * <p>
       * Subclasses may want to override this method to to customize the
       * processing.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to read the response from
       *
       * @throws IOException when errors occur reading the status line
       * @throws HttpException If there is no status line, the protocol is not
       *      recognised, if we are unable to parse the status code from the line,
       *      or there was no status text
       * @throws HttpRecoverableException when the status line is null and the
       *      request should be retried
       *
       * @see StatusLine
       */
      protected void readStatusLine(HttpState state, HttpConnection conn)
      throws IOException, HttpRecoverableException, HttpException {
          log.trace(
              "enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
  
          //read out the HTTP status string
          String statusString = conn.readLine();
          while ((statusString != null) && !statusString.startsWith("HTTP/")) {
              statusString = conn.readLine();
          }
          if (statusString == null) {
              // A null statusString means the connection was lost before we got a
              // response.  Try again.
              throw new HttpRecoverableException("Error in parsing the status "
                  + " line from the response: unable to find line starting with"
                  + " \"HTTP/\"");
          }
  
          //create the status line from the status string
          statusLine = new StatusLine(statusString);
  
          //check for a valid HTTP-Version
          String httpVersion = statusLine.getHttpVersion();
          if (httpVersion.equals("HTTP/1.0")){
              http11 = false;
          } else if (httpVersion.equals("HTTP/1.1")){
              http11 = true;
          } else {
              throw new HttpException("Unrecognized server protocol: '"
                                      + httpVersion + "'");
          }
  
      }
  
      // ------------------------------------------------------ Protected Methods
  
      /**
       * <p>
       * Writes my request to the given {@link HttpConnection}.
       * </p>
       *
       * <p>
       * The request is written according to the following logic:
       * </p>
       *
       * <ol>
       * <li>
       * {@link #writeRequestLine writeRequestLine(HttpState, HttpConnection)} is
       * invoked to write the request line.
       * </li>
       * <li>
       * {@link #writeRequestHeaders writeRequestHeaders(HttpState,
       * HttpConnection)} is invoked to write the associated headers.
       * </li>
       * <li>
       * <tt>\r\n</tt> is sent to close the head part of the request.
       * </li>
       * <li>
       * {@link #writeRequestBody writeRequestBody(HttpState, HttpConnection)} is
       * invoked to write the body part of the request.
       * </li>
       * </ol>
       *
       * <p>
       * Subclasses may want to override one or more of the above methods to to
       * customize the processing. (Or they may choose to override this method
       * if dramatically different processing is required.)
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to write the request to
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       */
      protected void writeRequest(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace(
              "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
          writeRequestLine(state, conn);
          writeRequestHeaders(state, conn);
          conn.writeLine(); // close head
          bodySent = writeRequestBody(state, conn);
      }
  
      /**
       * Write the request body to the given {@link HttpConnection}.
       *
       * <p>
       * If an expectation is required, this method should ensure that it has
       * been sent by checking the {@link #getStatusCode status code}.
       * </p>
       *
       * <p>
       * This method should return <tt>true</tt> if the request body was actually
       * sent (or is empty), or <tt>false</tt> if it could not be sent for some
       * reason (for example, expectation required but not present).
       * </p>
       *
       * <p>
       * This implementation writes nothing and returns <tt>true</tt>.
       * </p>
       *
       * @param state the client state
       * @param conn the connection to write to
       *
       * @return <tt>true</tt>
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       */
      protected boolean writeRequestBody(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          return true;
      }
  
      /**
       * Writes the request headers to the given {@link HttpConnection}.
       *
       * <p>
       * This implementation invokes {@link #addRequestHeaders
       * addRequestHeaders(HttpState,HttpConnection)}, and then writes each
       * header to the request stream.
       * </p>
       *
       * <p>
       * Subclasses may want to override this method to to customize the
       * processing.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to write to
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       *
       * @see #addRequestHeaders
       * @see #getRequestHeaders
       */
      protected void writeRequestHeaders(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
              + "HttpConnection)");
          addRequestHeaders(state, conn);
          Iterator it = requestHeaders.values().iterator();
          while (it.hasNext()) {
              conn.print(((Header) it.next()).toExternalForm());
          }
      }
  
      /**
       * Writes the "request line" to the given {@link HttpConnection}.
       *
       * <p>
       * Subclasses may want to override this method to to customize the
       * processing.
       * </p>
       *
       * @param state the client state
       * @param conn the {@link HttpConnection} to write to
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       *
       * @see #generateRequestLine
       */
      protected void writeRequestLine(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          log.trace(
              "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
          String requestLine = getRequestLine(conn);
          conn.print(requestLine);
      }
  
      /**
       * Gets the request line that was sent to the http server.
       * Consider making this public.  Consider creating a new class
       * RequestLine for this purpose.
       */
      private String getRequestLine(HttpConnection conn) {
          return  HttpMethodBase.generateRequestLine(conn, getName(),
                  getPath(), getQueryString(), getHttpVersion());
      }
  
      /**
       * Get the HTTP version.
       *
       * @return HTTP/1.1 if http11, HTTP/1.0 otherwise
       *
       * @since 2.0
       */
      private String getHttpVersion() {
          return (http11 ? "HTTP/1.1" : "HTTP/1.0");
      }
  
      /**
       * Determines if the provided value is a valid IPv4 internet address.
       *
       * @param value - value to check
       *
       * @return boolean - true if value is valid, otherwise false
       */
      private static boolean isIpAddress(String value) {
          log.trace("enter HttpMethodBase.isIpAddress(String)");
  
          value = value.trim();
  
          // prevent input values of 127.0.0.1. or .127.0.0.1, etc.
          if (value.startsWith(".") || value.endsWith(".")) {
              return false;
          }
  
          StringTokenizer tokenizer = new StringTokenizer(value, ".");
          if (tokenizer.countTokens() == 4) {
              while (tokenizer.hasMoreTokens()) {
                  try {
                      int i = Integer.parseInt(tokenizer.nextToken());
                      if ((i < 0) || (i > 255)) {
                          // parsed section of address is not in the proper range
                          return false;
                      }
                  } catch (NumberFormatException nfe) {
                      return false;
                  }
              }
          } else {
              // wrong number of tokens
              return false;
          }
          return true;
      }
  
  
      /**
       * "It must be possible to combine the multiple header fields into one
       * "field-name: field-value" pair, without changing the semantics of the
       * message, by appending each subsequent field-value to the first, each
       * separated by a comma."
       * //TODO: This method is trying to make up for deficiencies in Header.
       *
       * @param existingHeader the current header
       * @param value DOCUMENT ME!
       *
       * @return DOCUMENT ME!
       */
      private String getNewHeaderValue(Header existingHeader, String value) {
          String existingValue = existingHeader.getValue();
          if (existingValue == null) {
              existingValue = "";
          }
          String newValue = value;
          if (value == null) {
              newValue = "";
          }
          return existingValue + ", " + newValue;
      }
  
      /**
       * Sets the specified response header.
       *
       * @param header the header to set.
       *
       * @since 2.0
       */
      private void setResponseHeader(Header header) {
          if (header == null) {
              return;
          }
          responseHeaders.put(header.getName().toLowerCase(), header);
      }
  
      /**
       * Per RFC 2616 section 4.3, some response can never contain a message
       * body.
       *
       * @param status - the HTTP status code
       *
       * @return true if the message may contain a body, false if it can not
       *         contain a message body
       */
      private static boolean canResponseHaveBody(int status) {
          log.trace("enter HttpMethodBase.canResponseHaveBody(int)");
  
          boolean result = true;
  
          if ((status >= 100 && status <= 199) || (status == 204)
              || (status == 304)) { // NOT MODIFIED
              result = false;
          }
  
          return result;
      }
  
      /**
       * Generates a key used for idenifying visited URLs.
       *
       * @param conn DOCUMENT ME!
       *
       * @return DOCUMENT ME!
       */
      private String generateVisitedKey(HttpConnection conn) {
          return conn.getHost() + ":" + conn.getPort() + "|"
                 + generateRequestLine(conn, getName(), getPath(),
                                       getQueryString(), getHttpVersion());
      }
  
      /**
       * process a response that requires authentication
       *
       * @param state the current state
       * @param connection the connection for communication
       *
       * @return true if the request has completed process, false if more
       *         attempts are needed
       */
      private boolean processAuthenticationResponse(HttpState state,
                                                    HttpConnection connection) {
          log.trace("enter HttpMethodBase.processAuthenticationResponse("
              + "HttpState, HttpConnection)");
  
          int statusCode = statusLine.getStatusCode();
          // handle authentication required
          Header wwwauth = null;
          Set realmsUsed = null;
          switch (statusCode) {
              case HttpStatus.SC_UNAUTHORIZED:
                  wwwauth = getResponseHeader(Authenticator.WWW_AUTH);
                  realmsUsed = realms;
                  break;
              case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
                  wwwauth = getResponseHeader(Authenticator.PROXY_AUTH);
                  realmsUsed = proxyRealms;
                  break;
          }
          boolean authenticated = false;
          // if there was a header requesting authentication
          if (null != wwwauth) {
              String pathAndCreds = getPath() + ":" + wwwauth.getValue();
              if (realmsUsed.contains(pathAndCreds)) {
                  if (log.isInfoEnabled()) {
                      log.info("Already tried to authenticate to \""
                               + wwwauth.getValue() + "\" but still receiving "
                               + statusCode + ".");
                  }
                  return true;
              } else {
                  realmsUsed.add(pathAndCreds);
              }
  
              try {
                  //remove preemptive header and reauthenticate
                  switch (statusCode) {
                      case HttpStatus.SC_UNAUTHORIZED:
                          removeRequestHeader(Authenticator.WWW_AUTH_RESP);
                          authenticated = Authenticator.authenticate(this, state);
                          break;
                      case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
                          removeRequestHeader(Authenticator.PROXY_AUTH_RESP);
                          authenticated = Authenticator.authenticateProxy(this,
                                                                          state);
                          break;
                  }
              } catch (HttpException httpe) {
                  log.warn(httpe.getMessage());
                  return true; // finished request
              } catch (UnsupportedOperationException uoe) {
                  log.warn(uoe.getMessage());
                  //FIXME: should this return true?
              }
  
              if (!authenticated) {
                  // won't be able to authenticate to this challenge
                  // without additional information
                  log.debug("HttpMethodBase.execute(): Server demands "
                            + "authentication credentials, but none are "
                            + "available, so aborting.");
              } else {
                  log.debug("HttpMethodBase.execute(): Server demanded "
                            + "authentication credentials, will try again.");
                  // let's try it again, using the credentials
              }
          }
  
          return !authenticated; // finished processing if we aren't authenticated
      }
  
      /**
       * Write a request and read the response. Both the write to the server will
       * be retried {@link #maxRetries} times if the operation fails with a
       * HttpRecoverableException. The write will only be attempted if the read
       * has succeeded.
       *
       * <p>
       * The <i>used</i> is set to true if the write succeeds.
       * </p>
       *
       * @param state the current state
       * @param connection the connection for communication
       *
       * @throws HttpException when errors occur as part of the HTTP protocol
       *         conversation
       * @throws IOException when an I/O error occurs communicating with the
       *         server
       *
       * @see writeRequest(HttpState,HttpConnection)
       * @see readResponse(HttpState,HttpConnection)
       */
      private void processRequest(HttpState state, HttpConnection connection)
      throws HttpException, IOException {
          log.trace(
              "enter HttpMethodBase.processRequest(HttpState, HttpConnection)");
  
          //try to do the write
          int retryCount = 0;
          do {
              retryCount++;
              if (log.isTraceEnabled()) {
                  log.trace("Attempt number " + retryCount + " to write request");
              }
              try {
                  if (!connection.isOpen()) {
                      log.debug("Opening the connection.");
                      connection.open();
                  }
                  writeRequest(state, connection);
                  used = true; //write worked, mark this method as used
                  break; //move onto the write
              } catch (HttpRecoverableException httpre) {
                  if (log.isDebugEnabled()) {
                      log.debug("Closing the connection.");
                  }
  
                  connection.close();
                  log.info("Recoverable exception caught when writing request");
                  if (retryCount == maxRetries) {
                      log.warn(
                          "Attempt to write request has reached max retries: "
                          + maxRetries);
                      throw httpre;
                  }
              }
          } while (retryCount <= maxRetries);
  
          //try to do the read
          try {
              readResponse(state, connection);
          } catch (HttpRecoverableException httpre) {
              log.warn("Recoverable exception caught when reading response");
              if (log.isDebugEnabled()) {
                  log.debug("Closing the connection.");
              }
  
              connection.close();
              throw httpre;
          }
          //everything should be OK at this point
      }
  
      /**
       * On a {@link HttpStatus#SC_CONTINUE continue}, if there are more request
       * bytes to be sent, write them to the connection
       *
       * @param state the current state
       * @param connection the connection for communication
       *
       * @throws HttpException when errors occur as part of the HTTP protocol
       *         conversation
       * @throws IOException when an I/O error occurs communicating with the
       *         server
       */
      private void writeRemainingRequestBody(HttpState state,
                                             HttpConnection connection)
      throws HttpException, IOException {
          log.trace("enter writeRemainingRequestBody(HttpState, HttpConnection)");
  
          if (HttpStatus.SC_CONTINUE == statusLine.getStatusCode()) {
              if (!bodySent) {
                  bodySent = writeRequestBody(state, connection);
              } else {
                  log.warn("Received status CONTINUE but the body has already "
                      + "been sent");
                  // According to RFC 2616 this respose should be ignored
              }
              readResponse(state, connection);
          }
      }
  
  
      protected static String getContentCharSet(Header contentheader)
      {
          log.trace("enter getContentCharSet( Header contentheader )");
          String charset = null;
          if (contentheader != null){
              try {
                  HeaderElement values[] = contentheader.getValues();
                  // I expect only one header element to be there
                  // No more. no less
                  if (values.length == 1) {
                      NameValuePair param = values[0].getParameterByName("charset");
                      if (param != null) {
                          // If I get anything "funny" UnsupportedEncondingException will result
                          charset = param.getValue();
                      }
                  }
              }
              catch(HttpException e){
                  log.error(e);
              }
          }
          if (charset == null) {
              if (log.isDebugEnabled()) {
                  log.debug("Default charset used: " + DEFAULT_CHARSET);
              }
              charset = DEFAULT_CHARSET;
          }
          return charset;
      }
  
  
      public String getRequestCharSet() {
          return getContentCharSet(getRequestHeader("Content-Type"));
      }
  
  
      public String getResponseCharSet() {
          return getContentCharSet(getResponseHeader("Content-Type"));
      }
  
      /**
       * Releases this connection from its connectionManager when the response has
       * been read.
       */
      private class ResponseAutoReleaseInputStream extends InputStream {
  
          private InputStream is;
  
          public ResponseAutoReleaseInputStream(InputStream is) {
              this.is = is;
          }
  
          /**
           * @see java.io.InputStream#close()
           */
          public void close() throws IOException {
              is.close();
              releaseConnection();
          }
  
          /**
           * @see java.io.InputStream#read()
           */
          public int read() throws IOException {
              int b = is.read();
  
              if ( b == -1 ) {
                  releaseConnection();
              }
  
              return b;
          }
  
          /**
           * @see java.io.InputStream#read(byte, int, int)
           */
          public int read(byte[] array, int off, int len) throws IOException {
              int b = is.read(array, off, len);
  
              if ( b == -1 ) {
                  releaseConnection();
              }
  
              return b;
          }
  
          /**
           * @see java.io.InputStream#read(byte)
           */
          public int read(byte[] array) throws IOException {
              int b = is.read(array);
  
              if ( b == -1 ) {
                  releaseConnection();
              }
  
              return b;
          }
  
      }
  
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/MalformedCookieException.java
  
  Index: MalformedCookieException.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient.cookie;
  
  /**
   * Signals that a cookie is in some way invalid or illegal in a given
   * context
   *
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   * 
   * @since 2.0
   */
  
  import org.apache.commons.httpclient.HttpException;
  
  public class MalformedCookieException extends HttpException
  {
      /**
       * Creates a new HttpException.
       */
  
      public MalformedCookieException()
      {
          super();
      }
  
      /**
       * Creates a new HttpException with the specified message.
       *
       * @param message exception message
       */
  
      public MalformedCookieException(String message)
      {
          super(message);
      }
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/NetscapeDraftSpec.java
  
  Index: NetscapeDraftSpec.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient.cookie;
  
  import java.util.StringTokenizer;
  import org.apache.commons.httpclient.Cookie;
  
  /**
   * <p>
   * Netscape draft specific cookie management functions
   * </p>
   *
   * @author  B.C. Holmes
   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
   * @author Rod Waldhoff
   * @author dIon Gillard
   * @author Sean C. Sullivan
   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
   * @author Marc A. Saegesser
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   * 
   * @since 2.0 
   */
  
  public class NetscapeDraftSpec extends CookieSpecBase
  {
  
      /** Default constructor */
  
      public NetscapeDraftSpec()
      {
          super();
      }
  
  
      /**
        * Performs Netscape draft spesific cookie {@link Cookie} validation
        *
        * @param host the host from which the {@link Cookie} was received
        * @param port the port from which the {@link Cookie} was received
        * @param path the path from which the {@link Cookie} was received
        * @param secure <tt>true</tt> when the {@link Cookie} was received over HTTPS
        * @throws MalformedCookieException if an exception occurs during validation
        * @throws java.lang.IllegalArgumentException if an input parameter is illegal
        */
  
      public void validate(String host, int port, String path, boolean secure, final Cookie cookie) throws MalformedCookieException
      {
          log.trace("enterNetscapeDraftCookieProcessor RCF2109CookieProcessor.validate(Cookie)");
          // Perform generic validation
          super.validate(host, port, path, secure, cookie);
          // Perform Netscape Cookie draft specific validation
          if (host.indexOf(".") >= 0)
          {
              int domainParts = new StringTokenizer(cookie.getDomain(), ".").countTokens();
  
              if (isSpecialDomain(cookie.getDomain()))
              {
                  if(domainParts < 2)
                  {
                      throw new MalformedCookieException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification for special domains");
                  }
              }
              else
              {
              if(domainParts < 3)
              {
                  throw new MalformedCookieException("Domain attribute \""+ cookie.getDomain() + "\" violates the Netscape cookie specification");
              }            
          }
          }
      }
      
      /**
       * Checks if the given domain is in one of the seven special
       * top level domains defined by the Netscape cookie specification.
       */
      private static boolean isSpecialDomain(String domain)
      {
          String ucDomain = domain.toUpperCase();
          if(ucDomain.endsWith(".COM") ||
             ucDomain.endsWith(".EDU") ||
             ucDomain.endsWith(".NET") ||
             ucDomain.endsWith(".GOV") ||
             ucDomain.endsWith(".MIL") ||
             ucDomain.endsWith(".ORG") ||
             ucDomain.endsWith(".INT")){
              return true;
          }
          return false;
      }
      
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java
  
  Index: RFC2109Spec.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * 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/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient.cookie;
  
  import org.apache.commons.httpclient.Header;
  import org.apache.commons.httpclient.Cookie;
  
  /**
   * <p>
   * RFC 2109 specific cookie management functions
   * </p>
   *
   * @author  B.C. Holmes
   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
   * @author Rod Waldhoff
   * @author dIon Gillard
   * @author Sean C. Sullivan
   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
   * @author Marc A. Saegesser
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   * 
   * @since 2.0 
   */
  
  public class RFC2109Spec extends CookieSpecBase
  {
  
      /** Default constructor */
      
      public RFC2109Spec()
      {
          super();
      }
  
  
      public void validate(String host, int port, String path, boolean secure, final Cookie cookie) throws MalformedCookieException
      {
          log.trace("enter RFC2109Spec.validate(String, int, String, boolean, Cookie)");
          // Perform generic validation
          super.validate(host, port, path, secure, cookie);
          // Perform RFC 2109 specific validation
          if (cookie.isDomainAttributeSpecified() && (!cookie.getDomain().equals(host)))
          {
              // domain must start with dot
              if (!cookie.getDomain().startsWith("."))
              {
                  throw new MalformedCookieException("Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot");
              }
              // domain must have at least one embedded dot
              int dotIndex = cookie.getDomain().indexOf('.', 1);
              if(dotIndex < 0 || dotIndex == cookie.getDomain().length()-1)
              {
                  throw new MalformedCookieException("Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2109: domain must contain an embedded dot");
              }
              // host minus domain may not contain any dots
              if (host.substring(0,
                      host.length() -
                      cookie.getDomain().length()).indexOf('.') != -1)
              {
                  throw new MalformedCookieException("Domain attribute \"" + cookie.getDomain() + "\" violates RFC 2109: host minus domain may not contain any dots");
              }
          }
      }
  
  
      /**
       * Return a string suitable for sending in a Cookie header
       * @param cookie {@link Cookie} to be formatted as string
       * @return a string suitable for sending in a Cookie header.
       */
  
      public String formatCookie(Cookie cookie)
      {
          log.trace("enter RFC2109Spec.formatCookie(Cookie)");
          if(cookie == null)
          {
              throw new IllegalArgumentException("Cookie may not be null");
          }
          StringBuffer buf = new StringBuffer();
          buf.append(cookie.getName());
          buf.append("=\"");
          buf.append(cookie.getValue());
          buf.append("\"");
          if (cookie.getDomain() != null && cookie.isDomainAttributeSpecified()) {
              buf.append("; $Domain=\"");
              buf.append(cookie.getDomain());
              buf.append("\"");
          }
          if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
              buf.append("; $Path=\"");
              buf.append(cookie.getPath());
              buf.append("\"");
          }
          return buf.toString();
      }
  
  
      /**
       * Creates a RFC 2109 compliant <tt>Cookie</tt> header value containing all cookies 
       * in <i>cookies</i> suitable for sending in a Cookie header
       * @param an array of <tt>Cookie</tt>s to be formatted
       * @return a string suitable for sending in a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public String formatCookies(Cookie[] cookies)
      {
          log.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
          String value = super.formatCookies(cookies);
          int version = Integer.MAX_VALUE;
          // Pick the lowerest common denominator
          for (int i = 0; i < cookies.length; i++)
          {
              Cookie cookie = cookies[i];
              if (cookie.getVersion() < version)
              {
                  version = cookie.getVersion();
              }
          }
          StringBuffer buffer = new StringBuffer();
          buffer.append("$Version=\"");
          buffer.append(version);
          buffer.append("\"; ");
          buffer.append(value);
          return buffer.toString();
      }
      
      
      /**
       * Creates a <tt>Cookie</tt> header containing the <i>cookie</i>,
       * @param <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt> header
       * @return a Cookie header.
       * @throws java.lang.IllegalArgumentException if an input parameter is illegal
       */
  
      public Header formatCookieHeader(Cookie cookie)
      {
          log.trace("enter RFC2109Spec.formatCookieHeader(Cookie)");
          String value = formatCookie(cookie);
          StringBuffer buffer = new StringBuffer();
          buffer.append("$Version=\"");
          buffer.append(cookie.getVersion());
          buffer.append("\"; ");
          buffer.append(value);
          return new Header("Cookie", buffer.toString());
      }
  }
  
  
  
  1.15      +85 -23    jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestCookie.java
  
  Index: TestCookie.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestCookie.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- TestCookie.java	29 Nov 2002 19:03:02 -0000	1.14
  +++ TestCookie.java	8 Dec 2002 06:09:46 -0000	1.15
  @@ -70,6 +70,7 @@
   import java.util.SortedSet;
   import java.util.TreeSet;
   import java.util.Iterator;
  +import org.apache.commons.httpclient.cookie.*;
   
   
   /**
  @@ -224,23 +225,8 @@
           assertTrue("Secure",!parsed[0].getSecure());
           assertEquals("Version",1,parsed[0].getVersion());
       }
  -/*
  -    public void testParseNoName() throws Exception {
  -        Header setCookie = new Header("Set-Cookie","=cookie-value");
  -        Cookie[] parsed = Cookie.parse("127.0.0.1","/",setCookie);
  -        assertEquals("Found 1 cookie.",1,parsed.length);
  -        assertEquals("Name","",parsed[0].getName());
  -        assertEquals("Value","cookie-value",parsed[0].getValue());
  -        assertTrue("Comment",null == parsed[0].getComment());
  -        assertTrue("ExpiryDate",null == parsed[0].getExpiryDate());
  -        //assertTrue("isToBeDiscarded",parsed[0].isToBeDiscarded());
  -        assertTrue("isPersistent",!parsed[0].isPersistent());
  -        assertEquals("Domain","127.0.0.1",parsed[0].getDomain());
  -        assertEquals("Path","/",parsed[0].getPath());
  -        assertTrue("Secure",!parsed[0].getSecure());
  -        assertEquals("Version",0,parsed[0].getVersion());
  -    }
  -*/
  + 
  + 
       public void testParseNoValue() throws Exception {
           Header setCookie = new Header("Set-Cookie","cookie-name=");
           Cookie[] parsed = Cookie.parse("127.0.0.1","/",setCookie);
  @@ -685,9 +671,9 @@
       /**
        * Tests default constructor.
        */
  -    public void testDefaultConsttuctor() throws Exception {
  +    public void testDefaultConsttuctor() {
           Cookie dummy = new Cookie();
  -        assertEquals( dummy.toExternalForm(), "noname=null" );
  +        assertEquals( "noname=\"null\"", dummy.toExternalForm() );
       }
   
       /**
  @@ -704,5 +690,81 @@
               fail("Unexpected exception: " + e.toString());
           }
       }
  +    
  +    /**
  +     * Tests if cookie constructor rejects cookie name containing blanks.
  +     */
  +    public void testInvalidCookieName() {
  +        try
  +        {
  +            Cookie dummy = new Cookie("localhost", "invalid name", "cooke name may not have blanks");
  +            fail("IllegalArgumentException must have been thrown");
  +        }
  +        catch(IllegalArgumentException e)
  +        {
  +            // Expected            
  +        }
  +    }
  +
  +
  +    /**
  +     * Tests if cookie constructor rejects cookie name starting with $.
  +     */
  +    public void testInvalidCookieName2() {
  +        try
  +        {
  +            Cookie dummy = new Cookie("localhost", "$invalid_name", "cooke name may not start with $");
  +            fail("IllegalArgumentException must have been thrown");
  +        }
  +        catch(IllegalArgumentException e)
  +        {
  +            // Expected            
  +        }
  +    }
  +
  +
  +    /**
  +     * Tests Netscape specific cookie formatting.
  +     */
  +    
  +    public void testNetscapeCookieFormatting() {
  +        Header setCookie = new Header(
  +          "Set-Cookie", "name=value; path=/; domain=.mydomain.com");
  +        try {
  +            CookieSpec parser = CookiePolicy.getSpecByPolicy(CookiePolicy.NETSCAPE_DRAFT);
  +            Cookie[] cookies = parser.parse("myhost.mydomain.com", 80, "/", false, setCookie );
  +            parser.validate("myhost.mydomain.com", 80, "/", false, cookies[0]);
  +            String s = parser.formatCookie(cookies[0]);
  +            assertEquals("name=value", s);
  +        }
  +        catch(HttpException e) {
  +            e.printStackTrace();
  +            fail("Unexpected exception: " + e.toString());
  +        }
  +    }
  +    
  +
  +    /**
  +     * Tests RFC 2109 compiant cookie formatting.
  +     */
  +    
  +    public void testRFC2109CookieFormatting() {
  +        Header setCookie = new Header(
  +          "Set-Cookie", "name=\"value\"; path=\"/\"; domain=\".mydomain.com\"");
  +        try {
  +            CookieSpec parser = CookiePolicy.getSpecByPolicy(CookiePolicy.RFC2109);
  +            Cookie[] cookies = parser.parse("myhost.mydomain.com", 80, "/", false, setCookie );
  +            parser.validate("myhost.mydomain.com", 80, "/", false, cookies[0]);
  +            String s = parser.formatCookie(cookies[0]);
  +            assertEquals(s, "name=\"value\"; $Domain=\".mydomain.com\"; $Path=\"/\"");
  +        }
  +        catch(HttpException e) {
  +            e.printStackTrace();
  +            fail("Unexpected exception: " + e.toString());
  +        }
  +    }
  +    
  +
  +    
   }
   
  
  
  
  1.5       +75 -51    jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappCookie.java
  
  Index: TestWebappCookie.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappCookie.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- TestWebappCookie.java	15 Mar 2002 22:52:07 -0000	1.4
  +++ TestWebappCookie.java	8 Dec 2002 06:09:46 -0000	1.5
  @@ -105,7 +105,8 @@
   
       public void testSetCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           GetMethod method = new GetMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set");
           method.setUseDisk(false);
  @@ -125,7 +126,7 @@
   
       public void testSetCookiePost() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
           PostMethod method = new PostMethod("/" + context + "/cookie/write");
           method.addParameter("simple","set");
           method.setUseDisk(false);
  @@ -145,7 +146,8 @@
   
       public void testSetCookiePut() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           PutMethod method = new PutMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set");
           method.setRequestBody("data to be sent via http post");
  @@ -165,7 +167,8 @@
   
       public void testSetExpiredCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           GetMethod method = new GetMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=unset");
           method.setUseDisk(false);
  @@ -183,7 +186,8 @@
   
       public void testSetExpiredCookiePut() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           PutMethod method = new PutMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=unset");
           method.setRequestBody("data to be sent via http post");
  @@ -201,7 +205,8 @@
   
       public void testSetUnsetCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           GetMethod method = new GetMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set");
           method.setUseDisk(false);
  @@ -236,7 +241,8 @@
   
       public void testSetMultiCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           GetMethod method = new GetMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set&domain=set");
           method.setUseDisk(false);
  @@ -259,7 +265,8 @@
   
       public void testSetMultiCookiePut() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           PutMethod method = new PutMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set&domain=set");
           method.setRequestBody("data to be sent via http post");
  @@ -282,7 +289,8 @@
   
       public void testSendCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           GetMethod method = new GetMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set");
           method.setUseDisk(false);
  @@ -308,14 +316,17 @@
               fail("Unable to execute method : " + t.toString());
           }
           assertEquals(200,method2.getStatusCode());
  -        assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -        assertTrue(method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value</tt></p>") >= 0);
  -        assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
  +        String s = method2.getResponseBodyAsString();
  +        assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +        assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"</tt></p>") >= 0);
  +        assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
       }
   
       public void testMultiSendCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.setStrictMode(true);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           GetMethod method = new GetMethod("/" + context + "/cookie/write");
           method.setQueryString("simple=set&domain=set");
           method.setUseDisk(false);
  @@ -344,15 +355,17 @@
               fail("Unable to execute method : " + t.toString());
           }
           assertEquals(200,method2.getStatusCode());
  -        assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -        assertTrue(method2.getResponseBodyAsString(),method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
  -        assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
  -        assertTrue(method2.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
  +        String s = method2.getResponseBodyAsString();
  +        assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +        assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
  +        assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
  +        assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
       }
   
       public void testDeleteCookieGet() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
   
           {
               GetMethod method = new GetMethod("/" + context + "/cookie/write");
  @@ -385,10 +398,11 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method2.getStatusCode());
  -            assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -            assertTrue(method2.getResponseBodyAsString(),method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
  -            assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
  -            assertTrue(method2.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
  +            String s = method2.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +            assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
           }
   
           {
  @@ -419,15 +433,17 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method4.getStatusCode());
  -            assertTrue(method4.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -            assertTrue(method4.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
  -            assertTrue(method4.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
  +            String s = method4.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +            assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
           }
       }
   
       public void testDeleteCookiePut() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
   
           {
               PutMethod method = new PutMethod("/" + context + "/cookie/write");
  @@ -460,10 +476,11 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method2.getStatusCode());
  -            assertTrue(method2.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
  -            assertTrue(method2.getResponseBodyAsString(),method2.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; simplecookie=value; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
  -            assertTrue(method2.getResponseBodyAsString().indexOf("<tt>simplecookie=value</tt><br>") >= 0);
  -            assertTrue(method2.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
  +            String s = method2.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
  +            assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; simplecookie=\"value\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>simplecookie=\"value\"</tt><br>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
           }
   
           {
  @@ -494,15 +511,17 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method4.getStatusCode());
  -            assertTrue(method4.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
  -            assertTrue(method4.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; domaincookie=value; $Domain=" + host + "</tt></p>") >= 0);
  -            assertTrue(method4.getResponseBodyAsString().indexOf("<tt>domaincookie=value</tt><br>") >= 0);
  +            String s = method4.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: PUT</title>") >= 0);
  +            assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; domaincookie=\"value\"; $Domain=\"" + host + "\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>domaincookie=\"value\"</tt><br>") >= 0);
           }
       }
   
       public void testPathCookie1() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
   
           {
               GetMethod method = new GetMethod("/" + context + "/cookie/write");
  @@ -531,15 +550,17 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method.getStatusCode());
  -            assertTrue(method.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -            assertTrue(method.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; pathcookie=value; $Path=/</tt></p>") >= 0);
  -            assertTrue(method.getResponseBodyAsString().indexOf("<tt>pathcookie=value</tt><br>") >= 0);
  +            String s = method.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +            assertTrue(s ,s.indexOf("<p><tt>Cookie: $Version=\"1\"; pathcookie=\"value\"; $Path=\"/\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>pathcookie=\"value\"</tt><br>") >= 0);
           }
       }
   
       public void testPathCookie2() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
   
           {
               GetMethod method = new GetMethod("/" + context + "/cookie/write");
  @@ -568,16 +589,17 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method.getStatusCode());
  -            assertTrue(method.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -            assertTrue(method.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; pathcookie=value; $Path=/" + context +"</tt></p>") >= 0);
  -            assertTrue(method.getResponseBodyAsString().indexOf("<tt>pathcookie=value</tt><br>") >= 0);
  +            String s = method.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +            assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; pathcookie=\"value\"; $Path=\"/" + context +"\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>pathcookie=\"value\"</tt><br>") >= 0);
           }
       }
   
       public void testPathCookie3() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  -
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
           {
               GetMethod method = new GetMethod("/" + context + "/cookie/write");
               method.setQueryString("path=/" + context + "/cookie");
  @@ -605,15 +627,17 @@
                   fail("Unable to execute method : " + t.toString());
               }
               assertEquals(200,method.getStatusCode());
  -            assertTrue(method.getResponseBodyAsString().indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  -            assertTrue(method.getResponseBodyAsString().indexOf("<p><tt>Cookie: $Version=1; pathcookie=value; $Path=/" + context + "/cookie</tt></p>") >= 0);
  -            assertTrue(method.getResponseBodyAsString().indexOf("<tt>pathcookie=value</tt><br>") >= 0);
  +            String s = method.getResponseBodyAsString();
  +            assertTrue(s, s.indexOf("<title>ReadCookieServlet: GET</title>") >= 0);
  +            assertTrue(s, s.indexOf("<p><tt>Cookie: $Version=\"1\"; pathcookie=\"value\"; $Path=\"/" + context + "/cookie\"</tt></p>") >= 0);
  +            assertTrue(s, s.indexOf("<tt>pathcookie=\"value\"</tt><br>") >= 0);
           }
       }
   
       public void testPathCookie4() throws Exception {
           HttpClient client = new HttpClient();
  -        client.startSession(host, port);
  +        client.getHostConfiguration().setHost(host, port, "http");
  +        client.setStrictMode(true);
   
           {
               GetMethod method = new GetMethod("/" + context + "/cookie/write");
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>