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