0001 /*
0002 * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package java.net;
0027
0028 import java.util.List;
0029 import java.util.StringTokenizer;
0030 import java.util.NoSuchElementException;
0031 import java.text.SimpleDateFormat;
0032 import java.util.TimeZone;
0033 import java.util.Date;
0034
0035 import java.lang.NullPointerException; // for javadoc
0036
0037 /**
0038 * An HttpCookie object represents an http cookie, which carries state
0039 * information between server and user agent. Cookie is widely adopted
0040 * to create stateful sessions.
0041 *
0042 * <p>There are 3 http cookie specifications:
0043 * <blockquote>
0044 * Netscape draft<br>
0045 * RFC 2109 - <a href="http://www.ietf.org/rfc/rfc2109.txt">
0046 * <i>http://www.ietf.org/rfc/rfc2109.txt</i></a><br>
0047 * RFC 2965 - <a href="http://www.ietf.org/rfc/rfc2965.txt">
0048 * <i>http://www.ietf.org/rfc/rfc2965.txt</i></a>
0049 * </blockquote>
0050 *
0051 * <p>HttpCookie class can accept all these 3 forms of syntax.
0052 *
0053 * @version 1.11, 07/05/05
0054 * @author Edward Wang
0055 * @since 1.6
0056 */
0057 public final class HttpCookie implements Cloneable {
0058 /* ---------------- Fields -------------- */
0059
0060 //
0061 // The value of the cookie itself.
0062 //
0063 private String name; // NAME= ... "$Name" style is reserved
0064 private String value; // value of NAME
0065
0066 //
0067 // Attributes encoded in the header's cookie fields.
0068 //
0069
0070 private String comment; // Comment=VALUE ... describes cookie's use
0071 private String commentURL; // CommentURL="http URL" ... describes cookie's use
0072 private boolean toDiscard; // Discard ... discard cookie unconditionally
0073 private String domain; // Domain=VALUE ... domain that sees cookie
0074 private long maxAge = MAX_AGE_UNSPECIFIED; // Max-Age=VALUE ... cookies auto-expire
0075 private String path; // Path=VALUE ... URLs that see the cookie
0076 private String portlist; // Port[="portlist"] ... the port cookie may be returned to
0077 private boolean secure; // Secure ... e.g. use SSL
0078 private int version = 1; // Version=1 ... RFC 2965 style
0079
0080 //
0081 // Hold the creation time (in seconds) of the http cookie for later
0082 // expiration calculation
0083 //
0084 private long whenCreated = 0;
0085
0086 //
0087 // Since the positive and zero max-age have their meanings,
0088 // this value serves as a hint as 'not specify max-age'
0089 //
0090 private final static long MAX_AGE_UNSPECIFIED = -1;
0091
0092 //
0093 // date format used by Netscape's cookie draft
0094 //
0095 private final static String NETSCAPE_COOKIE_DATE_FORMAT = "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'";
0096
0097 //
0098 // constant strings represent set-cookie header token
0099 //
0100 private final static String SET_COOKIE = "set-cookie:";
0101 private final static String SET_COOKIE2 = "set-cookie2:";
0102
0103 /* ---------------- Ctors -------------- */
0104
0105 /**
0106 * Constructs a cookie with a specified name and value.
0107 *
0108 * <p>The name must conform to RFC 2965. That means it can contain
0109 * only ASCII alphanumeric characters and cannot contain commas,
0110 * semicolons, or white space or begin with a $ character. The cookie's
0111 * name cannot be changed after creation.
0112 *
0113 * <p>The value can be anything the server chooses to send. Its
0114 * value is probably of interest only to the server. The cookie's
0115 * value can be changed after creation with the
0116 * <code>setValue</code> method.
0117 *
0118 * <p>By default, cookies are created according to the RFC 2965
0119 * cookie specification. The version can be changed with the
0120 * <code>setVersion</code> method.
0121 *
0122 *
0123 * @param name a <code>String</code> specifying the name of the cookie
0124 *
0125 * @param value a <code>String</code> specifying the value of the cookie
0126 *
0127 * @throws IllegalArgumentException if the cookie name contains illegal characters
0128 * or it is one of the tokens reserved for use
0129 * by the cookie protocol
0130 * @throws NullPointerException if <tt>name</tt> is <tt>null</tt>
0131 * @see #setValue
0132 * @see #setVersion
0133 *
0134 */
0135
0136 public HttpCookie(String name, String value) {
0137 name = name.trim();
0138 if (name.length() == 0 || !isToken(name) || isReserved(name)) {
0139 throw new IllegalArgumentException("Illegal cookie name");
0140 }
0141
0142 this .name = name;
0143 this .value = value;
0144 toDiscard = false;
0145 secure = false;
0146
0147 whenCreated = System.currentTimeMillis();
0148 }
0149
0150 /**
0151 * Constructs cookies from set-cookie or set-cookie2 header string.
0152 * RFC 2965 section 3.2.2 set-cookie2 syntax indicates that one header line
0153 * may contain more than one cookie definitions, so this is a static
0154 * utility method instead of another constructor.
0155 *
0156 * @param header a <tt>String</tt> specifying the set-cookie header.
0157 * The header should start with "set-cookie", or "set-cookie2"
0158 * token; or it should have no leading token at all.
0159 * @return a List of cookie parsed from header line string
0160 * @throws IllegalArgumentException if header string violates the cookie
0161 * specification's syntax, or the cookie
0162 * name contains llegal characters, or
0163 * the cookie name is one of the tokens
0164 * reserved for use by the cookie protocol
0165 * @throws NullPointerException if the header string is <tt>null</tt>
0166 */
0167 public static List<HttpCookie> parse(String header) {
0168 int version = guessCookieVersion(header);
0169
0170 // if header start with set-cookie or set-cookie2, strip it off
0171 if (startsWithIgnoreCase(header, SET_COOKIE2)) {
0172 header = header.substring(SET_COOKIE2.length());
0173 } else if (startsWithIgnoreCase(header, SET_COOKIE)) {
0174 header = header.substring(SET_COOKIE.length());
0175 }
0176
0177 List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>();
0178 // The Netscape cookie may have a comma in its expires attribute,
0179 // while the comma is the delimiter in rfc 2965/2109 cookie header string.
0180 // so the parse logic is slightly different
0181 if (version == 0) {
0182 // Netscape draft cookie
0183 HttpCookie cookie = parseInternal(header);
0184 cookie.setVersion(0);
0185 cookies.add(cookie);
0186 } else {
0187 // rfc2965/2109 cookie
0188 // if header string contains more than one cookie,
0189 // it'll separate them with comma
0190 List<String> cookieStrings = splitMultiCookies(header);
0191 for (String cookieStr : cookieStrings) {
0192 HttpCookie cookie = parseInternal(cookieStr);
0193 cookie.setVersion(1);
0194 cookies.add(cookie);
0195 }
0196 }
0197
0198 return cookies;
0199 }
0200
0201 /* ---------------- Public operations -------------- */
0202
0203 /**
0204 * Reports whether this http cookie has expired or not.
0205 *
0206 * @return <tt>true</tt> to indicate this http cookie has expired;
0207 * otherwise, <tt>false</tt>
0208 */
0209 public boolean hasExpired() {
0210 if (maxAge == 0)
0211 return true;
0212
0213 // if not specify max-age, this cookie should be
0214 // discarded when user agent is to be closed, but
0215 // it is not expired.
0216 if (maxAge == MAX_AGE_UNSPECIFIED)
0217 return false;
0218
0219 long deltaSecond = (System.currentTimeMillis() - whenCreated) / 1000;
0220 if (deltaSecond > maxAge)
0221 return true;
0222 else
0223 return false;
0224 }
0225
0226 /**
0227 *
0228 * Specifies a comment that describes a cookie's purpose.
0229 * The comment is useful if the browser presents the cookie
0230 * to the user. Comments
0231 * are not supported by Netscape Version 0 cookies.
0232 *
0233 * @param purpose a <code>String</code> specifying the comment
0234 * to display to the user
0235 *
0236 * @see #getComment
0237 *
0238 */
0239
0240 public void setComment(String purpose) {
0241 comment = purpose;
0242 }
0243
0244 /**
0245 * Returns the comment describing the purpose of this cookie, or
0246 * <code>null</code> if the cookie has no comment.
0247 *
0248 * @return a <code>String</code> containing the comment,
0249 * or <code>null</code> if none
0250 *
0251 * @see #setComment
0252 *
0253 */
0254
0255 public String getComment() {
0256 return comment;
0257 }
0258
0259 /**
0260 *
0261 * Specifies a comment url that describes a cookie's purpose.
0262 * The comment url is useful if the browser presents the cookie
0263 * to the user. Comment url is RFC 2965 only.
0264 *
0265 * @param purpose a <code>String</code> specifying the comment url
0266 * to display to the user
0267 *
0268 * @see #getCommentURL
0269 *
0270 */
0271
0272 public void setCommentURL(String purpose) {
0273 commentURL = purpose;
0274 }
0275
0276 /**
0277 * Returns the comment url describing the purpose of this cookie, or
0278 * <code>null</code> if the cookie has no comment url.
0279 *
0280 * @return a <code>String</code> containing the comment url,
0281 * or <code>null</code> if none
0282 *
0283 * @see #setCommentURL
0284 *
0285 */
0286
0287 public String getCommentURL() {
0288 return commentURL;
0289 }
0290
0291 /**
0292 * Specify whether user agent should discard the cookie unconditionally.
0293 * This is RFC 2965 only attribute.
0294 *
0295 * @param discard <tt>true</tt> indicates to discard cookie unconditionally
0296 *
0297 * @see #getDiscard
0298 */
0299
0300 public void setDiscard(boolean discard) {
0301 toDiscard = discard;
0302 }
0303
0304 /**
0305 * Return the discard attribute of the cookie
0306 *
0307 * @return a <tt>boolean</tt> to represent this cookie's discard attribute
0308 *
0309 * @see #setDiscard
0310 */
0311
0312 public boolean getDiscard() {
0313 return toDiscard;
0314 }
0315
0316 /**
0317 * Specify the portlist of the cookie, which restricts the port(s)
0318 * to which a cookie may be sent back in a Cookie header.
0319 *
0320 * @param ports a <tt>String</tt> specify the port list, which is
0321 * comma seperated series of digits
0322 * @see #getPortlist
0323 */
0324
0325 public void setPortlist(String ports) {
0326 portlist = ports;
0327 }
0328
0329 /**
0330 * Return the port list attribute of the cookie
0331 *
0332 * @return a <tt>String</tt> contains the port list
0333 * or <tt>null</tt> if none
0334 * @see #setPortlist
0335 */
0336
0337 public String getPortlist() {
0338 return portlist;
0339 }
0340
0341 /**
0342 *
0343 * Specifies the domain within which this cookie should be presented.
0344 *
0345 * <p>The form of the domain name is specified by RFC 2965. A domain
0346 * name begins with a dot (<code>.foo.com</code>) and means that
0347 * the cookie is visible to servers in a specified Domain Name System
0348 * (DNS) zone (for example, <code>www.foo.com</code>, but not
0349 * <code>a.b.foo.com</code>). By default, cookies are only returned
0350 * to the server that sent them.
0351 *
0352 *
0353 * @param pattern a <code>String</code> containing the domain name
0354 * within which this cookie is visible;
0355 * form is according to RFC 2965
0356 *
0357 * @see #getDomain
0358 *
0359 */
0360
0361 public void setDomain(String pattern) {
0362 if (pattern != null)
0363 domain = pattern.toLowerCase();
0364 else
0365 domain = pattern;
0366 }
0367
0368 /**
0369 * Returns the domain name set for this cookie. The form of
0370 * the domain name is set by RFC 2965.
0371 *
0372 * @return a <code>String</code> containing the domain name
0373 *
0374 * @see #setDomain
0375 *
0376 */
0377
0378 public String getDomain() {
0379 return domain;
0380 }
0381
0382 /**
0383 * Sets the maximum age of the cookie in seconds.
0384 *
0385 * <p>A positive value indicates that the cookie will expire
0386 * after that many seconds have passed. Note that the value is
0387 * the <i>maximum</i> age when the cookie will expire, not the cookie's
0388 * current age.
0389 *
0390 * <p>A negative value means
0391 * that the cookie is not stored persistently and will be deleted
0392 * when the Web browser exits. A zero value causes the cookie
0393 * to be deleted.
0394 *
0395 * @param expiry an integer specifying the maximum age of the
0396 * cookie in seconds; if zero, the cookie
0397 * should be discarded immediately;
0398 * otherwise, the cookie's max age is unspecified.
0399 *
0400 * @see #getMaxAge
0401 *
0402 */
0403 public void setMaxAge(long expiry) {
0404 maxAge = expiry;
0405 }
0406
0407 /**
0408 * Returns the maximum age of the cookie, specified in seconds.
0409 * By default, <code>-1</code> indicating the cookie will persist
0410 * until browser shutdown.
0411 *
0412 *
0413 * @return an integer specifying the maximum age of the
0414 * cookie in seconds
0415 *
0416 *
0417 * @see #setMaxAge
0418 *
0419 */
0420
0421 public long getMaxAge() {
0422 return maxAge;
0423 }
0424
0425 /**
0426 * Specifies a path for the cookie
0427 * to which the client should return the cookie.
0428 *
0429 * <p>The cookie is visible to all the pages in the directory
0430 * you specify, and all the pages in that directory's subdirectories.
0431 * A cookie's path must include the servlet that set the cookie,
0432 * for example, <i>/catalog</i>, which makes the cookie
0433 * visible to all directories on the server under <i>/catalog</i>.
0434 *
0435 * <p>Consult RFC 2965 (available on the Internet) for more
0436 * information on setting path names for cookies.
0437 *
0438 *
0439 * @param uri a <code>String</code> specifying a path
0440 *
0441 *
0442 * @see #getPath
0443 *
0444 */
0445
0446 public void setPath(String uri) {
0447 path = uri;
0448 }
0449
0450 /**
0451 * Returns the path on the server
0452 * to which the browser returns this cookie. The
0453 * cookie is visible to all subpaths on the server.
0454 *
0455 *
0456 * @return a <code>String</code> specifying a path that contains
0457 * a servlet name, for example, <i>/catalog</i>
0458 *
0459 * @see #setPath
0460 *
0461 */
0462
0463 public String getPath() {
0464 return path;
0465 }
0466
0467 /**
0468 * Indicates to the browser whether the cookie should only be sent
0469 * using a secure protocol, such as HTTPS or SSL.
0470 *
0471 * <p>The default value is <code>false</code>.
0472 *
0473 * @param flag if <code>true</code>, sends the cookie from the browser
0474 * to the server using only when using a secure protocol;
0475 * if <code>false</code>, sent on any protocol
0476 *
0477 * @see #getSecure
0478 *
0479 */
0480
0481 public void setSecure(boolean flag) {
0482 secure = flag;
0483 }
0484
0485 /**
0486 * Returns <code>true</code> if the browser is sending cookies
0487 * only over a secure protocol, or <code>false</code> if the
0488 * browser can send cookies using any protocol.
0489 *
0490 * @return <code>true</code> if the browser can use
0491 * any standard protocol; otherwise, <code>false</code>
0492 *
0493 * @see #setSecure
0494 *
0495 */
0496
0497 public boolean getSecure() {
0498 return secure;
0499 }
0500
0501 /**
0502 * Returns the name of the cookie. The name cannot be changed after
0503 * creation.
0504 *
0505 * @return a <code>String</code> specifying the cookie's name
0506 *
0507 */
0508
0509 public String getName() {
0510 return name;
0511 }
0512
0513 /**
0514 *
0515 * Assigns a new value to a cookie after the cookie is created.
0516 * If you use a binary value, you may want to use BASE64 encoding.
0517 *
0518 * <p>With Version 0 cookies, values should not contain white
0519 * space, brackets, parentheses, equals signs, commas,
0520 * double quotes, slashes, question marks, at signs, colons,
0521 * and semicolons. Empty values may not behave the same way
0522 * on all browsers.
0523 *
0524 * @param newValue a <code>String</code> specifying the new value
0525 *
0526 *
0527 * @see #getValue
0528 *
0529 */
0530
0531 public void setValue(String newValue) {
0532 value = newValue;
0533 }
0534
0535 /**
0536 * Returns the value of the cookie.
0537 *
0538 * @return a <code>String</code> containing the cookie's
0539 * present value
0540 *
0541 * @see #setValue
0542 *
0543 */
0544
0545 public String getValue() {
0546 return value;
0547 }
0548
0549 /**
0550 * Returns the version of the protocol this cookie complies
0551 * with. Version 1 complies with RFC 2965/2109,
0552 * and version 0 complies with the original
0553 * cookie specification drafted by Netscape. Cookies provided
0554 * by a browser use and identify the browser's cookie version.
0555 *
0556 *
0557 * @return 0 if the cookie complies with the
0558 * original Netscape specification; 1
0559 * if the cookie complies with RFC 2965/2109
0560 *
0561 * @see #setVersion
0562 *
0563 */
0564
0565 public int getVersion() {
0566 return version;
0567 }
0568
0569 /**
0570 * Sets the version of the cookie protocol this cookie complies
0571 * with. Version 0 complies with the original Netscape cookie
0572 * specification. Version 1 complies with RFC 2965/2109.
0573 *
0574 *
0575 * @param v 0 if the cookie should comply with
0576 * the original Netscape specification;
0577 * 1 if the cookie should comply with RFC 2965/2109
0578 *
0579 * @throws IllegalArgumentException if <tt>v</tt> is neither 0 nor 1
0580 *
0581 * @see #getVersion
0582 *
0583 */
0584
0585 public void setVersion(int v) {
0586 if (v != 0 && v != 1) {
0587 throw new IllegalArgumentException(
0588 "cookie version should be 0 or 1");
0589 }
0590
0591 version = v;
0592 }
0593
0594 /**
0595 * The utility method to check whether a host name is in a domain
0596 * or not.
0597 *
0598 * <p>This concept is described in the cookie specification.
0599 * To understand the concept, some terminologies need to be defined first:
0600 * <blockquote>
0601 * effective host name = hostname if host name contains dot<br>
0602 * or = hostname.local if not
0603 * </blockquote>
0604 * <p>Host A's name domain-matches host B's if:
0605 * <blockquote><ul>
0606 * <li>their host name strings string-compare equal; or</li>
0607 * <li>A is a HDN string and has the form NB, where N is a non-empty
0608 * name string, B has the form .B', and B' is a HDN string. (So,
0609 * x.y.com domain-matches .Y.com but not Y.com.)</li>
0610 * </ul></blockquote>
0611 *
0612 * <p>A host isn't in a domain (RFC 2965 sec. 3.3.2) if:
0613 * <blockquote><ul>
0614 * <li>The value for the Domain attribute contains no embedded dots,
0615 * and the value is not .local.</li>
0616 * <li>The effective host name that derives from the request-host does
0617 * not domain-match the Domain attribute.</li>
0618 * <li>The request-host is a HDN (not IP address) and has the form HD,
0619 * where D is the value of the Domain attribute, and H is a string
0620 * that contains one or more dots.</li>
0621 * </ul></blockquote>
0622 *
0623 * <p>Examples:
0624 * <blockquote><ul>
0625 * <li>A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com
0626 * would be rejected, because H is y.x and contains a dot.</li>
0627 * <li>A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com
0628 * would be accepted.</li>
0629 * <li>A Set-Cookie2 with Domain=.com or Domain=.com., will always be
0630 * rejected, because there is no embedded dot.</li>
0631 * <li>A Set-Cookie2 with Domain=ajax.com will be accepted, and the
0632 * value for Domain will be taken to be .ajax.com, because a dot
0633 * gets prepended to the value.</li>
0634 * <li>A Set-Cookie2 from request-host example for Domain=.local will
0635 * be accepted, because the effective host name for the request-
0636 * host is example.local, and example.local domain-matches .local.</li>
0637 * </ul></blockquote>
0638 *
0639 * @param domain the domain name to check host name with
0640 * @param host the host name in question
0641 * @return <tt>true</tt> if they domain-matches; <tt>false</tt> if not
0642 */
0643 public static boolean domainMatches(String domain, String host) {
0644 if (domain == null || host == null)
0645 return false;
0646
0647 // if there's no embedded dot in domain and domain is not .local
0648 boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
0649 int embeddedDotInDomain = domain.indexOf('.');
0650 if (embeddedDotInDomain == 0)
0651 embeddedDotInDomain = domain.indexOf('.', 1);
0652 if (!isLocalDomain
0653 && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain
0654 .length() - 1))
0655 return false;
0656
0657 // if the host name contains no dot and the domain name is .local
0658 int firstDotInHost = host.indexOf('.');
0659 if (firstDotInHost == -1 && isLocalDomain)
0660 return true;
0661
0662 int domainLength = domain.length();
0663 int lengthDiff = host.length() - domainLength;
0664 if (lengthDiff == 0) {
0665 // if the host name and the domain name are just string-compare euqal
0666 return host.equalsIgnoreCase(domain);
0667 } else if (lengthDiff > 0) {
0668 // need to check H & D component
0669 String H = host.substring(0, lengthDiff);
0670 String D = host.substring(lengthDiff);
0671
0672 return (H.indexOf('.') == -1 && D.equalsIgnoreCase(domain));
0673 } else if (lengthDiff == -1) {
0674 // if domain is actually .host
0675 return (domain.charAt(0) == '.' && host
0676 .equalsIgnoreCase(domain.substring(1)));
0677 }
0678
0679 return false;
0680 }
0681
0682 /**
0683 * Constructs a cookie header string representation of this cookie,
0684 * which is in the format defined by corresponding cookie specification,
0685 * but without the leading "Cookie:" token.
0686 *
0687 * @return a string form of the cookie. The string has the defined format
0688 */
0689 public String toString() {
0690 if (getVersion() > 0) {
0691 return toRFC2965HeaderString();
0692 } else {
0693 return toNetscapeHeaderString();
0694 }
0695 }
0696
0697 /**
0698 * Test the equality of two http cookies.
0699 *
0700 * <p> The result is <tt>true</tt> only if two cookies
0701 * come from same domain (case-insensitive),
0702 * have same name (case-insensitive),
0703 * and have same path (case-sensitive).
0704 *
0705 * @return <tt>true</tt> if 2 http cookies equal to each other;
0706 * otherwise, <tt>false</tt>
0707 */
0708 public boolean equals(Object obj) {
0709 if (obj == this )
0710 return true;
0711 if (!(obj instanceof HttpCookie))
0712 return false;
0713 HttpCookie other = (HttpCookie) obj;
0714
0715 // One http cookie equals to another cookie (RFC 2965 sec. 3.3.3) if:
0716 // 1. they come from same domain (case-insensitive),
0717 // 2. have same name (case-insensitive),
0718 // 3. and have same path (case-sensitive).
0719 return equalsIgnoreCase(getName(), other.getName())
0720 && equalsIgnoreCase(getDomain(), other.getDomain())
0721 && equals(getPath(), other.getPath());
0722 }
0723
0724 /**
0725 * Return hash code of this http cookie. The result is the sum of
0726 * hash code value of three significant components of this cookie:
0727 * name, domain, and path.
0728 * That is, the hash code is the value of the expression:
0729 * <blockquote>
0730 * getName().toLowerCase().hashCode()<br>
0731 * + getDomain().toLowerCase().hashCode()<br>
0732 * + getPath().hashCode()
0733 * </blockquote>
0734 *
0735 * @return this http cookie's hash code
0736 */
0737 public int hashCode() {
0738 int h1 = name.toLowerCase().hashCode();
0739 int h2 = (domain != null) ? domain.toLowerCase().hashCode() : 0;
0740 int h3 = (path != null) ? path.hashCode() : 0;
0741
0742 return h1 + h2 + h3;
0743 }
0744
0745 /**
0746 * Create and return a copy of this object.
0747 *
0748 * @return a clone of this http cookie
0749 */
0750 public Object clone() {
0751 try {
0752 return super .clone();
0753 } catch (CloneNotSupportedException e) {
0754 throw new RuntimeException(e.getMessage());
0755 }
0756 }
0757
0758 /* ---------------- Private operations -------------- */
0759
0760 // Note -- disabled for now to allow full Netscape compatibility
0761 // from RFC 2068, token special case characters
0762 //
0763 // private static final String tspecials = "()<>@,;:\\\"/[]?={} \t";
0764 private static final String tspecials = ",;";
0765
0766 /*
0767 * Tests a string and returns true if the string counts as a
0768 * token.
0769 *
0770 * @param value the <code>String</code> to be tested
0771 *
0772 * @return <code>true</code> if the <code>String</code> is
0773 * a token; <code>false</code> if it is not
0774 */
0775
0776 private static boolean isToken(String value) {
0777 int len = value.length();
0778
0779 for (int i = 0; i < len; i++) {
0780 char c = value.charAt(i);
0781
0782 if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1)
0783 return false;
0784 }
0785 return true;
0786 }
0787
0788 /*
0789 * @param name the name to be tested
0790 * @return <tt>true</tt> if the name is reserved by cookie
0791 * specification, <tt>false</tt> if it is not
0792 */
0793 private static boolean isReserved(String name) {
0794 if (name.equalsIgnoreCase("Comment")
0795 || name.equalsIgnoreCase("CommentURL") // rfc2965 only
0796 || name.equalsIgnoreCase("Discard") // rfc2965 only
0797 || name.equalsIgnoreCase("Domain")
0798 || name.equalsIgnoreCase("Expires") // netscape draft only
0799 || name.equalsIgnoreCase("Max-Age")
0800 || name.equalsIgnoreCase("Path")
0801 || name.equalsIgnoreCase("Port") // rfc2965 only
0802 || name.equalsIgnoreCase("Secure")
0803 || name.equalsIgnoreCase("Version")
0804 || name.charAt(0) == '$') {
0805 return true;
0806 }
0807
0808 return false;
0809 }
0810
0811 /*
0812 * Parse header string to cookie object.
0813 *
0814 * @param header header string; should contain only one NAME=VALUE pair
0815 *
0816 * @return an HttpCookie being extracted
0817 *
0818 * @throws IllegalArgumentException if header string violates the cookie
0819 * specification
0820 */
0821 private static HttpCookie parseInternal(String header) {
0822 HttpCookie cookie = null;
0823 String namevaluePair = null;
0824
0825 StringTokenizer tokenizer = new StringTokenizer(header, ";");
0826
0827 // there should always have at least on name-value pair;
0828 // it's cookie's name
0829 try {
0830 namevaluePair = tokenizer.nextToken();
0831 int index = namevaluePair.indexOf('=');
0832 if (index != -1) {
0833 String name = namevaluePair.substring(0, index).trim();
0834 String value = namevaluePair.substring(index + 1)
0835 .trim();
0836 cookie = new HttpCookie(name,
0837 stripOffSurroundingQuote(value));
0838 } else {
0839 // no "=" in name-value pair; it's an error
0840 throw new IllegalArgumentException(
0841 "Invalid cookie name-value pair");
0842 }
0843 } catch (NoSuchElementException ignored) {
0844 throw new IllegalArgumentException(
0845 "Empty cookie header string");
0846 }
0847
0848 // remaining name-value pairs are cookie's attributes
0849 while (tokenizer.hasMoreTokens()) {
0850 namevaluePair = tokenizer.nextToken();
0851 int index = namevaluePair.indexOf('=');
0852 String name, value;
0853 if (index != -1) {
0854 name = namevaluePair.substring(0, index).trim();
0855 value = namevaluePair.substring(index + 1).trim();
0856 } else {
0857 name = namevaluePair.trim();
0858 value = null;
0859 }
0860
0861 // assign attribute to cookie
0862 assignAttribute(cookie, name, value);
0863 }
0864
0865 return cookie;
0866 }
0867
0868 /*
0869 * assign cookie attribute value to attribute name;
0870 * use a map to simulate method dispatch
0871 */
0872 static interface CookieAttributeAssignor {
0873 public void assign(HttpCookie cookie, String attrName,
0874 String attrValue);
0875 }
0876
0877 static java.util.Map<String, CookieAttributeAssignor> assignors = null;
0878 static {
0879 assignors = new java.util.HashMap<String, CookieAttributeAssignor>();
0880 assignors.put("comment", new CookieAttributeAssignor() {
0881 public void assign(HttpCookie cookie, String attrName,
0882 String attrValue) {
0883 if (cookie.getComment() == null)
0884 cookie.setComment(attrValue);
0885 }
0886 });
0887 assignors.put("commenturl", new CookieAttributeAssignor() {
0888 public void assign(HttpCookie cookie, String attrName,
0889 String attrValue) {
0890 if (cookie.getCommentURL() == null)
0891 cookie.setCommentURL(attrValue);
0892 }
0893 });
0894 assignors.put("discard", new CookieAttributeAssignor() {
0895 public void assign(HttpCookie cookie, String attrName,
0896 String attrValue) {
0897 cookie.setDiscard(true);
0898 }
0899 });
0900 assignors.put("domain", new CookieAttributeAssignor() {
0901 public void assign(HttpCookie cookie, String attrName,
0902 String attrValue) {
0903 if (cookie.getDomain() == null)
0904 cookie.setDomain(attrValue);
0905 }
0906 });
0907 assignors.put("max-age", new CookieAttributeAssignor() {
0908 public void assign(HttpCookie cookie, String attrName,
0909 String attrValue) {
0910 try {
0911 long maxage = Long.parseLong(attrValue);
0912 if (cookie.getMaxAge() == MAX_AGE_UNSPECIFIED)
0913 cookie.setMaxAge(maxage);
0914 } catch (NumberFormatException ignored) {
0915 throw new IllegalArgumentException(
0916 "Illegal cookie max-age attribute");
0917 }
0918 }
0919 });
0920 assignors.put("path", new CookieAttributeAssignor() {
0921 public void assign(HttpCookie cookie, String attrName,
0922 String attrValue) {
0923 if (cookie.getPath() == null)
0924 cookie.setPath(attrValue);
0925 }
0926 });
0927 assignors.put("port", new CookieAttributeAssignor() {
0928 public void assign(HttpCookie cookie, String attrName,
0929 String attrValue) {
0930 if (cookie.getPortlist() == null)
0931 cookie.setPortlist(attrValue);
0932 }
0933 });
0934 assignors.put("secure", new CookieAttributeAssignor() {
0935 public void assign(HttpCookie cookie, String attrName,
0936 String attrValue) {
0937 cookie.setSecure(true);
0938 }
0939 });
0940 assignors.put("version", new CookieAttributeAssignor() {
0941 public void assign(HttpCookie cookie, String attrName,
0942 String attrValue) {
0943 try {
0944 int version = Integer.parseInt(attrValue);
0945 cookie.setVersion(version);
0946 } catch (NumberFormatException ignored) {
0947 throw new IllegalArgumentException(
0948 "Illegal cookie version attribute");
0949 }
0950 }
0951 });
0952 assignors.put("expires", new CookieAttributeAssignor() { // Netscape only
0953 public void assign(HttpCookie cookie,
0954 String attrName, String attrValue) {
0955 if (cookie.getMaxAge() == MAX_AGE_UNSPECIFIED) {
0956 cookie
0957 .setMaxAge(cookie
0958 .expiryDate2DeltaSeconds(attrValue));
0959 }
0960 }
0961 });
0962 }
0963
0964 private static void assignAttribute(HttpCookie cookie,
0965 String attrName, String attrValue) {
0966 // strip off the surrounding "-sign if there's any
0967 attrValue = stripOffSurroundingQuote(attrValue);
0968
0969 CookieAttributeAssignor assignor = assignors.get(attrName
0970 .toLowerCase());
0971 if (assignor != null) {
0972 assignor.assign(cookie, attrName, attrValue);
0973 } else {
0974 // must be an error
0975 throw new IllegalArgumentException(
0976 "Illegal cookie attribute");
0977 }
0978 }
0979
0980 /*
0981 * Constructs a string representation of this cookie. The string format is
0982 * as Netscape spec, but without leading "Cookie:" token.
0983 */
0984 private String toNetscapeHeaderString() {
0985 StringBuilder sb = new StringBuilder();
0986
0987 sb.append(getName() + "=" + getValue());
0988
0989 return sb.toString();
0990 }
0991
0992 /*
0993 * Constructs a string representation of this cookie. The string format is
0994 * as RFC 2965/2109, but without leading "Cookie:" token.
0995 */
0996 private String toRFC2965HeaderString() {
0997 StringBuilder sb = new StringBuilder();
0998
0999 sb.append(getName()).append("=\"").append(getValue()).append(
1000 '"');
1001 if (getPath() != null)
1002 sb.append(";$Path=\"").append(getPath()).append('"');
1003 if (getDomain() != null)
1004 sb.append(";$Domain=\"").append(getDomain()).append('"');
1005 if (getPortlist() != null)
1006 sb.append(";$Port=\"").append(getPortlist()).append('"');
1007
1008 return sb.toString();
1009 }
1010
1011 /*
1012 * @param dateString a date string in format of
1013 * "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",
1014 * which defined in Netscape cookie spec
1015 *
1016 * @return delta seconds between this cookie's creation
1017 * time and the time specified by dateString
1018 */
1019 private long expiryDate2DeltaSeconds(String dateString) {
1020 SimpleDateFormat df = new SimpleDateFormat(
1021 NETSCAPE_COOKIE_DATE_FORMAT);
1022 df.setTimeZone(TimeZone.getTimeZone("GMT"));
1023
1024 try {
1025 Date date = df.parse(dateString);
1026 return (date.getTime() - whenCreated) / 1000;
1027 } catch (Exception e) {
1028 return 0;
1029 }
1030 }
1031
1032 /*
1033 * try to guess the cookie version through set-cookie header string
1034 */
1035 private static int guessCookieVersion(String header) {
1036 int version = 0;
1037
1038 header = header.toLowerCase();
1039 if (header.indexOf("expires=") != -1) {
1040 // only netscape cookie using 'expires'
1041 version = 0;
1042 } else if (header.indexOf("version=") != -1) {
1043 // version is mandatory for rfc 2965/2109 cookie
1044 version = 1;
1045 } else if (header.indexOf("max-age") != -1) {
1046 // rfc 2965/2109 use 'max-age'
1047 version = 1;
1048 } else if (startsWithIgnoreCase(header, SET_COOKIE2)) {
1049 // only rfc 2965 cookie starts with 'set-cookie2'
1050 version = 1;
1051 }
1052
1053 return version;
1054 }
1055
1056 private static String stripOffSurroundingQuote(String str) {
1057 if (str != null && str.length() > 0 && str.charAt(0) == '"'
1058 && str.charAt(str.length() - 1) == '"') {
1059 return str.substring(1, str.length() - 1);
1060 } else {
1061 return str;
1062 }
1063 }
1064
1065 private static boolean equalsIgnoreCase(String s, String t) {
1066 if (s == t)
1067 return true;
1068 if ((s != null) && (t != null)) {
1069 return s.equalsIgnoreCase(t);
1070 }
1071 return false;
1072 }
1073
1074 private static boolean equals(String s, String t) {
1075 if (s == t)
1076 return true;
1077 if ((s != null) && (t != null)) {
1078 return s.equals(t);
1079 }
1080 return false;
1081 }
1082
1083 private static boolean startsWithIgnoreCase(String s, String start) {
1084 if (s == null || start == null)
1085 return false;
1086
1087 if (s.length() >= start.length()
1088 && start.equalsIgnoreCase(s
1089 .substring(0, start.length()))) {
1090 return true;
1091 }
1092
1093 return false;
1094 }
1095
1096 /*
1097 * Split cookie header string according to rfc 2965:
1098 * 1) split where it is a comma;
1099 * 2) but not the comma surrounding by double-quotes, which is the comma
1100 * inside port list or embeded URIs.
1101 *
1102 * @param header the cookie header string to split
1103 *
1104 * @return list of strings; never null
1105 *
1106 */
1107 private static List<String> splitMultiCookies(String header) {
1108 List<String> cookies = new java.util.ArrayList<String>();
1109 int quoteCount = 0;
1110 int p, q;
1111
1112 for (p = 0, q = 0; p < header.length(); p++) {
1113 char c = header.charAt(p);
1114 if (c == '"')
1115 quoteCount++;
1116 if (c == ',' && (quoteCount % 2 == 0)) { // it is comma and not surrounding by double-quotes
1117 cookies.add(header.substring(q, p));
1118 q = p + 1;
1119 }
1120 }
1121
1122 cookies.add(header.substring(q));
1123
1124 return cookies;
1125 }
1126 }
|