View Javadoc

    1   /*
    2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java,v 1.21 2004/06/05 16:49:20 olegk Exp $
    3    * $Revision: 507134 $
    4    * $Date: 2007-02-13 19:18:05 +0100 (Tue, 13 Feb 2007) $
    5    *
    6    * ====================================================================
    7    *
    8    *  Licensed to the Apache Software Foundation (ASF) under one or more
    9    *  contributor license agreements.  See the NOTICE file distributed with
    10   *  this work for additional information regarding copyright ownership.
    11   *  The ASF licenses this file to You under the Apache License, Version 2.0
    12   *  (the "License"); you may not use this file except in compliance with
    13   *  the License.  You may obtain a copy of the License at
    14   *
    15   *      http://www.apache.org/licenses/LICENSE-2.0
    16   *
    17   *  Unless required by applicable law or agreed to in writing, software
    18   *  distributed under the License is distributed on an "AS IS" BASIS,
    19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    20   *  See the License for the specific language governing permissions and
    21   *  limitations under the License.
    22   * ====================================================================
    23   *
    24   * This software consists of voluntary contributions made by many
    25   * individuals on behalf of the Apache Software Foundation.  For more
    26   * information on the Apache Software Foundation, please see
    27   * <http://www.apache.org/>.
    28   *
    29   */
    30
    31  package org.apache.commons.httpclient.cookie;
    32
    33  import org.apache.commons.httpclient.NameValuePair;
    34  import org.apache.commons.httpclient.Cookie;
    35  import org.apache.commons.httpclient.util.ParameterFormatter;
    36
    37  /**
    38   * <p>RFC 2109 specific cookie management functions
    39   *
    40   * @author  B.C. Holmes
    41   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
    42   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
    43   * @author Rod Waldhoff
    44   * @author dIon Gillard
    45   * @author Sean C. Sullivan
    46   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
    47   * @author Marc A. Saegesser
    48   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    49   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
    50   *
    51   * @since 2.0
    52   */
    53
    54  public class RFC2109Spec extends CookieSpecBase {
    55
    56      private final ParameterFormatter formatter;
    57
    58      /**
    59       * Cookie Response Header  name for cookies processed
    60       * by this spec.
    61       */
    62      public final static String SET_COOKIE_KEY = "set-cookie";
    63
    64      /** Default constructor */
    65      public RFC2109Spec() {
    66          super();
    67          this.formatter = new ParameterFormatter();
    68          this.formatter.setAlwaysUseQuotes(true);
    69      }
    70
    71      /**
    72        * Parse RFC 2109 specific cookie attribute and update the corresponsing
    73        * {@link Cookie} properties.
    74        *
    75        * @param attribute {@link NameValuePair} cookie attribute from the
    76        * <tt>Set- Cookie</tt>
    77        * @param cookie {@link Cookie} to be updated
    78        * @throws MalformedCookieException if an exception occurs during parsing
    79        */
    80      public void parseAttribute(
    81          final NameValuePair attribute, final Cookie cookie)
    82          throws MalformedCookieException {
    83
    84          if (attribute == null) {
    85              throw new IllegalArgumentException("Attribute may not be null.");
    86          }
    87          if (cookie == null) {
    88              throw new IllegalArgumentException("Cookie may not be null.");
    89          }
    90          final String paramName = attribute.getName().toLowerCase();
    91          final String paramValue = attribute.getValue();
    92
    93          if (paramName.equals("path")) {
    94              if (paramValue == null) {
    95                  throw new MalformedCookieException(
    96                      "Missing value for path attribute");
    97              }
    98              if (paramValue.trim().equals("")) {
    99                  throw new MalformedCookieException(
    100                     "Blank value for path attribute");
    101             }
    102             cookie.setPath(paramValue);
    103             cookie.setPathAttributeSpecified(true);
    104         } else if (paramName.equals("version")) {
    105
    106             if (paramValue == null) {
    107                 throw new MalformedCookieException(
    108                     "Missing value for version attribute");
    109             }
    110             try {
    111                cookie.setVersion(Integer.parseInt(paramValue));
    112             } catch (NumberFormatException e) {
    113                 throw new MalformedCookieException("Invalid version: "
    114                     + e.getMessage());
    115             }
    116
    117         } else {
    118             super.parseAttribute(attribute, cookie);
    119         }
    120     }
    121
    122     /**
    123       * Performs RFC 2109 compliant {@link Cookie} validation
    124       *
    125       * @param host the host from which the {@link Cookie} was received
    126       * @param port the port from which the {@link Cookie} was received
    127       * @param path the path from which the {@link Cookie} was received
    128       * @param secure <tt>true</tt> when the {@link Cookie} was received using a
    129       * secure connection
    130       * @param cookie The cookie to validate
    131       * @throws MalformedCookieException if an exception occurs during
    132       * validation
    133       */
    134     public void validate(String host, int port, String path,
    135         boolean secure, final Cookie cookie) throws MalformedCookieException {
    136
    137         LOG.trace("enter RFC2109Spec.validate(String, int, String, "
    138             + "boolean, Cookie)");
    139
    140         // Perform generic validation
    141         super.validate(host, port, path, secure, cookie);
    142         // Perform RFC 2109 specific validation
    143
    144         if (cookie.getName().indexOf(' ') != -1) {
    145             throw new MalformedCookieException("Cookie name may not contain blanks");
    146         }
    147         if (cookie.getName().startsWith("$")) {
    148             throw new MalformedCookieException("Cookie name may not start with $");
    149         }
    150
    151         if (cookie.isDomainAttributeSpecified()
    152             && (!cookie.getDomain().equals(host))) {
    153
    154             // domain must start with dot
    155             if (!cookie.getDomain().startsWith(".")) {
    156                 throw new MalformedCookieException("Domain attribute \""
    157                     + cookie.getDomain()
    158                     + "\" violates RFC 2109: domain must start with a dot");
    159             }
    160             // domain must have at least one embedded dot
    161             int dotIndex = cookie.getDomain().indexOf('.', 1);
    162             if (dotIndex < 0 || dotIndex == cookie.getDomain().length() - 1) {
    163                 throw new MalformedCookieException("Domain attribute \""
    164                     + cookie.getDomain()
    165                     + "\" violates RFC 2109: domain must contain an embedded dot");
    166             }
    167             host = host.toLowerCase();
    168             if (!host.endsWith(cookie.getDomain())) {
    169                 throw new MalformedCookieException(
    170                     "Illegal domain attribute \"" + cookie.getDomain()
    171                     + "\". Domain of origin: \"" + host + "\"");
    172             }
    173             // host minus domain may not contain any dots
    174             String hostWithoutDomain = host.substring(0, host.length()
    175                 - cookie.getDomain().length());
    176             if (hostWithoutDomain.indexOf('.') != -1) {
    177                 throw new MalformedCookieException("Domain attribute \""
    178                     + cookie.getDomain()
    179                     + "\" violates RFC 2109: host minus domain may not contain any dots");
    180             }
    181         }
    182     }
    183
    184     /**
    185      * Performs domain-match as defined by the RFC2109.
    186      * @param host The target host.
    187      * @param domain The cookie domain attribute.
    188      * @return true if the specified host matches the given domain.
    189      *
    190      * @since 3.0
    191      */
    192     public boolean domainMatch(String host, String domain) {
    193         boolean match = host.equals(domain)
    194             || (domain.startsWith(".") && host.endsWith(domain));
    195
    196         return match;
    197     }
    198
    199     /**
    200      * Return a name/value string suitable for sending in a <tt>"Cookie"</tt>
    201      * header as defined in RFC 2109 for backward compatibility with cookie
    202      * version 0
    203      * @param buffer The string buffer to use for output
    204      * @param param The parameter.
    205      * @param version The cookie version
    206      */
    207     private void formatParam(final StringBuffer buffer, final NameValuePair param, int version) {
    208         if (version < 1) {
    209             buffer.append(param.getName());
    210             buffer.append("=");
    211             if (param.getValue() != null) {
    212                 buffer.append(param.getValue());
    213             }
    214         } else {
    215             this.formatter.format(buffer, param);
    216         }
    217     }
    218
    219     /**
    220      * Return a string suitable for sending in a <tt>"Cookie"</tt> header
    221      * as defined in RFC 2109 for backward compatibility with cookie version 0
    222      * @param buffer The string buffer to use for output
    223      * @param cookie The {@link Cookie} to be formatted as string
    224      * @param version The version to use.
    225      */
    226     private void formatCookieAsVer(final StringBuffer buffer, final Cookie cookie, int version) {
    227         String value = cookie.getValue();
    228         if (value == null) {
    229             value = "";
    230         }
    231         formatParam(buffer, new NameValuePair(cookie.getName(), value), version);
    232         if ((cookie.getPath() != null) && cookie.isPathAttributeSpecified()) {
    233           buffer.append("; ");
    234           formatParam(buffer, new NameValuePair("$Path", cookie.getPath()), version);
    235         }
    236         if ((cookie.getDomain() != null)
    237             && cookie.isDomainAttributeSpecified()) {
    238             buffer.append("; ");
    239             formatParam(buffer, new NameValuePair("$Domain", cookie.getDomain()), version);
    240         }
    241     }
    242
    243     /**
    244      * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
    245      * defined in RFC 2109
    246      * @param cookie a {@link Cookie} to be formatted as string
    247      * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
    248      */
    249     public String formatCookie(Cookie cookie) {
    250         LOG.trace("enter RFC2109Spec.formatCookie(Cookie)");
    251         if (cookie == null) {
    252             throw new IllegalArgumentException("Cookie may not be null");
    253         }
    254         int version = cookie.getVersion();
    255         StringBuffer buffer = new StringBuffer();
    256         formatParam(buffer,
    257                 new NameValuePair("$Version", Integer.toString(version)),
    258                 version);
    259         buffer.append("; ");
    260         formatCookieAsVer(buffer, cookie, version);
    261         return buffer.toString();
    262     }
    263
    264     /**
    265      * Create a RFC 2109 compliant <tt>"Cookie"</tt> header value containing all
    266      * {@link Cookie}s in <i>cookies</i> suitable for sending in a <tt>"Cookie"
    267      * </tt> header
    268      * @param cookies an array of {@link Cookie}s to be formatted
    269      * @return a string suitable for sending in a Cookie header.
    270      */
    271     public String formatCookies(Cookie[] cookies) {
    272         LOG.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
    273         int version = Integer.MAX_VALUE;
    274         // Pick the lowerest common denominator
    275         for (int i = 0; i < cookies.length; i++) {
    276             Cookie cookie = cookies[i];
    277             if (cookie.getVersion() < version) {
    278                 version = cookie.getVersion();
    279             }
    280         }
    281         final StringBuffer buffer = new StringBuffer();
    282         formatParam(buffer,
    283                 new NameValuePair("$Version", Integer.toString(version)),
    284                 version);
    285         for (int i = 0; i < cookies.length; i++) {
    286             buffer.append("; ");
    287             formatCookieAsVer(buffer, cookies[i], version);
    288         }
    289         return buffer.toString();
    290     }
    291
    292 }