1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.apache.commons.httpclient.cookie;
31
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Comparator;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.StringTokenizer;
42
43 import org.apache.commons.httpclient.Cookie;
44 import org.apache.commons.httpclient.Header;
45 import org.apache.commons.httpclient.HeaderElement;
46 import org.apache.commons.httpclient.NameValuePair;
47 import org.apache.commons.httpclient.util.ParameterFormatter;
48
49 /***
50 * <p>RFC 2965 specific cookie management functions.</p>
51 *
52 * @author jain.samit@gmail.com (Samit Jain)
53 *
54 * @since 3.1
55 */
56 public class RFC2965Spec extends CookieSpecBase implements CookieVersionSupport {
57
58 private static final Comparator PATH_COMPOARATOR = new CookiePathComparator();
59
60 /***
61 * Cookie Response Header name for cookies processed
62 * by this spec.
63 */
64 public static String SET_COOKIE2_KEY = "set-cookie2";
65
66 /***
67 * used for formatting RFC 2956 style cookies
68 */
69 private final ParameterFormatter formatter;
70
71 /***
72 * Stores the list of attribute handlers
73 */
74 private final List attribHandlerList;
75
76 /***
77 * Stores attribute name -> attribute handler mappings
78 */
79 private final Map attribHandlerMap;
80
81 /***
82 * Fallback cookie spec (RFC 2109)
83 */
84 private final CookieSpec rfc2109;
85
86 /***
87 * Default constructor
88 * */
89 public RFC2965Spec() {
90 super();
91 this.formatter = new ParameterFormatter();
92 this.formatter.setAlwaysUseQuotes(true);
93 this.attribHandlerMap = new HashMap(10);
94 this.attribHandlerList = new ArrayList(10);
95 this.rfc2109 = new RFC2109Spec();
96
97 registerAttribHandler(Cookie2.PATH, new Cookie2PathAttributeHandler());
98 registerAttribHandler(Cookie2.DOMAIN, new Cookie2DomainAttributeHandler());
99 registerAttribHandler(Cookie2.PORT, new Cookie2PortAttributeHandler());
100 registerAttribHandler(Cookie2.MAXAGE, new Cookie2MaxageAttributeHandler());
101 registerAttribHandler(Cookie2.SECURE, new CookieSecureAttributeHandler());
102 registerAttribHandler(Cookie2.COMMENT, new CookieCommentAttributeHandler());
103 registerAttribHandler(Cookie2.COMMENTURL, new CookieCommentUrlAttributeHandler());
104 registerAttribHandler(Cookie2.DISCARD, new CookieDiscardAttributeHandler());
105 registerAttribHandler(Cookie2.VERSION, new Cookie2VersionAttributeHandler());
106 }
107
108 protected void registerAttribHandler(
109 final String name, final CookieAttributeHandler handler) {
110 if (name == null) {
111 throw new IllegalArgumentException("Attribute name may not be null");
112 }
113 if (handler == null) {
114 throw new IllegalArgumentException("Attribute handler may not be null");
115 }
116 if (!this.attribHandlerList.contains(handler)) {
117 this.attribHandlerList.add(handler);
118 }
119 this.attribHandlerMap.put(name, handler);
120 }
121
122 /***
123 * Finds an attribute handler {@link CookieAttributeHandler} for the
124 * given attribute. Returns <tt>null</tt> if no attribute handler is
125 * found for the specified attribute.
126 *
127 * @param name attribute name. e.g. Domain, Path, etc.
128 * @return an attribute handler or <tt>null</tt>
129 */
130 protected CookieAttributeHandler findAttribHandler(final String name) {
131 return (CookieAttributeHandler) this.attribHandlerMap.get(name);
132 }
133
134 /***
135 * Gets attribute handler {@link CookieAttributeHandler} for the
136 * given attribute.
137 *
138 * @param name attribute name. e.g. Domain, Path, etc.
139 * @throws IllegalStateException if handler not found for the
140 * specified attribute.
141 */
142 protected CookieAttributeHandler getAttribHandler(final String name) {
143 CookieAttributeHandler handler = findAttribHandler(name);
144 if (handler == null) {
145 throw new IllegalStateException("Handler not registered for " +
146 name + " attribute.");
147 } else {
148 return handler;
149 }
150 }
151
152 protected Iterator getAttribHandlerIterator() {
153 return this.attribHandlerList.iterator();
154 }
155
156 /***
157 * Parses the Set-Cookie2 value into an array of <tt>Cookie</tt>s.
158 *
159 * <P>The syntax for the Set-Cookie2 response header is:
160 *
161 * <PRE>
162 * set-cookie = "Set-Cookie2:" cookies
163 * cookies = 1#cookie
164 * cookie = NAME "=" VALUE * (";" cookie-av)
165 * NAME = attr
166 * VALUE = value
167 * cookie-av = "Comment" "=" value
168 * | "CommentURL" "=" <"> http_URL <">
169 * | "Discard"
170 * | "Domain" "=" value
171 * | "Max-Age" "=" value
172 * | "Path" "=" value
173 * | "Port" [ "=" <"> portlist <"> ]
174 * | "Secure"
175 * | "Version" "=" 1*DIGIT
176 * portlist = 1#portnum
177 * portnum = 1*DIGIT
178 * </PRE>
179 *
180 * @param host the host from which the <tt>Set-Cookie2</tt> value was
181 * received
182 * @param port the port from which the <tt>Set-Cookie2</tt> value was
183 * received
184 * @param path the path from which the <tt>Set-Cookie2</tt> value was
185 * received
186 * @param secure <tt>true</tt> when the <tt>Set-Cookie2</tt> value was
187 * received over secure conection
188 * @param header the <tt>Set-Cookie2</tt> <tt>Header</tt> received from the server
189 * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie2 value
190 * @throws MalformedCookieException if an exception occurs during parsing
191 */
192 public Cookie[] parse(
193 String host, int port, String path, boolean secure, final Header header)
194 throws MalformedCookieException {
195 LOG.trace("enter RFC2965.parse("
196 + "String, int, String, boolean, Header)");
197
198 if (header == null) {
199 throw new IllegalArgumentException("Header may not be null.");
200 }
201 if (header.getName() == null) {
202 throw new IllegalArgumentException("Header name may not be null.");
203 }
204
205 if (header.getName().equalsIgnoreCase(SET_COOKIE2_KEY)) {
206
207 return parse(host, port, path, secure, header.getValue());
208 } else if (header.getName().equalsIgnoreCase(RFC2109Spec.SET_COOKIE_KEY)) {
209
210 return this.rfc2109.parse(host, port, path, secure, header.getValue());
211 } else {
212 throw new MalformedCookieException("Header name is not valid. " +
213 "RFC 2965 supports \"set-cookie\" " +
214 "and \"set-cookie2\" headers.");
215 }
216 }
217
218 /***
219 * @see #parse(String, int, String, boolean, org.apache.commons.httpclient.Header)
220 */
221 public Cookie[] parse(String host, int port, String path,
222 boolean secure, final String header)
223 throws MalformedCookieException {
224 LOG.trace("enter RFC2965Spec.parse("
225 + "String, int, String, boolean, String)");
226
227
228 if (host == null) {
229 throw new IllegalArgumentException(
230 "Host of origin may not be null");
231 }
232 if (host.trim().equals("")) {
233 throw new IllegalArgumentException(
234 "Host of origin may not be blank");
235 }
236 if (port < 0) {
237 throw new IllegalArgumentException("Invalid port: " + port);
238 }
239 if (path == null) {
240 throw new IllegalArgumentException(
241 "Path of origin may not be null.");
242 }
243 if (header == null) {
244 throw new IllegalArgumentException("Header may not be null.");
245 }
246
247 if (path.trim().equals("")) {
248 path = PATH_DELIM;
249 }
250 host = getEffectiveHost(host);
251
252 HeaderElement[] headerElements =
253 HeaderElement.parseElements(header.toCharArray());
254
255 List cookies = new LinkedList();
256 for (int i = 0; i < headerElements.length; i++) {
257 HeaderElement headerelement = headerElements[i];
258 Cookie2 cookie = null;
259 try {
260 cookie = new Cookie2(host,
261 headerelement.getName(),
262 headerelement.getValue(),
263 path,
264 null,
265 false,
266 new int[] {port});
267 } catch (IllegalArgumentException ex) {
268 throw new MalformedCookieException(ex.getMessage());
269 }
270 NameValuePair[] parameters = headerelement.getParameters();
271
272 if (parameters != null) {
273
274 Map attribmap = new HashMap(parameters.length);
275 for (int j = parameters.length - 1; j >= 0; j--) {
276 NameValuePair param = parameters[j];
277 attribmap.put(param.getName().toLowerCase(), param);
278 }
279 for (Iterator it = attribmap.entrySet().iterator(); it.hasNext(); ) {
280 Map.Entry entry = (Map.Entry) it.next();
281 parseAttribute((NameValuePair) entry.getValue(), cookie);
282 }
283 }
284 cookies.add(cookie);
285
286 }
287 return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
288 }
289
290 /***
291 * Parse RFC 2965 specific cookie attribute and update the corresponsing
292 * {@link org.apache.commons.httpclient.Cookie} properties.
293 *
294 * @param attribute {@link org.apache.commons.httpclient.NameValuePair} cookie attribute from the
295 * <tt>Set-Cookie2</tt> header.
296 * @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated
297 * @throws MalformedCookieException if an exception occurs during parsing
298 */
299 public void parseAttribute(
300 final NameValuePair attribute, final Cookie cookie)
301 throws MalformedCookieException {
302 if (attribute == null) {
303 throw new IllegalArgumentException("Attribute may not be null.");
304 }
305 if (attribute.getName() == null) {
306 throw new IllegalArgumentException("Attribute Name may not be null.");
307 }
308 if (cookie == null) {
309 throw new IllegalArgumentException("Cookie may not be null.");
310 }
311 final String paramName = attribute.getName().toLowerCase();
312 final String paramValue = attribute.getValue();
313
314 CookieAttributeHandler handler = findAttribHandler(paramName);
315 if (handler == null) {
316
317 if (LOG.isDebugEnabled())
318 LOG.debug("Unrecognized cookie attribute: " +
319 attribute.toString());
320 } else {
321 handler.parse(cookie, paramValue);
322 }
323 }
324
325 /***
326 * Performs RFC 2965 compliant {@link org.apache.commons.httpclient.Cookie} validation
327 *
328 * @param host the host from which the {@link org.apache.commons.httpclient.Cookie} was received
329 * @param port the port from which the {@link org.apache.commons.httpclient.Cookie} was received
330 * @param path the path from which the {@link org.apache.commons.httpclient.Cookie} was received
331 * @param secure <tt>true</tt> when the {@link org.apache.commons.httpclient.Cookie} was received using a
332 * secure connection
333 * @param cookie The cookie to validate
334 * @throws MalformedCookieException if an exception occurs during
335 * validation
336 */
337 public void validate(final String host, int port, final String path,
338 boolean secure, final Cookie cookie)
339 throws MalformedCookieException {
340
341 LOG.trace("enter RFC2965Spec.validate(String, int, String, "
342 + "boolean, Cookie)");
343
344 if (cookie instanceof Cookie2) {
345 if (cookie.getName().indexOf(' ') != -1) {
346 throw new MalformedCookieException("Cookie name may not contain blanks");
347 }
348 if (cookie.getName().startsWith("$")) {
349 throw new MalformedCookieException("Cookie name may not start with $");
350 }
351 CookieOrigin origin = new CookieOrigin(getEffectiveHost(host), port, path, secure);
352 for (Iterator i = getAttribHandlerIterator(); i.hasNext(); ) {
353 CookieAttributeHandler handler = (CookieAttributeHandler) i.next();
354 handler.validate(cookie, origin);
355 }
356 } else {
357
358 this.rfc2109.validate(host, port, path, secure, cookie);
359 }
360 }
361
362 /***
363 * Return <tt>true</tt> if the cookie should be submitted with a request
364 * with given attributes, <tt>false</tt> otherwise.
365 * @param host the host to which the request is being submitted
366 * @param port the port to which the request is being submitted (ignored)
367 * @param path the path to which the request is being submitted
368 * @param secure <tt>true</tt> if the request is using a secure connection
369 * @return true if the cookie matches the criterium
370 */
371 public boolean match(String host, int port, String path,
372 boolean secure, final Cookie cookie) {
373
374 LOG.trace("enter RFC2965.match("
375 + "String, int, String, boolean, Cookie");
376 if (cookie == null) {
377 throw new IllegalArgumentException("Cookie may not be null");
378 }
379 if (cookie instanceof Cookie2) {
380
381 if (cookie.isPersistent() && cookie.isExpired()) {
382 return false;
383 }
384 CookieOrigin origin = new CookieOrigin(getEffectiveHost(host), port, path, secure);
385 for (Iterator i = getAttribHandlerIterator(); i.hasNext(); ) {
386 CookieAttributeHandler handler = (CookieAttributeHandler) i.next();
387 if (!handler.match(cookie, origin)) {
388 return false;
389 }
390 }
391 return true;
392 } else {
393
394 return this.rfc2109.match(host, port, path, secure, cookie);
395 }
396 }
397
398 private void doFormatCookie2(final Cookie2 cookie, final StringBuffer buffer) {
399 String name = cookie.getName();
400 String value = cookie.getValue();
401 if (value == null) {
402 value = "";
403 }
404 this.formatter.format(buffer, new NameValuePair(name, value));
405
406 if (cookie.getDomain() != null && cookie.isDomainAttributeSpecified()) {
407 buffer.append("; ");
408 this.formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain()));
409 }
410
411 if ((cookie.getPath() != null) && (cookie.isPathAttributeSpecified())) {
412 buffer.append("; ");
413 this.formatter.format(buffer, new NameValuePair("$Path", cookie.getPath()));
414 }
415
416 if (cookie.isPortAttributeSpecified()) {
417 String portValue = "";
418 if (!cookie.isPortAttributeBlank()) {
419 portValue = createPortAttribute(cookie.getPorts());
420 }
421 buffer.append("; ");
422 this.formatter.format(buffer, new NameValuePair("$Port", portValue));
423 }
424 }
425
426 /***
427 * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
428 * defined in RFC 2965
429 * @param cookie a {@link org.apache.commons.httpclient.Cookie} to be formatted as string
430 * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
431 */
432 public String formatCookie(final Cookie cookie) {
433 LOG.trace("enter RFC2965Spec.formatCookie(Cookie)");
434
435 if (cookie == null) {
436 throw new IllegalArgumentException("Cookie may not be null");
437 }
438 if (cookie instanceof Cookie2) {
439 Cookie2 cookie2 = (Cookie2) cookie;
440 int version = cookie2.getVersion();
441 final StringBuffer buffer = new StringBuffer();
442 this.formatter.format(buffer, new NameValuePair("$Version", Integer.toString(version)));
443 buffer.append("; ");
444 doFormatCookie2(cookie2, buffer);
445 return buffer.toString();
446 } else {
447
448 return this.rfc2109.formatCookie(cookie);
449 }
450 }
451
452 /***
453 * Create a RFC 2965 compliant <tt>"Cookie"</tt> header value containing all
454 * {@link org.apache.commons.httpclient.Cookie}s suitable for
455 * sending in a <tt>"Cookie"</tt> header
456 * @param cookies an array of {@link org.apache.commons.httpclient.Cookie}s to be formatted
457 * @return a string suitable for sending in a Cookie header.
458 */
459 public String formatCookies(final Cookie[] cookies) {
460 LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])");
461
462 if (cookies == null) {
463 throw new IllegalArgumentException("Cookies may not be null");
464 }
465
466 boolean hasOldStyleCookie = false;
467 int version = -1;
468 for (int i = 0; i < cookies.length; i++) {
469 Cookie cookie = cookies[i];
470 if (!(cookie instanceof Cookie2)) {
471 hasOldStyleCookie = true;
472 break;
473 }
474 if (cookie.getVersion() > version) {
475 version = cookie.getVersion();
476 }
477 }
478 if (version < 0) {
479 version = 0;
480 }
481 if (hasOldStyleCookie || version < 1) {
482
483 return this.rfc2109.formatCookies(cookies);
484 }
485
486 Arrays.sort(cookies, PATH_COMPOARATOR);
487
488 final StringBuffer buffer = new StringBuffer();
489
490 this.formatter.format(buffer, new NameValuePair("$Version", Integer.toString(version)));
491 for (int i = 0; i < cookies.length; i++) {
492 buffer.append("; ");
493 Cookie2 cookie = (Cookie2) cookies[i];
494
495 doFormatCookie2(cookie, buffer);
496 }
497 return buffer.toString();
498 }
499
500 /***
501 * Retrieves valid Port attribute value for the given ports array.
502 * e.g. "8000,8001,8002"
503 *
504 * @param ports int array of ports
505 */
506 private String createPortAttribute(int[] ports) {
507 StringBuffer portValue = new StringBuffer();
508 for (int i = 0, len = ports.length; i < len; i++) {
509 if (i > 0) {
510 portValue.append(",");
511 }
512 portValue.append(ports[i]);
513 }
514 return portValue.toString();
515 }
516
517 /***
518 * Parses the given Port attribute value (e.g. "8000,8001,8002")
519 * into an array of ports.
520 *
521 * @param portValue port attribute value
522 * @return parsed array of ports
523 * @throws MalformedCookieException if there is a problem in
524 * parsing due to invalid portValue.
525 */
526 private int[] parsePortAttribute(final String portValue)
527 throws MalformedCookieException {
528 StringTokenizer st = new StringTokenizer(portValue, ",");
529 int[] ports = new int[st.countTokens()];
530 try {
531 int i = 0;
532 while(st.hasMoreTokens()) {
533 ports[i] = Integer.parseInt(st.nextToken().trim());
534 if (ports[i] < 0) {
535 throw new MalformedCookieException ("Invalid Port attribute.");
536 }
537 ++i;
538 }
539 } catch (NumberFormatException e) {
540 throw new MalformedCookieException ("Invalid Port "
541 + "attribute: " + e.getMessage());
542 }
543 return ports;
544 }
545
546 /***
547 * Gets 'effective host name' as defined in RFC 2965.
548 * <p>
549 * If a host name contains no dots, the effective host name is
550 * that name with the string .local appended to it. Otherwise
551 * the effective host name is the same as the host name. Note
552 * that all effective host names contain at least one dot.
553 *
554 * @param host host name where cookie is received from or being sent to.
555 * @return
556 */
557 private static String getEffectiveHost(final String host) {
558 String effectiveHost = host.toLowerCase();
559 if (host.indexOf('.') < 0) {
560 effectiveHost += ".local";
561 }
562 return effectiveHost;
563 }
564
565 /***
566 * Performs domain-match as defined by the RFC2965.
567 * <p>
568 * Host A's name domain-matches host B's if
569 * <ol>
570 * <ul>their host name strings string-compare equal; or</ul>
571 * <ul>A is a HDN string and has the form NB, where N is a non-empty
572 * name string, B has the form .B', and B' is a HDN string. (So,
573 * x.y.com domain-matches .Y.com but not Y.com.)</ul>
574 * </ol>
575 *
576 * @param host host name where cookie is received from or being sent to.
577 * @param domain The cookie domain attribute.
578 * @return true if the specified host matches the given domain.
579 */
580 public boolean domainMatch(String host, String domain) {
581 boolean match = host.equals(domain)
582 || (domain.startsWith(".") && host.endsWith(domain));
583
584 return match;
585 }
586
587 /***
588 * Returns <tt>true</tt> if the given port exists in the given
589 * ports list.
590 *
591 * @param port port of host where cookie was received from or being sent to.
592 * @param ports port list
593 * @return true returns <tt>true</tt> if the given port exists in
594 * the given ports list; <tt>false</tt> otherwise.
595 */
596 private boolean portMatch(int port, int[] ports) {
597 boolean portInList = false;
598 for (int i = 0, len = ports.length; i < len; i++) {
599 if (port == ports[i]) {
600 portInList = true;
601 break;
602 }
603 }
604 return portInList;
605 }
606
607 /***
608 * <tt>"Path"</tt> attribute handler for RFC 2965 cookie spec.
609 */
610 private class Cookie2PathAttributeHandler
611 implements CookieAttributeHandler {
612
613 /***
614 * Parse cookie path attribute.
615 */
616 public void parse(final Cookie cookie, final String path)
617 throws MalformedCookieException {
618 if (cookie == null) {
619 throw new IllegalArgumentException("Cookie may not be null");
620 }
621 if (path == null) {
622 throw new MalformedCookieException(
623 "Missing value for path attribute");
624 }
625 if (path.trim().equals("")) {
626 throw new MalformedCookieException(
627 "Blank value for path attribute");
628 }
629 cookie.setPath(path);
630 cookie.setPathAttributeSpecified(true);
631 }
632
633 /***
634 * Validate cookie path attribute. The value for the Path attribute must be a
635 * prefix of the request-URI (case-sensitive matching).
636 */
637 public void validate(final Cookie cookie, final CookieOrigin origin)
638 throws MalformedCookieException {
639 if (cookie == null) {
640 throw new IllegalArgumentException("Cookie may not be null");
641 }
642 if (origin == null) {
643 throw new IllegalArgumentException("Cookie origin may not be null");
644 }
645 String path = origin.getPath();
646 if (path == null) {
647 throw new IllegalArgumentException(
648 "Path of origin host may not be null.");
649 }
650 if (cookie.getPath() == null) {
651 throw new MalformedCookieException("Invalid cookie state: " +
652 "path attribute is null.");
653 }
654 if (path.trim().equals("")) {
655 path = PATH_DELIM;
656 }
657
658 if (!pathMatch(path, cookie.getPath())) {
659 throw new MalformedCookieException(
660 "Illegal path attribute \"" + cookie.getPath()
661 + "\". Path of origin: \"" + path + "\"");
662 }
663 }
664
665 /***
666 * Match cookie path attribute. The value for the Path attribute must be a
667 * prefix of the request-URI (case-sensitive matching).
668 */
669 public boolean match(final Cookie cookie, final CookieOrigin origin) {
670 if (cookie == null) {
671 throw new IllegalArgumentException("Cookie may not be null");
672 }
673 if (origin == null) {
674 throw new IllegalArgumentException("Cookie origin may not be null");
675 }
676 String path = origin.getPath();
677 if (cookie.getPath() == null) {
678 LOG.warn("Invalid cookie state: path attribute is null.");
679 return false;
680 }
681 if (path.trim().equals("")) {
682 path = PATH_DELIM;
683 }
684
685 if (!pathMatch(path, cookie.getPath())) {
686 return false;
687 }
688 return true;
689 }
690 }
691
692 /***
693 * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec.
694 */
695 private class Cookie2DomainAttributeHandler
696 implements CookieAttributeHandler {
697
698 /***
699 * Parse cookie domain attribute.
700 */
701 public void parse(final Cookie cookie, String domain)
702 throws MalformedCookieException {
703 if (cookie == null) {
704 throw new IllegalArgumentException("Cookie may not be null");
705 }
706 if (domain == null) {
707 throw new MalformedCookieException(
708 "Missing value for domain attribute");
709 }
710 if (domain.trim().equals("")) {
711 throw new MalformedCookieException(
712 "Blank value for domain attribute");
713 }
714 domain = domain.toLowerCase();
715 if (!domain.startsWith(".")) {
716
717
718
719
720
721 domain = "." + domain;
722 }
723 cookie.setDomain(domain);
724 cookie.setDomainAttributeSpecified(true);
725 }
726
727 /***
728 * Validate cookie domain attribute.
729 */
730 public void validate(final Cookie cookie, final CookieOrigin origin)
731 throws MalformedCookieException {
732 if (cookie == null) {
733 throw new IllegalArgumentException("Cookie may not be null");
734 }
735 if (origin == null) {
736 throw new IllegalArgumentException("Cookie origin may not be null");
737 }
738 String host = origin.getHost().toLowerCase();
739 if (cookie.getDomain() == null) {
740 throw new MalformedCookieException("Invalid cookie state: " +
741 "domain not specified");
742 }
743 String cookieDomain = cookie.getDomain().toLowerCase();
744
745 if (cookie.isDomainAttributeSpecified()) {
746
747 if (!cookieDomain.startsWith(".")) {
748 throw new MalformedCookieException("Domain attribute \"" +
749 cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot");
750 }
751
752
753
754 int dotIndex = cookieDomain.indexOf('.', 1);
755 if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1))
756 && (!cookieDomain.equals(".local"))) {
757 throw new MalformedCookieException(
758 "Domain attribute \"" + cookie.getDomain()
759 + "\" violates RFC 2965: the value contains no embedded dots "
760 + "and the value is not .local");
761 }
762
763
764 if (!domainMatch(host, cookieDomain)) {
765 throw new MalformedCookieException(
766 "Domain attribute \"" + cookie.getDomain()
767 + "\" violates RFC 2965: effective host name does not "
768 + "domain-match domain attribute.");
769 }
770
771
772 String effectiveHostWithoutDomain = host.substring(
773 0, host.length() - cookieDomain.length());
774 if (effectiveHostWithoutDomain.indexOf('.') != -1) {
775 throw new MalformedCookieException("Domain attribute \""
776 + cookie.getDomain() + "\" violates RFC 2965: "
777 + "effective host minus domain may not contain any dots");
778 }
779 } else {
780
781
782 if (!cookie.getDomain().equals(host)) {
783 throw new MalformedCookieException("Illegal domain attribute: \""
784 + cookie.getDomain() + "\"."
785 + "Domain of origin: \""
786 + host + "\"");
787 }
788 }
789 }
790
791 /***
792 * Match cookie domain attribute.
793 */
794 public boolean match(final Cookie cookie, final CookieOrigin origin) {
795 if (cookie == null) {
796 throw new IllegalArgumentException("Cookie may not be null");
797 }
798 if (origin == null) {
799 throw new IllegalArgumentException("Cookie origin may not be null");
800 }
801 String host = origin.getHost().toLowerCase();
802 String cookieDomain = cookie.getDomain();
803
804
805
806 if (!domainMatch(host, cookieDomain)) {
807 return false;
808 }
809
810 String effectiveHostWithoutDomain = host.substring(
811 0, host.length() - cookieDomain.length());
812 if (effectiveHostWithoutDomain.indexOf('.') != -1) {
813 return false;
814 }
815 return true;
816 }
817
818 }
819
820 /***
821 * <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec.
822 */
823 private class Cookie2PortAttributeHandler
824 implements CookieAttributeHandler {
825
826 /***
827 * Parse cookie port attribute.
828 */
829 public void parse(final Cookie cookie, final String portValue)
830 throws MalformedCookieException {
831 if (cookie == null) {
832 throw new IllegalArgumentException("Cookie may not be null");
833 }
834 if (cookie instanceof Cookie2) {
835 Cookie2 cookie2 = (Cookie2) cookie;
836 if ((portValue == null) || (portValue.trim().equals(""))) {
837
838
839
840
841 cookie2.setPortAttributeBlank(true);
842 } else {
843 int[] ports = parsePortAttribute(portValue);
844 cookie2.setPorts(ports);
845 }
846 cookie2.setPortAttributeSpecified(true);
847 }
848 }
849
850 /***
851 * Validate cookie port attribute. If the Port attribute was specified
852 * in header, the request port must be in cookie's port list.
853 */
854 public void validate(final Cookie cookie, final CookieOrigin origin)
855 throws MalformedCookieException {
856 if (cookie == null) {
857 throw new IllegalArgumentException("Cookie may not be null");
858 }
859 if (origin == null) {
860 throw new IllegalArgumentException("Cookie origin may not be null");
861 }
862 if (cookie instanceof Cookie2) {
863 Cookie2 cookie2 = (Cookie2) cookie;
864 int port = origin.getPort();
865 if (cookie2.isPortAttributeSpecified()) {
866 if (!portMatch(port, cookie2.getPorts())) {
867 throw new MalformedCookieException(
868 "Port attribute violates RFC 2965: "
869 + "Request port not found in cookie's port list.");
870 }
871 }
872 }
873 }
874
875 /***
876 * Match cookie port attribute. If the Port attribute is not specified
877 * in header, the cookie can be sent to any port. Otherwise, the request port
878 * must be in the cookie's port list.
879 */
880 public boolean match(final Cookie cookie, final CookieOrigin origin) {
881 if (cookie == null) {
882 throw new IllegalArgumentException("Cookie may not be null");
883 }
884 if (origin == null) {
885 throw new IllegalArgumentException("Cookie origin may not be null");
886 }
887 if (cookie instanceof Cookie2) {
888 Cookie2 cookie2 = (Cookie2) cookie;
889 int port = origin.getPort();
890 if (cookie2.isPortAttributeSpecified()) {
891 if (cookie2.getPorts() == null) {
892 LOG.warn("Invalid cookie state: port not specified");
893 return false;
894 }
895 if (!portMatch(port, cookie2.getPorts())) {
896 return false;
897 }
898 }
899 return true;
900 } else {
901 return false;
902 }
903 }
904 }
905
906 /***
907 * <tt>"Max-age"</tt> cookie attribute handler for RFC 2965 cookie spec.
908 */
909 private class Cookie2MaxageAttributeHandler
910 implements CookieAttributeHandler {
911
912 /***
913 * Parse cookie max-age attribute.
914 */
915 public void parse(final Cookie cookie, final String value)
916 throws MalformedCookieException {
917 if (cookie == null) {
918 throw new IllegalArgumentException("Cookie may not be null");
919 }
920 if (value == null) {
921 throw new MalformedCookieException(
922 "Missing value for max-age attribute");
923 }
924 int age = -1;
925 try {
926 age = Integer.parseInt(value);
927 } catch (NumberFormatException e) {
928 age = -1;
929 }
930 if (age < 0) {
931 throw new MalformedCookieException ("Invalid max-age attribute.");
932 }
933 cookie.setExpiryDate(new Date(System.currentTimeMillis() + age * 1000L));
934 }
935
936 /***
937 * validate cookie max-age attribute.
938 */
939 public void validate(final Cookie cookie, final CookieOrigin origin) {
940 }
941
942 /***
943 * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
944 */
945 public boolean match(final Cookie cookie, final CookieOrigin origin) {
946 return true;
947 }
948
949 }
950
951 /***
952 * <tt>"Secure"</tt> cookie attribute handler for RFC 2965 cookie spec.
953 */
954 private class CookieSecureAttributeHandler
955 implements CookieAttributeHandler {
956
957 public void parse(final Cookie cookie, final String secure)
958 throws MalformedCookieException {
959 cookie.setSecure(true);
960 }
961
962 public void validate(final Cookie cookie, final CookieOrigin origin)
963 throws MalformedCookieException {
964 }
965
966 public boolean match(final Cookie cookie, final CookieOrigin origin) {
967 if (cookie == null) {
968 throw new IllegalArgumentException("Cookie may not be null");
969 }
970 if (origin == null) {
971 throw new IllegalArgumentException("Cookie origin may not be null");
972 }
973 return cookie.getSecure() == origin.isSecure();
974 }
975
976 }
977
978 /***
979 * <tt>"Commant"</tt> cookie attribute handler for RFC 2965 cookie spec.
980 */
981 private class CookieCommentAttributeHandler
982 implements CookieAttributeHandler {
983
984 public void parse(final Cookie cookie, final String comment)
985 throws MalformedCookieException {
986 cookie.setComment(comment);
987 }
988
989 public void validate(final Cookie cookie, final CookieOrigin origin)
990 throws MalformedCookieException {
991 }
992
993 public boolean match(final Cookie cookie, final CookieOrigin origin) {
994 return true;
995 }
996
997 }
998
999 /***
1000 * <tt>"CommantURL"</tt> cookie attribute handler for RFC 2965 cookie spec.
1001 */
1002 private class CookieCommentUrlAttributeHandler
1003 implements CookieAttributeHandler {
1004
1005 public void parse(final Cookie cookie, final String commenturl)
1006 throws MalformedCookieException {
1007 if (cookie instanceof Cookie2) {
1008 Cookie2 cookie2 = (Cookie2) cookie;
1009 cookie2.setCommentURL(commenturl);
1010 }
1011 }
1012
1013 public void validate(final Cookie cookie, final CookieOrigin origin)
1014 throws MalformedCookieException {
1015 }
1016
1017 public boolean match(final Cookie cookie, final CookieOrigin origin) {
1018 return true;
1019 }
1020
1021 }
1022
1023 /***
1024 * <tt>"Discard"</tt> cookie attribute handler for RFC 2965 cookie spec.
1025 */
1026 private class CookieDiscardAttributeHandler
1027 implements CookieAttributeHandler {
1028
1029 public void parse(final Cookie cookie, final String commenturl)
1030 throws MalformedCookieException {
1031 if (cookie instanceof Cookie2) {
1032 Cookie2 cookie2 = (Cookie2) cookie;
1033 cookie2.setDiscard(true);
1034 }
1035 }
1036
1037 public void validate(final Cookie cookie, final CookieOrigin origin)
1038 throws MalformedCookieException {
1039 }
1040
1041 public boolean match(final Cookie cookie, final CookieOrigin origin) {
1042 return true;
1043 }
1044
1045 }
1046
1047 /***
1048 * <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec.
1049 */
1050 private class Cookie2VersionAttributeHandler
1051 implements CookieAttributeHandler {
1052
1053 /***
1054 * Parse cookie version attribute.
1055 */
1056 public void parse(final Cookie cookie, final String value)
1057 throws MalformedCookieException {
1058 if (cookie == null) {
1059 throw new IllegalArgumentException("Cookie may not be null");
1060 }
1061 if (cookie instanceof Cookie2) {
1062 Cookie2 cookie2 = (Cookie2) cookie;
1063 if (value == null) {
1064 throw new MalformedCookieException(
1065 "Missing value for version attribute");
1066 }
1067 int version = -1;
1068 try {
1069 version = Integer.parseInt(value);
1070 } catch (NumberFormatException e) {
1071 version = -1;
1072 }
1073 if (version < 0) {
1074 throw new MalformedCookieException("Invalid cookie version.");
1075 }
1076 cookie2.setVersion(version);
1077 cookie2.setVersionAttributeSpecified(true);
1078 }
1079 }
1080
1081 /***
1082 * validate cookie version attribute. Version attribute is REQUIRED.
1083 */
1084 public void validate(final Cookie cookie, final CookieOrigin origin)
1085 throws MalformedCookieException {
1086 if (cookie == null) {
1087 throw new IllegalArgumentException("Cookie may not be null");
1088 }
1089 if (cookie instanceof Cookie2) {
1090 Cookie2 cookie2 = (Cookie2) cookie;
1091 if (!cookie2.isVersionAttributeSpecified()) {
1092 throw new MalformedCookieException(
1093 "Violates RFC 2965. Version attribute is required.");
1094 }
1095 }
1096 }
1097
1098 public boolean match(final Cookie cookie, final CookieOrigin origin) {
1099 return true;
1100 }
1101
1102 }
1103
1104 public int getVersion() {
1105 return 1;
1106 }
1107
1108 public Header getVersionHeader() {
1109 ParameterFormatter formatter = new ParameterFormatter();
1110 StringBuffer buffer = new StringBuffer();
1111 formatter.format(buffer, new NameValuePair("$Version",
1112 Integer.toString(getVersion())));
1113 return new Header("Cookie2", buffer.toString(), true);
1114 }
1115
1116 }
1117