001: /*
002: * $Header$
003: * $Revision: 4672 $
004: * $Date: 2006-09-27 00:03:16 +0000 (Wed, 27 Sep 2006) $
005: *
006: * ====================================================================
007: *
008: * Copyright 1999-2004 The Apache Software Foundation
009: *
010: * Licensed under the Apache License, Version 2.0 (the "License");
011: * you may not use this file except in compliance with the License.
012: * You may obtain a copy of the License at
013: *
014: * http://www.apache.org/licenses/LICENSE-2.0
015: *
016: * Unless required by applicable law or agreed to in writing, software
017: * distributed under the License is distributed on an "AS IS" BASIS,
018: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019: * See the License for the specific language governing permissions and
020: * limitations under the License.
021: * ====================================================================
022: *
023: * This software consists of voluntary contributions made by many
024: * individuals on behalf of the Apache Software Foundation. For more
025: * information on the Apache Software Foundation, please see
026: * <http://www.apache.org/>.
027: *
028: */
029:
030: package org.apache.commons.httpclient;
031:
032: import java.io.Serializable;
033: import java.text.RuleBasedCollator;
034: import java.util.Comparator;
035: import java.util.Date;
036: import java.util.Locale;
037:
038: import org.apache.commons.httpclient.cookie.CookiePolicy;
039: import org.apache.commons.httpclient.cookie.CookieSpec;
040: import org.apache.commons.httpclient.util.LangUtils;
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043:
044: /**
045: * <p>
046: * HTTP "magic-cookie" represents a piece of state information
047: * that the HTTP agent and the target server can exchange to maintain
048: * a session.
049: * </p>
050: *
051: * @author B.C. Holmes
052: * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
053: * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
054: * @author Rod Waldhoff
055: * @author dIon Gillard
056: * @author Sean C. Sullivan
057: * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
058: * @author Marc A. Saegesser
059: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
060: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
061: *
062: * @version $Revision: 4672 $ $Date: 2006-09-27 00:03:16 +0000 (Wed, 27 Sep 2006) $
063: */
064: @SuppressWarnings("serial")
065: public class Cookie extends NameValuePair implements Serializable,
066: Comparator {
067:
068: // ----------------------------------------------------------- Constructors
069:
070: /**
071: * Default constructor. Creates a blank cookie
072: */
073:
074: public Cookie() {
075: this (null, "noname", null, null, null, false);
076: }
077:
078: /**
079: * Creates a cookie with the given name, value and domain attribute.
080: *
081: * @param name the cookie name
082: * @param value the cookie value
083: * @param domain the domain this cookie can be sent to
084: */
085: public Cookie(String domain, String name, String value) {
086: this (domain, name, value, null, null, false);
087: }
088:
089: /**
090: * Creates a cookie with the given name, value, domain attribute,
091: * path attribute, expiration attribute, and secure attribute
092: *
093: * @param name the cookie name
094: * @param value the cookie value
095: * @param domain the domain this cookie can be sent to
096: * @param path the path prefix for which this cookie can be sent
097: * @param expires the {@link Date} at which this cookie expires,
098: * or <tt>null</tt> if the cookie expires at the end
099: * of the session
100: * @param secure if true this cookie can only be sent over secure
101: * connections
102: * @throws IllegalArgumentException If cookie name is null or blank,
103: * cookie name contains a blank, or cookie name starts with character $
104: *
105: */
106: public Cookie(String domain, String name, String value,
107: String path, Date expires, boolean secure) {
108:
109: super (name, value);
110: LOG
111: .trace("enter Cookie(String, String, String, String, Date, boolean)");
112: if (name == null) {
113: throw new IllegalArgumentException(
114: "Cookie name may not be null");
115: }
116: if (name.trim().equals("")) {
117: throw new IllegalArgumentException(
118: "Cookie name may not be blank");
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,
141: String path, int maxAge, boolean secure) {
142:
143: this (domain, name, value, path, null, secure);
144: if (maxAge < -1) {
145: throw new IllegalArgumentException("Invalid max age: "
146: + Integer.toString(maxAge));
147: }
148: if (maxAge >= 0) {
149: setExpiryDate(new Date(System.currentTimeMillis() + maxAge
150: * 1000L));
151: }
152: }
153:
154: /**
155: * Returns the comment describing the purpose of this cookie, or
156: * <tt>null</tt> if no such comment has been defined.
157: *
158: * @return comment
159: *
160: * @see #setComment(String)
161: */
162: public String getComment() {
163: return cookieComment;
164: }
165:
166: /**
167: * If a user agent (web browser) presents this cookie to a user, the
168: * cookie's purpose will be described using this comment.
169: *
170: * @param comment
171: *
172: * @see #getComment()
173: */
174: public void setComment(String comment) {
175: cookieComment = comment;
176: }
177:
178: /**
179: * Returns the expiration {@link Date} of the cookie, or <tt>null</tt>
180: * if none exists.
181: * <p><strong>Note:</strong> the object returned by this method is
182: * considered immutable. Changing it (e.g. using setTime()) could result
183: * in undefined behaviour. Do so at your peril. </p>
184: * @return Expiration {@link Date}, or <tt>null</tt>.
185: *
186: * @see #setExpiryDate(java.util.Date)
187: *
188: */
189: public Date getExpiryDate() {
190: return cookieExpiryDate;
191: }
192:
193: /**
194: * Sets expiration date.
195: * <p><strong>Note:</strong> the object returned by this method is considered
196: * immutable. Changing it (e.g. using setTime()) could result in undefined
197: * behaviour. Do so at your peril.</p>
198: *
199: * @param expiryDate the {@link Date} after which this cookie is no longer valid.
200: *
201: * @see #getExpiryDate
202: *
203: */
204: public void setExpiryDate(Date expiryDate) {
205: cookieExpiryDate = expiryDate;
206: }
207:
208: /**
209: * Returns <tt>false</tt> if the cookie should be discarded at the end
210: * of the "session"; <tt>true</tt> otherwise.
211: *
212: * @return <tt>false</tt> if the cookie should be discarded at the end
213: * of the "session"; <tt>true</tt> otherwise
214: */
215: public boolean isPersistent() {
216: return (null != cookieExpiryDate);
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: * Returns the path attribute of the cookie
249: *
250: * @return The value of the path attribute.
251: *
252: * @see #setPath(java.lang.String)
253: */
254: public String getPath() {
255: return cookiePath;
256: }
257:
258: /**
259: * Sets the path attribute.
260: *
261: * @param path The value of the path attribute
262: *
263: * @see #getPath
264: *
265: */
266: public void setPath(String path) {
267: cookiePath = path;
268: }
269:
270: /**
271: * @return <code>true</code> if this cookie should only be sent over secure connections.
272: * @see #setSecure(boolean)
273: */
274: public boolean getSecure() {
275: return isSecure;
276: }
277:
278: /**
279: * Sets the secure attribute of the cookie.
280: * <p>
281: * When <tt>true</tt> the cookie should only be sent
282: * using a secure protocol (https). This should only be set when
283: * the cookie's originating server used a secure protocol to set the
284: * cookie's value.
285: *
286: * @param secure The value of the secure attribute
287: *
288: * @see #getSecure()
289: */
290: public void setSecure(boolean secure) {
291: isSecure = secure;
292: }
293:
294: /**
295: * Returns the version of the cookie specification to which this
296: * cookie conforms.
297: *
298: * @return the version of the cookie.
299: *
300: * @see #setVersion(int)
301: *
302: */
303: public int getVersion() {
304: return cookieVersion;
305: }
306:
307: /**
308: * Sets the version of the cookie specification to which this
309: * cookie conforms.
310: *
311: * @param version the version of the cookie.
312: *
313: * @see #getVersion
314: */
315: public void setVersion(int version) {
316: cookieVersion = version;
317: }
318:
319: /**
320: * Returns true if this cookie has expired.
321: *
322: * @return <tt>true</tt> if the cookie has expired.
323: */
324: public boolean isExpired() {
325: return (cookieExpiryDate != null && cookieExpiryDate.getTime() <= System
326: .currentTimeMillis());
327: }
328:
329: /**
330: * Returns true if this cookie has expired according to the time passed in.
331: *
332: * @param now The current time.
333: *
334: * @return <tt>true</tt> if the cookie expired.
335: */
336: public boolean isExpired(Date now) {
337: return (cookieExpiryDate != null && cookieExpiryDate.getTime() <= now
338: .getTime());
339: }
340:
341: /**
342: * Indicates whether the cookie had a path specified in a
343: * path attribute of the <tt>Set-Cookie</tt> header. This value
344: * is important for generating the <tt>Cookie</tt> header because
345: * some cookie specifications require that the <tt>Cookie</tt> header
346: * should only include a path attribute if the cookie's path
347: * was specified in the <tt>Set-Cookie</tt> header.
348: *
349: * @param value <tt>true</tt> if the cookie's path was explicitly
350: * set, <tt>false</tt> otherwise.
351: *
352: * @see #isPathAttributeSpecified
353: */
354: public void setPathAttributeSpecified(boolean value) {
355: hasPathAttribute = value;
356: }
357:
358: /**
359: * Returns <tt>true</tt> if cookie's path was set via a path attribute
360: * in the <tt>Set-Cookie</tt> header.
361: *
362: * @return value <tt>true</tt> if the cookie's path was explicitly
363: * set, <tt>false</tt> otherwise.
364: *
365: * @see #setPathAttributeSpecified
366: */
367: public boolean isPathAttributeSpecified() {
368: return hasPathAttribute;
369: }
370:
371: /**
372: * Indicates whether the cookie had a domain specified in a
373: * domain attribute of the <tt>Set-Cookie</tt> header. This value
374: * is important for generating the <tt>Cookie</tt> header because
375: * some cookie specifications require that the <tt>Cookie</tt> header
376: * should only include a domain attribute if the cookie's domain
377: * was specified in the <tt>Set-Cookie</tt> header.
378: *
379: * @param value <tt>true</tt> if the cookie's domain was explicitly
380: * set, <tt>false</tt> otherwise.
381: *
382: * @see #isDomainAttributeSpecified
383: */
384: public void setDomainAttributeSpecified(boolean value) {
385: hasDomainAttribute = value;
386: }
387:
388: /**
389: * Returns <tt>true</tt> if cookie's domain was set via a domain
390: * attribute in the <tt>Set-Cookie</tt> header.
391: *
392: * @return value <tt>true</tt> if the cookie's domain was explicitly
393: * set, <tt>false</tt> otherwise.
394: *
395: * @see #setDomainAttributeSpecified
396: */
397: public boolean isDomainAttributeSpecified() {
398: return hasDomainAttribute;
399: }
400:
401: /**
402: * Returns a hash code in keeping with the
403: * {@link Object#hashCode} general hashCode contract.
404: * @return A hash code
405: */
406: public int hashCode() {
407: int hash = LangUtils.HASH_SEED;
408: hash = LangUtils.hashCode(hash, this .getName());
409: hash = LangUtils.hashCode(hash, this .cookieDomain);
410: hash = LangUtils.hashCode(hash, this .cookiePath);
411: return hash;
412: }
413:
414: /**
415: * Two cookies are equal if the name, path and domain match.
416: * @param obj The object to compare against.
417: * @return true if the two objects are equal.
418: */
419: public boolean equals(Object obj) {
420: if (obj == null)
421: return false;
422: if (this == obj)
423: return true;
424: if (obj instanceof Cookie) {
425: Cookie that = (Cookie) obj;
426: return LangUtils.equals(this .getName(), that.getName())
427: && LangUtils.equals(this .cookieDomain,
428: that.cookieDomain)
429: && LangUtils.equals(this .cookiePath,
430: that.cookiePath);
431: } else {
432: return false;
433: }
434: }
435:
436: /**
437: * Return a textual representation of the cookie.
438: *
439: * @return string.
440: */
441: public String toExternalForm() {
442: CookieSpec spec = null;
443: if (getVersion() > 0) {
444: spec = CookiePolicy.getDefaultSpec();
445: } else {
446: spec = CookiePolicy.getCookieSpec(CookiePolicy.NETSCAPE);
447: }
448: return spec.formatCookie(this );
449: }
450:
451: /**
452: * <p>Compares two cookies to determine order for cookie header.</p>
453: * <p>Most specific should be first. </p>
454: * <p>This method is implemented so a cookie can be used as a comparator for
455: * a SortedSet of cookies. Specifically it's used above in the
456: * createCookieHeader method.</p>
457: * @param o1 The first object to be compared
458: * @param o2 The second object to be compared
459: * @return See {@link java.util.Comparator#compare(Object,Object)}
460: */
461: public int compare(Object o1, Object o2) {
462: LOG.trace("enter Cookie.compare(Object, Object)");
463:
464: if (!(o1 instanceof Cookie)) {
465: throw new ClassCastException(o1.getClass().getName());
466: }
467: if (!(o2 instanceof Cookie)) {
468: throw new ClassCastException(o2.getClass().getName());
469: }
470: Cookie c1 = (Cookie) o1;
471: Cookie c2 = (Cookie) o2;
472: if (c1.getPath() == null && c2.getPath() == null) {
473: return 0;
474: } else if (c1.getPath() == null) {
475: // null is assumed to be "/"
476: if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
477: return 0;
478: } else {
479: return -1;
480: }
481: } else if (c2.getPath() == null) {
482: // null is assumed to be "/"
483: if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
484: return 0;
485: } else {
486: return 1;
487: }
488: } else {
489: return STRING_COLLATOR.compare(c1.getPath(), c2.getPath());
490: }
491: }
492:
493: /**
494: * Return a textual representation of the cookie.
495: *
496: * @return string.
497: *
498: * @see #toExternalForm
499: */
500: public String toString() {
501: return toExternalForm();
502: }
503:
504: // BEGIN IA ADDITION
505: /**
506: * Create a 'sort key' for this Cookie that will cause it to sort
507: * alongside other Cookies of the same domain (with or without leading
508: * '.'). This helps cookie-match checks consider only narrow set of
509: * possible matches, rather than all cookies.
510: *
511: * Only two cookies that are equals() (same domain, path, name) will have
512: * the same sort key. The '\1' separator character is important in
513: * conjunction with Cookie.DOMAIN+OVERBOUNDS, allowing keys based on the
514: * domain plus an extension to define the relevant range in a SortedMap.
515: * @return String sort key for this cookie
516: */
517: public String getSortKey() {
518: String domain = getDomain();
519: return (domain.startsWith(".")) ? domain.substring(1) + "\1.\1"
520: + getPath() + "\1" + getName() : domain + "\1\1"
521: + getPath() + "\1" + getName();
522: }
523:
524: // END IA ADDITION
525:
526: // ----------------------------------------------------- Instance Variables
527:
528: /** Comment attribute. */
529: private String cookieComment;
530:
531: /** Domain attribute. */
532: private String cookieDomain;
533:
534: /** Expiration {@link Date}. */
535: private Date cookieExpiryDate;
536:
537: /** Path attribute. */
538: private String cookiePath;
539:
540: /** My secure flag. */
541: private boolean isSecure;
542:
543: /**
544: * Specifies if the set-cookie header included a Path attribute for this
545: * cookie
546: */
547: private boolean hasPathAttribute = false;
548:
549: /**
550: * Specifies if the set-cookie header included a Domain attribute for this
551: * cookie
552: */
553: private boolean hasDomainAttribute = false;
554:
555: /** The version of the cookie specification I was created from. */
556: private int cookieVersion = 0;
557:
558: // -------------------------------------------------------------- Constants
559:
560: /**
561: * Collator for Cookie comparisons. Could be replaced with references to
562: * specific Locales.
563: */
564: private static final RuleBasedCollator STRING_COLLATOR = (RuleBasedCollator) RuleBasedCollator
565: .getInstance(new Locale("en", "US", ""));
566:
567: /** Log object for this class */
568: private static final Log LOG = LogFactory.getLog(Cookie.class);
569:
570: // BEGIN IA ADDITION
571: /**
572: * Character which, if appended to end of a domain, will give a
573: * boundary key that sorts past all Cookie sortKeys for the same
574: * domain.
575: */
576: public static final char DOMAIN_OVERBOUNDS = '\2';
577: // END IA ADDITION
578: }
|