diff --git a/module-client/src/main/java/org/apache/http/cookie/SetCookie2.java b/module-client/src/main/java/org/apache/http/cookie/SetCookie2.java index 0de2a79aa..c2b09bfeb 100644 --- a/module-client/src/main/java/org/apache/http/cookie/SetCookie2.java +++ b/module-client/src/main/java/org/apache/http/cookie/SetCookie2.java @@ -39,7 +39,7 @@ package org.apache.http.cookie; * * @since 4.0 */ -public interface SetCookie2 extends Cookie { +public interface SetCookie2 extends SetCookie { /** * If a user agent (web browser) presents this cookie to a user, the @@ -53,5 +53,14 @@ public interface SetCookie2 extends Cookie { */ void setPorts(int[] ports); + /** + * Set the Discard attribute. + * + * Note: Discard attribute overrides Max-age. + * + * @see #isPersistent() + */ + void setDiscard(boolean discard); + } diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java b/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java index 431fad75a..d5579e968 100644 --- a/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java +++ b/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java @@ -44,6 +44,7 @@ public class BasicClientCookie2 extends BasicClientCookie implements SetCookie2 private String commentURL; private int[] ports; + private boolean discard; /** * Default Constructor taking a name and a value. The value may be null. @@ -71,5 +72,13 @@ public class BasicClientCookie2 extends BasicClientCookie implements SetCookie2 this.commentURL = commentURL; } + public void setDiscard(boolean discard) { + this.discard = discard; + } + + public boolean isPersistent() { + return !this.discard && super.isPersistent(); + } + } diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java new file mode 100644 index 000000000..bfd9a6643 --- /dev/null +++ b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java @@ -0,0 +1,35 @@ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * "CommantURL" cookie attribute handler for RFC 2965 cookie spec. + */ + public class RFC2965CommentUrlAttributeHandler implements CookieAttributeHandler { + + public RFC2965CommentUrlAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) 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; + } + + } \ No newline at end of file diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java new file mode 100644 index 000000000..0ba9c4b19 --- /dev/null +++ b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java @@ -0,0 +1,35 @@ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * "Discard" cookie attribute handler for RFC 2965 cookie spec. + */ + public class RFC2965DiscardAttributeHandler implements CookieAttributeHandler { + + public RFC2965DiscardAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) 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; + } + + } \ No newline at end of file diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java new file mode 100644 index 000000000..1d2cb716d --- /dev/null +++ b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java @@ -0,0 +1,165 @@ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +/** + * "Domain" cookie attribute handler for RFC 2965 cookie spec. + * + * @author jain.samit@gmail.com (Samit Jain) + * + * @since 3.1 + */ +public class RFC2965DomainAttributeHandler implements CookieAttributeHandler { + + public RFC2965DomainAttributeHandler() { + super(); + } + + /** + * Parse cookie domain attribute. + */ + public void parse(final SetCookie 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); + } + + /** + * Performs domain-match as defined by the RFC2965. + *

+ * Host A's name domain-matches host B's if + *

    + * + * + *
+ * + * @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; + } + + /** + * 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 instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + // 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 at least 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; + } + +} \ No newline at end of file diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java new file mode 100644 index 000000000..fd922f750 --- /dev/null +++ b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java @@ -0,0 +1,140 @@ +package org.apache.http.impl.cookie; + +import java.util.StringTokenizer; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * "Port" cookie attribute handler for RFC 2965 cookie spec. + */ +public class RFC2965PortAttributeHandler implements CookieAttributeHandler { + + /** + * @param spec + */ + public RFC2965PortAttributeHandler() { + super(); + } + + /** + * 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 static 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; + } + + /** + * Returns true 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 true if the given port exists in + * the given ports list; false otherwise. + */ + private static 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; + } + + /** + * Parse cookie port attribute. + */ + public void parse(final SetCookie cookie, final String portValue) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + if (portValue != null && !portValue.equals("")) { + int[] ports = parsePortAttribute(portValue); + cookie2.setPorts(ports); + } + } + } + + /** + * 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"); + } + int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (!portMatch(port, cookie.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"); + } + int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (cookie.getPorts() == null) { + // Invalid cookie state: port not specified + return false; + } + if (!portMatch(port, cookie.getPorts())) { + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java new file mode 100644 index 000000000..d6e18a5e2 --- /dev/null +++ b/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java @@ -0,0 +1,64 @@ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +/** + * "Version" cookie attribute handler for RFC 2965 cookie spec. + */ +public class RFC2965VersionAttributeHandler implements CookieAttributeHandler { + + public RFC2965VersionAttributeHandler() { + super(); + } + + /** + * Parse cookie version attribute. + */ + public void parse(final SetCookie 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 version attribute"); + } + int version = -1; + try { + version = Integer.parseInt(value); + } catch (NumberFormatException e) { + version = -1; + } + if (version < 0) { + throw new MalformedCookieException("Invalid cookie version."); + } + cookie.setVersion(version); + } + + /** + * 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 ClientCookie) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.VERSION_ATTR)) { + throw new MalformedCookieException( + "Violates RFC 2965. Version attribute is required."); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + +} \ No newline at end of file