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