History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/java/net/HttpCookie.java (1 of 18352 files). Show all

Unified · Split

java/pkg/java/net/HttpCookie.java+1206−0
@@ -0,0 +1,1206 @@
1/*
2 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.net;
27
28import java.util.List;
29import java.util.StringTokenizer;
30import java.util.NoSuchElementException;
31import java.text.SimpleDateFormat;
32import java.util.TimeZone;
33import java.util.Date;
34
35import java.lang.NullPointerException; // for javadoc
36import java.util.Locale;
37import java.util.Objects;
38
39/**
40 * An HttpCookie object represents an http cookie, which carries state
41 * information between server and user agent. Cookie is widely adopted
42 * to create stateful sessions.
43 *
44 * <p>There are 3 http cookie specifications:
45 * <blockquote>
46 * Netscape draft<br>
47 * RFC 2109 - <a href="http://www.ietf.org/rfc/rfc2109.txt">
48 * <i>http://www.ietf.org/rfc/rfc2109.txt</i></a><br>
49 * RFC 2965 - <a href="http://www.ietf.org/rfc/rfc2965.txt">
50 * <i>http://www.ietf.org/rfc/rfc2965.txt</i></a>
51 * </blockquote>
52 *
53 * <p>HttpCookie class can accept all these 3 forms of syntax.
54 *
55 * @author Edward Wang
56 * @since 1.6
57 */
58public final class HttpCookie implements Cloneable {
59 /* ---------------- Fields -------------- */
60
61 //
62 // The value of the cookie itself.
63 //
64
65 private String name; // NAME= ... "$Name" style is reserved
66 private String value; // value of NAME
67
68 //
69 // Attributes encoded in the header's cookie fields.
70 //
71
72 private String comment; // Comment=VALUE ... describes cookie's use
73 private String commentURL; // CommentURL="http URL" ... describes cookie's use
74 private boolean toDiscard; // Discard ... discard cookie unconditionally
75 private String domain; // Domain=VALUE ... domain that sees cookie
76 private long maxAge = MAX_AGE_UNSPECIFIED; // Max-Age=VALUE ... cookies auto-expire
77 private String path; // Path=VALUE ... URLs that see the cookie
78 private String portlist; // Port[="portlist"] ... the port cookie may be returned to
79 private boolean secure; // Secure ... e.g. use SSL
80 private boolean httpOnly; // HttpOnly ... i.e. not accessible to scripts
81 private int version = 1; // Version=1 ... RFC 2965 style
82
83 //
84 // Hold the creation time (in seconds) of the http cookie for later
85 // expiration calculation
86 //
87 private long whenCreated = 0;
88
89
90 //
91 // Since the positive and zero max-age have their meanings,
92 // this value serves as a hint as 'not specify max-age'
93 //
94 private final static long MAX_AGE_UNSPECIFIED = -1;
95
96
97 //
98 // date formats used by Netscape's cookie draft
99 // as well as formats seen on various sites
100 //
101 private final static String[] COOKIE_DATE_FORMATS = {
102 "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",
103 "EEE',' dd MMM yyyy HH:mm:ss 'GMT'",
104 "EEE MMM dd yyyy HH:mm:ss 'GMT'Z"
105 };
106
107 //
108 // constant strings represent set-cookie header token
109 //
110 private final static String SET_COOKIE = "set-cookie:";
111 private final static String SET_COOKIE2 = "set-cookie2:";
112
113
114 /* ---------------- Ctors -------------- */
115
116 /**
117 * Constructs a cookie with a specified name and value.
118 *
119 * <p>The name must conform to RFC 2965. That means it can contain
120 * only ASCII alphanumeric characters and cannot contain commas,
121 * semicolons, or white space or begin with a $ character. The cookie's
122 * name cannot be changed after creation.
123 *
124 * <p>The value can be anything the server chooses to send. Its
125 * value is probably of interest only to the server. The cookie's
126 * value can be changed after creation with the
127 * <code>setValue</code> method.
128 *
129 * <p>By default, cookies are created according to the RFC 2965
130 * cookie specification. The version can be changed with the
131 * <code>setVersion</code> method.
132 *
133 *
134 * @param name a <code>String</code> specifying the name of the cookie
135 *
136 * @param value a <code>String</code> specifying the value of the cookie
137 *
138 * @throws IllegalArgumentException if the cookie name contains illegal characters
139 * or it is one of the tokens reserved for use
140 * by the cookie protocol
141 * @throws NullPointerException if <tt>name</tt> is <tt>null</tt>
142 * @see #setValue
143 * @see #setVersion
144 *
145 */
146
147 public HttpCookie(String name, String value) {
148 name = name.trim();
149 if (name.length() == 0 || !isToken(name) || isReserved(name)) {
150 throw new IllegalArgumentException("Illegal cookie name");
151 }
152
153 this.name = name;
154 this.value = value;
155 toDiscard = false;
156 secure = false;
157
158 whenCreated = System.currentTimeMillis();
159 portlist = null;
160 }
161
162
163 /**
164 * Constructs cookies from set-cookie or set-cookie2 header string.
165 * RFC 2965 section 3.2.2 set-cookie2 syntax indicates that one header line
166 * may contain more than one cookie definitions, so this is a static
167 * utility method instead of another constructor.
168 *
169 * @param header a <tt>String</tt> specifying the set-cookie header.
170 * The header should start with "set-cookie", or "set-cookie2"
171 * token; or it should have no leading token at all.
172 * @return a List of cookie parsed from header line string
173 * @throws IllegalArgumentException if header string violates the cookie
174 * specification's syntax, or the cookie
175 * name contains llegal characters, or
176 * the cookie name is one of the tokens
177 * reserved for use by the cookie protocol
178 * @throws NullPointerException if the header string is <tt>null</tt>
179 */
180 public static List<HttpCookie> parse(String header) {
181 int version = guessCookieVersion(header);
182
183 // if header start with set-cookie or set-cookie2, strip it off
184 if (startsWithIgnoreCase(header, SET_COOKIE2)) {
185 header = header.substring(SET_COOKIE2.length());
186 } else if (startsWithIgnoreCase(header, SET_COOKIE)) {
187 header = header.substring(SET_COOKIE.length());
188 }
189
190
191 List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>();
192 // The Netscape cookie may have a comma in its expires attribute,
193 // while the comma is the delimiter in rfc 2965/2109 cookie header string.
194 // so the parse logic is slightly different
195 if (version == 0) {
196 // Netscape draft cookie
197 HttpCookie cookie = parseInternal(header);
198 cookie.setVersion(0);
199 cookies.add(cookie);
200 } else {
201 // rfc2965/2109 cookie
202 // if header string contains more than one cookie,
203 // it'll separate them with comma
204 List<String> cookieStrings = splitMultiCookies(header);
205 for (String cookieStr : cookieStrings) {
206 HttpCookie cookie = parseInternal(cookieStr);
207 cookie.setVersion(1);
208 cookies.add(cookie);
209 }
210 }
211
212 return cookies;
213 }
214
215
216
217
218 /* ---------------- Public operations -------------- */
219
220
221 /**
222 * Reports whether this http cookie has expired or not.
223 *
224 * @return <tt>true</tt> to indicate this http cookie has expired;
225 * otherwise, <tt>false</tt>
226 */
227 public boolean hasExpired() {
228 if (maxAge == 0) return true;
229
230 // if not specify max-age, this cookie should be
231 // discarded when user agent is to be closed, but
232 // it is not expired.
233 if (maxAge == MAX_AGE_UNSPECIFIED) return false;
234
235 long deltaSecond = (System.currentTimeMillis() - whenCreated) / 1000;
236 if (deltaSecond > maxAge)
237 return true;
238 else
239 return false;
240 }
241
242 /**
243 *
244 * Specifies a comment that describes a cookie's purpose.
245 * The comment is useful if the browser presents the cookie
246 * to the user. Comments
247 * are not supported by Netscape Version 0 cookies.
248 *
249 * @param purpose a <code>String</code> specifying the comment
250 * to display to the user
251 *
252 * @see #getComment
253 *
254 */
255
256 public void setComment(String purpose) {
257 comment = purpose;
258 }
259
260
261
262
263 /**
264 * Returns the comment describing the purpose of this cookie, or
265 * <code>null</code> if the cookie has no comment.
266 *
267 * @return a <code>String</code> containing the comment,
268 * or <code>null</code> if none
269 *
270 * @see #setComment
271 *
272 */
273
274 public String getComment() {
275 return comment;
276 }
277
278
279 /**
280 *
281 * Specifies a comment url that describes a cookie's purpose.
282 * The comment url is useful if the browser presents the cookie
283 * to the user. Comment url is RFC 2965 only.
284 *
285 * @param purpose a <code>String</code> specifying the comment url
286 * to display to the user
287 *
288 * @see #getCommentURL
289 *
290 */
291
292 public void setCommentURL(String purpose) {
293 commentURL = purpose;
294 }
295
296
297
298
299 /**
300 * Returns the comment url describing the purpose of this cookie, or
301 * <code>null</code> if the cookie has no comment url.
302 *
303 * @return a <code>String</code> containing the comment url,
304 * or <code>null</code> if none
305 *
306 * @see #setCommentURL
307 *
308 */
309
310 public String getCommentURL() {
311 return commentURL;
312 }
313
314
315 /**
316 * Specify whether user agent should discard the cookie unconditionally.
317 * This is RFC 2965 only attribute.
318 *
319 * @param discard <tt>true</tt> indicates to discard cookie unconditionally
320 *
321 * @see #getDiscard
322 */
323
324 public void setDiscard(boolean discard) {
325 toDiscard = discard;
326 }
327
328
329
330
331 /**
332 * Return the discard attribute of the cookie
333 *
334 * @return a <tt>boolean</tt> to represent this cookie's discard attribute
335 *
336 * @see #setDiscard
337 */
338
339 public boolean getDiscard() {
340 return toDiscard;
341 }
342
343
344 /**
345 * Specify the portlist of the cookie, which restricts the port(s)
346 * to which a cookie may be sent back in a Cookie header.
347 *
348 * @param ports a <tt>String</tt> specify the port list, which is
349 * comma seperated series of digits
350 * @see #getPortlist
351 */
352
353 public void setPortlist(String ports) {
354 portlist = ports;
355 }
356
357
358
359
360 /**
361 * Return the port list attribute of the cookie
362 *
363 * @return a <tt>String</tt> contains the port list
364 * or <tt>null</tt> if none
365 * @see #setPortlist
366 */
367
368 public String getPortlist() {
369 return portlist;
370 }
371
372 /**
373 *
374 * Specifies the domain within which this cookie should be presented.
375 *
376 * <p>The form of the domain name is specified by RFC 2965. A domain
377 * name begins with a dot (<code>.foo.com</code>) and means that
378 * the cookie is visible to servers in a specified Domain Name System
379 * (DNS) zone (for example, <code>www.foo.com</code>, but not
380 * <code>a.b.foo.com</code>). By default, cookies are only returned
381 * to the server that sent them.
382 *
383 *
384 * @param pattern a <code>String</code> containing the domain name
385 * within which this cookie is visible;
386 * form is according to RFC 2965
387 *
388 * @see #getDomain
389 *
390 */
391
392 public void setDomain(String pattern) {
393 if (pattern != null)
394 domain = pattern.toLowerCase();
395 else
396 domain = pattern;
397 }
398
399
400
401
402
403 /**
404 * Returns the domain name set for this cookie. The form of
405 * the domain name is set by RFC 2965.
406 *
407 * @return a <code>String</code> containing the domain name
408 *
409 * @see #setDomain
410 *
411 */
412
413 public String getDomain() {
414 return domain;
415 }
416
417
418 /**
419 * Sets the maximum age of the cookie in seconds.
420 *
421 * <p>A positive value indicates that the cookie will expire
422 * after that many seconds have passed. Note that the value is
423 * the <i>maximum</i> age when the cookie will expire, not the cookie's
424 * current age.
425 *
426 * <p>A negative value means
427 * that the cookie is not stored persistently and will be deleted
428 * when the Web browser exits. A zero value causes the cookie
429 * to be deleted.
430 *
431 * @param expiry an integer specifying the maximum age of the
432 * cookie in seconds; if zero, the cookie
433 * should be discarded immediately;
434 * otherwise, the cookie's max age is unspecified.
435 *
436 * @see #getMaxAge
437 *
438 */
439 public void setMaxAge(long expiry) {
440 maxAge = expiry;
441 }
442
443
444
445
446 /**
447 * Returns the maximum age of the cookie, specified in seconds.
448 * By default, <code>-1</code> indicating the cookie will persist
449 * until browser shutdown.
450 *
451 *
452 * @return an integer specifying the maximum age of the
453 * cookie in seconds
454 *
455 *
456 * @see #setMaxAge
457 *
458 */
459
460 public long getMaxAge() {
461 return maxAge;
462 }
463
464
465
466
467 /**
468 * Specifies a path for the cookie
469 * to which the client should return the cookie.
470 *
471 * <p>The cookie is visible to all the pages in the directory
472 * you specify, and all the pages in that directory's subdirectories.
473 * A cookie's path must include the servlet that set the cookie,
474 * for example, <i>/catalog</i>, which makes the cookie
475 * visible to all directories on the server under <i>/catalog</i>.
476 *
477 * <p>Consult RFC 2965 (available on the Internet) for more
478 * information on setting path names for cookies.
479 *
480 *
481 * @param uri a <code>String</code> specifying a path
482 *
483 *
484 * @see #getPath
485 *
486 */
487
488 public void setPath(String uri) {
489 path = uri;
490 }
491
492
493
494
495 /**
496 * Returns the path on the server
497 * to which the browser returns this cookie. The
498 * cookie is visible to all subpaths on the server.
499 *
500 *
501 * @return a <code>String</code> specifying a path that contains
502 * a servlet name, for example, <i>/catalog</i>
503 *
504 * @see #setPath
505 *
506 */
507
508 public String getPath() {
509 return path;
510 }
511
512
513
514
515
516 /**
517 * Indicates whether the cookie should only be sent using a secure protocol,
518 * such as HTTPS or SSL.
519 *
520 * <p>The default value is <code>false</code>.
521 *
522 * @param flag If <code>true</code>, the cookie can only be sent over
523 * a secure protocol like https.
524 * If <code>false</code>, it can be sent over any protocol.
525 *
526 * @see #getSecure
527 *
528 */
529
530 public void setSecure(boolean flag) {
531 secure = flag;
532 }
533
534
535
536
537 /**
538 * Returns <code>true</code> if sending this cookie should be
539 * restricted to a secure protocol, or <code>false</code> if the
540 * it can be sent using any protocol.
541 *
542 * @return <code>false</code> if the cookie can be sent over
543 * any standard protocol; otherwise, <code>true</code>
544 *
545 * @see #setSecure
546 *
547 */
548
549 public boolean getSecure() {
550 return secure;
551 }
552
553
554
555
556
557 /**
558 * Returns the name of the cookie. The name cannot be changed after
559 * creation.
560 *
561 * @return a <code>String</code> specifying the cookie's name
562 *
563 */
564
565 public String getName() {
566 return name;
567 }
568
569
570
571
572
573 /**
574 *
575 * Assigns a new value to a cookie after the cookie is created.
576 * If you use a binary value, you may want to use BASE64 encoding.
577 *
578 * <p>With Version 0 cookies, values should not contain white
579 * space, brackets, parentheses, equals signs, commas,
580 * double quotes, slashes, question marks, at signs, colons,
581 * and semicolons. Empty values may not behave the same way
582 * on all browsers.
583 *
584 * @param newValue a <code>String</code> specifying the new value
585 *
586 *
587 * @see #getValue
588 *
589 */
590
591 public void setValue(String newValue) {
592 value = newValue;
593 }
594
595
596
597
598 /**
599 * Returns the value of the cookie.
600 *
601 * @return a <code>String</code> containing the cookie's
602 * present value
603 *
604 * @see #setValue
605 *
606 */
607
608 public String getValue() {
609 return value;
610 }
611
612
613
614
615 /**
616 * Returns the version of the protocol this cookie complies
617 * with. Version 1 complies with RFC 2965/2109,
618 * and version 0 complies with the original
619 * cookie specification drafted by Netscape. Cookies provided
620 * by a browser use and identify the browser's cookie version.
621 *
622 *
623 * @return 0 if the cookie complies with the
624 * original Netscape specification; 1
625 * if the cookie complies with RFC 2965/2109
626 *
627 * @see #setVersion
628 *
629 */
630
631 public int getVersion() {
632 return version;
633 }
634
635
636
637
638 /**
639 * Sets the version of the cookie protocol this cookie complies
640 * with. Version 0 complies with the original Netscape cookie
641 * specification. Version 1 complies with RFC 2965/2109.
642 *
643 *
644 * @param v 0 if the cookie should comply with
645 * the original Netscape specification;
646 * 1 if the cookie should comply with RFC 2965/2109
647 *
648 * @throws IllegalArgumentException if <tt>v</tt> is neither 0 nor 1
649 *
650 * @see #getVersion
651 *
652 */
653
654 public void setVersion(int v) {
655 if (v != 0 && v != 1) {
656 throw new IllegalArgumentException("cookie version should be 0 or 1");
657 }
658
659 version = v;
660 }
661
662 /**
663 * Returns {@code true} if this cookie contains the <i>HttpOnly</i>
664 * attribute. This means that the cookie should not be accessible to
665 * scripting engines, like javascript.
666 *
667 * @return {@code true} if this cookie should be considered http only.
668 * @see #setHttpOnly(boolean)
669 */
670 public boolean isHttpOnly()
671 {
672 return httpOnly;
673 }
674
675 /**
676 * Indicates whether the cookie should be considered HTTP Only. If set to
677 * {@code true} it means the cookie should not be accessible to scripting
678 * engines like javascript.
679 *
680 * @param httpOnly if {@code true} make the cookie HTTP only, i.e.
681 * only visible as part of an HTTP request.
682 * @see #isHttpOnly()
683 */
684 public void setHttpOnly(boolean httpOnly)
685 {
686 this.httpOnly = httpOnly;
687 }
688
689 /**
690 * The utility method to check whether a host name is in a domain
691 * or not.
692 *
693 * <p>This concept is described in the cookie specification.
694 * To understand the concept, some terminologies need to be defined first:
695 * <blockquote>
696 * effective host name = hostname if host name contains dot<br>
697 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;or = hostname.local if not
698 * </blockquote>
699 * <p>Host A's name domain-matches host B's if:
700 * <blockquote><ul>
701 * <li>their host name strings string-compare equal; or</li>
702 * <li>A is a HDN string and has the form NB, where N is a non-empty
703 * name string, B has the form .B', and B' is a HDN string. (So,
704 * x.y.com domain-matches .Y.com but not Y.com.)</li>
705 * </ul></blockquote>
706 *
707 * <p>A host isn't in a domain (RFC 2965 sec. 3.3.2) if:
708 * <blockquote><ul>
709 * <li>The value for the Domain attribute contains no embedded dots,
710 * and the value is not .local.</li>
711 * <li>The effective host name that derives from the request-host does
712 * not domain-match the Domain attribute.</li>
713 * <li>The request-host is a HDN (not IP address) and has the form HD,
714 * where D is the value of the Domain attribute, and H is a string
715 * that contains one or more dots.</li>
716 * </ul></blockquote>
717 *
718 * <p>Examples:
719 * <blockquote><ul>
720 * <li>A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com
721 * would be rejected, because H is y.x and contains a dot.</li>
722 * <li>A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com
723 * would be accepted.</li>
724 * <li>A Set-Cookie2 with Domain=.com or Domain=.com., will always be
725 * rejected, because there is no embedded dot.</li>
726 * <li>A Set-Cookie2 with Domain=ajax.com will be accepted, and the
727 * value for Domain will be taken to be .ajax.com, because a dot
728 * gets prepended to the value.</li>
729 * <li>A Set-Cookie2 from request-host example for Domain=.local will
730 * be accepted, because the effective host name for the request-
731 * host is example.local, and example.local domain-matches .local.</li>
732 * </ul></blockquote>
733 *
734 * @param domain the domain name to check host name with
735 * @param host the host name in question
736 * @return <tt>true</tt> if they domain-matches; <tt>false</tt> if not
737 */
738 public static boolean domainMatches(String domain, String host) {
739 if (domain == null || host == null)
740 return false;
741
742 // if there's no embedded dot in domain and domain is not .local
743 boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
744 int embeddedDotInDomain = domain.indexOf('.');
745 if (embeddedDotInDomain == 0)
746 embeddedDotInDomain = domain.indexOf('.', 1);
747 if (!isLocalDomain
748 && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1))
749 return false;
750
751 // if the host name contains no dot and the domain name is .local
752 int firstDotInHost = host.indexOf('.');
753 if (firstDotInHost == -1 && isLocalDomain)
754 return true;
755
756 int domainLength = domain.length();
757 int lengthDiff = host.length() - domainLength;
758 if (lengthDiff == 0) {
759 // if the host name and the domain name are just string-compare euqal
760 return host.equalsIgnoreCase(domain);
761 }
762 else if (lengthDiff > 0) {
763 // need to check H & D component
764 String H = host.substring(0, lengthDiff);
765 String D = host.substring(lengthDiff);
766
767 return (H.indexOf('.') == -1 && D.equalsIgnoreCase(domain));
768 }
769 else if (lengthDiff == -1) {
770 // if domain is actually .host
771 return (domain.charAt(0) == '.' &&
772 host.equalsIgnoreCase(domain.substring(1)));
773 }
774
775 return false;
776 }
777
778
779 /**
780 * Constructs a cookie header string representation of this cookie,
781 * which is in the format defined by corresponding cookie specification,
782 * but without the leading "Cookie:" token.
783 *
784 * @return a string form of the cookie. The string has the defined format
785 */
786 @Override
787 public String toString() {
788 if (getVersion() > 0) {
789 return toRFC2965HeaderString();
790 } else {
791 return toNetscapeHeaderString();
792 }
793 }
794
795
796 /**
797 * Test the equality of two http cookies.
798 *
799 * <p> The result is <tt>true</tt> only if two cookies
800 * come from same domain (case-insensitive),
801 * have same name (case-insensitive),
802 * and have same path (case-sensitive).
803 *
804 * @return <tt>true</tt> if 2 http cookies equal to each other;
805 * otherwise, <tt>false</tt>
806 */
807 @Override
808 public boolean equals(Object obj) {
809 if (obj == this)
810 return true;
811 if (!(obj instanceof HttpCookie))
812 return false;
813 HttpCookie other = (HttpCookie)obj;
814
815 // One http cookie equals to another cookie (RFC 2965 sec. 3.3.3) if:
816 // 1. they come from same domain (case-insensitive),
817 // 2. have same name (case-insensitive),
818 // 3. and have same path (case-sensitive).
819 return equalsIgnoreCase(getName(), other.getName()) &&
820 equalsIgnoreCase(getDomain(), other.getDomain()) &&
821 Objects.equals(getPath(), other.getPath());
822 }
823
824
825 /**
826 * Return hash code of this http cookie. The result is the sum of
827 * hash code value of three significant components of this cookie:
828 * name, domain, and path.
829 * That is, the hash code is the value of the expression:
830 * <blockquote>
831 * getName().toLowerCase().hashCode()<br>
832 * + getDomain().toLowerCase().hashCode()<br>
833 * + getPath().hashCode()
834 * </blockquote>
835 *
836 * @return this http cookie's hash code
837 */
838 @Override
839 public int hashCode() {
840 int h1 = name.toLowerCase().hashCode();
841 int h2 = (domain!=null) ? domain.toLowerCase().hashCode() : 0;
842 int h3 = (path!=null) ? path.hashCode() : 0;
843
844 return h1 + h2 + h3;
845 }
846
847 /**
848 * Create and return a copy of this object.
849 *
850 * @return a clone of this http cookie
851 */
852 @Override
853 public Object clone() {
854 try {
855 return super.clone();
856 } catch (CloneNotSupportedException e) {
857 throw new RuntimeException(e.getMessage());
858 }
859 }
860
861
862 /* ---------------- Private operations -------------- */
863
864 // Note -- disabled for now to allow full Netscape compatibility
865 // from RFC 2068, token special case characters
866 //
867 // private static final String tspecials = "()<>@,;:\\\"/[]?={} \t";
868 private static final String tspecials = ",;";
869
870 /*
871 * Tests a string and returns true if the string counts as a
872 * token.
873 *
874 * @param value the <code>String</code> to be tested
875 *
876 * @return <code>true</code> if the <code>String</code> is
877 * a token; <code>false</code> if it is not
878 */
879
880 private static boolean isToken(String value) {
881 int len = value.length();
882
883 for (int i = 0; i < len; i++) {
884 char c = value.charAt(i);
885
886 if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1)
887 return false;
888 }
889 return true;
890 }
891
892
893 /*
894 * @param name the name to be tested
895 * @return <tt>true</tt> if the name is reserved by cookie
896 * specification, <tt>false</tt> if it is not
897 */
898 private static boolean isReserved(String name) {
899 if (name.equalsIgnoreCase("Comment")
900 || name.equalsIgnoreCase("CommentURL") // rfc2965 only
901 || name.equalsIgnoreCase("Discard") // rfc2965 only
902 || name.equalsIgnoreCase("Domain")
903 || name.equalsIgnoreCase("Expires") // netscape draft only
904 || name.equalsIgnoreCase("Max-Age")
905 || name.equalsIgnoreCase("Path")
906 || name.equalsIgnoreCase("Port") // rfc2965 only
907 || name.equalsIgnoreCase("Secure")
908 || name.equalsIgnoreCase("Version")
909 || name.equalsIgnoreCase("HttpOnly")
910 || name.charAt(0) == '$')
911 {
912 return true;
913 }
914
915 return false;
916 }
917
918
919 /*
920 * Parse header string to cookie object.
921 *
922 * @param header header string; should contain only one NAME=VALUE pair
923 *
924 * @return an HttpCookie being extracted
925 *
926 * @throws IllegalArgumentException if header string violates the cookie
927 * specification
928 */
929 private static HttpCookie parseInternal(String header)
930 {
931 HttpCookie cookie = null;
932 String namevaluePair = null;
933
934 StringTokenizer tokenizer = new StringTokenizer(header, ";");
935
936 // there should always have at least on name-value pair;
937 // it's cookie's name
938 try {
939 namevaluePair = tokenizer.nextToken();
940 int index = namevaluePair.indexOf('=');
941 if (index != -1) {
942 String name = namevaluePair.substring(0, index).trim();
943 String value = namevaluePair.substring(index + 1).trim();
944 cookie = new HttpCookie(name, stripOffSurroundingQuote(value));
945 } else {
946 // no "=" in name-value pair; it's an error
947 throw new IllegalArgumentException("Invalid cookie name-value pair");
948 }
949 } catch (NoSuchElementException ignored) {
950 throw new IllegalArgumentException("Empty cookie header string");
951 }
952
953 // remaining name-value pairs are cookie's attributes
954 while (tokenizer.hasMoreTokens()) {
955 namevaluePair = tokenizer.nextToken();
956 int index = namevaluePair.indexOf('=');
957 String name, value;
958 if (index != -1) {
959 name = namevaluePair.substring(0, index).trim();
960 value = namevaluePair.substring(index + 1).trim();
961 } else {
962 name = namevaluePair.trim();
963 value = null;
964 }
965
966 // assign attribute to cookie
967 assignAttribute(cookie, name, value);
968 }
969
970 return cookie;
971 }
972
973
974 /*
975 * assign cookie attribute value to attribute name;
976 * use a map to simulate method dispatch
977 */
978 static interface CookieAttributeAssignor {
979 public void assign(HttpCookie cookie, String attrName, String attrValue);
980 }
981 static java.util.Map<String, CookieAttributeAssignor> assignors = null;
982 static {
983 assignors = new java.util.HashMap<String, CookieAttributeAssignor>();
984 assignors.put("comment", new CookieAttributeAssignor(){
985 public void assign(HttpCookie cookie, String attrName, String attrValue) {
986 if (cookie.getComment() == null) cookie.setComment(attrValue);
987 }
988 });
989 assignors.put("commenturl", new CookieAttributeAssignor(){
990 public void assign(HttpCookie cookie, String attrName, String attrValue) {
991 if (cookie.getCommentURL() == null) cookie.setCommentURL(attrValue);
992 }
993 });
994 assignors.put("discard", new CookieAttributeAssignor(){
995 public void assign(HttpCookie cookie, String attrName, String attrValue) {
996 cookie.setDiscard(true);
997 }
998 });
999 assignors.put("domain", new CookieAttributeAssignor(){
1000 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1001 if (cookie.getDomain() == null) cookie.setDomain(attrValue);
1002 }
1003 });
1004 assignors.put("max-age", new CookieAttributeAssignor(){
1005 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1006 try {
1007 long maxage = Long.parseLong(attrValue);
1008 if (cookie.getMaxAge() == MAX_AGE_UNSPECIFIED) cookie.setMaxAge(maxage);
1009 } catch (NumberFormatException ignored) {
1010 throw new IllegalArgumentException("Illegal cookie max-age attribute");
1011 }
1012 }
1013 });
1014 assignors.put("path", new CookieAttributeAssignor(){
1015 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1016 if (cookie.getPath() == null) cookie.setPath(attrValue);
1017 }
1018 });
1019 assignors.put("port", new CookieAttributeAssignor(){
1020 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1021 if (cookie.getPortlist() == null) cookie.setPortlist(attrValue == null ? "" : attrValue);
1022 }
1023 });
1024 assignors.put("secure", new CookieAttributeAssignor(){
1025 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1026 cookie.setSecure(true);
1027 }
1028 });
1029 assignors.put("httponly", new CookieAttributeAssignor(){
1030 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1031 cookie.setHttpOnly(true);
1032 }
1033 });
1034 assignors.put("version", new CookieAttributeAssignor(){
1035 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1036 try {
1037 int version = Integer.parseInt(attrValue);
1038 cookie.setVersion(version);
1039 } catch (NumberFormatException ignored) {
1040 // Just ignore bogus version, it will default to 0 or 1
1041 }
1042 }
1043 });
1044 assignors.put("expires", new CookieAttributeAssignor(){ // Netscape only
1045 public void assign(HttpCookie cookie, String attrName, String attrValue) {
1046 if (cookie.getMaxAge() == MAX_AGE_UNSPECIFIED) {
1047 cookie.setMaxAge(cookie.expiryDate2DeltaSeconds(attrValue));
1048 }
1049 }
1050 });
1051 }
1052 private static void assignAttribute(HttpCookie cookie,
1053 String attrName,
1054 String attrValue)
1055 {
1056 // strip off the surrounding "-sign if there's any
1057 attrValue = stripOffSurroundingQuote(attrValue);
1058
1059 CookieAttributeAssignor assignor = assignors.get(attrName.toLowerCase());
1060 if (assignor != null) {
1061 assignor.assign(cookie, attrName, attrValue);
1062 } else {
1063 // Ignore the attribute as per RFC 2965
1064 }
1065 }
1066
1067 /*
1068 * Constructs a string representation of this cookie. The string format is
1069 * as Netscape spec, but without leading "Cookie:" token.
1070 */
1071 private String toNetscapeHeaderString() {
1072 StringBuilder sb = new StringBuilder();
1073
1074 sb.append(getName() + "=" + getValue());
1075
1076 return sb.toString();
1077 }
1078
1079 /*
1080 * Constructs a string representation of this cookie. The string format is
1081 * as RFC 2965/2109, but without leading "Cookie:" token.
1082 */
1083 private String toRFC2965HeaderString() {
1084 StringBuilder sb = new StringBuilder();
1085
1086 sb.append(getName()).append("=\"").append(getValue()).append('"');
1087 if (getPath() != null)
1088 sb.append(";$Path=\"").append(getPath()).append('"');
1089 if (getDomain() != null)
1090 sb.append(";$Domain=\"").append(getDomain()).append('"');
1091 if (getPortlist() != null)
1092 sb.append(";$Port=\"").append(getPortlist()).append('"');
1093
1094 return sb.toString();
1095 }
1096
1097 static final TimeZone GMT = TimeZone.getTimeZone("GMT");
1098
1099 /*
1100 * @param dateString a date string in one of the formats
1101 * defined in Netscape cookie spec
1102 *
1103 * @return delta seconds between this cookie's creation
1104 * time and the time specified by dateString
1105 */
1106 private long expiryDate2DeltaSeconds(String dateString) {
1107 for (int i = 0; i < COOKIE_DATE_FORMATS.length; i++) {
1108 SimpleDateFormat df = new SimpleDateFormat(COOKIE_DATE_FORMATS[i], Locale.US);
1109 df.setTimeZone(GMT);
1110 try {
1111 Date date = df.parse(dateString);
1112 return (date.getTime() - whenCreated) / 1000;
1113 } catch (Exception e) {
1114 // Ignore, try the next date format
1115 }
1116 }
1117 return 0;
1118 }
1119
1120
1121
1122 /*
1123 * try to guess the cookie version through set-cookie header string
1124 */
1125 private static int guessCookieVersion(String header) {
1126 int version = 0;
1127
1128 header = header.toLowerCase();
1129 if (header.indexOf("expires=") != -1) {
1130 // only netscape cookie using 'expires'
1131 version = 0;
1132 } else if (header.indexOf("version=") != -1) {
1133 // version is mandatory for rfc 2965/2109 cookie
1134 version = 1;
1135 } else if (header.indexOf("max-age") != -1) {
1136 // rfc 2965/2109 use 'max-age'
1137 version = 1;
1138 } else if (startsWithIgnoreCase(header, SET_COOKIE2)) {
1139 // only rfc 2965 cookie starts with 'set-cookie2'
1140 version = 1;
1141 }
1142
1143 return version;
1144 }
1145
1146 private static String stripOffSurroundingQuote(String str) {
1147 if (str != null && str.length() > 2 &&
1148 str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"') {
1149 return str.substring(1, str.length() - 1);
1150 }
1151 if (str != null && str.length() > 2 &&
1152 str.charAt(0) == '\'' && str.charAt(str.length() - 1) == '\'') {
1153 return str.substring(1, str.length() - 1);
1154 }
1155 return str;
1156 }
1157
1158 private static boolean equalsIgnoreCase(String s, String t) {
1159 if (s == t) return true;
1160 if ((s != null) && (t != null)) {
1161 return s.equalsIgnoreCase(t);
1162 }
1163 return false;
1164 }
1165
1166 private static boolean startsWithIgnoreCase(String s, String start) {
1167 if (s == null || start == null) return false;
1168
1169 if (s.length() >= start.length() &&
1170 start.equalsIgnoreCase(s.substring(0, start.length()))) {
1171 return true;
1172 }
1173
1174 return false;
1175 }
1176
1177 /*
1178 * Split cookie header string according to rfc 2965:
1179 * 1) split where it is a comma;
1180 * 2) but not the comma surrounding by double-quotes, which is the comma
1181 * inside port list or embeded URIs.
1182 *
1183 * @param header the cookie header string to split
1184 *
1185 * @return list of strings; never null
1186 *
1187 */
1188 private static List<String> splitMultiCookies(String header) {
1189 List<String> cookies = new java.util.ArrayList<String>();
1190 int quoteCount = 0;
1191 int p, q;
1192
1193 for (p = 0, q = 0; p < header.length(); p++) {
1194 char c = header.charAt(p);
1195 if (c == '"') quoteCount++;
1196 if (c == ',' && (quoteCount % 2 == 0)) { // it is comma and not surrounding by double-quotes
1197 cookies.add(header.substring(q, p));
1198 q = p + 1;
1199 }
1200 }
1201
1202 cookies.add(header.substring(q));
1203
1204 return cookies;
1205 }
1206}