View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/cookie/RFC2109Spec.java $
3    * $Revision: 1425331 $
4    * $Date: 2012-12-22 18:29:41 +0000 (Sat, 22 Dec 2012) $
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 }