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