KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > cookie > CookieSpecBase


1 /*
2  * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java,v 1.16.2.3 2004/02/22 18:21:15 olegk Exp $
3  * $Revision: 1.16.2.3 $
4  * $Date: 2004/02/22 18:21:15 $
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 java.util.Date JavaDoc;
35 import java.util.LinkedList JavaDoc;
36 import java.util.List JavaDoc;
37
38 import org.apache.commons.httpclient.Cookie;
39 import org.apache.commons.httpclient.Header;
40 import org.apache.commons.httpclient.HeaderElement;
41 import org.apache.commons.httpclient.HttpException;
42 import org.apache.commons.httpclient.NameValuePair;
43 import org.apache.commons.httpclient.util.DateParseException;
44 import org.apache.commons.httpclient.util.DateParser;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47
48 /**
49  *
50  * Cookie management functions shared by all specification.
51  *
52  * @author B.C. Holmes
53  * @author <a HREF="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
54  * @author <a HREF="mailto:dsale@us.britannica.com">Doug Sale</a>
55  * @author Rod Waldhoff
56  * @author dIon Gillard
57  * @author Sean C. Sullivan
58  * @author <a HREF="mailto:JEvans@Cyveillance.com">John Evans</a>
59  * @author Marc A. Saegesser
60  * @author <a HREF="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
61  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
62  *
63  * @since 2.0
64  */

65 public class CookieSpecBase implements CookieSpec {
66     
67     /** Log object */
68     protected static final Log LOG = LogFactory.getLog(CookieSpec.class);
69
70     /** Default constructor */
71     public CookieSpecBase() {
72         super();
73     }
74
75
76     /**
77       * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s.
78       *
79       * <P>The syntax for the Set-Cookie response header is:
80       *
81       * <PRE>
82       * set-cookie = "Set-Cookie:" cookies
83       * cookies = 1#cookie
84       * cookie = NAME "=" VALUE * (";" cookie-av)
85       * NAME = attr
86       * VALUE = value
87       * cookie-av = "Comment" "=" value
88       * | "Domain" "=" value
89       * | "Max-Age" "=" value
90       * | "Path" "=" value
91       * | "Secure"
92       * | "Version" "=" 1*DIGIT
93       * </PRE>
94       *
95       * @param host the host from which the <tt>Set-Cookie</tt> value was
96       * received
97       * @param port the port from which the <tt>Set-Cookie</tt> value was
98       * received
99       * @param path the path from which the <tt>Set-Cookie</tt> value was
100       * received
101       * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was
102       * received over secure conection
103       * @param header the <tt>Set-Cookie</tt> received from the server
104       * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
105       * @throws MalformedCookieException if an exception occurs during parsing
106       */

107     public Cookie[] parse(String JavaDoc host, int port, String JavaDoc path,
108         boolean secure, final String JavaDoc header)
109         throws MalformedCookieException {
110             
111         LOG.trace("enter CookieSpecBase.parse("
112             + "String, port, path, boolean, Header)");
113
114         if (host == null) {
115             throw new IllegalArgumentException JavaDoc(
116                 "Host of origin may not be null");
117         }
118         if (host.trim().equals("")) {
119             throw new IllegalArgumentException JavaDoc(
120                 "Host of origin may not be blank");
121         }
122         if (port < 0) {
123             throw new IllegalArgumentException JavaDoc("Invalid port: " + port);
124         }
125         if (path == null) {
126             throw new IllegalArgumentException JavaDoc(
127                 "Path of origin may not be null.");
128         }
129         if (header == null) {
130             throw new IllegalArgumentException JavaDoc("Header may not be null.");
131         }
132
133         if (path.trim().equals("")) {
134             path = PATH_DELIM;
135         }
136         host = host.toLowerCase();
137     
138         HeaderElement[] headerElements = null;
139         try {
140             headerElements = HeaderElement.parse(header);
141         } catch (HttpException e) {
142             throw new MalformedCookieException(e.getMessage());
143         }
144     
145         String JavaDoc defaultPath = path;
146         int lastSlashIndex = defaultPath.lastIndexOf(PATH_DELIM);
147         if (lastSlashIndex >= 0) {
148             if (lastSlashIndex == 0) {
149                 //Do not remove the very first slash
150
lastSlashIndex = 1;
151             }
152             defaultPath = defaultPath.substring(0, lastSlashIndex);
153         }
154         
155         Cookie[] cookies = new Cookie[headerElements.length];
156
157         for (int i = 0; i < headerElements.length; i++) {
158
159             HeaderElement headerelement = headerElements[i];
160             Cookie cookie = null;
161             try {
162                 cookie = new Cookie(host,
163                                     headerelement.getName(),
164                                     headerelement.getValue(),
165                                     defaultPath,
166                                     null,
167                                     false);
168             } catch (IllegalArgumentException JavaDoc e) {
169                 throw new MalformedCookieException(e.getMessage());
170             }
171             // cycle through the parameters
172
NameValuePair[] parameters = headerelement.getParameters();
173             // could be null. In case only a header element and no parameters.
174
if (parameters != null) {
175
176                 for (int j = 0; j < parameters.length; j++) {
177                     parseAttribute(parameters[j], cookie);
178                 }
179             }
180             cookies[i] = cookie;
181         }
182         return cookies;
183     }
184
185
186     /**
187       * Parse the <tt>"Set-Cookie"</tt> {@link Header} into an array of {@link
188       * Cookie}s.
189       *
190       * <P>The syntax for the Set-Cookie response header is:
191       *
192       * <PRE>
193       * set-cookie = "Set-Cookie:" cookies
194       * cookies = 1#cookie
195       * cookie = NAME "=" VALUE * (";" cookie-av)
196       * NAME = attr
197       * VALUE = value
198       * cookie-av = "Comment" "=" value
199       * | "Domain" "=" value
200       * | "Max-Age" "=" value
201       * | "Path" "=" value
202       * | "Secure"
203       * | "Version" "=" 1*DIGIT
204       * </PRE>
205       *
206       * @param host the host from which the <tt>Set-Cookie</tt> header was
207       * received
208       * @param port the port from which the <tt>Set-Cookie</tt> header was
209       * received
210       * @param path the path from which the <tt>Set-Cookie</tt> header was
211       * received
212       * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> header was
213       * received over secure conection
214       * @param header the <tt>Set-Cookie</tt> received from the server
215       * @return an array of <tt>Cookie</tt>s parsed from the <tt>"Set-Cookie"
216       * </tt> header
217       * @throws MalformedCookieException if an exception occurs during parsing
218       */

219     public Cookie[] parse(
220         String JavaDoc host, int port, String JavaDoc path, boolean secure, final Header header)
221         throws MalformedCookieException {
222             
223         LOG.trace("enter CookieSpecBase.parse("
224             + "String, port, path, boolean, String)");
225         if (header == null) {
226             throw new IllegalArgumentException JavaDoc("Header may not be null.");
227         }
228         return parse(host, port, path, secure, header.getValue());
229     }
230
231
232     /**
233       * Parse the cookie attribute and update the corresponsing {@link Cookie}
234       * properties.
235       *
236       * @param attribute {@link HeaderElement} cookie attribute from the
237       * <tt>Set- Cookie</tt>
238       * @param cookie {@link Cookie} to be updated
239       * @throws MalformedCookieException if an exception occurs during parsing
240       */

241
242     public void parseAttribute(
243         final NameValuePair attribute, final Cookie cookie)
244         throws MalformedCookieException {
245             
246         if (attribute == null) {
247             throw new IllegalArgumentException JavaDoc("Attribute may not be null.");
248         }
249         if (cookie == null) {
250             throw new IllegalArgumentException JavaDoc("Cookie may not be null.");
251         }
252         final String JavaDoc paramName = attribute.getName().toLowerCase();
253         String JavaDoc paramValue = attribute.getValue();
254
255         if (paramName.equals("path")) {
256
257             if ((paramValue == null) || (paramValue.trim().equals(""))) {
258                 paramValue = "/";
259             }
260             cookie.setPath(paramValue);
261             cookie.setPathAttributeSpecified(true);
262
263         } else if (paramName.equals("domain")) {
264
265             if (paramValue == null) {
266                 throw new MalformedCookieException(
267                     "Missing value for domain attribute");
268             }
269             if (paramValue.trim().equals("")) {
270                 throw new MalformedCookieException(
271                     "Blank value for domain attribute");
272             }
273             cookie.setDomain(paramValue);
274             cookie.setDomainAttributeSpecified(true);
275
276         } else if (paramName.equals("max-age")) {
277
278             if (paramValue == null) {
279                 throw new MalformedCookieException(
280                     "Missing value for max-age attribute");
281             }
282             int age;
283             try {
284                 age = Integer.parseInt(paramValue);
285             } catch (NumberFormatException JavaDoc e) {
286                 throw new MalformedCookieException ("Invalid max-age "
287                     + "attribute: " + e.getMessage());
288             }
289             cookie.setExpiryDate(
290                 new Date JavaDoc(System.currentTimeMillis() + age * 1000L));
291
292         } else if (paramName.equals("secure")) {
293
294             cookie.setSecure(true);
295
296         } else if (paramName.equals("comment")) {
297
298             cookie.setComment(paramValue);
299
300         } else if (paramName.equals("expires")) {
301
302             if (paramValue == null) {
303                 throw new MalformedCookieException(
304                     "Missing value for expires attribute");
305             }
306             // trim single quotes around expiry if present
307
// see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5279
308
if (paramValue.length() > 1
309                 && paramValue.startsWith("'")
310                 && paramValue.endsWith("'")) {
311                 paramValue
312                     = paramValue.substring (1, paramValue.length() - 1);
313             }
314
315             try {
316                 cookie.setExpiryDate(DateParser.parseDate(paramValue));
317             } catch (DateParseException dpe) {
318                 LOG.debug("Error parsing cookie date", dpe);
319                 throw new MalformedCookieException(
320                     "Unable to parse expiration date parameter: "
321                     + paramValue);
322             }
323         } else {
324             if (LOG.isDebugEnabled()) {
325                 LOG.debug("Unrecognized cookie attribute: "
326                     + attribute.toString());
327             }
328         }
329     }
330
331     
332     /**
333       * Performs most common {@link Cookie} validation
334       *
335       * @param host the host from which the {@link Cookie} was received
336       * @param port the port from which the {@link Cookie} was received
337       * @param path the path from which the {@link Cookie} was received
338       * @param secure <tt>true</tt> when the {@link Cookie} was received using a
339       * secure connection
340       * @param cookie The cookie to validate.
341       * @throws MalformedCookieException if an exception occurs during
342       * validation
343       */

344     
345     public void validate(String JavaDoc host, int port, String JavaDoc path,
346         boolean secure, final Cookie cookie)
347         throws MalformedCookieException {
348             
349         LOG.trace("enter CookieSpecBase.validate("
350             + "String, port, path, boolean, Cookie)");
351         if (host == null) {
352             throw new IllegalArgumentException JavaDoc(
353                 "Host of origin may not be null");
354         }
355         if (host.trim().equals("")) {
356             throw new IllegalArgumentException JavaDoc(
357                 "Host of origin may not be blank");
358         }
359         if (port < 0) {
360             throw new IllegalArgumentException JavaDoc("Invalid port: " + port);
361         }
362         if (path == null) {
363             throw new IllegalArgumentException JavaDoc(
364                 "Path of origin may not be null.");
365         }
366         if (path.trim().equals("")) {
367             path = PATH_DELIM;
368         }
369         host = host.toLowerCase();
370         // check version
371
if (cookie.getVersion() < 0) {
372             throw new MalformedCookieException ("Illegal version number "
373                 + cookie.getValue());
374         }
375
376         // security check... we musn't allow the server to give us an
377
// invalid domain scope
378

379         // Validate the cookies domain attribute. NOTE: Domains without
380
// any dots are allowed to support hosts on private LANs that don't
381
// have DNS names. Since they have no dots, to domain-match the
382
// request-host and domain must be identical for the cookie to sent
383
// back to the origin-server.
384
if (host.indexOf(".") >= 0) {
385             // Not required to have at least two dots. RFC 2965.
386
// A Set-Cookie2 with Domain=ajax.com will be accepted.
387

388             // domain must match host
389
if (!host.endsWith(cookie.getDomain())) {
390                 String JavaDoc s = cookie.getDomain();
391                 if (s.startsWith(".")) {
392                     s = s.substring(1, s.length());
393                 }
394                 if (!host.equals(s)) {
395                     throw new MalformedCookieException(
396                         "Illegal domain attribute \"" + cookie.getDomain()
397                         + "\". Domain of origin: \"" + host + "\"");
398                 }
399             }
400         } else {
401             if (!host.equals(cookie.getDomain())) {
402                 throw new MalformedCookieException(
403                     "Illegal domain attribute \"" + cookie.getDomain()
404                     + "\". Domain of origin: \"" + host + "\"");
405             }
406         }
407
408         // another security check... we musn't allow the server to give us a
409
// cookie that doesn't match this path
410

411         if (!path.startsWith(cookie.getPath())) {
412             throw new MalformedCookieException(
413                 "Illegal path attribute \"" + cookie.getPath()
414                 + "\". Path of origin: \"" + path + "\"");
415         }
416     }
417
418
419     /**
420      * Return <tt>true</tt> if the cookie should be submitted with a request
421      * with given attributes, <tt>false</tt> otherwise.
422      * @param host the host to which the request is being submitted
423      * @param port the port to which the request is being submitted (ignored)
424      * @param path the path to which the request is being submitted
425      * @param secure <tt>true</tt> if the request is using a secure connection
426      * @param cookie {@link Cookie} to be matched
427      * @return true if the cookie matches the criterium
428      */

429
430     public boolean match(String JavaDoc host, int port, String JavaDoc path,
431         boolean secure, final Cookie cookie) {
432             
433         LOG.trace("enter CookieSpecBase.match("
434             + "String, int, String, boolean, Cookie");
435             
436         if (host == null) {
437             throw new IllegalArgumentException JavaDoc(
438                 "Host of origin may not be null");
439         }
440         if (host.trim().equals("")) {
441             throw new IllegalArgumentException JavaDoc(
442                 "Host of origin may not be blank");
443         }
444         if (port < 0) {
445             throw new IllegalArgumentException JavaDoc("Invalid port: " + port);
446         }
447         if (path == null) {
448             throw new IllegalArgumentException JavaDoc(
449                 "Path of origin may not be null.");
450         }
451         if (cookie == null) {
452             throw new IllegalArgumentException JavaDoc("Cookie may not be null");
453         }
454         if (path.trim().equals("")) {
455             path = PATH_DELIM;
456         }
457         host = host.toLowerCase();
458         if (cookie.getDomain() == null) {
459             LOG.warn("Invalid cookie state: domain not specified");
460             return false;
461         }
462         if (cookie.getPath() == null) {
463             LOG.warn("Invalid cookie state: path not specified");
464             return false;
465         }
466         
467         return
468             // only add the cookie if it hasn't yet expired
469
(cookie.getExpiryDate() == null
470                 || cookie.getExpiryDate().after(new Date JavaDoc()))
471             // and the domain pattern matches
472
&& (domainMatch(host, cookie.getDomain()))
473             // and the path is null or matching
474
&& (pathMatch(path, cookie.getPath()))
475             // and if the secure flag is set, only if the request is
476
// actually secure
477
&& (cookie.getSecure() ? secure : true);
478     }
479
480     /**
481      * Performs a domain-match as described in RFC2109.
482      * @param host The host to check.
483      * @param domain The domain.
484      * @return true if the specified host matches the given domain.
485      */

486     private static boolean domainMatch(String JavaDoc host, String JavaDoc domain) {
487         boolean match = host.equals(domain)
488             || (domain.startsWith(".") && host.endsWith(domain));
489
490         return match;
491     }
492
493     /**
494      * Performs a path-match slightly smarter than a straight-forward startsWith
495      * check.
496      * @param path The path to check.
497      * @param topmostPath The path to check against.
498      * @return true if the paths match
499      */

500     private static boolean pathMatch(
501         final String JavaDoc path, final String JavaDoc topmostPath) {
502             
503         boolean match = path.startsWith (topmostPath);
504         
505         // if there is a match and these values are not exactly the same we have
506
// to make sure we're not matcing "/foobar" and "/foo"
507
if (match && path.length() != topmostPath.length()) {
508             if (!topmostPath.endsWith(PATH_DELIM)) {
509                 match = (path.charAt(topmostPath.length()) == PATH_DELIM_CHAR);
510             }
511         }
512         return match;
513     }
514
515     /**
516      * Return an array of {@link Cookie}s that should be submitted with a
517      * request with given attributes, <tt>false</tt> otherwise.
518      * @param host the host to which the request is being submitted
519      * @param port the port to which the request is being submitted (currently
520      * ignored)
521      * @param path the path to which the request is being submitted
522      * @param secure <tt>true</tt> if the request is using a secure protocol
523      * @param cookies an array of <tt>Cookie</tt>s to be matched
524      * @return an array of <tt>Cookie</tt>s matching the criterium
525      */

526
527     public Cookie[] match(String JavaDoc host, int port, String JavaDoc path,
528         boolean secure, final Cookie cookies[]) {
529             
530         LOG.trace("enter CookieSpecBase.match("
531             + "String, int, String, boolean, Cookie[])");
532
533         if (host == null) {
534             throw new IllegalArgumentException JavaDoc(
535                 "Host of origin may not be null");
536         }
537         if (host.trim().equals("")) {
538             throw new IllegalArgumentException JavaDoc(
539                 "Host of origin may not be blank");
540         }
541         if (port < 0) {
542             throw new IllegalArgumentException JavaDoc("Invalid port: " + port);
543         }
544         if (path == null) {
545             throw new IllegalArgumentException JavaDoc(
546                 "Path of origin may not be null.");
547         }
548         if (cookies == null) {
549             throw new IllegalArgumentException JavaDoc("Cookie array may not be null");
550         }
551         if (path.trim().equals("")) {
552             path = PATH_DELIM;
553         }
554         host = host.toLowerCase();
555
556         if (cookies.length <= 0) {
557             return null;
558         }
559         List JavaDoc matching = new LinkedList JavaDoc();
560         for (int i = 0; i < cookies.length; i++) {
561             if (match(host, port, path, secure, cookies[i])) {
562                 addInPathOrder(matching, cookies[i]);
563             }
564         }
565         return (Cookie[]) matching.toArray(new Cookie[matching.size()]);
566     }
567
568
569     /**
570      * Adds the given cookie into the given list in descending path order. That
571      * is, more specific path to least specific paths. This may not be the
572      * fastest algorythm, but it'll work OK for the small number of cookies
573      * we're generally dealing with.
574      *
575      * @param list - the list to add the cookie to
576      * @param addCookie - the Cookie to add to list
577      */

578     private static void addInPathOrder(List JavaDoc list, Cookie addCookie) {
579         int i = 0;
580
581         for (i = 0; i < list.size(); i++) {
582             Cookie c = (Cookie) list.get(i);
583             if (addCookie.compare(addCookie, c) > 0) {
584                 break;
585             }
586         }
587         list.add(i, addCookie);
588     }
589
590     /**
591      * Return a string suitable for sending in a <tt>"Cookie"</tt> header
592      * @param cookie a {@link Cookie} to be formatted as string
593      * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
594      */

595     public String JavaDoc formatCookie(Cookie cookie) {
596         LOG.trace("enter CookieSpecBase.formatCookie(Cookie)");
597         if (cookie == null) {
598             throw new IllegalArgumentException JavaDoc("Cookie may not be null");
599         }
600         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
601         buf.append(cookie.getName());
602         buf.append("=");
603         String JavaDoc s = cookie.getValue();
604         if (s != null) {
605             buf.append(s);
606         };
607         return buf.toString();
608     }
609
610     /**
611      * Create a <tt>"Cookie"</tt> header value containing all {@link Cookie}s in
612      * <i>cookies</i> suitable for sending in a <tt>"Cookie"</tt> header
613      * @param cookies an array of {@link Cookie}s to be formatted
614      * @return a string suitable for sending in a Cookie header.
615      * @throws IllegalArgumentException if an input parameter is illegal
616      */

617
618     public String JavaDoc formatCookies(Cookie[] cookies)
619       throws IllegalArgumentException JavaDoc {
620         LOG.trace("enter CookieSpecBase.formatCookies(Cookie[])");
621         if (cookies == null) {
622             throw new IllegalArgumentException JavaDoc("Cookie array may not be null");
623         }
624         if (cookies.length == 0) {
625             throw new IllegalArgumentException JavaDoc("Cookie array may not be empty");
626         }
627
628         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
629         for (int i = 0; i < cookies.length; i++) {
630             if (i > 0) {
631                 buffer.append("; ");
632             }
633             buffer.append(formatCookie(cookies[i]));
634         }
635         return buffer.toString();
636     }
637
638
639     /**
640      * Create a <tt>"Cookie"</tt> {@link Header} containing all {@link Cookie}s
641      * in <i>cookies</i>.
642      * @param cookies an array of {@link Cookie}s to be formatted as a <tt>"
643      * Cookie"</tt> header
644      * @return a <tt>"Cookie"</tt> {@link Header}.
645      */

646     public Header formatCookieHeader(Cookie[] cookies) {
647         LOG.trace("enter CookieSpecBase.formatCookieHeader(Cookie[])");
648         return new Header("Cookie", formatCookies(cookies));
649     }
650
651
652     /**
653      * Create a <tt>"Cookie"</tt> {@link Header} containing the {@link Cookie}.
654      * @param cookie <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt>
655      * header
656      * @return a Cookie header.
657      */

658     public Header formatCookieHeader(Cookie cookie) {
659         LOG.trace("enter CookieSpecBase.formatCookieHeader(Cookie)");
660         return new Header("Cookie", formatCookie(cookie));
661     }
662
663 }
664
Popular Tags