0001: /*
0002: * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/oac.hc3x/tags/HTTPCLIENT_3_1/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java $
0003: * $Revision: 507134 $
0004: * $Date: 2007-02-13 19:18:05 +0100 (Tue, 13 Feb 2007) $
0005: *
0006: * ====================================================================
0007: *
0008: * Licensed to the Apache Software Foundation (ASF) under one or more
0009: * contributor license agreements. See the NOTICE file distributed with
0010: * this work for additional information regarding copyright ownership.
0011: * The ASF licenses this file to You under the Apache License, Version 2.0
0012: * (the "License"); you may not use this file except in compliance with
0013: * the License. You may obtain a copy of the License at
0014: *
0015: * http://www.apache.org/licenses/LICENSE-2.0
0016: *
0017: * Unless required by applicable law or agreed to in writing, software
0018: * distributed under the License is distributed on an "AS IS" BASIS,
0019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0020: * See the License for the specific language governing permissions and
0021: * limitations under the License.
0022: * ====================================================================
0023: *
0024: * This software consists of voluntary contributions made by many
0025: * individuals on behalf of the Apache Software Foundation. For more
0026: * information on the Apache Software Foundation, please see
0027: * <http://www.apache.org/>.
0028: *
0029: */
0030:
0031: package org.apache.commons.httpclient.cookie;
0032:
0033: import java.util.ArrayList;
0034: import java.util.Arrays;
0035: import java.util.Comparator;
0036: import java.util.Date;
0037: import java.util.HashMap;
0038: import java.util.Iterator;
0039: import java.util.LinkedList;
0040: import java.util.List;
0041: import java.util.Map;
0042: import java.util.StringTokenizer;
0043:
0044: import org.apache.commons.httpclient.Cookie;
0045: import org.apache.commons.httpclient.Header;
0046: import org.apache.commons.httpclient.HeaderElement;
0047: import org.apache.commons.httpclient.NameValuePair;
0048: import org.apache.commons.httpclient.util.ParameterFormatter;
0049:
0050: /**
0051: * <p>RFC 2965 specific cookie management functions.</p>
0052: *
0053: * @author jain.samit@gmail.com (Samit Jain)
0054: *
0055: * @since 3.1
0056: */
0057: public class RFC2965Spec extends CookieSpecBase implements
0058: CookieVersionSupport {
0059:
0060: private static final Comparator PATH_COMPOARATOR = new CookiePathComparator();
0061:
0062: /**
0063: * Cookie Response Header name for cookies processed
0064: * by this spec.
0065: */
0066: public final static String SET_COOKIE2_KEY = "set-cookie2";
0067:
0068: /**
0069: * used for formatting RFC 2956 style cookies
0070: */
0071: private final ParameterFormatter formatter;
0072:
0073: /**
0074: * Stores the list of attribute handlers
0075: */
0076: private final List attribHandlerList;
0077:
0078: /**
0079: * Stores attribute name -> attribute handler mappings
0080: */
0081: private final Map attribHandlerMap;
0082:
0083: /**
0084: * Fallback cookie spec (RFC 2109)
0085: */
0086: private final CookieSpec rfc2109;
0087:
0088: /**
0089: * Default constructor
0090: * */
0091: public RFC2965Spec() {
0092: super ();
0093: this .formatter = new ParameterFormatter();
0094: this .formatter.setAlwaysUseQuotes(true);
0095: this .attribHandlerMap = new HashMap(10);
0096: this .attribHandlerList = new ArrayList(10);
0097: this .rfc2109 = new RFC2109Spec();
0098:
0099: registerAttribHandler(Cookie2.PATH,
0100: new Cookie2PathAttributeHandler());
0101: registerAttribHandler(Cookie2.DOMAIN,
0102: new Cookie2DomainAttributeHandler());
0103: registerAttribHandler(Cookie2.PORT,
0104: new Cookie2PortAttributeHandler());
0105: registerAttribHandler(Cookie2.MAXAGE,
0106: new Cookie2MaxageAttributeHandler());
0107: registerAttribHandler(Cookie2.SECURE,
0108: new CookieSecureAttributeHandler());
0109: registerAttribHandler(Cookie2.COMMENT,
0110: new CookieCommentAttributeHandler());
0111: registerAttribHandler(Cookie2.COMMENTURL,
0112: new CookieCommentUrlAttributeHandler());
0113: registerAttribHandler(Cookie2.DISCARD,
0114: new CookieDiscardAttributeHandler());
0115: registerAttribHandler(Cookie2.VERSION,
0116: new Cookie2VersionAttributeHandler());
0117: }
0118:
0119: protected void registerAttribHandler(final String name,
0120: final CookieAttributeHandler handler) {
0121: if (name == null) {
0122: throw new IllegalArgumentException(
0123: "Attribute name may not be null");
0124: }
0125: if (handler == null) {
0126: throw new IllegalArgumentException(
0127: "Attribute handler may not be null");
0128: }
0129: if (!this .attribHandlerList.contains(handler)) {
0130: this .attribHandlerList.add(handler);
0131: }
0132: this .attribHandlerMap.put(name, handler);
0133: }
0134:
0135: /**
0136: * Finds an attribute handler {@link CookieAttributeHandler} for the
0137: * given attribute. Returns <tt>null</tt> if no attribute handler is
0138: * found for the specified attribute.
0139: *
0140: * @param name attribute name. e.g. Domain, Path, etc.
0141: * @return an attribute handler or <tt>null</tt>
0142: */
0143: protected CookieAttributeHandler findAttribHandler(final String name) {
0144: return (CookieAttributeHandler) this .attribHandlerMap.get(name);
0145: }
0146:
0147: /**
0148: * Gets attribute handler {@link CookieAttributeHandler} for the
0149: * given attribute.
0150: *
0151: * @param name attribute name. e.g. Domain, Path, etc.
0152: * @throws IllegalStateException if handler not found for the
0153: * specified attribute.
0154: */
0155: protected CookieAttributeHandler getAttribHandler(final String name) {
0156: CookieAttributeHandler handler = findAttribHandler(name);
0157: if (handler == null) {
0158: throw new IllegalStateException(
0159: "Handler not registered for " + name
0160: + " attribute.");
0161: } else {
0162: return handler;
0163: }
0164: }
0165:
0166: protected Iterator getAttribHandlerIterator() {
0167: return this .attribHandlerList.iterator();
0168: }
0169:
0170: /**
0171: * Parses the Set-Cookie2 value into an array of <tt>Cookie</tt>s.
0172: *
0173: * <P>The syntax for the Set-Cookie2 response header is:
0174: *
0175: * <PRE>
0176: * set-cookie = "Set-Cookie2:" cookies
0177: * cookies = 1#cookie
0178: * cookie = NAME "=" VALUE * (";" cookie-av)
0179: * NAME = attr
0180: * VALUE = value
0181: * cookie-av = "Comment" "=" value
0182: * | "CommentURL" "=" <"> http_URL <">
0183: * | "Discard"
0184: * | "Domain" "=" value
0185: * | "Max-Age" "=" value
0186: * | "Path" "=" value
0187: * | "Port" [ "=" <"> portlist <"> ]
0188: * | "Secure"
0189: * | "Version" "=" 1*DIGIT
0190: * portlist = 1#portnum
0191: * portnum = 1*DIGIT
0192: * </PRE>
0193: *
0194: * @param host the host from which the <tt>Set-Cookie2</tt> value was
0195: * received
0196: * @param port the port from which the <tt>Set-Cookie2</tt> value was
0197: * received
0198: * @param path the path from which the <tt>Set-Cookie2</tt> value was
0199: * received
0200: * @param secure <tt>true</tt> when the <tt>Set-Cookie2</tt> value was
0201: * received over secure conection
0202: * @param header the <tt>Set-Cookie2</tt> <tt>Header</tt> received from the server
0203: * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie2 value
0204: * @throws MalformedCookieException if an exception occurs during parsing
0205: */
0206: public Cookie[] parse(String host, int port, String path,
0207: boolean secure, final Header header)
0208: throws MalformedCookieException {
0209: LOG.trace("enter RFC2965.parse("
0210: + "String, int, String, boolean, Header)");
0211:
0212: if (header == null) {
0213: throw new IllegalArgumentException(
0214: "Header may not be null.");
0215: }
0216: if (header.getName() == null) {
0217: throw new IllegalArgumentException(
0218: "Header name may not be null.");
0219: }
0220:
0221: if (header.getName().equalsIgnoreCase(SET_COOKIE2_KEY)) {
0222: // parse cookie2 cookies
0223: return parse(host, port, path, secure, header.getValue());
0224: } else if (header.getName().equalsIgnoreCase(
0225: RFC2109Spec.SET_COOKIE_KEY)) {
0226: // delegate parsing of old-style cookies to rfc2109Spec
0227: return this .rfc2109.parse(host, port, path, secure, header
0228: .getValue());
0229: } else {
0230: throw new MalformedCookieException(
0231: "Header name is not valid. "
0232: + "RFC 2965 supports \"set-cookie\" "
0233: + "and \"set-cookie2\" headers.");
0234: }
0235: }
0236:
0237: /**
0238: * @see #parse(String, int, String, boolean, org.apache.commons.httpclient.Header)
0239: */
0240: public Cookie[] parse(String host, int port, String path,
0241: boolean secure, final String header)
0242: throws MalformedCookieException {
0243: LOG.trace("enter RFC2965Spec.parse("
0244: + "String, int, String, boolean, String)");
0245:
0246: // before we do anything, lets check validity of arguments
0247: if (host == null) {
0248: throw new IllegalArgumentException(
0249: "Host of origin may not be null");
0250: }
0251: if (host.trim().equals("")) {
0252: throw new IllegalArgumentException(
0253: "Host of origin may not be blank");
0254: }
0255: if (port < 0) {
0256: throw new IllegalArgumentException("Invalid port: " + port);
0257: }
0258: if (path == null) {
0259: throw new IllegalArgumentException(
0260: "Path of origin may not be null.");
0261: }
0262: if (header == null) {
0263: throw new IllegalArgumentException(
0264: "Header may not be null.");
0265: }
0266:
0267: if (path.trim().equals("")) {
0268: path = PATH_DELIM;
0269: }
0270: host = getEffectiveHost(host);
0271:
0272: HeaderElement[] headerElements = HeaderElement
0273: .parseElements(header.toCharArray());
0274:
0275: List cookies = new LinkedList();
0276: for (int i = 0; i < headerElements.length; i++) {
0277: HeaderElement headerelement = headerElements[i];
0278: Cookie2 cookie = null;
0279: try {
0280: cookie = new Cookie2(host, headerelement.getName(),
0281: headerelement.getValue(), path, null, false,
0282: new int[] { port });
0283: } catch (IllegalArgumentException ex) {
0284: throw new MalformedCookieException(ex.getMessage());
0285: }
0286: NameValuePair[] parameters = headerelement.getParameters();
0287: // could be null. In case only a header element and no parameters.
0288: if (parameters != null) {
0289: // Eliminate duplicate attribues. The first occurence takes precedence
0290: Map attribmap = new HashMap(parameters.length);
0291: for (int j = parameters.length - 1; j >= 0; j--) {
0292: NameValuePair param = parameters[j];
0293: attribmap.put(param.getName().toLowerCase(), param);
0294: }
0295: for (Iterator it = attribmap.entrySet().iterator(); it
0296: .hasNext();) {
0297: Map.Entry entry = (Map.Entry) it.next();
0298: parseAttribute((NameValuePair) entry.getValue(),
0299: cookie);
0300: }
0301: }
0302: cookies.add(cookie);
0303: // cycle through the parameters
0304: }
0305: return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
0306: }
0307:
0308: /**
0309: * Parse RFC 2965 specific cookie attribute and update the corresponsing
0310: * {@link org.apache.commons.httpclient.Cookie} properties.
0311: *
0312: * @param attribute {@link org.apache.commons.httpclient.NameValuePair} cookie attribute from the
0313: * <tt>Set-Cookie2</tt> header.
0314: * @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated
0315: * @throws MalformedCookieException if an exception occurs during parsing
0316: */
0317: public void parseAttribute(final NameValuePair attribute,
0318: final Cookie cookie) throws MalformedCookieException {
0319: if (attribute == null) {
0320: throw new IllegalArgumentException(
0321: "Attribute may not be null.");
0322: }
0323: if (attribute.getName() == null) {
0324: throw new IllegalArgumentException(
0325: "Attribute Name may not be null.");
0326: }
0327: if (cookie == null) {
0328: throw new IllegalArgumentException(
0329: "Cookie may not be null.");
0330: }
0331: final String paramName = attribute.getName().toLowerCase();
0332: final String paramValue = attribute.getValue();
0333:
0334: CookieAttributeHandler handler = findAttribHandler(paramName);
0335: if (handler == null) {
0336: // ignore unknown attribute-value pairs
0337: if (LOG.isDebugEnabled())
0338: LOG.debug("Unrecognized cookie attribute: "
0339: + attribute.toString());
0340: } else {
0341: handler.parse(cookie, paramValue);
0342: }
0343: }
0344:
0345: /**
0346: * Performs RFC 2965 compliant {@link org.apache.commons.httpclient.Cookie} validation
0347: *
0348: * @param host the host from which the {@link org.apache.commons.httpclient.Cookie} was received
0349: * @param port the port from which the {@link org.apache.commons.httpclient.Cookie} was received
0350: * @param path the path from which the {@link org.apache.commons.httpclient.Cookie} was received
0351: * @param secure <tt>true</tt> when the {@link org.apache.commons.httpclient.Cookie} was received using a
0352: * secure connection
0353: * @param cookie The cookie to validate
0354: * @throws MalformedCookieException if an exception occurs during
0355: * validation
0356: */
0357: public void validate(final String host, int port,
0358: final String path, boolean secure, final Cookie cookie)
0359: throws MalformedCookieException {
0360:
0361: LOG.trace("enter RFC2965Spec.validate(String, int, String, "
0362: + "boolean, Cookie)");
0363:
0364: if (cookie instanceof Cookie2) {
0365: if (cookie.getName().indexOf(' ') != -1) {
0366: throw new MalformedCookieException(
0367: "Cookie name may not contain blanks");
0368: }
0369: if (cookie.getName().startsWith("$")) {
0370: throw new MalformedCookieException(
0371: "Cookie name may not start with $");
0372: }
0373: CookieOrigin origin = new CookieOrigin(
0374: getEffectiveHost(host), port, path, secure);
0375: for (Iterator i = getAttribHandlerIterator(); i.hasNext();) {
0376: CookieAttributeHandler handler = (CookieAttributeHandler) i
0377: .next();
0378: handler.validate(cookie, origin);
0379: }
0380: } else {
0381: // old-style cookies are validated according to the old rules
0382: this .rfc2109.validate(host, port, path, secure, cookie);
0383: }
0384: }
0385:
0386: /**
0387: * Return <tt>true</tt> if the cookie should be submitted with a request
0388: * with given attributes, <tt>false</tt> otherwise.
0389: * @param host the host to which the request is being submitted
0390: * @param port the port to which the request is being submitted (ignored)
0391: * @param path the path to which the request is being submitted
0392: * @param secure <tt>true</tt> if the request is using a secure connection
0393: * @return true if the cookie matches the criterium
0394: */
0395: public boolean match(String host, int port, String path,
0396: boolean secure, final Cookie cookie) {
0397:
0398: LOG.trace("enter RFC2965.match("
0399: + "String, int, String, boolean, Cookie");
0400: if (cookie == null) {
0401: throw new IllegalArgumentException("Cookie may not be null");
0402: }
0403: if (cookie instanceof Cookie2) {
0404: // check if cookie has expired
0405: if (cookie.isPersistent() && cookie.isExpired()) {
0406: return false;
0407: }
0408: CookieOrigin origin = new CookieOrigin(
0409: getEffectiveHost(host), port, path, secure);
0410: for (Iterator i = getAttribHandlerIterator(); i.hasNext();) {
0411: CookieAttributeHandler handler = (CookieAttributeHandler) i
0412: .next();
0413: if (!handler.match(cookie, origin)) {
0414: return false;
0415: }
0416: }
0417: return true;
0418: } else {
0419: // old-style cookies are matched according to the old rules
0420: return this .rfc2109.match(host, port, path, secure, cookie);
0421: }
0422: }
0423:
0424: private void doFormatCookie2(final Cookie2 cookie,
0425: final StringBuffer buffer) {
0426: String name = cookie.getName();
0427: String value = cookie.getValue();
0428: if (value == null) {
0429: value = "";
0430: }
0431: this .formatter.format(buffer, new NameValuePair(name, value));
0432: // format domain attribute
0433: if (cookie.getDomain() != null
0434: && cookie.isDomainAttributeSpecified()) {
0435: buffer.append("; ");
0436: this .formatter.format(buffer, new NameValuePair("$Domain",
0437: cookie.getDomain()));
0438: }
0439: // format path attribute
0440: if ((cookie.getPath() != null)
0441: && (cookie.isPathAttributeSpecified())) {
0442: buffer.append("; ");
0443: this .formatter.format(buffer, new NameValuePair("$Path",
0444: cookie.getPath()));
0445: }
0446: // format port attribute
0447: if (cookie.isPortAttributeSpecified()) {
0448: String portValue = "";
0449: if (!cookie.isPortAttributeBlank()) {
0450: portValue = createPortAttribute(cookie.getPorts());
0451: }
0452: buffer.append("; ");
0453: this .formatter.format(buffer, new NameValuePair("$Port",
0454: portValue));
0455: }
0456: }
0457:
0458: /**
0459: * Return a string suitable for sending in a <tt>"Cookie"</tt> header as
0460: * defined in RFC 2965
0461: * @param cookie a {@link org.apache.commons.httpclient.Cookie} to be formatted as string
0462: * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
0463: */
0464: public String formatCookie(final Cookie cookie) {
0465: LOG.trace("enter RFC2965Spec.formatCookie(Cookie)");
0466:
0467: if (cookie == null) {
0468: throw new IllegalArgumentException("Cookie may not be null");
0469: }
0470: if (cookie instanceof Cookie2) {
0471: Cookie2 cookie2 = (Cookie2) cookie;
0472: int version = cookie2.getVersion();
0473: final StringBuffer buffer = new StringBuffer();
0474: this .formatter.format(buffer, new NameValuePair("$Version",
0475: Integer.toString(version)));
0476: buffer.append("; ");
0477: doFormatCookie2(cookie2, buffer);
0478: return buffer.toString();
0479: } else {
0480: // old-style cookies are formatted according to the old rules
0481: return this .rfc2109.formatCookie(cookie);
0482: }
0483: }
0484:
0485: /**
0486: * Create a RFC 2965 compliant <tt>"Cookie"</tt> header value containing all
0487: * {@link org.apache.commons.httpclient.Cookie}s suitable for
0488: * sending in a <tt>"Cookie"</tt> header
0489: * @param cookies an array of {@link org.apache.commons.httpclient.Cookie}s to be formatted
0490: * @return a string suitable for sending in a Cookie header.
0491: */
0492: public String formatCookies(final Cookie[] cookies) {
0493: LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])");
0494:
0495: if (cookies == null) {
0496: throw new IllegalArgumentException(
0497: "Cookies may not be null");
0498: }
0499: // check if cookies array contains a set-cookie (old style) cookie
0500: boolean hasOldStyleCookie = false;
0501: int version = -1;
0502: for (int i = 0; i < cookies.length; i++) {
0503: Cookie cookie = cookies[i];
0504: if (!(cookie instanceof Cookie2)) {
0505: hasOldStyleCookie = true;
0506: break;
0507: }
0508: if (cookie.getVersion() > version) {
0509: version = cookie.getVersion();
0510: }
0511: }
0512: if (version < 0) {
0513: version = 0;
0514: }
0515: if (hasOldStyleCookie || version < 1) {
0516: // delegate old-style cookie formatting to rfc2109Spec
0517: return this .rfc2109.formatCookies(cookies);
0518: }
0519: // Arrange cookies by path
0520: Arrays.sort(cookies, PATH_COMPOARATOR);
0521:
0522: final StringBuffer buffer = new StringBuffer();
0523: // format cookie version
0524: this .formatter.format(buffer, new NameValuePair("$Version",
0525: Integer.toString(version)));
0526: for (int i = 0; i < cookies.length; i++) {
0527: buffer.append("; ");
0528: Cookie2 cookie = (Cookie2) cookies[i];
0529: // format cookie attributes
0530: doFormatCookie2(cookie, buffer);
0531: }
0532: return buffer.toString();
0533: }
0534:
0535: /**
0536: * Retrieves valid Port attribute value for the given ports array.
0537: * e.g. "8000,8001,8002"
0538: *
0539: * @param ports int array of ports
0540: */
0541: private String createPortAttribute(int[] ports) {
0542: StringBuffer portValue = new StringBuffer();
0543: for (int i = 0, len = ports.length; i < len; i++) {
0544: if (i > 0) {
0545: portValue.append(",");
0546: }
0547: portValue.append(ports[i]);
0548: }
0549: return portValue.toString();
0550: }
0551:
0552: /**
0553: * Parses the given Port attribute value (e.g. "8000,8001,8002")
0554: * into an array of ports.
0555: *
0556: * @param portValue port attribute value
0557: * @return parsed array of ports
0558: * @throws MalformedCookieException if there is a problem in
0559: * parsing due to invalid portValue.
0560: */
0561: private int[] parsePortAttribute(final String portValue)
0562: throws MalformedCookieException {
0563: StringTokenizer st = new StringTokenizer(portValue, ",");
0564: int[] ports = new int[st.countTokens()];
0565: try {
0566: int i = 0;
0567: while (st.hasMoreTokens()) {
0568: ports[i] = Integer.parseInt(st.nextToken().trim());
0569: if (ports[i] < 0) {
0570: throw new MalformedCookieException(
0571: "Invalid Port attribute.");
0572: }
0573: ++i;
0574: }
0575: } catch (NumberFormatException e) {
0576: throw new MalformedCookieException("Invalid Port "
0577: + "attribute: " + e.getMessage());
0578: }
0579: return ports;
0580: }
0581:
0582: /**
0583: * Gets 'effective host name' as defined in RFC 2965.
0584: * <p>
0585: * If a host name contains no dots, the effective host name is
0586: * that name with the string .local appended to it. Otherwise
0587: * the effective host name is the same as the host name. Note
0588: * that all effective host names contain at least one dot.
0589: *
0590: * @param host host name where cookie is received from or being sent to.
0591: * @return
0592: */
0593: private static String getEffectiveHost(final String host) {
0594: String effectiveHost = host.toLowerCase();
0595: if (host.indexOf('.') < 0) {
0596: effectiveHost += ".local";
0597: }
0598: return effectiveHost;
0599: }
0600:
0601: /**
0602: * Performs domain-match as defined by the RFC2965.
0603: * <p>
0604: * Host A's name domain-matches host B's if
0605: * <ol>
0606: * <ul>their host name strings string-compare equal; or</ul>
0607: * <ul>A is a HDN string and has the form NB, where N is a non-empty
0608: * name string, B has the form .B', and B' is a HDN string. (So,
0609: * x.y.com domain-matches .Y.com but not Y.com.)</ul>
0610: * </ol>
0611: *
0612: * @param host host name where cookie is received from or being sent to.
0613: * @param domain The cookie domain attribute.
0614: * @return true if the specified host matches the given domain.
0615: */
0616: public boolean domainMatch(String host, String domain) {
0617: boolean match = host.equals(domain)
0618: || (domain.startsWith(".") && host.endsWith(domain));
0619:
0620: return match;
0621: }
0622:
0623: /**
0624: * Returns <tt>true</tt> if the given port exists in the given
0625: * ports list.
0626: *
0627: * @param port port of host where cookie was received from or being sent to.
0628: * @param ports port list
0629: * @return true returns <tt>true</tt> if the given port exists in
0630: * the given ports list; <tt>false</tt> otherwise.
0631: */
0632: private boolean portMatch(int port, int[] ports) {
0633: boolean portInList = false;
0634: for (int i = 0, len = ports.length; i < len; i++) {
0635: if (port == ports[i]) {
0636: portInList = true;
0637: break;
0638: }
0639: }
0640: return portInList;
0641: }
0642:
0643: /**
0644: * <tt>"Path"</tt> attribute handler for RFC 2965 cookie spec.
0645: */
0646: private class Cookie2PathAttributeHandler implements
0647: CookieAttributeHandler {
0648:
0649: /**
0650: * Parse cookie path attribute.
0651: */
0652: public void parse(final Cookie cookie, final String path)
0653: throws MalformedCookieException {
0654: if (cookie == null) {
0655: throw new IllegalArgumentException(
0656: "Cookie may not be null");
0657: }
0658: if (path == null) {
0659: throw new MalformedCookieException(
0660: "Missing value for path attribute");
0661: }
0662: if (path.trim().equals("")) {
0663: throw new MalformedCookieException(
0664: "Blank value for path attribute");
0665: }
0666: cookie.setPath(path);
0667: cookie.setPathAttributeSpecified(true);
0668: }
0669:
0670: /**
0671: * Validate cookie path attribute. The value for the Path attribute must be a
0672: * prefix of the request-URI (case-sensitive matching).
0673: */
0674: public void validate(final Cookie cookie,
0675: final CookieOrigin origin)
0676: throws MalformedCookieException {
0677: if (cookie == null) {
0678: throw new IllegalArgumentException(
0679: "Cookie may not be null");
0680: }
0681: if (origin == null) {
0682: throw new IllegalArgumentException(
0683: "Cookie origin may not be null");
0684: }
0685: String path = origin.getPath();
0686: if (path == null) {
0687: throw new IllegalArgumentException(
0688: "Path of origin host may not be null.");
0689: }
0690: if (cookie.getPath() == null) {
0691: throw new MalformedCookieException(
0692: "Invalid cookie state: "
0693: + "path attribute is null.");
0694: }
0695: if (path.trim().equals("")) {
0696: path = PATH_DELIM;
0697: }
0698:
0699: if (!pathMatch(path, cookie.getPath())) {
0700: throw new MalformedCookieException(
0701: "Illegal path attribute \"" + cookie.getPath()
0702: + "\". Path of origin: \"" + path
0703: + "\"");
0704: }
0705: }
0706:
0707: /**
0708: * Match cookie path attribute. The value for the Path attribute must be a
0709: * prefix of the request-URI (case-sensitive matching).
0710: */
0711: public boolean match(final Cookie cookie,
0712: final CookieOrigin origin) {
0713: if (cookie == null) {
0714: throw new IllegalArgumentException(
0715: "Cookie may not be null");
0716: }
0717: if (origin == null) {
0718: throw new IllegalArgumentException(
0719: "Cookie origin may not be null");
0720: }
0721: String path = origin.getPath();
0722: if (cookie.getPath() == null) {
0723: LOG
0724: .warn("Invalid cookie state: path attribute is null.");
0725: return false;
0726: }
0727: if (path.trim().equals("")) {
0728: path = PATH_DELIM;
0729: }
0730:
0731: if (!pathMatch(path, cookie.getPath())) {
0732: return false;
0733: }
0734: return true;
0735: }
0736: }
0737:
0738: /**
0739: * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec.
0740: */
0741: private class Cookie2DomainAttributeHandler implements
0742: CookieAttributeHandler {
0743:
0744: /**
0745: * Parse cookie domain attribute.
0746: */
0747: public void parse(final Cookie cookie, String domain)
0748: throws MalformedCookieException {
0749: if (cookie == null) {
0750: throw new IllegalArgumentException(
0751: "Cookie may not be null");
0752: }
0753: if (domain == null) {
0754: throw new MalformedCookieException(
0755: "Missing value for domain attribute");
0756: }
0757: if (domain.trim().equals("")) {
0758: throw new MalformedCookieException(
0759: "Blank value for domain attribute");
0760: }
0761: domain = domain.toLowerCase();
0762: if (!domain.startsWith(".")) {
0763: // Per RFC 2965 section 3.2.2
0764: // "... If an explicitly specified value does not start with
0765: // a dot, the user agent supplies a leading dot ..."
0766: // That effectively implies that the domain attribute
0767: // MAY NOT be an IP address of a host name
0768: domain = "." + domain;
0769: }
0770: cookie.setDomain(domain);
0771: cookie.setDomainAttributeSpecified(true);
0772: }
0773:
0774: /**
0775: * Validate cookie domain attribute.
0776: */
0777: public void validate(final Cookie cookie,
0778: final CookieOrigin origin)
0779: throws MalformedCookieException {
0780: if (cookie == null) {
0781: throw new IllegalArgumentException(
0782: "Cookie may not be null");
0783: }
0784: if (origin == null) {
0785: throw new IllegalArgumentException(
0786: "Cookie origin may not be null");
0787: }
0788: String host = origin.getHost().toLowerCase();
0789: if (cookie.getDomain() == null) {
0790: throw new MalformedCookieException(
0791: "Invalid cookie state: "
0792: + "domain not specified");
0793: }
0794: String cookieDomain = cookie.getDomain().toLowerCase();
0795:
0796: if (cookie.isDomainAttributeSpecified()) {
0797: // Domain attribute must start with a dot
0798: if (!cookieDomain.startsWith(".")) {
0799: throw new MalformedCookieException(
0800: "Domain attribute \""
0801: + cookie.getDomain()
0802: + "\" violates RFC 2109: domain must start with a dot");
0803: }
0804:
0805: // Domain attribute must contain atleast one embedded dot,
0806: // or the value must be equal to .local.
0807: int dotIndex = cookieDomain.indexOf('.', 1);
0808: if (((dotIndex < 0) || (dotIndex == cookieDomain
0809: .length() - 1))
0810: && (!cookieDomain.equals(".local"))) {
0811: throw new MalformedCookieException(
0812: "Domain attribute \""
0813: + cookie.getDomain()
0814: + "\" violates RFC 2965: the value contains no embedded dots "
0815: + "and the value is not .local");
0816: }
0817:
0818: // The effective host name must domain-match domain attribute.
0819: if (!domainMatch(host, cookieDomain)) {
0820: throw new MalformedCookieException(
0821: "Domain attribute \""
0822: + cookie.getDomain()
0823: + "\" violates RFC 2965: effective host name does not "
0824: + "domain-match domain attribute.");
0825: }
0826:
0827: // effective host name minus domain must not contain any dots
0828: String effectiveHostWithoutDomain = host.substring(0,
0829: host.length() - cookieDomain.length());
0830: if (effectiveHostWithoutDomain.indexOf('.') != -1) {
0831: throw new MalformedCookieException(
0832: "Domain attribute \""
0833: + cookie.getDomain()
0834: + "\" violates RFC 2965: "
0835: + "effective host minus domain may not contain any dots");
0836: }
0837: } else {
0838: // Domain was not specified in header. In this case, domain must
0839: // string match request host (case-insensitive).
0840: if (!cookie.getDomain().equals(host)) {
0841: throw new MalformedCookieException(
0842: "Illegal domain attribute: \""
0843: + cookie.getDomain() + "\"."
0844: + "Domain of origin: \"" + host
0845: + "\"");
0846: }
0847: }
0848: }
0849:
0850: /**
0851: * Match cookie domain attribute.
0852: */
0853: public boolean match(final Cookie cookie,
0854: final CookieOrigin origin) {
0855: if (cookie == null) {
0856: throw new IllegalArgumentException(
0857: "Cookie may not be null");
0858: }
0859: if (origin == null) {
0860: throw new IllegalArgumentException(
0861: "Cookie origin may not be null");
0862: }
0863: String host = origin.getHost().toLowerCase();
0864: String cookieDomain = cookie.getDomain();
0865:
0866: // The effective host name MUST domain-match the Domain
0867: // attribute of the cookie.
0868: if (!domainMatch(host, cookieDomain)) {
0869: return false;
0870: }
0871: // effective host name minus domain must not contain any dots
0872: String effectiveHostWithoutDomain = host.substring(0, host
0873: .length()
0874: - cookieDomain.length());
0875: if (effectiveHostWithoutDomain.indexOf('.') != -1) {
0876: return false;
0877: }
0878: return true;
0879: }
0880:
0881: }
0882:
0883: /**
0884: * <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec.
0885: */
0886: private class Cookie2PortAttributeHandler implements
0887: CookieAttributeHandler {
0888:
0889: /**
0890: * Parse cookie port attribute.
0891: */
0892: public void parse(final Cookie cookie, final String portValue)
0893: throws MalformedCookieException {
0894: if (cookie == null) {
0895: throw new IllegalArgumentException(
0896: "Cookie may not be null");
0897: }
0898: if (cookie instanceof Cookie2) {
0899: Cookie2 cookie2 = (Cookie2) cookie;
0900: if ((portValue == null)
0901: || (portValue.trim().equals(""))) {
0902: // If the Port attribute is present but has no value, the
0903: // cookie can only be sent to the request-port.
0904: // Since the default port list contains only request-port, we don't
0905: // need to do anything here.
0906: cookie2.setPortAttributeBlank(true);
0907: } else {
0908: int[] ports = parsePortAttribute(portValue);
0909: cookie2.setPorts(ports);
0910: }
0911: cookie2.setPortAttributeSpecified(true);
0912: }
0913: }
0914:
0915: /**
0916: * Validate cookie port attribute. If the Port attribute was specified
0917: * in header, the request port must be in cookie's port list.
0918: */
0919: public void validate(final Cookie cookie,
0920: final CookieOrigin origin)
0921: throws MalformedCookieException {
0922: if (cookie == null) {
0923: throw new IllegalArgumentException(
0924: "Cookie may not be null");
0925: }
0926: if (origin == null) {
0927: throw new IllegalArgumentException(
0928: "Cookie origin may not be null");
0929: }
0930: if (cookie instanceof Cookie2) {
0931: Cookie2 cookie2 = (Cookie2) cookie;
0932: int port = origin.getPort();
0933: if (cookie2.isPortAttributeSpecified()) {
0934: if (!portMatch(port, cookie2.getPorts())) {
0935: throw new MalformedCookieException(
0936: "Port attribute violates RFC 2965: "
0937: + "Request port not found in cookie's port list.");
0938: }
0939: }
0940: }
0941: }
0942:
0943: /**
0944: * Match cookie port attribute. If the Port attribute is not specified
0945: * in header, the cookie can be sent to any port. Otherwise, the request port
0946: * must be in the cookie's port list.
0947: */
0948: public boolean match(final Cookie cookie,
0949: final CookieOrigin origin) {
0950: if (cookie == null) {
0951: throw new IllegalArgumentException(
0952: "Cookie may not be null");
0953: }
0954: if (origin == null) {
0955: throw new IllegalArgumentException(
0956: "Cookie origin may not be null");
0957: }
0958: if (cookie instanceof Cookie2) {
0959: Cookie2 cookie2 = (Cookie2) cookie;
0960: int port = origin.getPort();
0961: if (cookie2.isPortAttributeSpecified()) {
0962: if (cookie2.getPorts() == null) {
0963: LOG
0964: .warn("Invalid cookie state: port not specified");
0965: return false;
0966: }
0967: if (!portMatch(port, cookie2.getPorts())) {
0968: return false;
0969: }
0970: }
0971: return true;
0972: } else {
0973: return false;
0974: }
0975: }
0976: }
0977:
0978: /**
0979: * <tt>"Max-age"</tt> cookie attribute handler for RFC 2965 cookie spec.
0980: */
0981: private class Cookie2MaxageAttributeHandler implements
0982: CookieAttributeHandler {
0983:
0984: /**
0985: * Parse cookie max-age attribute.
0986: */
0987: public void parse(final Cookie cookie, final String value)
0988: throws MalformedCookieException {
0989: if (cookie == null) {
0990: throw new IllegalArgumentException(
0991: "Cookie may not be null");
0992: }
0993: if (value == null) {
0994: throw new MalformedCookieException(
0995: "Missing value for max-age attribute");
0996: }
0997: int age = -1;
0998: try {
0999: age = Integer.parseInt(value);
1000: } catch (NumberFormatException e) {
1001: age = -1;
1002: }
1003: if (age < 0) {
1004: throw new MalformedCookieException(
1005: "Invalid max-age attribute.");
1006: }
1007: cookie.setExpiryDate(new Date(System.currentTimeMillis()
1008: + age * 1000L));
1009: }
1010:
1011: /**
1012: * validate cookie max-age attribute.
1013: */
1014: public void validate(final Cookie cookie,
1015: final CookieOrigin origin) {
1016: }
1017:
1018: /**
1019: * @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
1020: */
1021: public boolean match(final Cookie cookie,
1022: final CookieOrigin origin) {
1023: return true;
1024: }
1025:
1026: }
1027:
1028: /**
1029: * <tt>"Secure"</tt> cookie attribute handler for RFC 2965 cookie spec.
1030: */
1031: private class CookieSecureAttributeHandler implements
1032: CookieAttributeHandler {
1033:
1034: public void parse(final Cookie cookie, final String secure)
1035: throws MalformedCookieException {
1036: cookie.setSecure(true);
1037: }
1038:
1039: public void validate(final Cookie cookie,
1040: final CookieOrigin origin)
1041: throws MalformedCookieException {
1042: }
1043:
1044: public boolean match(final Cookie cookie,
1045: final CookieOrigin origin) {
1046: if (cookie == null) {
1047: throw new IllegalArgumentException(
1048: "Cookie may not be null");
1049: }
1050: if (origin == null) {
1051: throw new IllegalArgumentException(
1052: "Cookie origin may not be null");
1053: }
1054: return cookie.getSecure() == origin.isSecure();
1055: }
1056:
1057: }
1058:
1059: /**
1060: * <tt>"Commant"</tt> cookie attribute handler for RFC 2965 cookie spec.
1061: */
1062: private class CookieCommentAttributeHandler implements
1063: CookieAttributeHandler {
1064:
1065: public void parse(final Cookie cookie, final String comment)
1066: throws MalformedCookieException {
1067: cookie.setComment(comment);
1068: }
1069:
1070: public void validate(final Cookie cookie,
1071: final CookieOrigin origin)
1072: throws MalformedCookieException {
1073: }
1074:
1075: public boolean match(final Cookie cookie,
1076: final CookieOrigin origin) {
1077: return true;
1078: }
1079:
1080: }
1081:
1082: /**
1083: * <tt>"CommantURL"</tt> cookie attribute handler for RFC 2965 cookie spec.
1084: */
1085: private class CookieCommentUrlAttributeHandler implements
1086: CookieAttributeHandler {
1087:
1088: public void parse(final Cookie cookie, final String commenturl)
1089: throws MalformedCookieException {
1090: if (cookie instanceof Cookie2) {
1091: Cookie2 cookie2 = (Cookie2) cookie;
1092: cookie2.setCommentURL(commenturl);
1093: }
1094: }
1095:
1096: public void validate(final Cookie cookie,
1097: final CookieOrigin origin)
1098: throws MalformedCookieException {
1099: }
1100:
1101: public boolean match(final Cookie cookie,
1102: final CookieOrigin origin) {
1103: return true;
1104: }
1105:
1106: }
1107:
1108: /**
1109: * <tt>"Discard"</tt> cookie attribute handler for RFC 2965 cookie spec.
1110: */
1111: private class CookieDiscardAttributeHandler implements
1112: CookieAttributeHandler {
1113:
1114: public void parse(final Cookie cookie, final String commenturl)
1115: throws MalformedCookieException {
1116: if (cookie instanceof Cookie2) {
1117: Cookie2 cookie2 = (Cookie2) cookie;
1118: cookie2.setDiscard(true);
1119: }
1120: }
1121:
1122: public void validate(final Cookie cookie,
1123: final CookieOrigin origin)
1124: throws MalformedCookieException {
1125: }
1126:
1127: public boolean match(final Cookie cookie,
1128: final CookieOrigin origin) {
1129: return true;
1130: }
1131:
1132: }
1133:
1134: /**
1135: * <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec.
1136: */
1137: private class Cookie2VersionAttributeHandler implements
1138: CookieAttributeHandler {
1139:
1140: /**
1141: * Parse cookie version attribute.
1142: */
1143: public void parse(final Cookie cookie, final String value)
1144: throws MalformedCookieException {
1145: if (cookie == null) {
1146: throw new IllegalArgumentException(
1147: "Cookie may not be null");
1148: }
1149: if (cookie instanceof Cookie2) {
1150: Cookie2 cookie2 = (Cookie2) cookie;
1151: if (value == null) {
1152: throw new MalformedCookieException(
1153: "Missing value for version attribute");
1154: }
1155: int version = -1;
1156: try {
1157: version = Integer.parseInt(value);
1158: } catch (NumberFormatException e) {
1159: version = -1;
1160: }
1161: if (version < 0) {
1162: throw new MalformedCookieException(
1163: "Invalid cookie version.");
1164: }
1165: cookie2.setVersion(version);
1166: cookie2.setVersionAttributeSpecified(true);
1167: }
1168: }
1169:
1170: /**
1171: * validate cookie version attribute. Version attribute is REQUIRED.
1172: */
1173: public void validate(final Cookie cookie,
1174: final CookieOrigin origin)
1175: throws MalformedCookieException {
1176: if (cookie == null) {
1177: throw new IllegalArgumentException(
1178: "Cookie may not be null");
1179: }
1180: if (cookie instanceof Cookie2) {
1181: Cookie2 cookie2 = (Cookie2) cookie;
1182: if (!cookie2.isVersionAttributeSpecified()) {
1183: throw new MalformedCookieException(
1184: "Violates RFC 2965. Version attribute is required.");
1185: }
1186: }
1187: }
1188:
1189: public boolean match(final Cookie cookie,
1190: final CookieOrigin origin) {
1191: return true;
1192: }
1193:
1194: }
1195:
1196: public int getVersion() {
1197: return 1;
1198: }
1199:
1200: public Header getVersionHeader() {
1201: ParameterFormatter formatter = new ParameterFormatter();
1202: StringBuffer buffer = new StringBuffer();
1203: formatter.format(buffer, new NameValuePair("$Version", Integer
1204: .toString(getVersion())));
1205: return new Header("Cookie2", buffer.toString(), true);
1206: }
1207:
1208: }
|