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