View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java,v 1.14.2.3 2004/06/05 16:32:01 olegk Exp $
3    * $Revision: 1.14.2.3 $
4    * $Date: 2004/06/05 16:32:01 $
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   * [Additional notices, if required by prior licensing conditions]
29   *
30   */
31  
32  package org.apache.commons.httpclient.cookie;
33  
34  import org.apache.commons.httpclient.NameValuePair;
35  import org.apache.commons.httpclient.Cookie;
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      /*** Default constructor */    
57      public RFC2109Spec() {
58          super();
59      }
60  
61  
62      /***
63        * Parse RFC 2109 specific cookie attribute and update the corresponsing
64        * {@link Cookie} properties.
65        *
66        * @param attribute {@link NameValuePair} cookie attribute from the
67        * <tt>Set- Cookie</tt>
68        * @param cookie {@link Cookie} to be updated
69        * @throws MalformedCookieException if an exception occurs during parsing
70        */
71      public void parseAttribute(
72          final NameValuePair attribute, final Cookie cookie)
73          throws MalformedCookieException {
74            
75          if (attribute == null) {
76              throw new IllegalArgumentException("Attribute may not be null.");
77          }
78          if (cookie == null) {
79              throw new IllegalArgumentException("Cookie may not be null.");
80          }
81          final String paramName = attribute.getName().toLowerCase();
82          final String paramValue = attribute.getValue();
83  
84          if (paramName.equals("path")) {
85              if (paramValue == null) {
86                  throw new MalformedCookieException(
87                      "Missing value for path attribute");
88              }
89              if (paramValue.trim().equals("")) {
90                  throw new MalformedCookieException(
91                      "Blank value for path attribute");
92              }
93              cookie.setPath(paramValue);
94              cookie.setPathAttributeSpecified(true);
95          } else if (paramName.equals("version")) {
96  
97              if (paramValue == null) {
98                  throw new MalformedCookieException(
99                      "Missing value for version attribute");
100             }
101             try {
102                cookie.setVersion(Integer.parseInt(paramValue));
103             } catch (NumberFormatException e) {
104                 throw new MalformedCookieException("Invalid version: " 
105                     + e.getMessage());
106             }
107 
108         } else {
109             super.parseAttribute(attribute, cookie);
110         }
111     }
112 
113     /***
114       * Performs RFC 2109 compliant {@link Cookie} validation
115       *
116       * @param host the host from which the {@link Cookie} was received
117       * @param port the port from which the {@link Cookie} was received
118       * @param path the path from which the {@link Cookie} was received
119       * @param secure <tt>true</tt> when the {@link Cookie} was received using a
120       * secure connection
121       * @param cookie The cookie to validate
122       * @throws MalformedCookieException if an exception occurs during
123       * validation
124       */
125     public void validate(String host, int port, String path, 
126         boolean secure, final Cookie cookie) throws MalformedCookieException {
127             
128         LOG.trace("enter RFC2109Spec.validate(String, int, String, "
129             + "boolean, Cookie)");
130             
131         // Perform generic validation
132         super.validate(host, port, path, secure, cookie);
133         // Perform RFC 2109 specific validation
134         
135         if (cookie.getName().indexOf(' ') != -1) {
136             throw new MalformedCookieException("Cookie name may not contain blanks");
137         }
138         if (cookie.getName().startsWith("$")) {
139             throw new MalformedCookieException("Cookie name may not start with $");
140         }
141         
142         if (cookie.isDomainAttributeSpecified() 
143             && (!cookie.getDomain().equals(host))) {
144                 
145             // domain must start with dot
146             if (!cookie.getDomain().startsWith(".")) {
147                 throw new MalformedCookieException("Domain attribute \"" 
148                     + cookie.getDomain() 
149                     + "\" violates RFC 2109: domain must start with a dot");
150             }
151             // domain must have at least one embedded dot
152             int dotIndex = cookie.getDomain().indexOf('.', 1);
153             if (dotIndex < 0 || dotIndex == cookie.getDomain().length() - 1) {
154                 throw new MalformedCookieException("Domain attribute \"" 
155                     + cookie.getDomain() 
156                     + "\" violates RFC 2109: domain must contain an embedded dot");
157             }
158             host = host.toLowerCase();
159             if (host.indexOf('.') >= 0) {
160                 if (!host.endsWith(cookie.getDomain())) {
161                     throw new MalformedCookieException(
162                         "Illegal domain attribute \"" + cookie.getDomain() 
163                         + "\". Domain of origin: \"" + host + "\"");
164                 }
165                 // host minus domain may not contain any dots
166                 String hostWithoutDomain = host.substring(0, host.length() 
167                     - cookie.getDomain().length());
168                 if (hostWithoutDomain.indexOf('.') != -1) {
169                     throw new MalformedCookieException("Domain attribute \"" 
170                         + cookie.getDomain() 
171                         + "\" violates RFC 2109: host minus domain may not contain any dots");
172                 }
173             }
174         }
175     }
176 
177 
178     /***
179      * Return a name/value string suitable for sending in a <tt>"Cookie"</tt>
180      * header as defined in RFC 2109 for backward compatibility with cookie
181      * version 0
182      * @param name The name.
183      * @param value The value
184      * @param version The cookie version 
185      * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
186      */
187 
188     private String formatNameValuePair(
189         final String name, final String value, int version) {
190             
191         final StringBuffer buffer = new StringBuffer();
192         if (version < 1) {
193             buffer.append(name);
194             buffer.append("=");
195             if (value != null) {
196                 buffer.append(value);   
197             }
198         } else {
199             buffer.append(name);
200             buffer.append("=\"");
201             if (value != null) {
202                 buffer.append(value);
203             }
204             buffer.append("\"");
205         }
206         return buffer.toString(); 
207     }
208 
209     /***
210      * Return a string suitable for sending in a <tt>"Cookie"</tt> header 
211      * as defined in RFC 2109 for backward compatibility with cookie version 0
212      * @param cookie a {@link Cookie} to be formatted as string
213      * @param version The version to use.
214      * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
215      */
216     private String formatCookieAsVer(Cookie cookie, int version) {
217         LOG.trace("enter RFC2109Spec.formatCookieAsVer(Cookie)");
218         if (cookie == null) {
219             throw new IllegalArgumentException("Cookie may not be null");
220         }
221         StringBuffer buf = new StringBuffer();
222         buf.append(formatNameValuePair(cookie.getName(), 
223             cookie.getValue(), version));
224         if (cookie.getDomain() != null 
225             && cookie.isDomainAttributeSpecified()) {
226                 
227             buf.append("; ");
228             buf.append(formatNameValuePair("$Domain", 
229                 cookie.getDomain(), version));
230         }
231         if (cookie.getPath() != null && cookie.isPathAttributeSpecified()) {
232             buf.append("; ");
233             buf.append(formatNameValuePair("$Path", cookie.getPath(), version));
234         }
235         return buf.toString();
236     }
237 
238 
239     /***
240      * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
241      * defined in RFC 2109
242      * @param cookie a {@link Cookie} to be formatted as string
243      * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
244      */
245     public String formatCookie(Cookie cookie) {
246         LOG.trace("enter RFC2109Spec.formatCookie(Cookie)");
247         if (cookie == null) {
248             throw new IllegalArgumentException("Cookie may not be null");
249         }
250         int ver = cookie.getVersion();
251         StringBuffer buffer = new StringBuffer();
252         buffer.append(formatNameValuePair("$Version", 
253           Integer.toString(ver), ver));
254         buffer.append("; ");
255         buffer.append(formatCookieAsVer(cookie, ver));
256         return buffer.toString();
257     }
258 
259     /***
260      * Create a RFC 2109 compliant <tt>"Cookie"</tt> header value containing all
261      * {@link Cookie}s in <i>cookies</i> suitable for sending in a <tt>"Cookie"
262      * </tt> header
263      * @param cookies an array of {@link Cookie}s to be formatted
264      * @return a string suitable for sending in a Cookie header.
265      */
266     public String formatCookies(Cookie[] cookies) {
267         LOG.trace("enter RFC2109Spec.formatCookieHeader(Cookie[])");
268         int version = Integer.MAX_VALUE;
269         // Pick the lowerest common denominator
270         for (int i = 0; i < cookies.length; i++) {
271             Cookie cookie = cookies[i];
272             if (cookie.getVersion() < version) {
273                 version = cookie.getVersion();
274             }
275         }
276         final StringBuffer buffer = new StringBuffer();
277         buffer.append(formatNameValuePair("$Version", 
278             Integer.toString(version), version));
279         for (int i = 0; i < cookies.length; i++) {
280             buffer.append("; ");
281             buffer.append(formatCookieAsVer(cookies[i], version));
282         }
283         return buffer.toString();
284     }
285 }