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: 157457 $ $Date: 2005-03-14 15:23:16 -0500 (Mon, 14 Mar 2005) $
63 */
64 public class Cookie extends NameValuePair implements Serializable, Comparator {
65
66
67
68 /***
69 * Default constructor. Creates a blank cookie
70 */
71
72 public Cookie() {
73 this(null, "noname", null, null, null, false);
74 }
75
76 /***
77 * Creates a cookie with the given name, value and domain attribute.
78 *
79 * @param name the cookie name
80 * @param value the cookie value
81 * @param domain the domain this cookie can be sent to
82 */
83 public Cookie(String domain, String name, String value) {
84 this(domain, name, value, null, null, false);
85 }
86
87 /***
88 * Creates a cookie with the given name, value, domain attribute,
89 * path attribute, expiration attribute, and secure attribute
90 *
91 * @param name the cookie name
92 * @param value the cookie value
93 * @param domain the domain this cookie can be sent to
94 * @param path the path prefix for which this cookie can be sent
95 * @param expires the {@link Date} at which this cookie expires,
96 * or <tt>null</tt> if the cookie expires at the end
97 * of the session
98 * @param secure if true this cookie can only be sent over secure
99 * connections
100 * @throws IllegalArgumentException If cookie name is null or blank,
101 * cookie name contains a blank, or cookie name starts with character $
102 *
103 */
104 public Cookie(String domain, String name, String value,
105 String path, Date expires, boolean secure) {
106
107 super(name, value);
108 LOG.trace("enter Cookie(String, String, String, String, Date, boolean)");
109 if (name == null) {
110 throw new IllegalArgumentException("Cookie name may not be null");
111 }
112 if (name.trim().equals("")) {
113 throw new IllegalArgumentException("Cookie name may not be blank");
114 }
115 this.setPath(path);
116 this.setDomain(domain);
117 this.setExpiryDate(expires);
118 this.setSecure(secure);
119 }
120
121 /***
122 * Creates a cookie with the given name, value, domain attribute,
123 * path attribute, maximum age attribute, and secure attribute
124 *
125 * @param name the cookie name
126 * @param value the cookie value
127 * @param domain the domain this cookie can be sent to
128 * @param path the path prefix for which this cookie can be sent
129 * @param maxAge the number of seconds for which this cookie is valid.
130 * maxAge is expected to be a non-negative number.
131 * <tt>-1</tt> signifies that the cookie should never expire.
132 * @param secure if <tt>true</tt> this cookie can only be sent over secure
133 * connections
134 */
135 public Cookie(String domain, String name, String value, String path,
136 int maxAge, boolean secure) {
137
138 this(domain, name, value, path, null, secure);
139 if (maxAge < -1) {
140 throw new IllegalArgumentException("Invalid max age: " + Integer.toString(maxAge));
141 }
142 if (maxAge >= 0) {
143 setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
144 }
145 }
146
147 /***
148 * Returns the comment describing the purpose of this cookie, or
149 * <tt>null</tt> if no such comment has been defined.
150 *
151 * @return comment
152 *
153 * @see #setComment(String)
154 */
155 public String getComment() {
156 return cookieComment;
157 }
158
159 /***
160 * If a user agent (web browser) presents this cookie to a user, the
161 * cookie's purpose will be described using this comment.
162 *
163 * @param comment
164 *
165 * @see #getComment()
166 */
167 public void setComment(String comment) {
168 cookieComment = comment;
169 }
170
171 /***
172 * Returns the expiration {@link Date} of the cookie, or <tt>null</tt>
173 * if none exists.
174 * <p><strong>Note:</strong> the object returned by this method is
175 * considered immutable. Changing it (e.g. using setTime()) could result
176 * in undefined behaviour. Do so at your peril. </p>
177 * @return Expiration {@link Date}, or <tt>null</tt>.
178 *
179 * @see #setExpiryDate(java.util.Date)
180 *
181 */
182 public Date getExpiryDate() {
183 return cookieExpiryDate;
184 }
185
186 /***
187 * Sets expiration date.
188 * <p><strong>Note:</strong> the object returned by this method is considered
189 * immutable. Changing it (e.g. using setTime()) could result in undefined
190 * behaviour. Do so at your peril.</p>
191 *
192 * @param expiryDate the {@link Date} after which this cookie is no longer valid.
193 *
194 * @see #getExpiryDate
195 *
196 */
197 public void setExpiryDate (Date expiryDate) {
198 cookieExpiryDate = expiryDate;
199 }
200
201
202 /***
203 * Returns <tt>false</tt> if the cookie should be discarded at the end
204 * of the "session"; <tt>true</tt> otherwise.
205 *
206 * @return <tt>false</tt> if the cookie should be discarded at the end
207 * of the "session"; <tt>true</tt> otherwise
208 */
209 public boolean isPersistent() {
210 return (null != cookieExpiryDate);
211 }
212
213
214 /***
215 * Returns domain attribute of the cookie.
216 *
217 * @return the value of the domain attribute
218 *
219 * @see #setDomain(java.lang.String)
220 */
221 public String getDomain() {
222 return cookieDomain;
223 }
224
225 /***
226 * Sets the domain attribute.
227 *
228 * @param domain The value of the domain attribute
229 *
230 * @see #getDomain
231 */
232 public void setDomain(String domain) {
233 if (domain != null) {
234 int ndx = domain.indexOf(":");
235 if (ndx != -1) {
236 domain = domain.substring(0, ndx);
237 }
238 cookieDomain = domain.toLowerCase();
239 }
240 }
241
242
243 /***
244 * Returns the path attribute of the cookie
245 *
246 * @return The value of the path attribute.
247 *
248 * @see #setPath(java.lang.String)
249 */
250 public String getPath() {
251 return cookiePath;
252 }
253
254 /***
255 * Sets the path attribute.
256 *
257 * @param path The value of the path attribute
258 *
259 * @see #getPath
260 *
261 */
262 public void setPath(String path) {
263 cookiePath = path;
264 }
265
266 /***
267 * @return <code>true</code> if this cookie should only be sent over secure connections.
268 * @see #setSecure(boolean)
269 */
270 public boolean getSecure() {
271 return isSecure;
272 }
273
274 /***
275 * Sets the secure attribute of the cookie.
276 * <p>
277 * When <tt>true</tt> the cookie should only be sent
278 * using a secure protocol (https). This should only be set when
279 * the cookie's originating server used a secure protocol to set the
280 * cookie's value.
281 *
282 * @param secure The value of the secure attribute
283 *
284 * @see #getSecure()
285 */
286 public void setSecure (boolean secure) {
287 isSecure = secure;
288 }
289
290 /***
291 * Returns the version of the cookie specification to which this
292 * cookie conforms.
293 *
294 * @return the version of the cookie.
295 *
296 * @see #setVersion(int)
297 *
298 */
299 public int getVersion() {
300 return cookieVersion;
301 }
302
303 /***
304 * Sets the version of the cookie specification to which this
305 * cookie conforms.
306 *
307 * @param version the version of the cookie.
308 *
309 * @see #getVersion
310 */
311 public void setVersion(int version) {
312 cookieVersion = version;
313 }
314
315 /***
316 * Returns true if this cookie has expired.
317 *
318 * @return <tt>true</tt> if the cookie has expired.
319 */
320 public boolean isExpired() {
321 return (cookieExpiryDate != null
322 && cookieExpiryDate.getTime() <= System.currentTimeMillis());
323 }
324
325 /***
326 * Returns true if this cookie has expired according to the time passed in.
327 *
328 * @param now The current time.
329 *
330 * @return <tt>true</tt> if the cookie expired.
331 */
332 public boolean isExpired(Date now) {
333 return (cookieExpiryDate != null
334 && cookieExpiryDate.getTime() <= now.getTime());
335 }
336
337
338 /***
339 * Indicates whether the cookie had a path specified in a
340 * path attribute of the <tt>Set-Cookie</tt> header. This value
341 * is important for generating the <tt>Cookie</tt> header because
342 * some cookie specifications require that the <tt>Cookie</tt> header
343 * should only include a path attribute if the cookie's path
344 * was specified in the <tt>Set-Cookie</tt> header.
345 *
346 * @param value <tt>true</tt> if the cookie's path was explicitly
347 * set, <tt>false</tt> otherwise.
348 *
349 * @see #isPathAttributeSpecified
350 */
351 public void setPathAttributeSpecified(boolean value) {
352 hasPathAttribute = value;
353 }
354
355 /***
356 * Returns <tt>true</tt> if cookie's path was set via a path attribute
357 * in the <tt>Set-Cookie</tt> header.
358 *
359 * @return value <tt>true</tt> if the cookie's path was explicitly
360 * set, <tt>false</tt> otherwise.
361 *
362 * @see #setPathAttributeSpecified
363 */
364 public boolean isPathAttributeSpecified() {
365 return hasPathAttribute;
366 }
367
368 /***
369 * Indicates whether the cookie had a domain specified in a
370 * domain attribute of the <tt>Set-Cookie</tt> header. This value
371 * is important for generating the <tt>Cookie</tt> header because
372 * some cookie specifications require that the <tt>Cookie</tt> header
373 * should only include a domain attribute if the cookie's domain
374 * was specified in the <tt>Set-Cookie</tt> header.
375 *
376 * @param value <tt>true</tt> if the cookie's domain was explicitly
377 * set, <tt>false</tt> otherwise.
378 *
379 * @see #isDomainAttributeSpecified
380 */
381 public void setDomainAttributeSpecified(boolean value) {
382 hasDomainAttribute = value;
383 }
384
385 /***
386 * Returns <tt>true</tt> if cookie's domain was set via a domain
387 * attribute in the <tt>Set-Cookie</tt> header.
388 *
389 * @return value <tt>true</tt> if the cookie's domain was explicitly
390 * set, <tt>false</tt> otherwise.
391 *
392 * @see #setDomainAttributeSpecified
393 */
394 public boolean isDomainAttributeSpecified() {
395 return hasDomainAttribute;
396 }
397
398 /***
399 * Returns a hash code in keeping with the
400 * {@link Object#hashCode} general hashCode contract.
401 * @return A hash code
402 */
403 public int hashCode() {
404 int hash = LangUtils.HASH_SEED;
405 hash = LangUtils.hashCode(hash, this.getName());
406 hash = LangUtils.hashCode(hash, this.cookieDomain);
407 hash = LangUtils.hashCode(hash, this.cookiePath);
408 return hash;
409 }
410
411
412 /***
413 * Two cookies are equal if the name, path and domain match.
414 * @param obj The object to compare against.
415 * @return true if the two objects are equal.
416 */
417 public boolean equals(Object obj) {
418 if (obj == null) return false;
419 if (this == obj) return true;
420 if (obj instanceof Cookie) {
421 Cookie that = (Cookie) obj;
422 return LangUtils.equals(this.getName(), that.getName())
423 && LangUtils.equals(this.cookieDomain, that.cookieDomain)
424 && LangUtils.equals(this.cookiePath, that.cookiePath);
425 } else {
426 return false;
427 }
428 }
429
430
431 /***
432 * Return a textual representation of the cookie.
433 *
434 * @return string.
435 */
436 public String toExternalForm() {
437 CookieSpec spec = null;
438 if (getVersion() > 0) {
439 spec = CookiePolicy.getDefaultSpec();
440 } else {
441 spec = CookiePolicy.getCookieSpec(CookiePolicy.NETSCAPE);
442 }
443 return spec.formatCookie(this);
444 }
445
446 /***
447 * <p>Compares two cookies to determine order for cookie header.</p>
448 * <p>Most specific should be first. </p>
449 * <p>This method is implemented so a cookie can be used as a comparator for
450 * a SortedSet of cookies. Specifically it's used above in the
451 * createCookieHeader method.</p>
452 * @param o1 The first object to be compared
453 * @param o2 The second object to be compared
454 * @return See {@link java.util.Comparator#compare(Object,Object)}
455 */
456 public int compare(Object o1, Object o2) {
457 LOG.trace("enter Cookie.compare(Object, Object)");
458
459 if (!(o1 instanceof Cookie)) {
460 throw new ClassCastException(o1.getClass().getName());
461 }
462 if (!(o2 instanceof Cookie)) {
463 throw new ClassCastException(o2.getClass().getName());
464 }
465 Cookie c1 = (Cookie) o1;
466 Cookie c2 = (Cookie) o2;
467 if (c1.getPath() == null && c2.getPath() == null) {
468 return 0;
469 } else if (c1.getPath() == null) {
470
471 if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
472 return 0;
473 } else {
474 return -1;
475 }
476 } else if (c2.getPath() == null) {
477
478 if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
479 return 0;
480 } else {
481 return 1;
482 }
483 } else {
484 return STRING_COLLATOR.compare(c1.getPath(), c2.getPath());
485 }
486 }
487
488 /***
489 * Return a textual representation of the cookie.
490 *
491 * @return string.
492 *
493 * @see #toExternalForm
494 */
495 public String toString() {
496 return toExternalForm();
497 }
498
499
500
501 /*** Comment attribute. */
502 private String cookieComment;
503
504 /*** Domain attribute. */
505 private String cookieDomain;
506
507 /*** Expiration {@link Date}. */
508 private Date cookieExpiryDate;
509
510 /*** Path attribute. */
511 private String cookiePath;
512
513 /*** My secure flag. */
514 private boolean isSecure;
515
516 /***
517 * Specifies if the set-cookie header included a Path attribute for this
518 * cookie
519 */
520 private boolean hasPathAttribute = false;
521
522 /***
523 * Specifies if the set-cookie header included a Domain attribute for this
524 * cookie
525 */
526 private boolean hasDomainAttribute = false;
527
528 /*** The version of the cookie specification I was created from. */
529 private int cookieVersion = 0;
530
531
532
533 /***
534 * Collator for Cookie comparisons. Could be replaced with references to
535 * specific Locales.
536 */
537 private static final RuleBasedCollator STRING_COLLATOR =
538 (RuleBasedCollator) RuleBasedCollator.getInstance(
539 new Locale("en", "US", ""));
540
541 /*** Log object for this class */
542 private static final Log LOG = LogFactory.getLog(Cookie.class);
543
544 }
545