/*
* $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/oac.hc3x/tags/HTTPCLIENT_3_1/src/java/org/apache/commons/httpclient/cookie/RFC2965Spec.java $
* $Revision: 507134 $
* $Date: 2007-02-13 19:18:05 +0100 (Tue, 13 Feb 2007) $
*
* ====================================================================
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.httpclient.cookie;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.util.ParameterFormatter;
/**
* <p>RFC 2965 specific cookie management functions.</p>
*
* @author [email protected] (Samit Jain)
*
* @since 3.1
*/
public class RFC2965Spec extends CookieSpecBase implements CookieVersionSupport {
private static final Comparator PATH_COMPOARATOR = new CookiePathComparator();
/**
* Cookie Response Header name for cookies processed
* by this spec.
*/
public final static String SET_COOKIE2_KEY = "set-cookie2";
/**
* used for formatting RFC 2956 style cookies
*/
private final ParameterFormatter formatter;
/**
* Stores the list of attribute handlers
*/
private final List attribHandlerList;
/**
* Stores attribute name -> attribute handler mappings
*/
private final Map attribHandlerMap;
/**
* Fallback cookie spec (RFC 2109)
*/
private final CookieSpec rfc2109;
/**
* Default constructor
* */
public RFC2965Spec() {
super();
this.formatter = new ParameterFormatter();
this.formatter.setAlwaysUseQuotes(true);
this.attribHandlerMap = new HashMap(10);
this.attribHandlerList = new ArrayList(10);
this.rfc2109 = new RFC2109Spec();
registerAttribHandler(Cookie2.PATH, new Cookie2PathAttributeHandler());
registerAttribHandler(Cookie2.DOMAIN, new Cookie2DomainAttributeHandler());
registerAttribHandler(Cookie2.PORT, new Cookie2PortAttributeHandler());
registerAttribHandler(Cookie2.MAXAGE, new Cookie2MaxageAttributeHandler());
registerAttribHandler(Cookie2.SECURE, new CookieSecureAttributeHandler());
registerAttribHandler(Cookie2.COMMENT, new CookieCommentAttributeHandler());
registerAttribHandler(Cookie2.COMMENTURL, new CookieCommentUrlAttributeHandler());
registerAttribHandler(Cookie2.DISCARD, new CookieDiscardAttributeHandler());
registerAttribHandler(Cookie2.VERSION, new Cookie2VersionAttributeHandler());
}
protected void registerAttribHandler(
final String name, final CookieAttributeHandler handler) {
if (name == null) {
throw new IllegalArgumentException("Attribute name may not be null");
}
if (handler == null) {
throw new IllegalArgumentException("Attribute handler may not be null");
}
if (!this.attribHandlerList.contains(handler)) {
this.attribHandlerList.add(handler);
}
this.attribHandlerMap.put(name, handler);
}
/**
* Finds an attribute handler {@link CookieAttributeHandler} for the
* given attribute. Returns <tt>null</tt> if no attribute handler is
* found for the specified attribute.
*
* @param name attribute name. e.g. Domain, Path, etc.
* @return an attribute handler or <tt>null</tt>
*/
protected CookieAttributeHandler findAttribHandler(final String name) {
return (CookieAttributeHandler) this.attribHandlerMap.get(name);
}
/**
* Gets attribute handler {@link CookieAttributeHandler} for the
* given attribute.
*
* @param name attribute name. e.g. Domain, Path, etc.
* @throws IllegalStateException if handler not found for the
* specified attribute.
*/
protected CookieAttributeHandler getAttribHandler(final String name) {
CookieAttributeHandler handler = findAttribHandler(name);
if (handler == null) {
throw new IllegalStateException("Handler not registered for " +
name + " attribute.");
} else {
return handler;
}
}
protected Iterator getAttribHandlerIterator() {
return this.attribHandlerList.iterator();
}
/**
* Parses the Set-Cookie2 value into an array of <tt>Cookie</tt>s.
*
* <P>The syntax for the Set-Cookie2 response header is:
*
* <PRE>
* set-cookie = "Set-Cookie2:" cookies
* cookies = 1#cookie
* cookie = NAME "=" VALUE * (";" cookie-av)
* NAME = attr
* VALUE = value
* cookie-av = "Comment" "=" value
* | "CommentURL" "=" <"> http_URL <">
* | "Discard"
* | "Domain" "=" value
* | "Max-Age" "=" value
* | "Path" "=" value
* | "Port" [ "=" <"> portlist <"> ]
* | "Secure"
* | "Version" "=" 1*DIGIT
* portlist = 1#portnum
* portnum = 1*DIGIT
* </PRE>
*
* @param host the host from which the <tt>Set-Cookie2</tt> value was
* received
* @param port the port from which the <tt>Set-Cookie2</tt> value was
* received
* @param path the path from which the <tt>Set-Cookie2</tt> value was
* received
* @param secure <tt>true</tt> when the <tt>Set-Cookie2</tt> value was
* received over secure conection
* @param header the <tt>Set-Cookie2</tt> <tt>Header</tt> received from the server
* @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie2 value
* @throws MalformedCookieException if an exception occurs during parsing
*/
public Cookie[] parse(
String host, int port, String path, boolean secure, final Header header)
throws MalformedCookieException {
LOG.trace("enter RFC2965.parse("
+ "String, int, String, boolean, Header)");
if (header == null) {
throw new IllegalArgumentException("Header may not be null.");
}
if (header.getName() == null) {
throw new IllegalArgumentException("Header name may not be null.");
}
if (header.getName().equalsIgnoreCase(SET_COOKIE2_KEY)) {
// parse cookie2 cookies
return parse(host, port, path, secure, header.getValue());
} else if (header.getName().equalsIgnoreCase(RFC2109Spec.SET_COOKIE_KEY)) {
// delegate parsing of old-style cookies to rfc2109Spec
return this.rfc2109.parse(host, port, path, secure, header.getValue());
} else {
throw new MalformedCookieException("Header name is not valid. " +
"RFC 2965 supports \"set-cookie\" " +
"and \"set-cookie2\" headers.");
}
}
/**
* @see #parse(String, int, String, boolean, org.apache.commons.httpclient.Header)
*/
public Cookie[] parse(String host, int port, String path,
boolean secure, final String header)
throws MalformedCookieException {
LOG.trace("enter RFC2965Spec.parse("
+ "String, int, String, boolean, String)");
// before we do anything, lets check validity of arguments
if (host == null) {
throw new IllegalArgumentException(
"Host of origin may not be null");
}
if (host.trim().equals("")) {
throw new IllegalArgumentException(
"Host of origin may not be blank");
}
if (port < 0) {
throw new IllegalArgumentException("Invalid port: " + port);
}
if (path == null) {
throw new IllegalArgumentException(
"Path of origin may not be null.");
}
if (header == null) {
throw new IllegalArgumentException("Header may not be null.");
}
if (path.trim().equals("")) {
path = PATH_DELIM;
}
host = getEffectiveHost(host);
HeaderElement[] headerElements =
HeaderElement.parseElements(header.toCharArray());
List cookies = new LinkedList();
for (int i = 0; i < headerElements.length; i++) {
HeaderElement headerelement = headerElements[i];
Cookie2 cookie = null;
try {
cookie = new Cookie2(host,
headerelement.getName(),
headerelement.getValue(),
path,
null,
false,
new int[] {port});
} catch (IllegalArgumentException ex) {
throw new MalformedCookieException(ex.getMessage());
}
NameValuePair[] parameters = headerelement.getParameters();
// could be null. In case only a header element and no parameters.
if (parameters != null) {
// Eliminate duplicate attribues. The first occurence takes precedence
Map attribmap = new HashMap(parameters.length);
for (int j = parameters.length - 1; j >= 0; j--) {
NameValuePair param = parameters[j];
attribmap.put(param.getName().toLowerCase(), param);
}
for (Iterator it = attribmap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
parseAttribute((NameValuePair) entry.getValue(), cookie);
}
}
cookies.add(cookie);
// cycle through the parameters
}
return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
}
/**
* Parse RFC 2965 specific cookie attribute and update the corresponsing
* {@link org.apache.commons.httpclient.Cookie} properties.
*
* @param attribute {@link org.apache.commons.httpclient.NameValuePair} cookie attribute from the
* <tt>Set-Cookie2</tt> header.
* @param cookie {@link org.apache.commons.httpclient.Cookie} to be updated
* @throws MalformedCookieException if an exception occurs during parsing
*/
public void parseAttribute(
final NameValuePair attribute, final Cookie cookie)
throws MalformedCookieException {
if (attribute == null) {
throw new IllegalArgumentException("Attribute may not be null.");
}
if (attribute.getName() == null) {
throw new IllegalArgumentException("Attribute Name may not be null.");
}
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null.");
}
final String paramName = attribute.getName().toLowerCase();
final String paramValue = attribute.getValue();
CookieAttributeHandler handler = findAttribHandler(paramName);
if (handler == null) {
// ignore unknown attribute-value pairs
if (LOG.isDebugEnabled())
LOG.debug("Unrecognized cookie attribute: " +
attribute.toString());
} else {
handler.parse(cookie, paramValue);
}
}
/**
* Performs RFC 2965 compliant {@link org.apache.commons.httpclient.Cookie} validation
*
* @param host the host from which the {@link org.apache.commons.httpclient.Cookie} was received
* @param port the port from which the {@link org.apache.commons.httpclient.Cookie} was received
* @param path the path from which the {@link org.apache.commons.httpclient.Cookie} was received
* @param secure <tt>true</tt> when the {@link org.apache.commons.httpclient.Cookie} was received using a
* secure connection
* @param cookie The cookie to validate
* @throws MalformedCookieException if an exception occurs during
* validation
*/
public void validate(final String host, int port, final String path,
boolean secure, final Cookie cookie)
throws MalformedCookieException {
LOG.trace("enter RFC2965Spec.validate(String, int, String, "
+ "boolean, Cookie)");
if (cookie instanceof Cookie2) {
if (cookie.getName().indexOf(' ') != -1) {
throw new MalformedCookieException("Cookie name may not contain blanks");
}
if (cookie.getName().startsWith("$")) {
throw new MalformedCookieException("Cookie name may not start with $");
}
CookieOrigin origin = new CookieOrigin(getEffectiveHost(host), port, path, secure);
for (Iterator i = getAttribHandlerIterator(); i.hasNext(); ) {
CookieAttributeHandler handler = (CookieAttributeHandler) i.next();
handler.validate(cookie, origin);
}
} else {
// old-style cookies are validated according to the old rules
this.rfc2109.validate(host, port, path, secure, cookie);
}
}
/**
* Return <tt>true</tt> if the cookie should be submitted with a request
* with given attributes, <tt>false</tt> otherwise.
* @param host the host to which the request is being submitted
* @param port the port to which the request is being submitted (ignored)
* @param path the path to which the request is being submitted
* @param secure <tt>true</tt> if the request is using a secure connection
* @return true if the cookie matches the criterium
*/
public boolean match(String host, int port, String path,
boolean secure, final Cookie cookie) {
LOG.trace("enter RFC2965.match("
+ "String, int, String, boolean, Cookie");
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (cookie instanceof Cookie2) {
// check if cookie has expired
if (cookie.isPersistent() && cookie.isExpired()) {
return false;
}
CookieOrigin origin = new CookieOrigin(getEffectiveHost(host), port, path, secure);
for (Iterator i = getAttribHandlerIterator(); i.hasNext(); ) {
CookieAttributeHandler handler = (CookieAttributeHandler) i.next();
if (!handler.match(cookie, origin)) {
return false;
}
}
return true;
} else {
// old-style cookies are matched according to the old rules
return this.rfc2109.match(host, port, path, secure, cookie);
}
}
private void doFormatCookie2(final Cookie2 cookie, final StringBuffer buffer) {
String name = cookie.getName();
String value = cookie.getValue();
if (value == null) {
value = "";
}
this.formatter.format(buffer, new NameValuePair(name, value));
// format domain attribute
if (cookie.getDomain() != null && cookie.isDomainAttributeSpecified()) {
buffer.append("; ");
this.formatter.format(buffer, new NameValuePair("$Domain", cookie.getDomain()));
}
// format path attribute
if ((cookie.getPath() != null) && (cookie.isPathAttributeSpecified())) {
buffer.append("; ");
this.formatter.format(buffer, new NameValuePair("$Path", cookie.getPath()));
}
// format port attribute
if (cookie.isPortAttributeSpecified()) {
String portValue = "";
if (!cookie.isPortAttributeBlank()) {
portValue = createPortAttribute(cookie.getPorts());
}
buffer.append("; ");
this.formatter.format(buffer, new NameValuePair("$Port", portValue));
}
}
/**
* Return a string suitable for sending in a <tt>"Cookie"</tt> header as
* defined in RFC 2965
* @param cookie a {@link org.apache.commons.httpclient.Cookie} to be formatted as string
* @return a string suitable for sending in a <tt>"Cookie"</tt> header.
*/
public String formatCookie(final Cookie cookie) {
LOG.trace("enter RFC2965Spec.formatCookie(Cookie)");
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
int version = cookie2.getVersion();
final StringBuffer buffer = new StringBuffer();
this.formatter.format(buffer, new NameValuePair("$Version", Integer.toString(version)));
buffer.append("; ");
doFormatCookie2(cookie2, buffer);
return buffer.toString();
} else {
// old-style cookies are formatted according to the old rules
return this.rfc2109.formatCookie(cookie);
}
}
/**
* Create a RFC 2965 compliant <tt>"Cookie"</tt> header value containing all
* {@link org.apache.commons.httpclient.Cookie}s suitable for
* sending in a <tt>"Cookie"</tt> header
* @param cookies an array of {@link org.apache.commons.httpclient.Cookie}s to be formatted
* @return a string suitable for sending in a Cookie header.
*/
public String formatCookies(final Cookie[] cookies) {
LOG.trace("enter RFC2965Spec.formatCookieHeader(Cookie[])");
if (cookies == null) {
throw new IllegalArgumentException("Cookies may not be null");
}
// check if cookies array contains a set-cookie (old style) cookie
boolean hasOldStyleCookie = false;
int version = -1;
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (!(cookie instanceof Cookie2)) {
hasOldStyleCookie = true;
break;
}
if (cookie.getVersion() > version) {
version = cookie.getVersion();
}
}
if (version < 0) {
version = 0;
}
if (hasOldStyleCookie || version < 1) {
// delegate old-style cookie formatting to rfc2109Spec
return this.rfc2109.formatCookies(cookies);
}
// Arrange cookies by path
Arrays.sort(cookies, PATH_COMPOARATOR);
final StringBuffer buffer = new StringBuffer();
// format cookie version
this.formatter.format(buffer, new NameValuePair("$Version", Integer.toString(version)));
for (int i = 0; i < cookies.length; i++) {
buffer.append("; ");
Cookie2 cookie = (Cookie2) cookies[i];
// format cookie attributes
doFormatCookie2(cookie, buffer);
}
return buffer.toString();
}
/**
* Retrieves valid Port attribute value for the given ports array.
* e.g. "8000,8001,8002"
*
* @param ports int array of ports
*/
private String createPortAttribute(int[] ports) {
StringBuffer portValue = new StringBuffer();
for (int i = 0, len = ports.length; i < len; i++) {
if (i > 0) {
portValue.append(",");
}
portValue.append(ports[i]);
}
return portValue.toString();
}
/**
* Parses the given Port attribute value (e.g. "8000,8001,8002")
* into an array of ports.
*
* @param portValue port attribute value
* @return parsed array of ports
* @throws MalformedCookieException if there is a problem in
* parsing due to invalid portValue.
*/
private int[] parsePortAttribute(final String portValue)
throws MalformedCookieException {
StringTokenizer st = new StringTokenizer(portValue, ",");
int[] ports = new int[st.countTokens()];
try {
int i = 0;
while(st.hasMoreTokens()) {
ports[i] = Integer.parseInt(st.nextToken().trim());
if (ports[i] < 0) {
throw new MalformedCookieException ("Invalid Port attribute.");
}
++i;
}
} catch (NumberFormatException e) {
throw new MalformedCookieException ("Invalid Port "
+ "attribute: " + e.getMessage());
}
return ports;
}
/**
* Gets 'effective host name' as defined in RFC 2965.
* <p>
* If a host name contains no dots, the effective host name is
* that name with the string .local appended to it. Otherwise
* the effective host name is the same as the host name. Note
* that all effective host names contain at least one dot.
*
* @param host host name where cookie is received from or being sent to.
* @return
*/
private static String getEffectiveHost(final String host) {
String effectiveHost = host.toLowerCase();
if (host.indexOf('.') < 0) {
effectiveHost += ".local";
}
return effectiveHost;
}
/**
* Performs domain-match as defined by the RFC2965.
* <p>
* Host A's name domain-matches host B's if
* <ol>
* <ul>their host name strings string-compare equal; or</ul>
* <ul>A is a HDN string and has the form NB, where N is a non-empty
* name string, B has the form .B', and B' is a HDN string. (So,
* x.y.com domain-matches .Y.com but not Y.com.)</ul>
* </ol>
*
* @param host host name where cookie is received from or being sent to.
* @param domain The cookie domain attribute.
* @return true if the specified host matches the given domain.
*/
public boolean domainMatch(String host, String domain) {
boolean match = host.equals(domain)
|| (domain.startsWith(".") && host.endsWith(domain));
return match;
}
/**
* Returns <tt>true</tt> if the given port exists in the given
* ports list.
*
* @param port port of host where cookie was received from or being sent to.
* @param ports port list
* @return true returns <tt>true</tt> if the given port exists in
* the given ports list; <tt>false</tt> otherwise.
*/
private boolean portMatch(int port, int[] ports) {
boolean portInList = false;
for (int i = 0, len = ports.length; i < len; i++) {
if (port == ports[i]) {
portInList = true;
break;
}
}
return portInList;
}
/**
* <tt>"Path"</tt> attribute handler for RFC 2965 cookie spec.
*/
private class Cookie2PathAttributeHandler
implements CookieAttributeHandler {
/**
* Parse cookie path attribute.
*/
public void parse(final Cookie cookie, final String path)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (path == null) {
throw new MalformedCookieException(
"Missing value for path attribute");
}
if (path.trim().equals("")) {
throw new MalformedCookieException(
"Blank value for path attribute");
}
cookie.setPath(path);
cookie.setPathAttributeSpecified(true);
}
/**
* Validate cookie path attribute. The value for the Path attribute must be a
* prefix of the request-URI (case-sensitive matching).
*/
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
String path = origin.getPath();
if (path == null) {
throw new IllegalArgumentException(
"Path of origin host may not be null.");
}
if (cookie.getPath() == null) {
throw new MalformedCookieException("Invalid cookie state: " +
"path attribute is null.");
}
if (path.trim().equals("")) {
path = PATH_DELIM;
}
if (!pathMatch(path, cookie.getPath())) {
throw new MalformedCookieException(
"Illegal path attribute \"" + cookie.getPath()
+ "\". Path of origin: \"" + path + "\"");
}
}
/**
* Match cookie path attribute. The value for the Path attribute must be a
* prefix of the request-URI (case-sensitive matching).
*/
public boolean match(final Cookie cookie, final CookieOrigin origin) {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
String path = origin.getPath();
if (cookie.getPath() == null) {
LOG.warn("Invalid cookie state: path attribute is null.");
return false;
}
if (path.trim().equals("")) {
path = PATH_DELIM;
}
if (!pathMatch(path, cookie.getPath())) {
return false;
}
return true;
}
}
/**
* <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class Cookie2DomainAttributeHandler
implements CookieAttributeHandler {
/**
* Parse cookie domain attribute.
*/
public void parse(final Cookie cookie, String domain)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (domain == null) {
throw new MalformedCookieException(
"Missing value for domain attribute");
}
if (domain.trim().equals("")) {
throw new MalformedCookieException(
"Blank value for domain attribute");
}
domain = domain.toLowerCase();
if (!domain.startsWith(".")) {
// Per RFC 2965 section 3.2.2
// "... If an explicitly specified value does not start with
// a dot, the user agent supplies a leading dot ..."
// That effectively implies that the domain attribute
// MAY NOT be an IP address of a host name
domain = "." + domain;
}
cookie.setDomain(domain);
cookie.setDomainAttributeSpecified(true);
}
/**
* Validate cookie domain attribute.
*/
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
String host = origin.getHost().toLowerCase();
if (cookie.getDomain() == null) {
throw new MalformedCookieException("Invalid cookie state: " +
"domain not specified");
}
String cookieDomain = cookie.getDomain().toLowerCase();
if (cookie.isDomainAttributeSpecified()) {
// Domain attribute must start with a dot
if (!cookieDomain.startsWith(".")) {
throw new MalformedCookieException("Domain attribute \"" +
cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot");
}
// Domain attribute must contain atleast one embedded dot,
// or the value must be equal to .local.
int dotIndex = cookieDomain.indexOf('.', 1);
if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1))
&& (!cookieDomain.equals(".local"))) {
throw new MalformedCookieException(
"Domain attribute \"" + cookie.getDomain()
+ "\" violates RFC 2965: the value contains no embedded dots "
+ "and the value is not .local");
}
// The effective host name must domain-match domain attribute.
if (!domainMatch(host, cookieDomain)) {
throw new MalformedCookieException(
"Domain attribute \"" + cookie.getDomain()
+ "\" violates RFC 2965: effective host name does not "
+ "domain-match domain attribute.");
}
// effective host name minus domain must not contain any dots
String effectiveHostWithoutDomain = host.substring(
0, host.length() - cookieDomain.length());
if (effectiveHostWithoutDomain.indexOf('.') != -1) {
throw new MalformedCookieException("Domain attribute \""
+ cookie.getDomain() + "\" violates RFC 2965: "
+ "effective host minus domain may not contain any dots");
}
} else {
// Domain was not specified in header. In this case, domain must
// string match request host (case-insensitive).
if (!cookie.getDomain().equals(host)) {
throw new MalformedCookieException("Illegal domain attribute: \""
+ cookie.getDomain() + "\"."
+ "Domain of origin: \""
+ host + "\"");
}
}
}
/**
* Match cookie domain attribute.
*/
public boolean match(final Cookie cookie, final CookieOrigin origin) {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
String host = origin.getHost().toLowerCase();
String cookieDomain = cookie.getDomain();
// The effective host name MUST domain-match the Domain
// attribute of the cookie.
if (!domainMatch(host, cookieDomain)) {
return false;
}
// effective host name minus domain must not contain any dots
String effectiveHostWithoutDomain = host.substring(
0, host.length() - cookieDomain.length());
if (effectiveHostWithoutDomain.indexOf('.') != -1) {
return false;
}
return true;
}
}
/**
* <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class Cookie2PortAttributeHandler
implements CookieAttributeHandler {
/**
* Parse cookie port attribute.
*/
public void parse(final Cookie cookie, final String portValue)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
if ((portValue == null) || (portValue.trim().equals(""))) {
// If the Port attribute is present but has no value, the
// cookie can only be sent to the request-port.
// Since the default port list contains only request-port, we don't
// need to do anything here.
cookie2.setPortAttributeBlank(true);
} else {
int[] ports = parsePortAttribute(portValue);
cookie2.setPorts(ports);
}
cookie2.setPortAttributeSpecified(true);
}
}
/**
* Validate cookie port attribute. If the Port attribute was specified
* in header, the request port must be in cookie's port list.
*/
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
int port = origin.getPort();
if (cookie2.isPortAttributeSpecified()) {
if (!portMatch(port, cookie2.getPorts())) {
throw new MalformedCookieException(
"Port attribute violates RFC 2965: "
+ "Request port not found in cookie's port list.");
}
}
}
}
/**
* Match cookie port attribute. If the Port attribute is not specified
* in header, the cookie can be sent to any port. Otherwise, the request port
* must be in the cookie's port list.
*/
public boolean match(final Cookie cookie, final CookieOrigin origin) {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
int port = origin.getPort();
if (cookie2.isPortAttributeSpecified()) {
if (cookie2.getPorts() == null) {
LOG.warn("Invalid cookie state: port not specified");
return false;
}
if (!portMatch(port, cookie2.getPorts())) {
return false;
}
}
return true;
} else {
return false;
}
}
}
/**
* <tt>"Max-age"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class Cookie2MaxageAttributeHandler
implements CookieAttributeHandler {
/**
* Parse cookie max-age attribute.
*/
public void parse(final Cookie cookie, final String value)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (value == null) {
throw new MalformedCookieException(
"Missing value for max-age attribute");
}
int age = -1;
try {
age = Integer.parseInt(value);
} catch (NumberFormatException e) {
age = -1;
}
if (age < 0) {
throw new MalformedCookieException ("Invalid max-age attribute.");
}
cookie.setExpiryDate(new Date(System.currentTimeMillis() + age * 1000L));
}
/**
* validate cookie max-age attribute.
*/
public void validate(final Cookie cookie, final CookieOrigin origin) {
}
/**
* @see CookieAttributeHandler#match(org.apache.commons.httpclient.Cookie, String)
*/
public boolean match(final Cookie cookie, final CookieOrigin origin) {
return true;
}
}
/**
* <tt>"Secure"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class CookieSecureAttributeHandler
implements CookieAttributeHandler {
public void parse(final Cookie cookie, final String secure)
throws MalformedCookieException {
cookie.setSecure(true);
}
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
}
public boolean match(final Cookie cookie, final CookieOrigin origin) {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (origin == null) {
throw new IllegalArgumentException("Cookie origin may not be null");
}
return cookie.getSecure() == origin.isSecure();
}
}
/**
* <tt>"Commant"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class CookieCommentAttributeHandler
implements CookieAttributeHandler {
public void parse(final Cookie cookie, final String comment)
throws MalformedCookieException {
cookie.setComment(comment);
}
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
}
public boolean match(final Cookie cookie, final CookieOrigin origin) {
return true;
}
}
/**
* <tt>"CommantURL"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class CookieCommentUrlAttributeHandler
implements CookieAttributeHandler {
public void parse(final Cookie cookie, final String commenturl)
throws MalformedCookieException {
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
cookie2.setCommentURL(commenturl);
}
}
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
}
public boolean match(final Cookie cookie, final CookieOrigin origin) {
return true;
}
}
/**
* <tt>"Discard"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class CookieDiscardAttributeHandler
implements CookieAttributeHandler {
public void parse(final Cookie cookie, final String commenturl)
throws MalformedCookieException {
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
cookie2.setDiscard(true);
}
}
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
}
public boolean match(final Cookie cookie, final CookieOrigin origin) {
return true;
}
}
/**
* <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec.
*/
private class Cookie2VersionAttributeHandler
implements CookieAttributeHandler {
/**
* Parse cookie version attribute.
*/
public void parse(final Cookie cookie, final String value)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
if (value == null) {
throw new MalformedCookieException(
"Missing value for version attribute");
}
int version = -1;
try {
version = Integer.parseInt(value);
} catch (NumberFormatException e) {
version = -1;
}
if (version < 0) {
throw new MalformedCookieException("Invalid cookie version.");
}
cookie2.setVersion(version);
cookie2.setVersionAttributeSpecified(true);
}
}
/**
* validate cookie version attribute. Version attribute is REQUIRED.
*/
public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
if (cookie == null) {
throw new IllegalArgumentException("Cookie may not be null");
}
if (cookie instanceof Cookie2) {
Cookie2 cookie2 = (Cookie2) cookie;
if (!cookie2.isVersionAttributeSpecified()) {
throw new MalformedCookieException(
"Violates RFC 2965. Version attribute is required.");
}
}
}
public boolean match(final Cookie cookie, final CookieOrigin origin) {
return true;
}
}
public int getVersion() {
return 1;
}
public Header getVersionHeader() {
ParameterFormatter formatter = new ParameterFormatter();
StringBuffer buffer = new StringBuffer();
formatter.format(buffer, new NameValuePair("$Version",
Integer.toString(getVersion())));
return new Header("Cookie2", buffer.toString(), true);
}
}
|