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