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