Skip to content
Permalink
Browse files

RFC 6265 compliant cookie spec

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1646864 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information...
ok2c committed Dec 19, 2014
1 parent 88b56dd commit 70489c4bb03491b6ea0bec60904fc78782963a3a
Showing with 1,824 additions and 205 deletions.
  1. +9 −1 httpclient/src/main/java/org/apache/http/client/protocol/RequestAddCookies.java
  2. +2 −0 httpclient/src/main/java/org/apache/http/cookie/Cookie.java
  3. +2 −1 httpclient/src/main/java/org/apache/http/cookie/CookieOrigin.java
  4. +2 −0 httpclient/src/main/java/org/apache/http/cookie/CookiePathComparator.java
  5. +69 −0 httpclient/src/main/java/org/apache/http/cookie/CookiePriorityComparator.java
  6. +16 −0 httpclient/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java
  7. +44 −28 httpclient/src/main/java/org/apache/http/impl/cookie/BasicDomainHandler.java
  8. +2 −2 httpclient/src/main/java/org/apache/http/impl/cookie/BasicExpiresHandler.java
  9. +3 −3 httpclient/src/main/java/org/apache/http/impl/cookie/BasicMaxAgeHandler.java
  10. +24 −18 httpclient/src/main/java/org/apache/http/impl/cookie/BasicPathHandler.java
  11. +1 −1 httpclient/src/main/java/org/apache/http/impl/cookie/DefaultCookieSpecProvider.java
  12. +220 −0 httpclient/src/main/java/org/apache/http/impl/cookie/LaxExpiresHandler.java
  13. +79 −0 httpclient/src/main/java/org/apache/http/impl/cookie/LaxMaxAgeHandler.java
  14. +23 −3 httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDomainHandler.java
  15. +1 −3 httpclient/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java
  16. +276 −0 httpclient/src/main/java/org/apache/http/impl/cookie/RFC6265CookieSpecBase.java
  17. +120 −0 httpclient/src/main/java/org/apache/http/impl/cookie/RFC6265CookieSpecProvider.java
  18. +61 −0 httpclient/src/main/java/org/apache/http/impl/cookie/RFC6265LaxSpec.java
  19. +67 −0 httpclient/src/main/java/org/apache/http/impl/cookie/RFC6265StrictSpec.java
  20. +11 −2 httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAddCookies.java
  21. +113 −0 httpclient/src/test/java/org/apache/http/cookie/TestCookiePriorityComparator.java
  22. +0 −143 httpclient/src/test/java/org/apache/http/impl/cookie/TestAbstractCookieSpec.java
  23. +25 −0 httpclient/src/test/java/org/apache/http/impl/cookie/TestBasicCookieAttribHandlers.java
  24. +317 −0 httpclient/src/test/java/org/apache/http/impl/cookie/TestLaxCookieAttribHandlers.java
  25. +337 −0 httpclient/src/test/java/org/apache/http/impl/cookie/TestRFC6265CookieSpecBase.java
@@ -156,10 +156,11 @@ public void process(final HttpRequest request, final HttpContext context)
}
final CookieSpec cookieSpec = provider.create(clientContext);
// Get all cookies available in the HTTP state
final List<Cookie> cookies = new ArrayList<Cookie>(cookieStore.getCookies());
final List<Cookie> cookies = cookieStore.getCookies();
// Find cookies matching the given origin
final List<Cookie> matchedCookies = new ArrayList<Cookie>();
final Date now = new Date();
boolean expired = false;
for (final Cookie cookie : cookies) {
if (!cookie.isExpired(now)) {
if (cookieSpec.match(cookie, cookieOrigin)) {
@@ -172,8 +173,15 @@ public void process(final HttpRequest request, final HttpContext context)
if (this.log.isDebugEnabled()) {
this.log.debug("Cookie " + cookie + " expired");
}
expired = true;
}
}
// Per RFC 6265, 5.3
// The user agent must evict all expired cookies if, at any time, an expired cookie
// exists in the cookie store
if (expired) {
cookieStore.clearExpired(now);
}
// Generate Cookie request headers
if (!matchedCookies.isEmpty()) {
final List<Header> headers = cookieSpec.formatCookies(matchedCookies);
@@ -133,5 +133,7 @@
*/
boolean isExpired(final Date date);

//TODO: RFC 6265 requires cookies to track their creation time; add #getCreationDate()

}

@@ -30,6 +30,7 @@

import org.apache.http.annotation.Immutable;
import org.apache.http.util.Args;
import org.apache.http.util.TextUtils;

/**
* CookieOrigin class encapsulates details of an origin server that
@@ -52,7 +53,7 @@ public CookieOrigin(final String host, final int port, final String path, final
Args.notNull(path, "Path");
this.host = host.toLowerCase(Locale.ROOT);
this.port = port;
if (!path.trim().isEmpty()) {
if (!TextUtils.isBlank(path)) {
this.path = path;
} else {
this.path = "/";
@@ -50,6 +50,8 @@
@Immutable
public class CookiePathComparator implements Serializable, Comparator<Cookie> {

public static final CookiePathComparator INSTANCE = new CookiePathComparator();

private static final long serialVersionUID = 7523645369616405818L;

private String normalizePath(final Cookie cookie) {
@@ -0,0 +1,69 @@
/*
* ====================================================================
* 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.http.cookie;

import java.util.Comparator;
import java.util.Date;

import org.apache.http.annotation.Immutable;
import org.apache.http.impl.cookie.BasicClientCookie;

/**
* This cookie comparator ensures that cookies with longer paths take precedence over
* cookies with shorter path. Among cookies with equal path length cookies with ealier
* creation time take precedence over cookies with later creation time
*
* @since 4.4
*/
@Immutable
public class CookiePriorityComparator implements Comparator<Cookie> {

public static final CookiePriorityComparator INSTANCE = new CookiePriorityComparator();

private int getPathLength(final Cookie cookie) {
final String path = cookie.getPath();
return path != null ? path.length() : 1;
}

@Override
public int compare(final Cookie c1, final Cookie c2) {
final int l1 = getPathLength(c1);
final int l2 = getPathLength(c2);
//TODO: update this class once Cookie interface has been expended with #getCreationTime method
final int result = l2 - l1;
if (result == 0 && c1 instanceof BasicClientCookie && c2 instanceof BasicClientCookie) {
final Date d1 = ((BasicClientCookie) c1).getCreationDate();
final Date d2 = ((BasicClientCookie) c2).getCreationDate();
if (d1 != null && d2 != null) {
return (int) (d1.getTime() - d2.getTime());
}
}
return result;
}

}
@@ -304,6 +304,20 @@ public boolean isExpired(final Date date) {
&& cookieExpiryDate.getTime() <= date.getTime());
}

/**
* @since 4.4
*/
public Date getCreationDate() {
return creationDate;
}

/**
* @since 4.4
*/
public void setCreationDate(final Date creationDate) {
this.creationDate = creationDate;
}

public void setAttribute(final String name, final String value) {
this.attribs.put(name, value);
}
@@ -385,5 +399,7 @@ public String toString() {
/** The version of the cookie specification I was created from. */
private int cookieVersion;

private Date creationDate;

}

@@ -26,7 +26,10 @@
*/
package org.apache.http.impl.cookie;

import java.util.Locale;

import org.apache.http.annotation.Immutable;
import org.apache.http.conn.util.InetAddressUtils;
import org.apache.http.cookie.ClientCookie;
import org.apache.http.cookie.CommonCookieAttributeHandler;
import org.apache.http.cookie.Cookie;
@@ -35,6 +38,7 @@
import org.apache.http.cookie.MalformedCookieException;
import org.apache.http.cookie.SetCookie;
import org.apache.http.util.Args;
import org.apache.http.util.TextUtils;

/**
*
@@ -51,13 +55,19 @@ public BasicDomainHandler() {
public void parse(final SetCookie cookie, final String value)
throws MalformedCookieException {
Args.notNull(cookie, "Cookie");
if (value == null) {
throw new MalformedCookieException("Missing value for domain attribute");
if (TextUtils.isBlank(value)) {
throw new MalformedCookieException("Blank or null value for domain attribute");
}
// Ignore domain attributes ending with '.' per RFC 6265, 4.1.2.3
if (value.endsWith(".")) {
return;
}
if (value.trim().isEmpty()) {
throw new MalformedCookieException("Blank value for domain attribute");
String domain = value;
if (domain.startsWith(".")) {
domain = domain.substring(1);
}
cookie.setDomain(value);
domain = domain.toLowerCase(Locale.ROOT);
cookie.setDomain(domain);
}

@Override
@@ -71,32 +81,32 @@ public void validate(final Cookie cookie, final CookieOrigin origin)
// request-host and domain must be identical for the cookie to sent
// back to the origin-server.
final String host = origin.getHost();
String domain = cookie.getDomain();
final String domain = cookie.getDomain();
if (domain == null) {
throw new CookieRestrictionViolationException("Cookie domain may not be null");
throw new CookieRestrictionViolationException("Cookie 'domain' may not be null");
}
if (host.contains(".")) {
// Not required to have at least two dots. RFC 2965.
// A Set-Cookie2 with Domain=ajax.com will be accepted.
if (!host.equals(domain) && !domainMatch(domain, host)) {
throw new CookieRestrictionViolationException(
"Illegal 'domain' attribute \"" + domain + "\". Domain of origin: \"" + host + "\"");
}
}

// domain must match host
if (!host.endsWith(domain)) {
if (domain.startsWith(".")) {
domain = domain.substring(1, domain.length());
}
if (!host.equals(domain)) {
throw new CookieRestrictionViolationException(
"Illegal domain attribute \"" + domain
+ "\". Domain of origin: \"" + host + "\"");
}
static boolean domainMatch(final String domain, final String host) {
if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host)) {
return false;
}
final String normalizedDomain = domain.startsWith(".") ? domain.substring(1) : domain;
if (host.endsWith(normalizedDomain)) {
final int prefix = host.length() - normalizedDomain.length();
// Either a full match or a prefix endidng with a '.'
if (prefix == 0) {
return true;
}
} else {
if (!host.equals(domain)) {
throw new CookieRestrictionViolationException(
"Illegal domain attribute \"" + domain
+ "\". Domain of origin: \"" + host + "\"");
if (prefix > 1 && host.charAt(prefix - 1) == '.') {
return true;
}
}
return false;
}

@Override
@@ -108,13 +118,19 @@ public boolean match(final Cookie cookie, final CookieOrigin origin) {
if (domain == null) {
return false;
}
if (domain.startsWith(".")) {
domain = domain.substring(1);
}
domain = domain.toLowerCase(Locale.ROOT);
if (host.equals(domain)) {
return true;
}
if (!domain.startsWith(".")) {
domain = '.' + domain;
if (cookie instanceof ClientCookie) {
if (((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
return domainMatch(domain, host);
}
}
return host.endsWith(domain) || host.equals(domain.substring(1));
return false;
}

@Override
@@ -56,11 +56,11 @@ public void parse(final SetCookie cookie, final String value)
throws MalformedCookieException {
Args.notNull(cookie, "Cookie");
if (value == null) {
throw new MalformedCookieException("Missing value for expires attribute");
throw new MalformedCookieException("Missing value for 'expires' attribute");
}
final Date expiry = DateUtils.parseDate(value, this.datepatterns);
if (expiry == null) {
throw new MalformedCookieException("Unable to parse expires attribute: "
throw new MalformedCookieException("Invalid 'expires' attribute: "
+ value);
}
cookie.setExpiryDate(expiry);
@@ -51,17 +51,17 @@ public void parse(final SetCookie cookie, final String value)
throws MalformedCookieException {
Args.notNull(cookie, "Cookie");
if (value == null) {
throw new MalformedCookieException("Missing value for max-age attribute");
throw new MalformedCookieException("Missing value for 'max-age' attribute");
}
final int age;
try {
age = Integer.parseInt(value);
} catch (final NumberFormatException e) {
throw new MalformedCookieException ("Invalid max-age attribute: "
throw new MalformedCookieException ("Invalid 'max-age' attribute: "
+ value);
}
if (age < 0) {
throw new MalformedCookieException ("Negative max-age attribute: "
throw new MalformedCookieException ("Negative 'max-age' attribute: "
+ value);
}
cookie.setExpiryDate(new Date(System.currentTimeMillis() + age * 1000L));
@@ -60,32 +60,38 @@ public void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
if (!match(cookie, origin)) {
throw new CookieRestrictionViolationException(
"Illegal path attribute \"" + cookie.getPath()
"Illegal 'path' attribute \"" + cookie.getPath()
+ "\". Path of origin: \"" + origin.getPath() + "\"");
}
}

@Override
public boolean match(final Cookie cookie, final CookieOrigin origin) {
Args.notNull(cookie, "Cookie");
Args.notNull(origin, "Cookie origin");
final String targetpath = origin.getPath();
String topmostPath = cookie.getPath();
if (topmostPath == null) {
topmostPath = "/";
static boolean pathMatch(final String uriPath, final String cookiePath) {
String normalizedCookiePath = cookiePath;
if (normalizedCookiePath == null) {
normalizedCookiePath = "/";
}
if (topmostPath.length() > 1 && topmostPath.endsWith("/")) {
topmostPath = topmostPath.substring(0, topmostPath.length() - 1);
if (normalizedCookiePath.length() > 1 && normalizedCookiePath.endsWith("/")) {
normalizedCookiePath = normalizedCookiePath.substring(0, normalizedCookiePath.length() - 1);
}
boolean match = targetpath.startsWith (topmostPath);
// if there is a match and these values are not exactly the same we have
// to make sure we're not matcing "/foobar" and "/foo"
if (match && targetpath.length() != topmostPath.length()) {
if (!topmostPath.endsWith("/")) {
match = (targetpath.charAt(topmostPath.length()) == '/');
if (uriPath.startsWith(normalizedCookiePath)) {
if (normalizedCookiePath.equals("/")) {
return true;
}
if (uriPath.length() == normalizedCookiePath.length()) {
return true;
}
if (uriPath.charAt(normalizedCookiePath.length()) == '/') {
return true;
}
}
return match;
return false;
}

@Override
public boolean match(final Cookie cookie, final CookieOrigin origin) {
Args.notNull(cookie, "Cookie");
Args.notNull(origin, "Cookie origin");
return pathMatch(origin.getPath(), cookie.getPath());
}

@Override
@@ -38,7 +38,7 @@

/**
* {@link org.apache.http.cookie.CookieSpecProvider} implementation that provides an instance of
* {@link BestMatchSpec}. The instance returned by this factory can
* {@link org.apache.http.impl.cookie.DefaultCookieSpec}. The instance returned by this factory can
* be shared by multiple threads.
*
* @since 4.4

0 comments on commit 70489c4

Please sign in to comment.
You can’t perform that action at this time.