KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > net > HttpCookie


1 /*
2  * @(#)HttpCookie.java 1.5 05/11/17
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.net;
9
10 import java.util.List JavaDoc;
11 import java.util.StringTokenizer JavaDoc;
12 import java.util.NoSuchElementException JavaDoc;
13 import java.text.SimpleDateFormat JavaDoc;
14 import java.util.TimeZone JavaDoc;
15 import java.util.Date JavaDoc;
16
17 import java.lang.NullPointerException JavaDoc; // for javadoc
18

19 /**
20  * An HttpCookie object represents an http cookie, which carries state
21  * information between server and user agent. Cookie is widely adopted
22  * to create stateful sessions.
23  *
24  * <p>There are 3 http cookie specifications:
25  * <blockquote>
26  * Netscape draft<br>
27  * RFC 2109 - <a HREF="http://www.ietf.org/rfc/rfc2109.txt">
28  * <i>http://www.ietf.org/rfc/rfc2109.txt</i></a><br>
29  * RFC 2965 - <a HREF="http://www.ietf.org/rfc/rfc2965.txt">
30  * <i>http://www.ietf.org/rfc/rfc2965.txt</i></a>
31  * </blockquote>
32  *
33  * <p>HttpCookie class can accept all these 3 forms of syntax.
34  *
35  * @version 1.5, 05/11/17
36  * @author Edward Wang
37  * @since 1.6
38  */

39 public final class HttpCookie implements Cloneable JavaDoc {
40     /* ---------------- Fields -------------- */
41     
42     //
43
// The value of the cookie itself.
44
//
45

46     private String JavaDoc name; // NAME= ... "$Name" style is reserved
47
private String JavaDoc value; // value of NAME
48

49     //
50
// Attributes encoded in the header's cookie fields.
51
//
52

53     private String JavaDoc comment; // Comment=VALUE ... describes cookie's use
54
private String JavaDoc commentURL; // CommentURL="http URL" ... describes cookie's use
55
private boolean toDiscard; // Discard ... discard cookie unconditionally
56
private String JavaDoc domain; // Domain=VALUE ... domain that sees cookie
57
private long maxAge = MAX_AGE_UNSPECIFIED; // Max-Age=VALUE ... cookies auto-expire
58
private String JavaDoc path; // Path=VALUE ... URLs that see the cookie
59
private String JavaDoc portlist; // Port[="portlist"] ... the port cookie may be returned to
60
private boolean secure; // Secure ... e.g. use SSL
61
private int version = 1; // Version=1 ... RFC 2965 style
62

63     //
64
// Hold the creation time (in seconds) of the http cookie for later
65
// expiration calculation
66
//
67
private long whenCreated = 0;
68     
69     
70     //
71
// Since the positive and zero max-age have their meanings,
72
// this value serves as a hint as 'not specify max-age'
73
//
74
private final static long MAX_AGE_UNSPECIFIED = -1;
75     
76     
77     //
78
// date format used by Netscape's cookie draft
79
//
80
private final static String JavaDoc NETSCAPE_COOKIE_DATE_FORMAT = "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'";
81     
82     //
83
// constant strings represent set-cookie header token
84
//
85
private final static String JavaDoc SET_COOKIE = "set-cookie:";
86     private final static String JavaDoc SET_COOKIE2 = "set-cookie2:";
87
88     
89     /* ---------------- Ctors -------------- */
90
91     /**
92      * Constructs a cookie with a specified name and value.
93      *
94      * <p>The name must conform to RFC 2965. That means it can contain
95      * only ASCII alphanumeric characters and cannot contain commas,
96      * semicolons, or white space or begin with a $ character. The cookie's
97      * name cannot be changed after creation.
98      *
99      * <p>The value can be anything the server chooses to send. Its
100      * value is probably of interest only to the server. The cookie's
101      * value can be changed after creation with the
102      * <code>setValue</code> method.
103      *
104      * <p>By default, cookies are created according to the RFC 2965
105      * cookie specification. The version can be changed with the
106      * <code>setVersion</code> method.
107      *
108      *
109      * @param name a <code>String</code> specifying the name of the cookie
110      *
111      * @param value a <code>String</code> specifying the value of the cookie
112      *
113      * @throws IllegalArgumentException if the cookie name contains illegal characters
114      * or it is one of the tokens reserved for use
115      * by the cookie protocol
116      * @throws NullPointerException if <tt>name</tt> is <tt>null</tt>
117      * @see #setValue
118      * @see #setVersion
119      *
120      */

121
122     public HttpCookie(String JavaDoc name, String JavaDoc value) {
123         name = name.trim();
124     if (name.length() == 0 || !isToken(name) || isReserved(name)) {
125         throw new IllegalArgumentException JavaDoc("Illegal cookie name");
126     }
127
128     this.name = name;
129     this.value = value;
130         toDiscard = false;
131         secure = false;
132         
133         whenCreated = System.currentTimeMillis();
134     }
135     
136     
137     /**
138      * Constructs cookies from set-cookie or set-cookie2 header string.
139      * RFC 2965 section 3.2.2 set-cookie2 syntax indicates that one header line
140      * may contain more than one cookie definitions, so this is a static
141      * utility method instead of another constructor.
142      *
143      * @param header a <tt>String</tt> specifying the set-cookie header.
144      * The header should start with "set-cookie", or "set-cookie2"
145      * token; or it should have no leading token at all.
146      * @return a List of cookie parsed from header line string
147      * @throws IllegalArgumentException if header string violates the cookie
148      * specification's syntax, or the cookie
149      * name contains llegal characters, or
150      * the cookie name is one of the tokens
151      * reserved for use by the cookie protocol
152      * @throws NullPointerException if the header string is <tt>null</tt>
153      */

154     public static List JavaDoc<HttpCookie JavaDoc> parse(String JavaDoc header) {
155         int version = guessCookieVersion(header);
156         
157         // if header start with set-cookie or set-cookie2, strip it off
158
if (startsWithIgnoreCase(header, SET_COOKIE2)) {
159             header = header.substring(SET_COOKIE2.length());
160         } else if (startsWithIgnoreCase(header, SET_COOKIE)) {
161             header = header.substring(SET_COOKIE.length());
162         }
163         
164         
165         List JavaDoc<HttpCookie JavaDoc> cookies = new java.util.ArrayList JavaDoc<HttpCookie JavaDoc>();
166         // The Netscape cookie may have a comma in its expires attribute,
167
// while the comma is the delimiter in rfc 2965/2109 cookie header string.
168
// so the parse logic is slightly different
169
if (version == 0) {
170             // Netscape draft cookie
171
HttpCookie JavaDoc cookie = parseInternal(header);
172             cookie.setVersion(0);
173             cookies.add(cookie);
174         } else {
175             // rfc2965/2109 cookie
176
// if header string contains more than one cookie,
177
// it'll separate them with comma
178
List JavaDoc<String JavaDoc> cookieStrings = splitMultiCookies(header);
179             for (String JavaDoc cookieStr : cookieStrings) {
180                 HttpCookie JavaDoc cookie = parseInternal(cookieStr);
181                 cookie.setVersion(1);
182                 cookies.add(cookie);
183             }
184         }
185         
186         return cookies;
187     }
188
189
190
191
192     /* ---------------- Public operations -------------- */
193
194     
195     /**
196      * Reports whether this http cookie has expired or not.
197      *
198      * @return <tt>true</tt> to indicate this http cookie has expired;
199      * otherwise, <tt>false</tt>
200      */

201     public boolean hasExpired() {
202         if (maxAge == 0) return true;
203         
204         // if not specify max-age, this cookie should be
205
// discarded when user agent is to be closed, but
206
// it is not expired.
207
if (maxAge == MAX_AGE_UNSPECIFIED) return false;
208         
209         long deltaSecond = (System.currentTimeMillis() - whenCreated) / 1000;
210         if (deltaSecond > maxAge)
211             return true;
212         else
213             return false;
214     }
215     
216     /**
217      *
218      * Specifies a comment that describes a cookie's purpose.
219      * The comment is useful if the browser presents the cookie
220      * to the user. Comments
221      * are not supported by Netscape Version 0 cookies.
222      *
223      * @param purpose a <code>String</code> specifying the comment
224      * to display to the user
225      *
226      * @see #getComment
227      *
228      */

229
230     public void setComment(String JavaDoc purpose) {
231     comment = purpose;
232     }
233     
234     
235     
236
237     /**
238      * Returns the comment describing the purpose of this cookie, or
239      * <code>null</code> if the cookie has no comment.
240      *
241      * @return a <code>String</code> containing the comment,
242      * or <code>null</code> if none
243      *
244      * @see #setComment
245      *
246      */

247
248     public String JavaDoc getComment() {
249     return comment;
250     }
251     
252     
253     /**
254      *
255      * Specifies a comment url that describes a cookie's purpose.
256      * The comment url is useful if the browser presents the cookie
257      * to the user. Comment url is RFC 2965 only.
258      *
259      * @param purpose a <code>String</code> specifying the comment url
260      * to display to the user
261      *
262      * @see #getCommentURL
263      *
264      */

265
266     public void setCommentURL(String JavaDoc purpose) {
267     commentURL = purpose;
268     }
269     
270     
271     
272
273     /**
274      * Returns the comment url describing the purpose of this cookie, or
275      * <code>null</code> if the cookie has no comment url.
276      *
277      * @return a <code>String</code> containing the comment url,
278      * or <code>null</code> if none
279      *
280      * @see #setCommentURL
281      *
282      */

283
284     public String JavaDoc getCommentURL() {
285     return commentURL;
286     }
287
288
289     /**
290      * Specify whether user agent should discard the cookie unconditionally.
291      * This is RFC 2965 only attribute.
292      *
293      * @param discard <tt>true</tt> indicates to discard cookie unconditionally
294      *
295      * @see #getDiscard
296      */

297
298     public void setDiscard(boolean discard) {
299     toDiscard = discard;
300     }
301     
302     
303     
304
305     /**
306      * Return the discard attribute of the cookie
307      *
308      * @return a <tt>boolean</tt> to represent this cookie's discard attribute
309      *
310      * @see #setDiscard
311      */

312
313     public boolean getDiscard() {
314     return toDiscard;
315     }
316
317
318     /**
319      * Specify the portlist of the cookie, which restricts the port(s)
320      * to which a cookie may be sent back in a Cookie header.
321      *
322      * @param ports a <tt>String</tt> specify the port list, which is
323      * comma seperated series of digits
324      * @see #getPortlist
325      */

326
327     public void setPortlist(String JavaDoc ports) {
328     portlist = ports;
329     }
330     
331     
332     
333
334     /**
335      * Return the port list attribute of the cookie
336      *
337      * @return a <tt>String</tt> contains the port list
338      * or <tt>null</tt> if none
339      * @see #setPortlist
340      */

341
342     public String JavaDoc getPortlist() {
343     return portlist;
344     }
345     
346     /**
347      *
348      * Specifies the domain within which this cookie should be presented.
349      *
350      * <p>The form of the domain name is specified by RFC 2965. A domain
351      * name begins with a dot (<code>.foo.com</code>) and means that
352      * the cookie is visible to servers in a specified Domain Name System
353      * (DNS) zone (for example, <code>www.foo.com</code>, but not
354      * <code>a.b.foo.com</code>). By default, cookies are only returned
355      * to the server that sent them.
356      *
357      *
358      * @param pattern a <code>String</code> containing the domain name
359      * within which this cookie is visible;
360      * form is according to RFC 2965
361      *
362      * @see #getDomain
363      *
364      */

365
366     public void setDomain(String JavaDoc pattern) {
367         if (pattern != null)
368             domain = pattern.toLowerCase();
369         else
370             domain = pattern;
371     }
372     
373     
374     
375     
376
377     /**
378      * Returns the domain name set for this cookie. The form of
379      * the domain name is set by RFC 2965.
380      *
381      * @return a <code>String</code> containing the domain name
382      *
383      * @see #setDomain
384      *
385      */

386
387     public String JavaDoc getDomain() {
388     return domain;
389     }
390
391
392     /**
393      * Sets the maximum age of the cookie in seconds.
394      *
395      * <p>A positive value indicates that the cookie will expire
396      * after that many seconds have passed. Note that the value is
397      * the <i>maximum</i> age when the cookie will expire, not the cookie's
398      * current age.
399      *
400      * <p>A negative value means
401      * that the cookie is not stored persistently and will be deleted
402      * when the Web browser exits. A zero value causes the cookie
403      * to be deleted.
404      *
405      * @param expiry an integer specifying the maximum age of the
406      * cookie in seconds; if zero, the cookie
407      * should be discarded immediately;
408      * otherwise, the cookie's max age is unspecified.
409      *
410      * @see #getMaxAge
411      *
412      */

413     public void setMaxAge(long expiry) {
414     maxAge = expiry;
415     }
416
417
418
419
420     /**
421      * Returns the maximum age of the cookie, specified in seconds.
422      * By default, <code>-1</code> indicating the cookie will persist
423      * until browser shutdown.
424      *
425      *
426      * @return an integer specifying the maximum age of the
427      * cookie in seconds
428      *
429      *
430      * @see #setMaxAge
431      *
432      */

433
434     public long getMaxAge() {
435     return maxAge;
436     }
437     
438     
439     
440     
441     /**
442      * Specifies a path for the cookie
443      * to which the client should return the cookie.
444      *
445      * <p>The cookie is visible to all the pages in the directory
446      * you specify, and all the pages in that directory's subdirectories.
447      * A cookie's path must include the servlet that set the cookie,
448      * for example, <i>/catalog</i>, which makes the cookie
449      * visible to all directories on the server under <i>/catalog</i>.
450      *
451      * <p>Consult RFC 2965 (available on the Internet) for more
452      * information on setting path names for cookies.
453      *
454      *
455      * @param uri a <code>String</code> specifying a path
456      *
457      *
458      * @see #getPath
459      *
460      */

461
462     public void setPath(String JavaDoc uri) {
463     path = uri;
464     }
465
466
467
468
469     /**
470      * Returns the path on the server
471      * to which the browser returns this cookie. The
472      * cookie is visible to all subpaths on the server.
473      *
474      *
475      * @return a <code>String</code> specifying a path that contains
476      * a servlet name, for example, <i>/catalog</i>
477      *
478      * @see #setPath
479      *
480      */

481
482     public String JavaDoc getPath() {
483     return path;
484     }
485
486
487
488
489
490     /**
491      * Indicates to the browser whether the cookie should only be sent
492      * using a secure protocol, such as HTTPS or SSL.
493      *
494      * <p>The default value is <code>false</code>.
495      *
496      * @param flag if <code>true</code>, sends the cookie from the browser
497      * to the server using only when using a secure protocol;
498      * if <code>false</code>, sent on any protocol
499      *
500      * @see #getSecure
501      *
502      */

503  
504     public void setSecure(boolean flag) {
505     secure = flag;
506     }
507
508
509
510
511     /**
512      * Returns <code>true</code> if the browser is sending cookies
513      * only over a secure protocol, or <code>false</code> if the
514      * browser can send cookies using any protocol.
515      *
516      * @return <code>true</code> if the browser can use
517      * any standard protocol; otherwise, <code>false</code>
518      *
519      * @see #setSecure
520      *
521      */

522
523     public boolean getSecure() {
524     return secure;
525     }
526
527
528
529
530
531     /**
532      * Returns the name of the cookie. The name cannot be changed after
533      * creation.
534      *
535      * @return a <code>String</code> specifying the cookie's name
536      *
537      */

538
539     public String JavaDoc getName() {
540     return name;
541     }
542
543
544
545
546
547     /**
548      *
549      * Assigns a new value to a cookie after the cookie is created.
550      * If you use a binary value, you may want to use BASE64 encoding.
551      *
552      * <p>With Version 0 cookies, values should not contain white
553      * space, brackets, parentheses, equals signs, commas,
554      * double quotes, slashes, question marks, at signs, colons,
555      * and semicolons. Empty values may not behave the same way
556      * on all browsers.
557      *
558      * @param newValue a <code>String</code> specifying the new value
559      *
560      *
561      * @see #getValue
562      *
563      */

564
565     public void setValue(String JavaDoc newValue) {
566     value = newValue;
567     }
568
569
570
571
572     /**
573      * Returns the value of the cookie.
574      *
575      * @return a <code>String</code> containing the cookie's
576      * present value
577      *
578      * @see #setValue
579      *
580      */

581
582     public String JavaDoc getValue() {
583     return value;
584     }
585
586
587
588
589     /**
590      * Returns the version of the protocol this cookie complies
591      * with. Version 1 complies with RFC 2965/2109,
592      * and version 0 complies with the original
593      * cookie specification drafted by Netscape. Cookies provided
594      * by a browser use and identify the browser's cookie version.
595      *
596      *
597      * @return 0 if the cookie complies with the
598      * original Netscape specification; 1
599      * if the cookie complies with RFC 2965/2109
600      *
601      * @see #setVersion
602      *
603      */

604
605     public int getVersion() {
606     return version;
607     }
608
609
610
611
612     /**
613      * Sets the version of the cookie protocol this cookie complies
614      * with. Version 0 complies with the original Netscape cookie
615      * specification. Version 1 complies with RFC 2965/2109.
616      *
617      *
618      * @param v 0 if the cookie should comply with
619      * the original Netscape specification;
620      * 1 if the cookie should comply with RFC 2965/2109
621      *
622      * @throws IllegalArgumentException if <tt>v</tt> is neither 0 nor 1
623      *
624      * @see #getVersion
625      *
626      */

627
628     public void setVersion(int v) {
629         if (v != 0 && v != 1) {
630             throw new IllegalArgumentException JavaDoc("cookie version should be 0 or 1");
631         }
632         
633     version = v;
634     }
635
636
637     /**
638      * The utility method to check whether a host name is in a domain
639      * or not.
640      *
641      * <p>This concept is described in the cookie specification.
642      * To understand the concept, some terminologies need to be defined first:
643      * <blockquote>
644      * effective host name = hostname if host name contains dot<br>
645      * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;or = hostname.local if not
646      * </blockquote>
647      * <p>Host A's name domain-matches host B's if:
648      * <blockquote><ul>
649      * <li>their host name strings string-compare equal; or</li>
650      * <li>A is a HDN string and has the form NB, where N is a non-empty
651      * name string, B has the form .B', and B' is a HDN string. (So,
652      * x.y.com domain-matches .Y.com but not Y.com.)</li>
653      * </ul></blockquote>
654      *
655      * <p>A host isn't in a domain (RFC 2965 sec. 3.3.2) if:
656      * <blockquote><ul>
657      * <li>The value for the Domain attribute contains no embedded dots,
658      * and the value is not .local.</li>
659      * <li>The effective host name that derives from the request-host does
660      * not domain-match the Domain attribute.</li>
661      * <li>The request-host is a HDN (not IP address) and has the form HD,
662      * where D is the value of the Domain attribute, and H is a string
663      * that contains one or more dots.</li>
664      * </ul></blockquote>
665      *
666      * <p>Examples:
667      * <blockquote><ul>
668      * <li>A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com
669      * would be rejected, because H is y.x and contains a dot.</li>
670      * <li>A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com
671      * would be accepted.</li>
672      * <li>A Set-Cookie2 with Domain=.com or Domain=.com., will always be
673      * rejected, because there is no embedded dot.</li>
674      * <li>A Set-Cookie2 with Domain=ajax.com will be accepted, and the
675      * value for Domain will be taken to be .ajax.com, because a dot
676      * gets prepended to the value.</li>
677      * <li>A Set-Cookie2 from request-host example for Domain=.local will
678      * be accepted, because the effective host name for the request-
679      * host is example.local, and example.local domain-matches .local.</li>
680      * </ul></blockquote>
681      *
682      * @param domain the domain name to check host name with
683      * @param host the host name in question
684      * @return <tt>true</tt> if they domain-matches; <tt>false</tt> if not
685      */

686     public static boolean domainMatches(String JavaDoc domain, String JavaDoc host) {
687         if (domain == null || host == null)
688             return false;
689         
690         // if there's no embedded dot in domain and domain is not .local
691
boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
692         int embeddedDotInDomain = domain.indexOf('.');
693         if (embeddedDotInDomain == 0)
694             embeddedDotInDomain = domain.indexOf('.', 1);
695         if (!isLocalDomain
696             && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1))
697             return false;
698         
699         // if the host name contains no dot and the domain name is .local
700
int firstDotInHost = host.indexOf('.');
701         if (firstDotInHost == -1 && isLocalDomain)
702             return true;
703         
704         int domainLength = domain.length();
705         int lengthDiff = host.length() - domainLength;
706         if (lengthDiff == 0) {
707             // if the host name and the domain name are just string-compare euqal
708
return host.equalsIgnoreCase(domain);
709         }
710         else if (lengthDiff > 0) {
711             // need to check H & D component
712
String JavaDoc H = host.substring(0, lengthDiff);
713             String JavaDoc D = host.substring(lengthDiff);
714             
715             return (H.indexOf('.') == -1 && D.equalsIgnoreCase(domain));
716         }
717         else if (lengthDiff == -1) {
718             // if domain is actually .host
719
return (domain.charAt(0) == '.' &&
720                         host.equalsIgnoreCase(domain.substring(1)));
721         }
722         
723         return false;
724     }
725
726
727     /**
728      * Constructs a cookie header string representation of this cookie,
729      * which is in the format defined by corresponding cookie specification,
730      * but without the leading "Cookie:" token.
731      *
732      * @return a string form of the cookie. The string has the defined format
733      */

734     public String JavaDoc toString() {
735         if (getVersion() > 0) {
736             return toRFC2965HeaderString();
737         } else {
738             return toNetscapeHeaderString();
739         }
740     }
741     
742     
743     /**
744      * Test the equality of two http cookies.
745      *
746      * <p> The result is <tt>true</tt> only if two cookies
747      * come from same domain (case-insensitive),
748      * have same name (case-insensitive),
749      * and have same path (case-sensitive).
750      *
751      * @return <tt>true</tt> if 2 http cookies equal to each other;
752      * otherwise, <tt>false</tt>
753      */

754     public boolean equals(Object JavaDoc obj) {
755         if (obj == this)
756             return true;
757         if (!(obj instanceof HttpCookie JavaDoc))
758             return false;
759         HttpCookie JavaDoc other = (HttpCookie JavaDoc)obj;
760
761         // One http cookie equals to another cookie (RFC 2965 sec. 3.3.3) if:
762
// 1. they come from same domain (case-insensitive),
763
// 2. have same name (case-insensitive),
764
// 3. and have same path (case-sensitive).
765
return equalsIgnoreCase(getName(), other.getName()) &&
766                equalsIgnoreCase(getDomain(), other.getDomain()) &&
767                equals(getPath(), other.getPath());
768     }
769     
770     
771     /**
772      * Return hash code of this http cookie. The result is the sum of
773      * hash code value of three significant components of this cookie:
774      * name, domain, and path.
775      * That is, the hash code is the value of the expression:
776      * <blockquote>
777      * getName().toLowerCase().hashCode()<br>
778      * + getDomain().toLowerCase().hashCode()<br>
779      * + getPath().hashCode()
780      * </blockquote>
781      *
782      * @return this http cookie's hash code
783      */

784     public int hashCode() {
785         int h1 = name.toLowerCase().hashCode();
786         int h2 = (domain!=null) ? domain.toLowerCase().hashCode() : 0;
787         int h3 = (path!=null) ? path.hashCode() : 0;
788         
789         return h1 + h2 + h3;
790     }
791     
792     /**
793      * Create and return a copy of this object.
794      *
795      * @return a clone of this http cookie
796      */

797     public Object JavaDoc clone() {
798     try {
799         return super.clone();
800     } catch (CloneNotSupportedException JavaDoc e) {
801         throw new RuntimeException JavaDoc(e.getMessage());
802     }
803     }
804
805
806     /* ---------------- Private operations -------------- */
807
808     // Note -- disabled for now to allow full Netscape compatibility
809
// from RFC 2068, token special case characters
810
//
811
// private static final String tspecials = "()<>@,;:\\\"/[]?={} \t";
812
private static final String JavaDoc tspecials = ",;";
813
814     /*
815      * Tests a string and returns true if the string counts as a
816      * token.
817      *
818      * @param value the <code>String</code> to be tested
819      *
820      * @return <code>true</code> if the <code>String</code> is
821      * a token; <code>false</code> if it is not
822      */

823
824     private static boolean isToken(String JavaDoc value) {
825     int len = value.length();
826
827     for (int i = 0; i < len; i++) {
828         char c = value.charAt(i);
829
830         if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1)
831         return false;
832     }
833     return true;
834     }
835     
836     
837     /*
838      * @param name the name to be tested
839      * @return <tt>true</tt> if the name is reserved by cookie
840      * specification, <tt>false</tt> if it is not
841      */

842     private static boolean isReserved(String JavaDoc name) {
843         if (name.equalsIgnoreCase("Comment")
844             || name.equalsIgnoreCase("CommentURL") // rfc2965 only
845
|| name.equalsIgnoreCase("Discard") // rfc2965 only
846
|| name.equalsIgnoreCase("Domain")
847             || name.equalsIgnoreCase("Expires") // netscape draft only
848
|| name.equalsIgnoreCase("Max-Age")
849             || name.equalsIgnoreCase("Path")
850             || name.equalsIgnoreCase("Port") // rfc2965 only
851
|| name.equalsIgnoreCase("Secure")
852             || name.equalsIgnoreCase("Version")
853             || name.charAt(0) == '$')
854         {
855             return true;
856         }
857         
858         return false;
859     }
860
861
862     /*
863      * Parse header string to cookie object.
864      *
865      * @param header header string; should contain only one NAME=VALUE pair
866      *
867      * @return an HttpCookie being extracted
868      *
869      * @throws IllegalArgumentException if header string violates the cookie
870      * specification
871      */

872     private static HttpCookie JavaDoc parseInternal(String JavaDoc header)
873     {
874         HttpCookie JavaDoc cookie = null;
875         String JavaDoc namevaluePair = null;
876         
877         StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(header, ";");
878         
879         // there should always have at least on name-value pair;
880
// it's cookie's name
881
try {
882             namevaluePair = tokenizer.nextToken();
883             int index = namevaluePair.indexOf('=');
884             if (index != -1) {
885                 String JavaDoc name = namevaluePair.substring(0, index).trim();
886                 String JavaDoc value = namevaluePair.substring(index + 1).trim();
887                 cookie = new HttpCookie JavaDoc(name, stripOffSurroundingQuote(value));
888             } else {
889                 // no "=" in name-value pair; it's an error
890
throw new IllegalArgumentException JavaDoc("Invalid cookie name-value pair");
891             }
892         } catch (NoSuchElementException JavaDoc ignored) {
893             throw new IllegalArgumentException JavaDoc("Empty cookie header string");
894         }
895
896         // remaining name-value pairs are cookie's attributes
897
while (tokenizer.hasMoreTokens()) {
898             namevaluePair = tokenizer.nextToken();
899             int index = namevaluePair.indexOf('=');
900             String JavaDoc name, value;
901             if (index != -1) {
902                 name = namevaluePair.substring(0, index).trim();
903                 value = namevaluePair.substring(index + 1).trim();
904             } else {
905                 name = namevaluePair.trim();
906                 value = null;
907             }
908             
909             // assign attribute to cookie
910
assignAttribute(cookie, name, value);
911         }
912         
913         return cookie;
914     }
915     
916     
917     /*
918      * assign cookie attribute value to attribute name;
919      * use a map to simulate method dispatch
920      */

921     static interface CookieAttributeAssignor {
922             public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue);
923     }
924     static java.util.Map JavaDoc<String JavaDoc, CookieAttributeAssignor> assignors = null;
925     static {
926         assignors = new java.util.HashMap JavaDoc<String JavaDoc, CookieAttributeAssignor>();
927         assignors.put("comment", new CookieAttributeAssignor(){
928                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
929                     if (cookie.getComment() == null) cookie.setComment(attrValue);
930                 }
931             });
932         assignors.put("commenturl", new CookieAttributeAssignor(){
933                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
934                     if (cookie.getCommentURL() == null) cookie.setCommentURL(attrValue);
935                 }
936             });
937         assignors.put("discard", new CookieAttributeAssignor(){
938                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
939                     cookie.setDiscard(true);
940                 }
941             });
942         assignors.put("domain", new CookieAttributeAssignor(){
943                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
944                     if (cookie.getDomain() == null) cookie.setDomain(attrValue);
945                 }
946             });
947         assignors.put("max-age", new CookieAttributeAssignor(){
948                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
949                     try {
950                         long maxage = Long.parseLong(attrValue);
951                         if (cookie.getMaxAge() == MAX_AGE_UNSPECIFIED) cookie.setMaxAge(maxage);
952                     } catch (NumberFormatException JavaDoc ignored) {
953                         throw new IllegalArgumentException JavaDoc("Illegal cookie max-age attribute");
954                     }
955                 }
956             });
957         assignors.put("path", new CookieAttributeAssignor(){
958                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
959                     if (cookie.getPath() == null) cookie.setPath(attrValue);
960                 }
961             });
962         assignors.put("port", new CookieAttributeAssignor(){
963                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
964                     if (cookie.getPortlist() == null) cookie.setPortlist(attrValue);
965                 }
966             });
967         assignors.put("secure", new CookieAttributeAssignor(){
968                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
969                     cookie.setSecure(true);
970                 }
971             });
972         assignors.put("version", new CookieAttributeAssignor(){
973                 public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
974                     try {
975                         int version = Integer.parseInt(attrValue);
976                         cookie.setVersion(version);
977                     } catch (NumberFormatException JavaDoc ignored) {
978                         throw new IllegalArgumentException JavaDoc("Illegal cookie version attribute");
979                     }
980                 }
981             });
982         assignors.put("expires", new CookieAttributeAssignor(){ // Netscape only
983
public void assign(HttpCookie JavaDoc cookie, String JavaDoc attrName, String JavaDoc attrValue) {
984                     if (cookie.getMaxAge() == MAX_AGE_UNSPECIFIED) {
985                         cookie.setMaxAge(cookie.expiryDate2DeltaSeconds(attrValue));
986                     }
987                 }
988             });
989     }
990     private static void assignAttribute(HttpCookie JavaDoc cookie,
991                                        String JavaDoc attrName,
992                                        String JavaDoc attrValue)
993     {
994         // strip off the surrounding "-sign if there's any
995
attrValue = stripOffSurroundingQuote(attrValue);
996         
997         CookieAttributeAssignor assignor = assignors.get(attrName.toLowerCase());
998         if (assignor != null) {
999             assignor.assign(cookie, attrName, attrValue);
1000        } else {
1001            // must be an error
1002
throw new IllegalArgumentException JavaDoc("Illegal cookie attribute");
1003        }
1004    }
1005    
1006    /*
1007     * Constructs a string representation of this cookie. The string format is
1008     * as Netscape spec, but without leading "Cookie:" token.
1009     */

1010    private String JavaDoc toNetscapeHeaderString() {
1011        StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
1012        
1013        sb.append(getName() + "=" + getValue());
1014        
1015        return sb.toString();
1016    }
1017    
1018    /*
1019     * Constructs a string representation of this cookie. The string format is
1020     * as RFC 2965/2109, but without leading "Cookie:" token.
1021     */

1022    private String JavaDoc toRFC2965HeaderString() {
1023        StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
1024        
1025        sb.append(getName()).append("=\"").append(getValue()).append('"');
1026        if (getPath() != null)
1027            sb.append(";$Path=\"").append(getPath()).append('"');
1028        if (getDomain() != null)
1029            sb.append(";$Domain=\"").append(getDomain()).append('"');
1030        if (getPortlist() != null)
1031            sb.append(";$Port=\"").append(getPortlist()).append('"');
1032        
1033        return sb.toString();
1034    }
1035    
1036    /*
1037     * @param dateString a date string in format of
1038     * "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",
1039     * which defined in Netscape cookie spec
1040     *
1041     * @return delta seconds between this cookie's creation
1042     * time and the time specified by dateString
1043     */

1044    private long expiryDate2DeltaSeconds(String JavaDoc dateString) {
1045        SimpleDateFormat JavaDoc df = new SimpleDateFormat JavaDoc(NETSCAPE_COOKIE_DATE_FORMAT);
1046        df.setTimeZone(TimeZone.getTimeZone("GMT"));
1047        
1048        try {
1049            Date JavaDoc date = df.parse(dateString);
1050            return (date.getTime() - whenCreated) / 1000;
1051        } catch (Exception JavaDoc e) {
1052            return 0;
1053        }
1054    }
1055    
1056    
1057    
1058    /*
1059     * try to guess the cookie version through set-cookie header string
1060     */

1061    private static int guessCookieVersion(String JavaDoc header) {
1062        int version = 0;
1063        
1064        header = header.toLowerCase();
1065        if (header.indexOf("expires=") != -1) {
1066            // only netscape cookie using 'expires'
1067
version = 0;
1068        } else if (header.indexOf("version=") != -1) {
1069            // version is mandatory for rfc 2965/2109 cookie
1070
version = 1;
1071        } else if (header.indexOf("max-age") != -1) {
1072            // rfc 2965/2109 use 'max-age'
1073
version = 1;
1074        } else if (startsWithIgnoreCase(header, SET_COOKIE2)) {
1075            // only rfc 2965 cookie starts with 'set-cookie2'
1076
version = 1;
1077        }
1078        
1079        return version;
1080    }
1081    
1082    private static String JavaDoc stripOffSurroundingQuote(String JavaDoc str) {
1083        if (str != null && str.length() > 0 &&
1084            str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"') {
1085            return str.substring(1, str.length() - 1);
1086        } else {
1087            return str;
1088        }
1089    }
1090
1091    private static boolean equalsIgnoreCase(String JavaDoc s, String JavaDoc t) {
1092    if (s == t) return true;
1093    if ((s != null) && (t != null)) {
1094            return s.equalsIgnoreCase(t);
1095    }
1096    return false;
1097    }
1098
1099    private static boolean equals(String JavaDoc s, String JavaDoc t) {
1100    if (s == t) return true;
1101    if ((s != null) && (t != null)) {
1102            return s.equals(t);
1103    }
1104    return false;
1105    }
1106    
1107    private static boolean startsWithIgnoreCase(String JavaDoc s, String JavaDoc start) {
1108        if (s == null || start == null) return false;
1109        
1110        if (s.length() >= start.length() &&
1111                start.equalsIgnoreCase(s.substring(0, start.length()))) {
1112            return true;
1113        }
1114        
1115        return false;
1116    }
1117    
1118    /*
1119     * Split cookie header string according to rfc 2965:
1120     * 1) split where it is a comma;
1121     * 2) but not the comma surrounding by double-quotes, which is the comma
1122     * inside port list or embeded URIs.
1123     *
1124     * @param header the cookie header string to split
1125     *
1126     * @return list of strings; never null
1127     *
1128     */

1129    private static List JavaDoc<String JavaDoc> splitMultiCookies(String JavaDoc header) {
1130        List JavaDoc<String JavaDoc> cookies = new java.util.ArrayList JavaDoc<String JavaDoc>();
1131        int quoteCount = 0;
1132        int p, q;
1133        
1134        for (p = 0, q = 0; p < header.length(); p++) {
1135            char c = header.charAt(p);
1136            if (c == '"') quoteCount++;
1137            if (c == ',' && (quoteCount % 2 == 0)) { // it is comma and not surrounding by double-quotes
1138
cookies.add(header.substring(q, p));
1139                q = p + 1;
1140            }
1141        }
1142        
1143        cookies.add(header.substring(q));
1144        
1145        return cookies;
1146    }
1147}
1148
1149
Popular Tags