View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/cookie/NetscapeDraftSpec.java $
3    * $Revision: 1425331 $
4    * $Date: 2012-12-22 19:29:41 +0100 (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 java.util.StringTokenizer;
34  import java.util.Date;
35  import java.util.Locale;   
36  import java.text.DateFormat; 
37  import java.text.SimpleDateFormat;  
38  import java.text.ParseException; 
39  
40  import org.apache.commons.httpclient.HeaderElement;
41  import org.apache.commons.httpclient.NameValuePair;
42  import org.apache.commons.httpclient.Cookie;
43  
44  /***
45   * <P>Netscape cookie draft specific cookie management functions
46   *
47   * @author  B.C. Holmes
48   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
49   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
50   * @author Rod Waldhoff
51   * @author dIon Gillard
52   * @author Sean C. Sullivan
53   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
54   * @author Marc A. Saegesser
55   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
56   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
57   * 
58   * @since 2.0 
59   */
60  
61  public class NetscapeDraftSpec extends CookieSpecBase {
62  
63      /*** Default constructor */
64      public NetscapeDraftSpec() {
65          super();
66      }
67  
68      /***
69        * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s.
70        *
71        * <p>Syntax of the Set-Cookie HTTP Response Header:</p>
72        * 
73        * <p>This is the format a CGI script would use to add to 
74        * the HTTP headers a new piece of data which is to be stored by 
75        * the client for later retrieval.</p>
76        *  
77        * <PRE>
78        *  Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
79        * </PRE>
80        *
81        * <p>Please note that Netscape draft specification does not fully 
82        * conform to the HTTP header format. Netscape draft does not specify 
83        * whether multiple cookies may be sent in one header. Hence, comma 
84        * character may be present in unquoted cookie value or unquoted 
85        * parameter value.</p>
86        * 
87        * @link http://wp.netscape.com/newsref/std/cookie_spec.html
88        * 
89        * @param host the host from which the <tt>Set-Cookie</tt> value was
90        * received
91        * @param port the port from which the <tt>Set-Cookie</tt> value was
92        * received
93        * @param path the path from which the <tt>Set-Cookie</tt> value was
94        * received
95        * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was
96        * received over secure conection
97        * @param header the <tt>Set-Cookie</tt> received from the server
98        * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
99        * @throws MalformedCookieException if an exception occurs during parsing
100       * 
101       * @since 3.0
102       */
103     public Cookie[] parse(String host, int port, String path, 
104         boolean secure, final String header) 
105         throws MalformedCookieException {
106             
107         LOG.trace("enter NetscapeDraftSpec.parse(String, port, path, boolean, Header)");
108 
109         if (host == null) {
110             throw new IllegalArgumentException("Host of origin may not be null");
111         }
112         if (host.trim().equals("")) {
113             throw new IllegalArgumentException("Host of origin may not be blank");
114         }
115         if (port < 0) {
116             throw new IllegalArgumentException("Invalid port: " + port);
117         }
118         if (path == null) {
119             throw new IllegalArgumentException("Path of origin may not be null.");
120         }
121         if (header == null) {
122             throw new IllegalArgumentException("Header may not be null.");
123         }
124 
125         if (path.trim().equals("")) {
126             path = PATH_DELIM;
127         }
128         host = host.toLowerCase();
129 
130         String defaultPath = path;    
131         int lastSlashIndex = defaultPath.lastIndexOf(PATH_DELIM);
132         if (lastSlashIndex >= 0) {
133             if (lastSlashIndex == 0) {
134                 //Do not remove the very first slash
135                 lastSlashIndex = 1;
136             }
137             defaultPath = defaultPath.substring(0, lastSlashIndex);
138         }
139         HeaderElement headerelement = new HeaderElement(header.toCharArray());
140         Cookie cookie = new Cookie(host,
141                        headerelement.getName(),
142                        headerelement.getValue(),
143                        defaultPath, 
144                        null,
145                        false);
146         // cycle through the parameters
147         NameValuePair[] parameters = headerelement.getParameters();
148         // could be null. In case only a header element and no parameters.
149         if (parameters != null) {
150             for (int j = 0; j < parameters.length; j++) {
151                 parseAttribute(parameters[j], cookie);
152             }
153         }
154         return new Cookie[] {cookie};
155     }
156 
157 
158     /***
159       * Parse the cookie attribute and update the corresponsing {@link Cookie}
160       * properties as defined by the Netscape draft specification
161       *
162       * @param attribute {@link NameValuePair} cookie attribute from the
163       * <tt>Set- Cookie</tt>
164       * @param cookie {@link Cookie} to be updated
165       * @throws MalformedCookieException if an exception occurs during parsing
166       */
167     public void parseAttribute(
168         final NameValuePair attribute, final Cookie cookie)
169         throws MalformedCookieException {
170             
171         if (attribute == null) {
172             throw new IllegalArgumentException("Attribute may not be null.");
173         }
174         if (cookie == null) {
175             throw new IllegalArgumentException("Cookie may not be null.");
176         }
177         final String paramName = attribute.getName().toLowerCase();
178         final String paramValue = attribute.getValue();
179 
180         if (paramName.equals("expires")) {
181 
182             if (paramValue == null) {
183                 throw new MalformedCookieException(
184                     "Missing value for expires attribute");
185             }
186             try {
187                 DateFormat expiryFormat = new SimpleDateFormat(
188                     "EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US);
189                 Date date = expiryFormat.parse(paramValue);
190                 cookie.setExpiryDate(date);
191             } catch (ParseException e) {
192                 throw new MalformedCookieException("Invalid expires "
193                     + "attribute: " + e.getMessage());
194             }
195         } else {
196             super.parseAttribute(attribute, cookie);
197         }
198     }
199 
200     /***
201      * Performs domain-match as described in the Netscape draft.
202      * @param host The target host.
203      * @param domain The cookie domain attribute.
204      * @return true if the specified host matches the given domain.
205      */
206     public boolean domainMatch(final String host, final String domain) {
207         return host.endsWith(domain);
208     }
209 
210     /***
211       * Performs Netscape draft compliant {@link Cookie} validation
212       *
213       * @param host the host from which the {@link Cookie} was received
214       * @param port the port from which the {@link Cookie} was received
215       * @param path the path from which the {@link Cookie} was received
216       * @param secure <tt>true</tt> when the {@link Cookie} was received 
217       * using a secure connection
218       * @param cookie The cookie to validate.
219       * @throws MalformedCookieException if an exception occurs during
220       * validation
221       */
222     public void validate(String host, int port, String path, 
223         boolean secure, final Cookie cookie) 
224         throws MalformedCookieException {
225             
226         LOG.trace("enterNetscapeDraftCookieProcessor "
227             + "RCF2109CookieProcessor.validate(Cookie)");
228         // Perform generic validation
229         super.validate(host, port, path, secure, cookie);
230         // Perform Netscape Cookie draft specific validation
231         if (host.indexOf(".") >= 0) {
232             int domainParts = new StringTokenizer(cookie.getDomain(), ".")
233                 .countTokens();
234 
235             if (isSpecialDomain(cookie.getDomain())) {
236                 if (domainParts < 2) {
237                     throw new MalformedCookieException("Domain attribute \""
238                         + cookie.getDomain() 
239                         + "\" violates the Netscape cookie specification for "
240                         + "special domains");
241                 }
242             } else {
243                 if (domainParts < 3) {
244                     throw new MalformedCookieException("Domain attribute \""
245                         + cookie.getDomain() 
246                         + "\" violates the Netscape cookie specification");
247                 }            
248             }
249         }
250     }
251     
252     /***
253      * Checks if the given domain is in one of the seven special
254      * top level domains defined by the Netscape cookie specification.
255      * @param domain The domain.
256      * @return True if the specified domain is "special"
257      */
258     private static boolean isSpecialDomain(final String domain) {
259         final String ucDomain = domain.toUpperCase();
260         if (ucDomain.endsWith(".COM") 
261            || ucDomain.endsWith(".EDU")
262            || ucDomain.endsWith(".NET")
263            || ucDomain.endsWith(".GOV")
264            || ucDomain.endsWith(".MIL")
265            || ucDomain.endsWith(".ORG")
266            || ucDomain.endsWith(".INT")) {
267             return true;
268         }
269         return false;
270     }
271 }