1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.apache.commons.httpclient;
31
32 import java.io.Serializable;
33 import java.text.RuleBasedCollator;
34 import java.util.Comparator;
35 import java.util.Date;
36 import java.util.Locale;
37
38 import org.apache.commons.httpclient.cookie.CookiePolicy;
39 import org.apache.commons.httpclient.cookie.CookieSpec;
40 import org.apache.commons.httpclient.util.LangUtils;
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43
44 /***
45 * <p>
46 * HTTP "magic-cookie" represents a piece of state information
47 * that the HTTP agent and the target server can exchange to maintain
48 * a session.
49 * </p>
50 *
51 * @author B.C. Holmes
52 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
53 * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
54 * @author Rod Waldhoff
55 * @author dIon Gillard
56 * @author Sean C. Sullivan
57 * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
58 * @author Marc A. Saegesser
59 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
60 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
61 *
62 * @version $Revision: 5562 $ $Date: 2007-11-16 00:53:10 +0000 (Fri, 16 Nov 2007) $
63 */
64 @SuppressWarnings("serial")
65 public class Cookie extends NameValuePair implements Serializable, Comparator {
66
67
68
69 /***
70 * Default constructor. Creates a blank cookie
71 */
72
73 public Cookie() {
74 this(null, "noname", null, null, null, false);
75 }
76
77 /***
78 * Creates a cookie with the given name, value and domain attribute.
79 *
80 * @param name the cookie name
81 * @param value the cookie value
82 * @param domain the domain this cookie can be sent to
83 */
84 public Cookie(String domain, String name, String value) {
85 this(domain, name, value, null, null, false);
86 }
87
88 /***
89 * Creates a cookie with the given name, value, domain attribute,
90 * path attribute, expiration attribute, and secure attribute
91 *
92 * @param name the cookie name
93 * @param value the cookie value
94 * @param domain the domain this cookie can be sent to
95 * @param path the path prefix for which this cookie can be sent
96 * @param expires the {@link Date} at which this cookie expires,
97 * or <tt>null</tt> if the cookie expires at the end
98 * of the session
99 * @param secure if true this cookie can only be sent over secure
100 * connections
101 * @throws IllegalArgumentException If cookie name is null or blank,
102 * cookie name contains a blank, or cookie name starts with character $
103 *
104 */
105 public Cookie(String domain, String name, String value,
106 String path, Date expires, boolean secure) {
107
108 super(name, value);
109 LOG.trace("enter Cookie(String, String, String, String, Date, boolean)");
110 if (name == null) {
111 throw new IllegalArgumentException("Cookie name may not be null");
112 }
113 if (name.trim().equals("")) {
114 throw new IllegalArgumentException("Cookie name may not be blank");
115 }
116 this.setPath(path);
117 this.setDomain(domain);
118 this.setExpiryDate(expires);
119 this.setSecure(secure);
120 }
121
122 /***
123 * Creates a cookie with the given name, value, domain attribute,
124 * path attribute, maximum age attribute, and secure attribute
125 *
126 * @param name the cookie name
127 * @param value the cookie value
128 * @param domain the domain this cookie can be sent to
129 * @param path the path prefix for which this cookie can be sent
130 * @param maxAge the number of seconds for which this cookie is valid.
131 * maxAge is expected to be a non-negative number.
132 * <tt>-1</tt> signifies that the cookie should never expire.
133 * @param secure if <tt>true</tt> this cookie can only be sent over secure
134 * connections
135 */
136 public Cookie(String domain, String name, String value, String path,
137 int maxAge, boolean secure) {
138
139 this(domain, name, value, path, null, secure);
140 if (maxAge < -1) {
141 throw new IllegalArgumentException("Invalid max age: " + Integer.toString(maxAge));
142 }
143 if (maxAge >= 0) {
144 setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
145 }
146 }
147
148 /***
149 * Returns the comment describing the purpose of this cookie, or
150 * <tt>null</tt> if no such comment has been defined.
151 *
152 * @return comment
153 *
154 * @see #setComment(String)
155 */
156 public String getComment() {
157 return cookieComment;
158 }
159
160 /***
161 * If a user agent (web browser) presents this cookie to a user, the
162 * cookie's purpose will be described using this comment.
163 *
164 * @param comment
165 *
166 * @see #getComment()
167 */
168 public void setComment(String comment) {
169 cookieComment = comment;
170 }
171
172 /***
173 * Returns the expiration {@link Date} of the cookie, or <tt>null</tt>
174 * if none exists.
175 * <p><strong>Note:</strong> the object returned by this method is
176 * considered immutable. Changing it (e.g. using setTime()) could result
177 * in undefined behaviour. Do so at your peril. </p>
178 * @return Expiration {@link Date}, or <tt>null</tt>.
179 *
180 * @see #setExpiryDate(java.util.Date)
181 *
182 */
183 public Date getExpiryDate() {
184 return cookieExpiryDate;
185 }
186
187 /***
188 * Sets expiration date.
189 * <p><strong>Note:</strong> the object returned by this method is considered
190 * immutable. Changing it (e.g. using setTime()) could result in undefined
191 * behaviour. Do so at your peril.</p>
192 *
193 * @param expiryDate the {@link Date} after which this cookie is no longer valid.
194 *
195 * @see #getExpiryDate
196 *
197 */
198 public void setExpiryDate (Date expiryDate) {
199 cookieExpiryDate = expiryDate;
200 }
201
202
203 /***
204 * Returns <tt>false</tt> if the cookie should be discarded at the end
205 * of the "session"; <tt>true</tt> otherwise.
206 *
207 * @return <tt>false</tt> if the cookie should be discarded at the end
208 * of the "session"; <tt>true</tt> otherwise
209 */
210 public boolean isPersistent() {
211 return (null != cookieExpiryDate);
212 }
213
214
215 /***
216 * Returns domain attribute of the cookie.
217 *
218 * @return the value of the domain attribute
219 *
220 * @see #setDomain(java.lang.String)
221 */
222 public String getDomain() {
223 return cookieDomain;
224 }
225
226 /***
227 * Sets the domain attribute.
228 *
229 * @param domain The value of the domain attribute
230 *
231 * @see #getDomain
232 */
233 public void setDomain(String domain) {
234 if (domain != null) {
235 int ndx = domain.indexOf(":");
236 if (ndx != -1) {
237 domain = domain.substring(0, ndx);
238 }
239 cookieDomain = domain.toLowerCase();
240 }
241 }
242
243
244 /***
245 * Returns the path attribute of the cookie
246 *
247 * @return The value of the path attribute.
248 *
249 * @see #setPath(java.lang.String)
250 */
251 public String getPath() {
252 return cookiePath;
253 }
254
255 /***
256 * Sets the path attribute.
257 *
258 * @param path The value of the path attribute
259 *
260 * @see #getPath
261 *
262 */
263 public void setPath(String path) {
264 cookiePath = path;
265 }
266
267 /***
268 * @return <code>true</code> if this cookie should only be sent over secure connections.
269 * @see #setSecure(boolean)
270 */
271 public boolean getSecure() {
272 return isSecure;
273 }
274
275 /***
276 * Sets the secure attribute of the cookie.
277 * <p>
278 * When <tt>true</tt> the cookie should only be sent
279 * using a secure protocol (https). This should only be set when
280 * the cookie's originating server used a secure protocol to set the
281 * cookie's value.
282 *
283 * @param secure The value of the secure attribute
284 *
285 * @see #getSecure()
286 */
287 public void setSecure (boolean secure) {
288 isSecure = secure;
289 }
290
291 /***
292 * Returns the version of the cookie specification to which this
293 * cookie conforms.
294 *
295 * @return the version of the cookie.
296 *
297 * @see #setVersion(int)
298 *
299 */
300 public int getVersion() {
301 return cookieVersion;
302 }
303
304 /***
305 * Sets the version of the cookie specification to which this
306 * cookie conforms.
307 *
308 * @param version the version of the cookie.
309 *
310 * @see #getVersion
311 */
312 public void setVersion(int version) {
313 cookieVersion = version;
314 }
315
316 /***
317 * Returns true if this cookie has expired.
318 *
319 * @return <tt>true</tt> if the cookie has expired.
320 */
321 public boolean isExpired() {
322 return (cookieExpiryDate != null
323 && cookieExpiryDate.getTime() <= System.currentTimeMillis());
324 }
325
326 /***
327 * Returns true if this cookie has expired according to the time passed in.
328 *
329 * @param now The current time.
330 *
331 * @return <tt>true</tt> if the cookie expired.
332 */
333 public boolean isExpired(Date now) {
334 return (cookieExpiryDate != null
335 && cookieExpiryDate.getTime() <= now.getTime());
336 }
337
338
339 /***
340 * Indicates whether the cookie had a path specified in a
341 * path attribute of the <tt>Set-Cookie</tt> header. This value
342 * is important for generating the <tt>Cookie</tt> header because
343 * some cookie specifications require that the <tt>Cookie</tt> header
344 * should only include a path attribute if the cookie's path
345 * was specified in the <tt>Set-Cookie</tt> header.
346 *
347 * @param value <tt>true</tt> if the cookie's path was explicitly
348 * set, <tt>false</tt> otherwise.
349 *
350 * @see #isPathAttributeSpecified
351 */
352 public void setPathAttributeSpecified(boolean value) {
353 hasPathAttribute = value;
354 }
355
356 /***
357 * Returns <tt>true</tt> if cookie's path was set via a path attribute
358 * in the <tt>Set-Cookie</tt> header.
359 *
360 * @return value <tt>true</tt> if the cookie's path was explicitly
361 * set, <tt>false</tt> otherwise.
362 *
363 * @see #setPathAttributeSpecified
364 */
365 public boolean isPathAttributeSpecified() {
366 return hasPathAttribute;
367 }
368
369 /***
370 * Indicates whether the cookie had a domain specified in a
371 * domain attribute of the <tt>Set-Cookie</tt> header. This value
372 * is important for generating the <tt>Cookie</tt> header because
373 * some cookie specifications require that the <tt>Cookie</tt> header
374 * should only include a domain attribute if the cookie's domain
375 * was specified in the <tt>Set-Cookie</tt> header.
376 *
377 * @param value <tt>true</tt> if the cookie's domain was explicitly
378 * set, <tt>false</tt> otherwise.
379 *
380 * @see #isDomainAttributeSpecified
381 */
382 public void setDomainAttributeSpecified(boolean value) {
383 hasDomainAttribute = value;
384 }
385
386 /***
387 * Returns <tt>true</tt> if cookie's domain was set via a domain
388 * attribute in the <tt>Set-Cookie</tt> header.
389 *
390 * @return value <tt>true</tt> if the cookie's domain was explicitly
391 * set, <tt>false</tt> otherwise.
392 *
393 * @see #setDomainAttributeSpecified
394 */
395 public boolean isDomainAttributeSpecified() {
396 return hasDomainAttribute;
397 }
398
399 /***
400 * Returns a hash code in keeping with the
401 * {@link Object#hashCode} general hashCode contract.
402 * @return A hash code
403 */
404 public int hashCode() {
405 int hash = LangUtils.HASH_SEED;
406 hash = LangUtils.hashCode(hash, this.getName());
407 hash = LangUtils.hashCode(hash, this.cookieDomain);
408 hash = LangUtils.hashCode(hash, this.cookiePath);
409 return hash;
410 }
411
412
413 /***
414 * Two cookies are equal if the name, path and domain match.
415 * @param obj The object to compare against.
416 * @return true if the two objects are equal.
417 */
418 public boolean equals(Object obj) {
419 if (obj == null) return false;
420 if (this == obj) return true;
421 if (obj instanceof Cookie) {
422 Cookie that = (Cookie) obj;
423 return LangUtils.equals(this.getName(), that.getName())
424 && LangUtils.equals(this.cookieDomain, that.cookieDomain)
425 && LangUtils.equals(this.cookiePath, that.cookiePath);
426 } else {
427 return false;
428 }
429 }
430
431
432 /***
433 * Return a textual representation of the cookie.
434 *
435 * @return string.
436 */
437 public String toExternalForm() {
438 CookieSpec spec = null;
439 if (getVersion() > 0) {
440 spec = CookiePolicy.getDefaultSpec();
441 } else {
442 spec = CookiePolicy.getCookieSpec(CookiePolicy.NETSCAPE);
443 }
444 return spec.formatCookie(this);
445 }
446
447 /***
448 * <p>Compares two cookies to determine order for cookie header.</p>
449 * <p>Most specific should be first. </p>
450 * <p>This method is implemented so a cookie can be used as a comparator for
451 * a SortedSet of cookies. Specifically it's used above in the
452 * createCookieHeader method.</p>
453 * @param o1 The first object to be compared
454 * @param o2 The second object to be compared
455 * @return See {@link java.util.Comparator#compare(Object,Object)}
456 */
457 public int compare(Object o1, Object o2) {
458 LOG.trace("enter Cookie.compare(Object, Object)");
459
460 if (!(o1 instanceof Cookie)) {
461 throw new ClassCastException(o1.getClass().getName());
462 }
463 if (!(o2 instanceof Cookie)) {
464 throw new ClassCastException(o2.getClass().getName());
465 }
466 Cookie c1 = (Cookie) o1;
467 Cookie c2 = (Cookie) o2;
468 if (c1.getPath() == null && c2.getPath() == null) {
469 return 0;
470 } else if (c1.getPath() == null) {
471
472 if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
473 return 0;
474 } else {
475 return -1;
476 }
477 } else if (c2.getPath() == null) {
478
479 if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
480 return 0;
481 } else {
482 return 1;
483 }
484 } else {
485 return STRING_COLLATOR.compare(c1.getPath(), c2.getPath());
486 }
487 }
488
489 /***
490 * Return a textual representation of the cookie.
491 *
492 * @return string.
493 *
494 * @see #toExternalForm
495 */
496 public String toString() {
497 return toExternalForm();
498 }
499
500
501 /***
502 * Create a 'sort key' for this Cookie that will cause it to sort
503 * alongside other Cookies of the same domain (with or without leading
504 * '.'). This helps cookie-match checks consider only narrow set of
505 * possible matches, rather than all cookies.
506 *
507 * Only two cookies that are equals() (same domain, path, name) will have
508 * the same sort key. The '\1' separator character is important in
509 * conjunction with Cookie.DOMAIN+OVERBOUNDS, allowing keys based on the
510 * domain plus an extension to define the relevant range in a SortedMap.
511 * @return String sort key for this cookie
512 */
513 public String getSortKey() {
514 String domain = getDomain();
515 return (domain.startsWith("."))
516 ? domain.substring(1) + "\1.\1" + getPath() + "\1" + getName()
517 : domain + "\1\1" + getPath() + "\1" + getName();
518 }
519
520
521
522
523 /*** Comment attribute. */
524 private String cookieComment;
525
526 /*** Domain attribute. */
527 private String cookieDomain;
528
529 /*** Expiration {@link Date}. */
530 private Date cookieExpiryDate;
531
532 /*** Path attribute. */
533 private String cookiePath;
534
535 /*** My secure flag. */
536 private boolean isSecure;
537
538 /***
539 * Specifies if the set-cookie header included a Path attribute for this
540 * cookie
541 */
542 private boolean hasPathAttribute = false;
543
544 /***
545 * Specifies if the set-cookie header included a Domain attribute for this
546 * cookie
547 */
548 private boolean hasDomainAttribute = false;
549
550 /*** The version of the cookie specification I was created from. */
551 private int cookieVersion = 0;
552
553
554
555 /***
556 * Collator for Cookie comparisons. Could be replaced with references to
557 * specific Locales.
558 */
559 private static final RuleBasedCollator STRING_COLLATOR =
560 (RuleBasedCollator) RuleBasedCollator.getInstance(
561 new Locale("en", "US", ""));
562
563 /*** Log object for this class */
564 private static final Log LOG = LogFactory.getLog(Cookie.class);
565
566
567 /***
568 * Character which, if appended to end of a domain, will give a
569 * boundary key that sorts past all Cookie sortKeys for the same
570 * domain.
571 */
572 public static final char DOMAIN_OVERBOUNDS = '\2';
573
574 }
575