Jetty 12 - New HTTP Cookie interface (#9205)
Convert HttpCookie to an interface.
This commit is contained in:
parent
b2bd8b969c
commit
50a88187fa
|
@ -77,7 +77,7 @@ public class HttpClientIdleTimeoutTest
|
|||
if (cookies == null || cookies.size() == 0)
|
||||
{
|
||||
// Send a cookie in the first response.
|
||||
Response.addCookie(response, new HttpCookie("name", "value"));
|
||||
Response.addCookie(response, HttpCookie.from("name", "value"));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.net.URI;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -48,7 +49,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
protected void service(Request request, org.eclipse.jetty.server.Response response)
|
||||
{
|
||||
org.eclipse.jetty.server.Response.addCookie(response, new org.eclipse.jetty.http.HttpCookie(name, value));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, org.eclipse.jetty.http.HttpCookie.from(name, value));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -179,7 +180,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue);
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue);
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -234,7 +235,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo/bar".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue);
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue);
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -290,7 +291,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, null, "/foo/bar");
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo/bar"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -346,7 +347,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo/bar".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, null, "/foo");
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -403,9 +404,9 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue1, null, "/foo");
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue1, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue2, null, "/foo");
|
||||
cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue2, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -462,9 +463,9 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue1, null, "/foo");
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue1, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue2, null, "/bar");
|
||||
cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue2, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/bar"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -528,9 +529,9 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue1, null, "/foo");
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue1, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue2, null, "/foo/bar");
|
||||
cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue2, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo/bar"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
@ -598,7 +599,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
|
|||
int r = (int)request.getHeaders().getLongField(headerName);
|
||||
if ("/foo/bar".equals(target) && r == 0)
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, null, "/foo/");
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue, Map.of(org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, "/foo/"));
|
||||
org.eclipse.jetty.server.Response.addCookie(response, cookie);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -15,8 +15,10 @@ package org.eclipse.jetty.http;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -48,7 +50,16 @@ public class CookieCache
|
|||
@Override
|
||||
protected void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment)
|
||||
{
|
||||
_cookieList.add(new HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath, -1, false, false, cookieComment, cookieVersion));
|
||||
if (StringUtil.isEmpty(cookieDomain) && StringUtil.isEmpty(cookiePath) && cookieVersion <= 0 && StringUtil.isEmpty(cookieComment))
|
||||
_cookieList.add(HttpCookie.from(cookieName, cookieValue));
|
||||
else
|
||||
{
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put(HttpCookie.DOMAIN_ATTRIBUTE, cookieDomain);
|
||||
attributes.put(HttpCookie.PATH_ATTRIBUTE, cookiePath);
|
||||
attributes.put(HttpCookie.COMMENT_ATTRIBUTE, cookieComment);
|
||||
_cookieList.add(HttpCookie.from(cookieName, cookieValue, cookieVersion, attributes));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,44 +18,48 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.NanoTime;
|
||||
import org.eclipse.jetty.util.Index;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Jetty Management of RFC6265 HTTP Cookies (with fallback support for RFC2965)
|
||||
*/
|
||||
public class HttpCookie
|
||||
public interface HttpCookie
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpCookie.class);
|
||||
Logger LOG = LoggerFactory.getLogger(HttpCookie.class);
|
||||
|
||||
private static final String __COOKIE_DELIM = "\",;\\ \t";
|
||||
private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
|
||||
String __COOKIE_DELIM = "\",;\\ \t";
|
||||
String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
|
||||
|
||||
/**
|
||||
* If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
|
||||
**/
|
||||
public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
|
||||
/**
|
||||
* These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
|
||||
**/
|
||||
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
|
||||
public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
|
||||
public static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
|
||||
public static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
|
||||
String COMMENT_ATTRIBUTE = "Comment";
|
||||
String DOMAIN_ATTRIBUTE = "Domain";
|
||||
String HTTP_ONLY_ATTRIBUTE = "HttpOnly";
|
||||
String MAX_AGE_ATTRIBUTE = "Max-Age";
|
||||
String PATH_ATTRIBUTE = "Path";
|
||||
String SAME_SITE_ATTRIBUTE = "SameSite";
|
||||
String SECURE_ATTRIBUTE = "Secure";
|
||||
Index<String> KNOWN_ATTRIBUTES = new Index.Builder<String>().caseSensitive(false)
|
||||
.with(COMMENT_ATTRIBUTE)
|
||||
.with(DOMAIN_ATTRIBUTE)
|
||||
.with(HTTP_ONLY_ATTRIBUTE)
|
||||
.with(MAX_AGE_ATTRIBUTE)
|
||||
.with(PATH_ATTRIBUTE)
|
||||
.with(SAME_SITE_ATTRIBUTE)
|
||||
.with(SECURE_ATTRIBUTE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Name of context attribute with default SameSite cookie value
|
||||
*/
|
||||
public static final String SAME_SITE_DEFAULT_ATTRIBUTE = "org.eclipse.jetty.cookie.sameSiteDefault";
|
||||
String SAME_SITE_DEFAULT_ATTRIBUTE = "org.eclipse.jetty.cookie.sameSiteDefault";
|
||||
|
||||
public enum SameSite
|
||||
enum SameSite
|
||||
{
|
||||
NONE("None"),
|
||||
STRICT("Strict"),
|
||||
|
@ -72,152 +76,31 @@ public class HttpCookie
|
|||
{
|
||||
return this.attributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Names of well-known Attributes that are parsed by the constructors and shouldn't be present in the stored {@link #getAttributes()} Map.
|
||||
*/
|
||||
private static final List<String> PARSED_ATTRIBUTE_NAMES = List.of("Domain", "Path", "Max-Age", "HttpOnly", "Secure", "Comment");
|
||||
private static final Index<SameSite> CACHE = new Index.Builder<SameSite>()
|
||||
.caseSensitive(false)
|
||||
.with(NONE.attributeValue, NONE)
|
||||
.with(STRICT.attributeValue, STRICT)
|
||||
.with(LAX.attributeValue, LAX)
|
||||
.build();
|
||||
|
||||
private final String _name;
|
||||
private final String _value;
|
||||
private final String _comment;
|
||||
private final String _domain;
|
||||
private final long _maxAge;
|
||||
private final String _path;
|
||||
private final boolean _secure;
|
||||
private final int _version;
|
||||
private final boolean _httpOnly;
|
||||
private final long _expiration;
|
||||
private final Map<String, String> _attributes;
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
*/
|
||||
public HttpCookie(String name, String value)
|
||||
{
|
||||
this(name, value, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
|
||||
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
|
||||
*/
|
||||
public HttpCookie(String name, String value, String domain, String path)
|
||||
{
|
||||
this(name, value, domain, path, -1, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
|
||||
*/
|
||||
public HttpCookie(String name, String value, long maxAge)
|
||||
{
|
||||
this(name, value, null, null, maxAge, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
|
||||
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
|
||||
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
|
||||
* @param httpOnly the {@code HttpOnly} attribute of the cookie
|
||||
* @param secure the {@code Secure} attribute of the cookie
|
||||
*/
|
||||
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
|
||||
{
|
||||
this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
|
||||
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
|
||||
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
|
||||
* @param httpOnly the {@code HttpOnly} attribute of the cookie
|
||||
* @param secure the {@code Secure} attribute of the cookie
|
||||
* @param comment the comment of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
*/
|
||||
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
|
||||
{
|
||||
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, (SameSite)null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values and {@link SameSite}.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
|
||||
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
|
||||
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
|
||||
* @param httpOnly the {@code HttpOnly} attribute of the cookie
|
||||
* @param secure the {@code Secure} attribute of the cookie
|
||||
* @param comment the comment of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
* @param sameSite the <a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis">{@code SameSite} attribute</a> value to use for this cookie (only for RFC6265 mode)
|
||||
*/
|
||||
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite)
|
||||
{
|
||||
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, Collections.singletonMap("SameSite", sameSite == null ? null : sameSite.getAttributeValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values and attributes.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param domain the {@code Domain} value used for Domain-Matching rules on the cookie
|
||||
* @param path the {@code Path} value to use for Path-Matching rules on the cookie
|
||||
* @param maxAge the {@code Max-Age} attribute value (in seconds) for the cookie
|
||||
* @param httpOnly the {@code HttpOnly} attribute of the cookie
|
||||
* @param secure the {@code Secure} attribute of the cookie
|
||||
* @param comment the comment of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
* @param attributes the map of attributes to use with this cookie (this map is copied over into the resulting HttpCookie, but without map entries that are also parameters on this constructor)
|
||||
*/
|
||||
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, Map<String, String> attributes)
|
||||
{
|
||||
_name = name;
|
||||
_value = value;
|
||||
_domain = domain;
|
||||
_path = path;
|
||||
_maxAge = maxAge;
|
||||
_httpOnly = httpOnly;
|
||||
_secure = secure;
|
||||
_comment = comment;
|
||||
_version = version;
|
||||
_expiration = maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(maxAge);
|
||||
Map<String, String> attrs = null;
|
||||
if (attributes == null || attributes.isEmpty())
|
||||
attrs = Collections.emptyMap(); // unmodifiable empty map
|
||||
else
|
||||
public static SameSite from(String sameSite)
|
||||
{
|
||||
// create new map, to only capture relevant attributes
|
||||
attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
attrs.putAll(attributes);
|
||||
PARSED_ATTRIBUTE_NAMES.forEach(attrs::remove); // remove names that are also fields
|
||||
attrs = Collections.unmodifiableMap(attrs); // don't allow attributes to be modified
|
||||
if (sameSite == null)
|
||||
return null;
|
||||
return CACHE.get(sameSite);
|
||||
}
|
||||
_attributes = attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HttpCookie from specific values.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
*/
|
||||
static HttpCookie from(String name, String value)
|
||||
{
|
||||
return new Immutable(name, value, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -225,160 +108,256 @@ public class HttpCookie
|
|||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param version the version of the cookie (not used in RFC6265 or Servlet 6+, only in RFC2965 mode)
|
||||
* @param attributes the map of attributes to use with this cookie (this map is used for field values
|
||||
* such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()},
|
||||
* {@link #isSecure()}, {@link #getComment()}. These attributes are removed from the stored
|
||||
* attributes returned from {@link #getAttributes()}.
|
||||
*/
|
||||
public HttpCookie(String name, String value, int version, Map<String, String> attributes)
|
||||
static HttpCookie from(String name, String value, Map<String, String> attributes)
|
||||
{
|
||||
_name = name;
|
||||
_value = value;
|
||||
_version = version;
|
||||
return new Immutable(name, value, 0, attributes);
|
||||
}
|
||||
|
||||
Map<String, String> attrs = null;
|
||||
/**
|
||||
* Create new HttpCookie from specific values and attributes.
|
||||
*
|
||||
* @param name the name of the cookie
|
||||
* @param value the value of the cookie
|
||||
* @param version the version of the cookie (only used in RFC2965 mode)
|
||||
* @param attributes the map of attributes to use with this cookie (this map is used for field values
|
||||
* such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()},
|
||||
* {@link #isSecure()}, {@link #getComment()}. These attributes are removed from the stored
|
||||
* attributes returned from {@link #getAttributes()}.
|
||||
*/
|
||||
static HttpCookie from(String name, String value, int version, Map<String, String> attributes)
|
||||
{
|
||||
if (attributes == null || attributes.isEmpty())
|
||||
attrs = Collections.emptyMap(); // unmodifiable empty map
|
||||
else
|
||||
{
|
||||
// create new map, to only capture relevant attributes
|
||||
attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
attrs.putAll(attributes);
|
||||
}
|
||||
return new Immutable(name, value, version, Collections.emptyMap());
|
||||
|
||||
// remove all the well-known attributes, leaving only those unique to attributes as pass-through ones
|
||||
_domain = attrs.remove("Domain");
|
||||
_path = attrs.remove("Path");
|
||||
Map<String, String> attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
attrs.putAll(attributes);
|
||||
|
||||
String tmp = attrs.remove("Max-Age");
|
||||
_maxAge = StringUtil.isBlank(tmp) ? -1L : Long.valueOf(tmp);
|
||||
_expiration = _maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(_maxAge);
|
||||
_httpOnly = Boolean.parseBoolean(attrs.remove("HttpOnly"));
|
||||
_secure = Boolean.parseBoolean(attrs.remove("Secure"));
|
||||
_comment = attrs.remove("Comment");
|
||||
return new Immutable(name, value, version, attrs);
|
||||
}
|
||||
|
||||
// don't allow attributes to be modified
|
||||
_attributes = attrs.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(attrs);
|
||||
/**
|
||||
* @param cookie A cookie to base the new cookie on.
|
||||
* @param additionalAttributes Additional name value pairs of strings to use as additional attributes
|
||||
* @return A new cookie based on the passed cookie plus additional attributes.
|
||||
*/
|
||||
static HttpCookie from(HttpCookie cookie, String... additionalAttributes)
|
||||
{
|
||||
if (additionalAttributes.length % 2 != 0)
|
||||
throw new IllegalArgumentException("additional attributes must have name and value");
|
||||
Map<String, String> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
attributes.putAll(Objects.requireNonNull(cookie).getAttributes());
|
||||
for (int i = 0; i < additionalAttributes.length; i += 2)
|
||||
attributes.put(additionalAttributes[i], additionalAttributes[i + 1]);
|
||||
return new Immutable(cookie.getName(), cookie.getValue(), cookie.getVersion(), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie name
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return the cookie value
|
||||
*/
|
||||
public String getValue()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie comment
|
||||
*/
|
||||
public String getComment()
|
||||
{
|
||||
return _comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie domain
|
||||
*/
|
||||
public String getDomain()
|
||||
{
|
||||
return _domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie max age in seconds
|
||||
*/
|
||||
public long getMaxAge()
|
||||
{
|
||||
return _maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie path
|
||||
*/
|
||||
public String getPath()
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the cookie is valid for secure domains
|
||||
*/
|
||||
public boolean isSecure()
|
||||
{
|
||||
return _secure;
|
||||
}
|
||||
String getValue();
|
||||
|
||||
/**
|
||||
* @return the cookie version
|
||||
*/
|
||||
public int getVersion()
|
||||
int getVersion();
|
||||
|
||||
/**
|
||||
* @return the cookie comment.
|
||||
* Equivalent to a `get` of {@link #COMMENT_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
default String getComment()
|
||||
{
|
||||
return _version;
|
||||
return getAttributes().get(COMMENT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie domain.
|
||||
* Equivalent to a `get` of {@link #DOMAIN_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
default String getDomain()
|
||||
{
|
||||
return getAttributes().get(DOMAIN_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie max age in seconds
|
||||
* Equivalent to a `get` of {@link #MAX_AGE_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
default long getMaxAge()
|
||||
{
|
||||
String ma = getAttributes().get(MAX_AGE_ATTRIBUTE);
|
||||
return ma == null ? -1 : Long.parseLong(ma);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie path
|
||||
* Equivalent to a `get` of {@link #PATH_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
default String getPath()
|
||||
{
|
||||
return getAttributes().get(PATH_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the cookie is valid for secure domains
|
||||
* Equivalent to a `get` of {@link #SECURE_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
default boolean isSecure()
|
||||
{
|
||||
return Boolean.parseBoolean(getAttributes().get(SECURE_ATTRIBUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie {@code SameSite} attribute value
|
||||
* Equivalent to a `get` of {@link #SAME_SITE_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
public SameSite getSameSite()
|
||||
default SameSite getSameSite()
|
||||
{
|
||||
String val = _attributes.get("SameSite");
|
||||
if (val == null)
|
||||
return null;
|
||||
return SameSite.valueOf(val.toUpperCase(Locale.ENGLISH));
|
||||
return SameSite.from(getAttributes().get(SAME_SITE_ATTRIBUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the cookie is valid for the http protocol only
|
||||
* Equivalent to a `get` of {@link #HTTP_ONLY_ATTRIBUTE} on {@link #getAttributes()}.
|
||||
*/
|
||||
public boolean isHttpOnly()
|
||||
default boolean isHttpOnly()
|
||||
{
|
||||
return _httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeNanos the time to check for cookie expiration, in nanoseconds
|
||||
* @return whether the cookie is expired by the given time
|
||||
*/
|
||||
public boolean isExpired(long timeNanos)
|
||||
{
|
||||
return _expiration != -1 && NanoTime.isBefore(_expiration, timeNanos);
|
||||
return Boolean.parseBoolean(getAttributes().get(HTTP_ONLY_ATTRIBUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the attributes associated with this cookie
|
||||
*/
|
||||
public Map<String, String> getAttributes()
|
||||
Map<String, String> getAttributes();
|
||||
|
||||
/**
|
||||
* @return a string representation of this cookie
|
||||
*/
|
||||
default String asString()
|
||||
{
|
||||
return _attributes;
|
||||
return HttpCookie.asString(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representation of this cookie
|
||||
*/
|
||||
public String asString()
|
||||
static String asString(HttpCookie httpCookie)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(getName()).append("=").append(getValue());
|
||||
if (getDomain() != null)
|
||||
builder.append(";$Domain=").append(getDomain());
|
||||
if (getPath() != null)
|
||||
builder.append(";$Path=").append(getPath());
|
||||
builder.append(httpCookie.getName()).append("=").append(httpCookie.getValue());
|
||||
String domain = httpCookie.getDomain();
|
||||
if (domain != null)
|
||||
builder.append(";$Domain=").append(domain);
|
||||
String path = httpCookie.getPath();
|
||||
if (path != null)
|
||||
builder.append(";$Path=").append(path);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
static String toString(HttpCookie httpCookie)
|
||||
{
|
||||
return "%x@%s".formatted(hashCode(), asString());
|
||||
return "%x@%s".formatted(httpCookie.hashCode(), asString(httpCookie));
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable implementation of HttpCookie.
|
||||
*/
|
||||
class Immutable implements HttpCookie
|
||||
{
|
||||
private final String _name;
|
||||
private final String _value;
|
||||
private final int _version;
|
||||
private final Map<String, String> _attributes;
|
||||
|
||||
Immutable(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite, Map<String, String> attributes)
|
||||
{
|
||||
_name = name;
|
||||
_value = value;
|
||||
_version = version;
|
||||
Map<String, String> attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
if (attributes != null)
|
||||
attrs.putAll(attributes);
|
||||
attrs.put(DOMAIN_ATTRIBUTE, domain);
|
||||
attrs.put(PATH_ATTRIBUTE, path);
|
||||
attrs.put(MAX_AGE_ATTRIBUTE, Long.toString(maxAge));
|
||||
attrs.put(HTTP_ONLY_ATTRIBUTE, Boolean.toString(httpOnly));
|
||||
attrs.put(SECURE_ATTRIBUTE, Boolean.toString(secure));
|
||||
attrs.put(COMMENT_ATTRIBUTE, comment);
|
||||
attrs.put(SAME_SITE_ATTRIBUTE, sameSite == null ? null : sameSite.getAttributeValue());
|
||||
_attributes = Collections.unmodifiableMap(attrs);
|
||||
}
|
||||
|
||||
Immutable(String name, String value, int version, Map<String, String> attributes)
|
||||
{
|
||||
_name = name;
|
||||
_value = value;
|
||||
_version = version;
|
||||
_attributes = attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie name
|
||||
*/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie value
|
||||
*/
|
||||
@Override
|
||||
public String getValue()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie version
|
||||
*/
|
||||
@Override
|
||||
public int getVersion()
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cookie {@code SameSite} attribute value
|
||||
*/
|
||||
@Override
|
||||
public SameSite getSameSite()
|
||||
{
|
||||
String val = _attributes.get(SAME_SITE_ATTRIBUTE);
|
||||
if (val == null)
|
||||
return null;
|
||||
return SameSite.valueOf(val.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the attributes associated with this cookie
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> getAttributes()
|
||||
{
|
||||
return _attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "%x@%s".formatted(hashCode(), asString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
|
||||
|
@ -417,45 +396,51 @@ public class HttpCookie
|
|||
return false;
|
||||
}
|
||||
|
||||
public String getSetCookie(CookieCompliance compliance)
|
||||
static String getSetCookie(HttpCookie httpCookie, CookieCompliance compliance)
|
||||
{
|
||||
if (compliance == CookieCompliance.RFC6265)
|
||||
return getRFC6265SetCookie();
|
||||
return getRFC6265SetCookie(httpCookie);
|
||||
if (compliance == CookieCompliance.RFC2965)
|
||||
return getRFC2965SetCookie();
|
||||
return getRFC2965SetCookie(httpCookie);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public String getRFC2965SetCookie()
|
||||
static String getRFC2965SetCookie(HttpCookie httpCookie)
|
||||
{
|
||||
// Check arguments
|
||||
if (_name == null || _name.length() == 0)
|
||||
String name = httpCookie.getName();
|
||||
if (name == null || name.length() == 0)
|
||||
throw new IllegalArgumentException("Bad cookie name");
|
||||
|
||||
// Format value and params
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
// Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
|
||||
boolean quoteName = isQuoteNeededForCookie(_name);
|
||||
quoteOnlyOrAppend(buf, _name, quoteName);
|
||||
boolean quoteName = isQuoteNeededForCookie(name);
|
||||
quoteOnlyOrAppend(buf, name, quoteName);
|
||||
|
||||
buf.append('=');
|
||||
|
||||
// Append the value
|
||||
boolean quoteValue = isQuoteNeededForCookie(_value);
|
||||
quoteOnlyOrAppend(buf, _value, quoteValue);
|
||||
String value = httpCookie.getValue();
|
||||
boolean quoteValue = isQuoteNeededForCookie(value);
|
||||
quoteOnlyOrAppend(buf, value, quoteValue);
|
||||
|
||||
// Look for domain and path fields and check if they need to be quoted
|
||||
boolean hasDomain = _domain != null && _domain.length() > 0;
|
||||
boolean quoteDomain = hasDomain && isQuoteNeededForCookie(_domain);
|
||||
boolean hasPath = _path != null && _path.length() > 0;
|
||||
boolean quotePath = hasPath && isQuoteNeededForCookie(_path);
|
||||
String domain = httpCookie.getDomain();
|
||||
boolean hasDomain = domain != null && domain.length() > 0;
|
||||
boolean quoteDomain = hasDomain && isQuoteNeededForCookie(domain);
|
||||
|
||||
String path = httpCookie.getPath();
|
||||
boolean hasPath = path != null && path.length() > 0;
|
||||
boolean quotePath = hasPath && isQuoteNeededForCookie(path);
|
||||
|
||||
// Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
|
||||
int version = _version;
|
||||
if (version == 0 && (_comment != null || quoteName || quoteValue || quoteDomain || quotePath ||
|
||||
QuotedStringTokenizer.isQuoted(_name) || QuotedStringTokenizer.isQuoted(_value) ||
|
||||
QuotedStringTokenizer.isQuoted(_path) || QuotedStringTokenizer.isQuoted(_domain)))
|
||||
int version = httpCookie.getVersion();
|
||||
String comment = httpCookie.getComment();
|
||||
if (version == 0 && (comment != null || quoteName || quoteValue || quoteDomain || quotePath ||
|
||||
QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) ||
|
||||
QuotedStringTokenizer.isQuoted(path) || QuotedStringTokenizer.isQuoted(domain)))
|
||||
version = 1;
|
||||
|
||||
// Append version
|
||||
|
@ -468,130 +453,123 @@ public class HttpCookie
|
|||
if (hasPath)
|
||||
{
|
||||
buf.append(";Path=");
|
||||
quoteOnlyOrAppend(buf, _path, quotePath);
|
||||
quoteOnlyOrAppend(buf, path, quotePath);
|
||||
}
|
||||
|
||||
// Append domain
|
||||
if (hasDomain)
|
||||
{
|
||||
buf.append(";Domain=");
|
||||
quoteOnlyOrAppend(buf, _domain, quoteDomain);
|
||||
quoteOnlyOrAppend(buf, domain, quoteDomain);
|
||||
}
|
||||
|
||||
// Handle max-age and/or expires
|
||||
if (_maxAge >= 0)
|
||||
long maxAge = httpCookie.getMaxAge();
|
||||
if (maxAge >= 0)
|
||||
{
|
||||
// Always use expires
|
||||
// This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
|
||||
buf.append(";Expires=");
|
||||
if (_maxAge == 0)
|
||||
if (maxAge == 0)
|
||||
buf.append(__01Jan1970_COOKIE);
|
||||
else
|
||||
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * _maxAge);
|
||||
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
|
||||
|
||||
buf.append(";Max-Age=");
|
||||
buf.append(_maxAge);
|
||||
buf.append(maxAge);
|
||||
}
|
||||
|
||||
// add the other fields
|
||||
if (_secure)
|
||||
if (httpCookie.isSecure())
|
||||
buf.append(";Secure");
|
||||
if (_httpOnly)
|
||||
if (httpCookie.isHttpOnly())
|
||||
buf.append(";HttpOnly");
|
||||
if (_comment != null)
|
||||
if (comment != null)
|
||||
{
|
||||
buf.append(";Comment=");
|
||||
quoteOnlyOrAppend(buf, _comment, isQuoteNeededForCookie(_comment));
|
||||
quoteOnlyOrAppend(buf, comment, isQuoteNeededForCookie(comment));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String getRFC6265SetCookie()
|
||||
static String getRFC6265SetCookie(HttpCookie httpCookie)
|
||||
{
|
||||
// Check arguments
|
||||
if (_name == null || _name.length() == 0)
|
||||
String name = httpCookie.getName();
|
||||
if (name == null || name.length() == 0)
|
||||
throw new IllegalArgumentException("Bad cookie name");
|
||||
|
||||
// Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
|
||||
// Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules
|
||||
Syntax.requireValidRFC2616Token(_name, "RFC6265 Cookie name");
|
||||
Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name");
|
||||
// Ensure that Per RFC6265, Cookie.value follows syntax rules
|
||||
Syntax.requireValidRFC6265CookieValue(_value);
|
||||
String value = httpCookie.getValue();
|
||||
Syntax.requireValidRFC6265CookieValue(value);
|
||||
|
||||
// Format value and params
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(_name).append('=').append(_value == null ? "" : _value);
|
||||
buf.append(name).append('=').append(value == null ? "" : value);
|
||||
|
||||
// Append path
|
||||
if (_path != null && _path.length() > 0)
|
||||
buf.append("; Path=").append(_path);
|
||||
String path = httpCookie.getPath();
|
||||
if (path != null && path.length() > 0)
|
||||
buf.append("; Path=").append(path);
|
||||
|
||||
// Append domain
|
||||
if (_domain != null && _domain.length() > 0)
|
||||
buf.append("; Domain=").append(_domain);
|
||||
String domain = httpCookie.getDomain();
|
||||
if (domain != null && domain.length() > 0)
|
||||
buf.append("; Domain=").append(domain);
|
||||
|
||||
// Handle max-age and/or expires
|
||||
if (_maxAge >= 0)
|
||||
long maxAge = httpCookie.getMaxAge();
|
||||
if (maxAge >= 0)
|
||||
{
|
||||
// Always use expires
|
||||
// This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
|
||||
buf.append("; Expires=");
|
||||
if (_maxAge == 0)
|
||||
if (maxAge == 0)
|
||||
buf.append(__01Jan1970_COOKIE);
|
||||
else
|
||||
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * _maxAge);
|
||||
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
|
||||
|
||||
buf.append("; Max-Age=");
|
||||
buf.append(_maxAge);
|
||||
buf.append(maxAge);
|
||||
}
|
||||
|
||||
// add the other fields
|
||||
if (_secure)
|
||||
if (httpCookie.isSecure())
|
||||
buf.append("; Secure");
|
||||
if (_httpOnly)
|
||||
if (httpCookie.isHttpOnly())
|
||||
buf.append("; HttpOnly");
|
||||
|
||||
String sameSite = _attributes.get("SameSite");
|
||||
if (sameSite != null)
|
||||
Map<String, String> attributes = httpCookie.getAttributes();
|
||||
|
||||
String sameSiteAttr = attributes.get(SAME_SITE_ATTRIBUTE);
|
||||
if (sameSiteAttr != null)
|
||||
{
|
||||
buf.append("; SameSite=");
|
||||
buf.append(sameSite);
|
||||
buf.append(sameSiteAttr);
|
||||
}
|
||||
else
|
||||
{
|
||||
SameSite sameSite = httpCookie.getSameSite();
|
||||
if (sameSite != null)
|
||||
{
|
||||
buf.append("; SameSite=");
|
||||
buf.append(sameSite.getAttributeValue());
|
||||
}
|
||||
}
|
||||
|
||||
//Add all other attributes
|
||||
_attributes.entrySet().stream().filter(e -> !"SameSite".equals(e.getKey())).forEach(e ->
|
||||
for (Map.Entry<String, String> e : attributes.entrySet())
|
||||
{
|
||||
buf.append("; " + e.getKey() + "=");
|
||||
if (KNOWN_ATTRIBUTES.contains(e.getKey()))
|
||||
continue;
|
||||
buf.append("; ").append(e.getKey()).append("=");
|
||||
buf.append(e.getValue());
|
||||
});
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static boolean isHttpOnlyInComment(String comment)
|
||||
{
|
||||
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
|
||||
}
|
||||
|
||||
public static SameSite getSameSiteFromComment(String comment)
|
||||
{
|
||||
if (comment != null)
|
||||
{
|
||||
if (comment.contains(SAME_SITE_STRICT_COMMENT))
|
||||
{
|
||||
return SameSite.STRICT;
|
||||
}
|
||||
if (comment.contains(SAME_SITE_LAX_COMMENT))
|
||||
{
|
||||
return SameSite.LAX;
|
||||
}
|
||||
if (comment.contains(SAME_SITE_NONE_COMMENT))
|
||||
{
|
||||
return SameSite.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -602,7 +580,7 @@ public class HttpCookie
|
|||
* @return the default SameSite value or null if one does not exist
|
||||
* @throws IllegalStateException if the default value is not a permitted value
|
||||
*/
|
||||
public static SameSite getSameSiteDefault(Attributes contextAttributes)
|
||||
static SameSite getSameSiteDefault(Attributes contextAttributes)
|
||||
{
|
||||
if (contextAttributes == null)
|
||||
return null;
|
||||
|
@ -643,7 +621,7 @@ public class HttpCookie
|
|||
* @param setCookieHeader the header as a string
|
||||
* @return a map containing the name, value, domain, path. max-age of the set cookie header
|
||||
*/
|
||||
public static Map<String, String> extractBasics(String setCookieHeader)
|
||||
static Map<String, String> extractBasics(String setCookieHeader)
|
||||
{
|
||||
//Parse the bare minimum
|
||||
List<java.net.HttpCookie> cookies = java.net.HttpCookie.parse(setCookieHeader);
|
||||
|
@ -668,7 +646,7 @@ public class HttpCookie
|
|||
* @param path the cookie path to check
|
||||
* @return true if all of the name, domain and path match the Set-Cookie header, false otherwise
|
||||
*/
|
||||
public static boolean match(String setCookieHeader, String name, String domain, String path)
|
||||
static boolean match(String setCookieHeader, String name, String domain, String path)
|
||||
{
|
||||
//Parse the bare minimum
|
||||
List<java.net.HttpCookie> cookies = java.net.HttpCookie.parse(setCookieHeader);
|
||||
|
@ -688,7 +666,7 @@ public class HttpCookie
|
|||
* @param path the cookie path to check
|
||||
* @return true if name, domain, and path, match all match the HttpCookie, false otherwise
|
||||
*/
|
||||
public static boolean match(HttpCookie cookie, String name, String domain, String path)
|
||||
static boolean match(HttpCookie cookie, String name, String domain, String path)
|
||||
{
|
||||
if (cookie == null)
|
||||
return false;
|
||||
|
@ -719,79 +697,18 @@ public class HttpCookie
|
|||
return false;
|
||||
|
||||
if (oldPath == null)
|
||||
{
|
||||
if (newPath != null)
|
||||
return false;
|
||||
}
|
||||
else if (!oldPath.equals(newPath))
|
||||
return false;
|
||||
return newPath == null;
|
||||
|
||||
return true;
|
||||
return oldPath.equals(newPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated We should not need to do this now
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getCommentWithoutAttributes(String comment)
|
||||
{
|
||||
if (comment == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String strippedComment = comment.trim();
|
||||
|
||||
strippedComment = StringUtil.strip(strippedComment, HTTP_ONLY_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_NONE_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_LAX_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_STRICT_COMMENT);
|
||||
|
||||
return strippedComment.length() == 0 ? null : strippedComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated We should not need to do this now
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getCommentWithAttributes(String comment, boolean httpOnly, SameSite sameSite)
|
||||
{
|
||||
if (comment == null && sameSite == null)
|
||||
return null;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (StringUtil.isNotBlank(comment))
|
||||
{
|
||||
comment = getCommentWithoutAttributes(comment);
|
||||
if (StringUtil.isNotBlank(comment))
|
||||
builder.append(comment);
|
||||
}
|
||||
if (httpOnly)
|
||||
builder.append(HTTP_ONLY_COMMENT);
|
||||
|
||||
if (sameSite != null)
|
||||
{
|
||||
switch (sameSite)
|
||||
{
|
||||
case NONE -> builder.append(SAME_SITE_NONE_COMMENT);
|
||||
case STRICT -> builder.append(SAME_SITE_STRICT_COMMENT);
|
||||
case LAX -> builder.append(SAME_SITE_LAX_COMMENT);
|
||||
default -> throw new IllegalArgumentException(sameSite.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.length() == 0)
|
||||
return null;
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static class SetCookieHttpField extends HttpField
|
||||
class SetCookieHttpField extends HttpField
|
||||
{
|
||||
final HttpCookie _cookie;
|
||||
|
||||
public SetCookieHttpField(HttpCookie cookie, CookieCompliance compliance)
|
||||
{
|
||||
super(HttpHeader.SET_COOKIE, cookie.getSetCookie(compliance));
|
||||
super(HttpHeader.SET_COOKIE, getSetCookie(cookie, compliance));
|
||||
this._cookie = cookie;
|
||||
}
|
||||
|
||||
|
@ -809,7 +726,7 @@ public class HttpCookie
|
|||
* @param attributes the context to check settings
|
||||
* @return either the original cookie, or a new one that has the samesit default set
|
||||
*/
|
||||
public static HttpCookie checkSameSite(HttpCookie cookie, Attributes attributes)
|
||||
static HttpCookie checkSameSite(HttpCookie cookie, Attributes attributes)
|
||||
{
|
||||
if (cookie == null || cookie.getSameSite() != null)
|
||||
return cookie;
|
||||
|
@ -819,15 +736,6 @@ public class HttpCookie
|
|||
if (contextDefault == null)
|
||||
return cookie; //no default set
|
||||
|
||||
return new HttpCookie(cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getDomain(),
|
||||
cookie.getPath(),
|
||||
cookie.getMaxAge(),
|
||||
cookie.isHttpOnly(),
|
||||
cookie.isSecure(),
|
||||
cookie.getComment(),
|
||||
cookie.getVersion(),
|
||||
contextDefault);
|
||||
return HttpCookie.from(cookie, HttpCookie.SAME_SITE_ATTRIBUTE, contextDefault.getAttributeValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.HttpCookie.SameSite;
|
||||
|
@ -21,15 +21,11 @@ import org.eclipse.jetty.util.AttributesMap;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
@ -82,7 +78,7 @@ public class HttpCookieTest
|
|||
"everything", "domain", "path"));
|
||||
|
||||
//match via cookie
|
||||
HttpCookie httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, "comment", 0);
|
||||
HttpCookie httpCookie = HttpCookie.from("everything", "value", 0, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.COMMENT_ATTRIBUTE, "comment"));
|
||||
assertTrue(HttpCookie.match(httpCookie, "everything", "domain", "path"));
|
||||
assertFalse(HttpCookie.match(httpCookie, "something", "domain", "path"));
|
||||
assertFalse(HttpCookie.match(httpCookie, "everything", "realm", "path"));
|
||||
|
@ -94,34 +90,34 @@ public class HttpCookieTest
|
|||
{
|
||||
HttpCookie httpCookie;
|
||||
|
||||
httpCookie = new HttpCookie("null", null, null, null, -1, false, false, null, -1);
|
||||
assertEquals("null=", httpCookie.getRFC2965SetCookie());
|
||||
httpCookie = HttpCookie.from("null", null, -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
assertEquals("null=", HttpCookie.getRFC2965SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("minimal", "value", null, null, -1, false, false, null, -1);
|
||||
assertEquals("minimal=value", httpCookie.getRFC2965SetCookie());
|
||||
httpCookie = HttpCookie.from("minimal", "value", -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
assertEquals("minimal=value", HttpCookie.getRFC2965SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("everything", "something", "domain", "path", 0, true, true, "noncomment", 0);
|
||||
assertEquals("everything=something;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=noncomment", httpCookie.getRFC2965SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "something", 0, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.COMMENT_ATTRIBUTE, "noncomment"));
|
||||
assertEquals("everything=something;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=noncomment", HttpCookie.getRFC2965SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, "comment", 0);
|
||||
assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment", httpCookie.getRFC2965SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "value", 0, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.COMMENT_ATTRIBUTE, "comment"));
|
||||
assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment", HttpCookie.getRFC2965SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("ev erything", "va lue", "do main", "pa th", 1, true, true, "co mment", 1);
|
||||
String setCookie = httpCookie.getRFC2965SetCookie();
|
||||
httpCookie = HttpCookie.from("ev erything", "va lue", 1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "do main", HttpCookie.PATH_ATTRIBUTE, "pa th", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.COMMENT_ATTRIBUTE, "co mment"));
|
||||
String setCookie = HttpCookie.getRFC2965SetCookie(httpCookie);
|
||||
assertThat(setCookie, Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
|
||||
assertThat(setCookie, Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
|
||||
|
||||
httpCookie = new HttpCookie("name", "value", null, null, -1, false, false, null, 0);
|
||||
setCookie = httpCookie.getRFC2965SetCookie();
|
||||
httpCookie = HttpCookie.from("name", "value", 0, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
setCookie = HttpCookie.getRFC2965SetCookie(httpCookie);
|
||||
assertEquals(-1, setCookie.indexOf("Version="));
|
||||
httpCookie = new HttpCookie("name", "v a l u e", null, null, -1, false, false, null, 0);
|
||||
setCookie = httpCookie.getRFC2965SetCookie();
|
||||
httpCookie = HttpCookie.from("name", "v a l u e", 0, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
setCookie = HttpCookie.getRFC2965SetCookie(httpCookie);
|
||||
|
||||
httpCookie = new HttpCookie("json", "{\"services\":[\"cwa\", \"aa\"]}", null, null, -1, false, false, null, -1);
|
||||
assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"", httpCookie.getRFC2965SetCookie());
|
||||
httpCookie = HttpCookie.from("json", "{\"services\":[\"cwa\", \"aa\"]}", -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"", HttpCookie.getRFC2965SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("name", "value%=", null, null, -1, false, false, null, 0);
|
||||
setCookie = httpCookie.getRFC2965SetCookie();
|
||||
httpCookie = HttpCookie.from("name", "value%=", 0, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
setCookie = HttpCookie.getRFC2965SetCookie(httpCookie);
|
||||
assertEquals("name=value%=", setCookie);
|
||||
}
|
||||
|
||||
|
@ -130,30 +126,27 @@ public class HttpCookieTest
|
|||
{
|
||||
HttpCookie httpCookie;
|
||||
|
||||
httpCookie = new HttpCookie("null", null, null, null, -1, false, false, null, -1);
|
||||
assertEquals("null=", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("null", null, -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
assertEquals("null=", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("minimal", "value", null, null, -1, false, false, null, -1);
|
||||
assertEquals("minimal=value", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("minimal", "value", -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false)));
|
||||
assertEquals("minimal=value", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
//test cookies with same name, domain and path
|
||||
httpCookie = new HttpCookie("everything", "something", "domain", "path", 0, true, true, null, -1);
|
||||
assertEquals("everything=something; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "something", -1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
assertEquals("everything=something; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1);
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "value", -1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.NONE);
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=None", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.NONE.getAttributeValue()));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=None", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.LAX);
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.LAX.getAttributeValue()));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.STRICT);
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", httpCookie.getRFC6265SetCookie());
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, Collections.singletonMap("SameSite", "None"));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=None", httpCookie.getRFC6265SetCookie());
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue()));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", HttpCookie.getRFC6265SetCookie(httpCookie));
|
||||
}
|
||||
|
||||
public static Stream<String> rfc6265BadNameSource()
|
||||
|
@ -178,8 +171,8 @@ public class HttpCookieTest
|
|||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() ->
|
||||
{
|
||||
HttpCookie httpCookie = new HttpCookie(badNameExample, "value", null, "/", 1, true, true, null, -1);
|
||||
httpCookie.getRFC6265SetCookie();
|
||||
HttpCookie httpCookie = HttpCookie.from(badNameExample, "value", -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
HttpCookie.getRFC6265SetCookie(httpCookie);
|
||||
});
|
||||
// make sure that exception mentions just how mad of a name it truly is
|
||||
assertThat("message", ex.getMessage(),
|
||||
|
@ -215,8 +208,8 @@ public class HttpCookieTest
|
|||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() ->
|
||||
{
|
||||
HttpCookie httpCookie = new HttpCookie("name", badValueExample, null, "/", 1, true, true, null, -1);
|
||||
httpCookie.getRFC6265SetCookie();
|
||||
HttpCookie httpCookie = HttpCookie.from("name", badValueExample, -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
HttpCookie.getRFC6265SetCookie(httpCookie);
|
||||
});
|
||||
assertThat("message", ex.getMessage(), containsString("RFC6265"));
|
||||
}
|
||||
|
@ -237,7 +230,7 @@ public class HttpCookieTest
|
|||
@MethodSource("rfc6265GoodNameSource")
|
||||
public void testSetRFC6265CookieGoodName(String goodNameExample)
|
||||
{
|
||||
new HttpCookie(goodNameExample, "value", null, "/", 1, true, true, null, -1);
|
||||
HttpCookie.from(goodNameExample, "value", -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
// should not throw an exception
|
||||
}
|
||||
|
||||
|
@ -259,143 +252,7 @@ public class HttpCookieTest
|
|||
@MethodSource("rfc6265GoodValueSource")
|
||||
public void testSetRFC6265CookieGoodValue(String goodValueExample)
|
||||
{
|
||||
new HttpCookie("name", goodValueExample, null, "/", 1, true, true, null, -1);
|
||||
HttpCookie.from("name", goodValueExample, -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
// should not throw an exception
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"__HTTP_ONLY__",
|
||||
"__HTTP_ONLY__comment",
|
||||
"comment__HTTP_ONLY__"
|
||||
})
|
||||
public void testIsHttpOnlyInCommentTrue(String comment)
|
||||
{
|
||||
assertTrue(HttpCookie.isHttpOnlyInComment(comment), "Comment \"" + comment + "\"");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"comment",
|
||||
"",
|
||||
"__",
|
||||
"__HTTP__ONLY__",
|
||||
"__http_only__",
|
||||
"HTTP_ONLY",
|
||||
"__HTTP__comment__ONLY__"
|
||||
})
|
||||
public void testIsHttpOnlyInCommentFalse(String comment)
|
||||
{
|
||||
assertFalse(HttpCookie.isHttpOnlyInComment(comment), "Comment \"" + comment + "\"");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"__SAME_SITE_NONE__",
|
||||
"__SAME_SITE_NONE____SAME_SITE_NONE__"
|
||||
})
|
||||
public void testGetSameSiteFromCommentNONE(String comment)
|
||||
{
|
||||
assertEquals(HttpCookie.getSameSiteFromComment(comment), HttpCookie.SameSite.NONE, "Comment \"" + comment + "\"");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"__SAME_SITE_LAX__",
|
||||
"__SAME_SITE_LAX____SAME_SITE_NONE__",
|
||||
"__SAME_SITE_NONE____SAME_SITE_LAX__",
|
||||
"__SAME_SITE_LAX____SAME_SITE_NONE__"
|
||||
})
|
||||
public void testGetSameSiteFromCommentLAX(String comment)
|
||||
{
|
||||
assertEquals(HttpCookie.getSameSiteFromComment(comment), HttpCookie.SameSite.LAX, "Comment \"" + comment + "\"");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"__SAME_SITE_STRICT__",
|
||||
"__SAME_SITE_NONE____SAME_SITE_STRICT____SAME_SITE_LAX__",
|
||||
"__SAME_SITE_STRICT____SAME_SITE_LAX____SAME_SITE_NONE__",
|
||||
"__SAME_SITE_STRICT____SAME_SITE_STRICT__"
|
||||
})
|
||||
public void testGetSameSiteFromCommentSTRICT(String comment)
|
||||
{
|
||||
assertEquals(HttpCookie.getSameSiteFromComment(comment), HttpCookie.SameSite.STRICT, "Comment \"" + comment + "\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment that does not have a declared SamesSite attribute defined
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"__HTTP_ONLY__",
|
||||
"comment",
|
||||
// not jetty attributes
|
||||
"SameSite=None",
|
||||
"SameSite=Lax",
|
||||
"SameSite=Strict",
|
||||
// incomplete jetty attributes
|
||||
"SAME_SITE_NONE",
|
||||
"SAME_SITE_LAX",
|
||||
"SAME_SITE_STRICT",
|
||||
})
|
||||
public void testGetSameSiteFromCommentUndefined(String comment)
|
||||
{
|
||||
assertNull(HttpCookie.getSameSiteFromComment(comment), "Comment \"" + comment + "\"");
|
||||
}
|
||||
|
||||
public static Stream<Arguments> getCommentWithoutAttributesSource()
|
||||
{
|
||||
return Stream.of(
|
||||
// normal - only attribute comment
|
||||
Arguments.of("__SAME_SITE_LAX__", null),
|
||||
// normal - no attribute comment
|
||||
Arguments.of("comment", "comment"),
|
||||
// mixed - attributes at end
|
||||
Arguments.of("comment__SAME_SITE_NONE__", "comment"),
|
||||
Arguments.of("comment__HTTP_ONLY____SAME_SITE_NONE__", "comment"),
|
||||
// mixed - attributes at start
|
||||
Arguments.of("__SAME_SITE_NONE__comment", "comment"),
|
||||
Arguments.of("__HTTP_ONLY____SAME_SITE_NONE__comment", "comment"),
|
||||
// mixed - attributes at start and end
|
||||
Arguments.of("__SAME_SITE_NONE__comment__HTTP_ONLY__", "comment"),
|
||||
Arguments.of("__HTTP_ONLY__comment__SAME_SITE_NONE__", "comment")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getCommentWithoutAttributesSource")
|
||||
public void testGetCommentWithoutAttributes(String rawComment, String expectedComment)
|
||||
{
|
||||
String actualComment = HttpCookie.getCommentWithoutAttributes(rawComment);
|
||||
if (expectedComment == null)
|
||||
{
|
||||
assertNull(actualComment);
|
||||
}
|
||||
else
|
||||
{
|
||||
assertEquals(actualComment, expectedComment);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommentWithAttributes()
|
||||
{
|
||||
assertThat(HttpCookie.getCommentWithAttributes(null, false, null), nullValue());
|
||||
assertThat(HttpCookie.getCommentWithAttributes("", false, null), nullValue());
|
||||
assertThat(HttpCookie.getCommentWithAttributes("hello", false, null), is("hello"));
|
||||
|
||||
assertThat(HttpCookie.getCommentWithAttributes(null, true, HttpCookie.SameSite.STRICT),
|
||||
is("__HTTP_ONLY____SAME_SITE_STRICT__"));
|
||||
assertThat(HttpCookie.getCommentWithAttributes("", true, HttpCookie.SameSite.NONE),
|
||||
is("__HTTP_ONLY____SAME_SITE_NONE__"));
|
||||
assertThat(HttpCookie.getCommentWithAttributes("hello", true, HttpCookie.SameSite.LAX),
|
||||
is("hello__HTTP_ONLY____SAME_SITE_LAX__"));
|
||||
|
||||
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__", false, null), nullValue());
|
||||
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__", true, HttpCookie.SameSite.NONE),
|
||||
is("__HTTP_ONLY____SAME_SITE_NONE__"));
|
||||
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__hello", true, HttpCookie.SameSite.LAX),
|
||||
is("hello__HTTP_ONLY____SAME_SITE_LAX__"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ public class CookiePatternRule extends PatternRule
|
|||
@Override
|
||||
public boolean process(Response response, Callback callback) throws Exception
|
||||
{
|
||||
Response.addCookie(response, new HttpCookie(_name, _value));
|
||||
Response.addCookie(response, HttpCookie.from(_name, _value));
|
||||
return super.process(response, callback);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -73,17 +73,12 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
private SessionCache _sessionCache;
|
||||
private Scheduler _scheduler;
|
||||
private boolean _ownScheduler = false;
|
||||
private boolean _httpOnly = false;
|
||||
private String _sessionCookie = __DefaultSessionCookie;
|
||||
private String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
|
||||
private String _sessionIdPathParameterNamePrefix = ";" + _sessionIdPathParameterName + "=";
|
||||
private String _sessionDomain;
|
||||
private String _sessionPath;
|
||||
private String _sessionComment;
|
||||
private final Map<String, String> _sessionAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private boolean _secureCookies = false;
|
||||
private Map<String, String> _sessionAttributesSecure;
|
||||
private boolean _secureRequestOnly = true;
|
||||
private int _maxCookieAge = -1;
|
||||
private int _refreshCookieAge;
|
||||
private boolean _checkingRemoteSessionIdEncoding;
|
||||
|
||||
|
@ -117,7 +112,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
(getMaxCookieAge() > 0 && getRefreshCookieAge() > 0 &&
|
||||
((now - session.getCookieSetTime()) / 1000 > getRefreshCookieAge()))))
|
||||
{
|
||||
return getSessionCookie(session, _context == null ? "/" : (_context.getContextPath()), secure);
|
||||
return getSessionCookie(session, secure);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -244,6 +239,11 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
_context = ContextHandler.getCurrentContext();
|
||||
_loader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
// ensure a session path is set for non root contexts
|
||||
String contextPath = _context == null ? "/" : _context.getContextPath();
|
||||
if (!"/".equals(contextPath) && getSessionPath() == null)
|
||||
setSessionPath(contextPath);
|
||||
|
||||
// Use a coarser lock to serialize concurrent start of many contexts.
|
||||
synchronized (server)
|
||||
{
|
||||
|
@ -301,6 +301,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
_sessionContext = new SessionContext(this);
|
||||
_sessionCache.initialize(_sessionContext);
|
||||
|
||||
secureRequestOnlyAttributes();
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
|
@ -312,13 +313,15 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public int getMaxCookieAge()
|
||||
{
|
||||
return _maxCookieAge;
|
||||
String mca = _sessionAttributes.get(HttpCookie.MAX_AGE_ATTRIBUTE);
|
||||
return mca == null ? -1 : Integer.parseInt(mca);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxCookieAge(int maxCookieAge)
|
||||
{
|
||||
_maxCookieAge = maxCookieAge;
|
||||
_sessionAttributes.put(HttpCookie.MAX_AGE_ATTRIBUTE, Integer.toString(maxCookieAge));
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -447,13 +450,27 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public String getSessionComment()
|
||||
{
|
||||
return _sessionComment;
|
||||
return _sessionAttributes.get(HttpCookie.COMMENT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionComment(String sessionComment)
|
||||
{
|
||||
_sessionComment = sessionComment;
|
||||
_sessionAttributes.put(HttpCookie.COMMENT_ATTRIBUTE, sessionComment);
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie.SameSite getSameSite()
|
||||
{
|
||||
return HttpCookie.SameSite.from(_sessionAttributes.get(HttpCookie.SAME_SITE_ATTRIBUTE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSameSite(HttpCookie.SameSite sessionSameSite)
|
||||
{
|
||||
_sessionAttributes.put(HttpCookie.SAME_SITE_ATTRIBUTE, sessionSameSite.getAttributeValue());
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
public SessionContext getSessionContext()
|
||||
|
@ -479,18 +496,20 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public String getSessionDomain()
|
||||
{
|
||||
return _sessionDomain;
|
||||
return _sessionAttributes.get(HttpCookie.DOMAIN_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionDomain(String domain)
|
||||
{
|
||||
_sessionDomain = domain;
|
||||
_sessionAttributes.put(HttpCookie.DOMAIN_ATTRIBUTE, domain);
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
public void setSessionAttribute(String name, String value)
|
||||
{
|
||||
_sessionAttributes.put(name, value);
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
public String getSessionAttribute(String name)
|
||||
|
@ -564,13 +583,14 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public String getSessionPath()
|
||||
{
|
||||
return _sessionPath;
|
||||
return _sessionAttributes.get(HttpCookie.PATH_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionPath(String sessionPath)
|
||||
{
|
||||
_sessionPath = sessionPath;
|
||||
_sessionAttributes.put(HttpCookie.PATH_ATTRIBUTE, sessionPath);
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -687,7 +707,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public boolean isHttpOnly()
|
||||
{
|
||||
return _httpOnly;
|
||||
return Boolean.parseBoolean(_sessionAttributes.get(HttpCookie.HTTP_ONLY_ATTRIBUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -699,7 +719,7 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public void setHttpOnly(boolean httpOnly)
|
||||
{
|
||||
_httpOnly = httpOnly;
|
||||
_sessionAttributes.put(HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(httpOnly));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -724,13 +744,14 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
@Override
|
||||
public boolean isSecureCookies()
|
||||
{
|
||||
return _secureCookies;
|
||||
return Boolean.parseBoolean(_sessionAttributes.get(HttpCookie.SECURE_ATTRIBUTE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSecureCookies(boolean secure)
|
||||
{
|
||||
_secureCookies = secure;
|
||||
_sessionAttributes.put(HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(secure));
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -752,6 +773,22 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
public void setSecureRequestOnly(boolean secureRequestOnly)
|
||||
{
|
||||
_secureRequestOnly = secureRequestOnly;
|
||||
secureRequestOnlyAttributes();
|
||||
}
|
||||
|
||||
private void secureRequestOnlyAttributes()
|
||||
{
|
||||
if (isSecureRequestOnly() && !Boolean.parseBoolean(_sessionAttributes.get(HttpCookie.SECURE_ATTRIBUTE)))
|
||||
{
|
||||
Map<String, String> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
attributes.putAll(_sessionAttributes);
|
||||
attributes.put(HttpCookie.SECURE_ATTRIBUTE, Boolean.TRUE.toString());
|
||||
_sessionAttributesSecure = attributes;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sessionAttributesSecure = _sessionAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1136,6 +1173,55 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* A session cookie is marked as secure IFF any of the following conditions are true:
|
||||
* <ol>
|
||||
* <li>SessionCookieConfig.setSecure == true</li>
|
||||
* <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
|
||||
* </ol>
|
||||
* According to SessionCookieConfig javadoc, case 1 can be used when:
|
||||
* "... even though the request that initiated the session came over HTTP,
|
||||
* is to support a topology where the web container is front-ended by an
|
||||
* SSL offloading load balancer. In this case, the traffic between the client
|
||||
* and the load balancer will be over HTTPS, whereas the traffic between the
|
||||
* load balancer and the web container will be over HTTP."
|
||||
* <p>
|
||||
* For case 2, you can use _secureRequestOnly to determine if you want the
|
||||
* Servlet Spec 3.0 default behavior when SessionCookieConfig.setSecure==false,
|
||||
* which is:
|
||||
* <cite>
|
||||
* "they shall be marked as secure only if the request that initiated the
|
||||
* corresponding session was also secure"
|
||||
* </cite>
|
||||
* <p>
|
||||
* The default for _secureRequestOnly is true, which gives the above behavior. If
|
||||
* you set it to false, then a session cookie is NEVER marked as secure, even if
|
||||
* the initiating request was secure.
|
||||
*
|
||||
* @param session the session to which the cookie should refer.
|
||||
* @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
|
||||
* @return if this <code>SessionManager</code> uses cookies, then this method will return a new
|
||||
* {@link HttpCookie cookie object} that should be set on the client in order to link future HTTP requests
|
||||
* with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
|
||||
*/
|
||||
@Override
|
||||
public HttpCookie getSessionCookie(Session session, boolean requestIsSecure)
|
||||
{
|
||||
if (isUsingCookies())
|
||||
{
|
||||
String name = getSessionCookie();
|
||||
if (name == null)
|
||||
name = _sessionAttributes.get("name");
|
||||
if (name == null)
|
||||
name = __DefaultSessionCookie;
|
||||
if (isSecureRequestOnly() && requestIsSecure && _sessionAttributesSecure != null && _sessionAttributes != _sessionAttributesSecure)
|
||||
return session.generateSetCookie(name, _sessionAttributesSecure);
|
||||
return session.generateSetCookie(name, _sessionAttributes);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* StreamWrapper to intercept commit and complete events to ensure
|
||||
* session handling happens in context, with request available.
|
||||
|
|
|
@ -150,10 +150,9 @@ public class Session
|
|||
_extendedId = extendedId;
|
||||
}
|
||||
|
||||
public HttpCookie generateSetCookie(String name, String domain, String path, int maxAge,
|
||||
boolean httpOnly, boolean secure, String comment, int version, Map<String, String> attributes)
|
||||
public HttpCookie generateSetCookie(String name, Map<String, String> attributes)
|
||||
{
|
||||
HttpCookie sessionCookie = new HttpCookie(name, getExtendedId(), domain, path, maxAge, httpOnly, secure, comment, version, attributes);
|
||||
HttpCookie sessionCookie = HttpCookie.from(name, getExtendedId(), attributes);
|
||||
onSetCookieGenerated();
|
||||
return sessionCookie;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ public interface SessionManager extends LifeCycle, SessionConfig
|
|||
|
||||
boolean isIdInUse(String id) throws Exception;
|
||||
|
||||
HttpCookie getSessionCookie(Session session, String contextPath, boolean requestIsSecure);
|
||||
HttpCookie getSessionCookie(Session session, boolean requestIsSecure);
|
||||
|
||||
void renewSessionId(String oldId, String oldExtendedId, String newId, String newExtendedId) throws Exception;
|
||||
|
||||
|
|
|
@ -37,12 +37,13 @@ public class AbstractSessionManagerTest
|
|||
//Make a session
|
||||
SessionData sessionData = new SessionData("1234", "_test", "0.0.0.0", 100, 200, 200, -1);
|
||||
TestableSessionManager sessionManager = new TestableSessionManager();
|
||||
sessionManager.setSessionPath("/test");
|
||||
Session session = new Session(sessionManager, sessionData);
|
||||
session.setExtendedId("1234.foo");
|
||||
session.getSessionData().setLastNode("foo");
|
||||
|
||||
//check cookie with all default cookie config settings
|
||||
HttpCookie cookie = sessionManager.getSessionCookie(session, "/test", false);
|
||||
HttpCookie cookie = sessionManager.getSessionCookie(session, false);
|
||||
assertNotNull(cookie);
|
||||
assertEquals(SessionManager.__DefaultSessionCookie, cookie.getName());
|
||||
assertEquals(SessionManager.__DefaultSessionDomain, cookie.getDomain());
|
||||
|
@ -54,7 +55,7 @@ public class AbstractSessionManagerTest
|
|||
sessionManager.setHttpOnly(true);
|
||||
sessionManager.setSecureRequestOnly(true);
|
||||
sessionManager.setSecureCookies(true);
|
||||
cookie = sessionManager.getSessionCookie(session, "/test", true);
|
||||
cookie = sessionManager.getSessionCookie(session, true);
|
||||
assertNotNull(cookie);
|
||||
assertEquals(SessionManager.__DefaultSessionCookie, cookie.getName());
|
||||
assertEquals(SessionManager.__DefaultSessionDomain, cookie.getDomain());
|
||||
|
@ -67,7 +68,7 @@ public class AbstractSessionManagerTest
|
|||
sessionManager.getCookieConfig().put(SessionManager.__SessionDomainProperty, "foo.bar");
|
||||
sessionManager.getCookieConfig().put(SessionManager.__SessionPathProperty, "/special");
|
||||
sessionManager.configureCookies();
|
||||
cookie = sessionManager.getSessionCookie(session, "/test", false);
|
||||
cookie = sessionManager.getSessionCookie(session, false);
|
||||
assertNotNull(cookie);
|
||||
assertEquals("MYSESSIONID", cookie.getName());
|
||||
assertEquals("foo.bar", cookie.getDomain());
|
||||
|
|
|
@ -13,19 +13,15 @@
|
|||
|
||||
package org.eclipse.jetty.session;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpCookie.SameSite;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* SimpleSessionHandler example
|
||||
|
@ -115,7 +111,7 @@ public class SimpleSessionHandler extends AbstractSessionManager implements Hand
|
|||
{
|
||||
newSession(this, _requestedSessionId, this::setCoreSession);
|
||||
session = _session.get();
|
||||
HttpCookie cookie = getSessionCookie(session, getContext().getContextPath(), getConnectionMetaData().isSecure());
|
||||
HttpCookie cookie = getSessionCookie(session, getConnectionMetaData().isSecure());
|
||||
if (cookie != null)
|
||||
Response.replaceCookie(_response, cookie);
|
||||
}
|
||||
|
@ -189,44 +185,7 @@ public class SimpleSessionHandler extends AbstractSessionManager implements Hand
|
|||
SessionManager sessionManager = _coreSession.getSessionManager();
|
||||
|
||||
if (sessionManager.isUsingCookies())
|
||||
Response.replaceCookie(response, sessionManager.getSessionCookie(getCoreSession(), request.getContext().getContextPath(), request.isSecure()));
|
||||
Response.replaceCookie(response, sessionManager.getSessionCookie(getCoreSession(), request.isSecure()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie getSessionCookie(Session session, String contextPath, boolean requestIsSecure)
|
||||
{
|
||||
if (isUsingCookies())
|
||||
{
|
||||
String sessionPath = getSessionPath();
|
||||
sessionPath = (sessionPath == null) ? contextPath : sessionPath;
|
||||
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
|
||||
SameSite sameSite = HttpCookie.getSameSiteFromComment(getSessionComment());
|
||||
Map<String, String> attributes = Collections.emptyMap();
|
||||
if (sameSite != null)
|
||||
attributes = Collections.singletonMap("SameSite", sameSite.getAttributeValue());
|
||||
return session.generateSetCookie((getSessionCookie() == null ? __DefaultSessionCookie : getSessionCookie()),
|
||||
getSessionDomain(),
|
||||
sessionPath,
|
||||
getMaxCookieAge(),
|
||||
isHttpOnly(),
|
||||
isSecureCookies() || (isSecureRequestOnly() && requestIsSecure),
|
||||
HttpCookie.getCommentWithoutAttributes(getSessionComment()),
|
||||
0,
|
||||
attributes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SameSite getSameSite()
|
||||
{
|
||||
return HttpCookie.getSameSiteFromComment(getSessionComment());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSameSite(SameSite sameSite)
|
||||
{
|
||||
setSessionComment(HttpCookie.getCommentWithAttributes(getSessionComment(), false, sameSite));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ public class SimpleSessionHandlerTest
|
|||
sessionManager.setSessionCookie("SIMPLE");
|
||||
sessionManager.setUsingCookies(true);
|
||||
sessionManager.setUsingURLs(false);
|
||||
sessionManager.setSessionPath("/");
|
||||
_server.setHandler(sessionManager);
|
||||
|
||||
sessionManager.setHandler(new Handler.Abstract()
|
||||
|
|
|
@ -14,12 +14,9 @@
|
|||
package org.eclipse.jetty.session;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpCookie.SameSite;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.session.Session.APISession;
|
||||
|
@ -144,74 +141,36 @@ public class TestableSessionManager extends AbstractSessionManager
|
|||
|
||||
protected void configureCookies()
|
||||
{
|
||||
String tmp = _cookieConfig.get(__SessionCookieProperty);
|
||||
if (tmp != null)
|
||||
setSessionCookie(tmp);
|
||||
String cookieName = _cookieConfig.get(__SessionCookieProperty);
|
||||
if (StringUtil.isNotBlank(cookieName))
|
||||
setSessionCookie(cookieName);
|
||||
|
||||
tmp = _cookieConfig.get(__SessionIdPathParameterNameProperty);
|
||||
if (tmp != null)
|
||||
setSessionIdPathParameterName(tmp);
|
||||
String paramName = _cookieConfig.get(__SessionIdPathParameterNameProperty);
|
||||
if (StringUtil.isNotBlank(paramName))
|
||||
setSessionIdPathParameterName(paramName);
|
||||
|
||||
// set up the max session cookie age if it isn't already
|
||||
if (getMaxCookieAge() == -1)
|
||||
{
|
||||
tmp = _cookieConfig.get(__MaxAgeProperty);
|
||||
if (tmp != null)
|
||||
setMaxCookieAge(Integer.parseInt(tmp.trim()));
|
||||
}
|
||||
// set up the max session cookie age
|
||||
String maxAge = _cookieConfig.get(__MaxAgeProperty);
|
||||
if (StringUtil.isNotBlank(maxAge))
|
||||
setMaxCookieAge(Integer.parseInt(maxAge));
|
||||
|
||||
// set up the session domain if it isn't already
|
||||
if (getSessionDomain() == null)
|
||||
setSessionDomain(_cookieConfig.get(__SessionDomainProperty));
|
||||
// set up the session domain
|
||||
String domain = _cookieConfig.get(__SessionDomainProperty);
|
||||
if (StringUtil.isNotBlank(domain))
|
||||
setSessionDomain(domain);
|
||||
|
||||
// set up the sessionPath if it isn't already
|
||||
if (getSessionPath() == null)
|
||||
setSessionPath(_cookieConfig.get(__SessionPathProperty));
|
||||
// set up the sessionPath
|
||||
String path = _cookieConfig.get(__SessionPathProperty);
|
||||
if (StringUtil.isNotBlank(path))
|
||||
setSessionPath(path);
|
||||
|
||||
tmp = _cookieConfig.get(__CheckRemoteSessionEncoding);
|
||||
if (tmp != null)
|
||||
setCheckingRemoteSessionIdEncoding(Boolean.parseBoolean(tmp));
|
||||
String checkEncoding = _cookieConfig.get(__CheckRemoteSessionEncoding);
|
||||
if (StringUtil.isNotBlank(checkEncoding))
|
||||
setCheckingRemoteSessionIdEncoding(Boolean.parseBoolean(checkEncoding));
|
||||
}
|
||||
|
||||
public Map<String, String> getCookieConfig()
|
||||
{
|
||||
return _cookieConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie getSessionCookie(Session session, String contextPath, boolean requestIsSecure)
|
||||
{
|
||||
if (isUsingCookies())
|
||||
{
|
||||
String sessionPath = getSessionPath();
|
||||
sessionPath = (sessionPath == null) ? contextPath : sessionPath;
|
||||
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
|
||||
SameSite sameSite = HttpCookie.getSameSiteFromComment(getSessionComment());
|
||||
Map<String, String> attributes = Collections.emptyMap();
|
||||
if (sameSite != null)
|
||||
attributes = Collections.singletonMap("SameSite", sameSite.getAttributeValue());
|
||||
return session.generateSetCookie((getSessionCookie() == null ? __DefaultSessionCookie : getSessionCookie()),
|
||||
getSessionDomain(),
|
||||
sessionPath,
|
||||
getMaxCookieAge(),
|
||||
isHttpOnly(),
|
||||
isSecureCookies() || (isSecureRequestOnly() && requestIsSecure),
|
||||
HttpCookie.getCommentWithoutAttributes(getSessionComment()),
|
||||
0,
|
||||
attributes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SameSite getSameSite()
|
||||
{
|
||||
return HttpCookie.getSameSiteFromComment(getSessionComment());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSameSite(SameSite sameSite)
|
||||
{
|
||||
setSessionComment(HttpCookie.getCommentWithAttributes(getSessionComment(), false, sameSite));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,7 +421,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
if (_coreSession == null)
|
||||
throw new IllegalStateException("Create session failed");
|
||||
|
||||
var cookie = _sessionManager.getSessionCookie(_coreSession, getContextPath(), isSecure());
|
||||
var cookie = _sessionManager.getSessionCookie(_coreSession, isSecure());
|
||||
|
||||
if (cookie != null)
|
||||
Response.replaceCookie(_request.getResponse(), cookie);
|
||||
|
@ -455,7 +455,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
|
||||
|
||||
if (getSessionManager().isUsingCookies())
|
||||
Response.replaceCookie(_request.getResponse(), getSessionManager().getSessionCookie(session, getContextPath(), isSecure()));
|
||||
Response.replaceCookie(_request.getResponse(), getSessionManager().getSessionCookie(session, isSecure()));
|
||||
|
||||
return session.getId();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
|||
import java.io.PrintWriter;
|
||||
import java.nio.channels.IllegalSelectorException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -86,12 +87,7 @@ public class ServletApiResponse implements HttpServletResponse
|
|||
if (StringUtil.isBlank(cookie.getName()))
|
||||
throw new IllegalArgumentException("Cookie.name cannot be blank/null");
|
||||
|
||||
// new style cookie, everything is an attribute
|
||||
addCookie(new HttpCookie(
|
||||
cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getVersion(),
|
||||
cookie.getAttributes()));
|
||||
addCookie(new HttpCookieFacade(cookie));
|
||||
}
|
||||
|
||||
public void addCookie(HttpCookie cookie)
|
||||
|
@ -570,7 +566,7 @@ public class ServletApiResponse implements HttpServletResponse
|
|||
SessionManager sessionManager = servletApiRequest.getSessionManager();
|
||||
if (sessionManager != null)
|
||||
{
|
||||
HttpCookie cookie = sessionManager.getSessionCookie(session, servletApiRequest.getContextPath(), servletApiRequest.getServletConnection().isSecure());
|
||||
HttpCookie cookie = sessionManager.getSessionCookie(session, servletApiRequest.getServletConnection().isSecure());
|
||||
if (cookie != null)
|
||||
addCookie(cookie);
|
||||
}
|
||||
|
@ -646,4 +642,92 @@ public class ServletApiResponse implements HttpServletResponse
|
|||
return fields;
|
||||
});
|
||||
}
|
||||
|
||||
static class HttpCookieFacade implements HttpCookie
|
||||
{
|
||||
private final Cookie _cookie;
|
||||
|
||||
public HttpCookieFacade(Cookie cookie)
|
||||
{
|
||||
_cookie = cookie;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment()
|
||||
{
|
||||
return _cookie.getComment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain()
|
||||
{
|
||||
return _cookie.getDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxAge()
|
||||
{
|
||||
return _cookie.getMaxAge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath()
|
||||
{
|
||||
return _cookie.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return _cookie.getSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _cookie.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue()
|
||||
{
|
||||
return _cookie.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion()
|
||||
{
|
||||
return _cookie.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SameSite getSameSite()
|
||||
{
|
||||
return SameSite.from(getAttributes().get(HttpCookie.SAME_SITE_ATTRIBUTE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHttpOnly()
|
||||
{
|
||||
return _cookie.isHttpOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes()
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString()
|
||||
{
|
||||
return HttpCookie.asString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return HttpCookie.toString(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -336,7 +336,7 @@ public class ServletContextResponse extends ContextResponse
|
|||
// TODO: use Jan's new static method.
|
||||
if (session instanceof SessionHandler.ServletAPISession apiSession)
|
||||
{
|
||||
HttpCookie c = sh.getSessionCookie(apiSession.getCoreSession(), _request.getContext().getContextPath(), _request.isSecure());
|
||||
HttpCookie c = sh.getSessionCookie(apiSession.getCoreSession(), _request.isSecure());
|
||||
if (c != null)
|
||||
Response.addCookie(_response, c);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.session.AbstractSessionManager;
|
||||
import org.eclipse.jetty.session.Session;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -397,60 +396,6 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Ne
|
|||
return apiRequest == null ? null : apiRequest.getCoreSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* A session cookie is marked as secure IFF any of the following conditions are true:
|
||||
* <ol>
|
||||
* <li>SessionCookieConfig.setSecure == true</li>
|
||||
* <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
|
||||
* </ol>
|
||||
* According to SessionCookieConfig javadoc, case 1 can be used when:
|
||||
* "... even though the request that initiated the session came over HTTP,
|
||||
* is to support a topology where the web container is front-ended by an
|
||||
* SSL offloading load balancer. In this case, the traffic between the client
|
||||
* and the load balancer will be over HTTPS, whereas the traffic between the
|
||||
* load balancer and the web container will be over HTTP."
|
||||
* <p>
|
||||
* For case 2, you can use _secureRequestOnly to determine if you want the
|
||||
* Servlet Spec 3.0 default behavior when SessionCookieConfig.setSecure==false,
|
||||
* which is:
|
||||
* <cite>
|
||||
* "they shall be marked as secure only if the request that initiated the
|
||||
* corresponding session was also secure"
|
||||
* </cite>
|
||||
* <p>
|
||||
* The default for _secureRequestOnly is true, which gives the above behavior. If
|
||||
* you set it to false, then a session cookie is NEVER marked as secure, even if
|
||||
* the initiating request was secure.
|
||||
*
|
||||
* @param session the session to which the cookie should refer.
|
||||
* @param contextPath the context to which the cookie should be linked.
|
||||
* The client will only send the cookie value when requesting resources under this path.
|
||||
* @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
|
||||
* @return if this <code>SessionManager</code> uses cookies, then this method will return a new
|
||||
* {@link HttpCookie cookie object} that should be set on the client in order to link future HTTP requests
|
||||
* with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
|
||||
*/
|
||||
@Override
|
||||
public HttpCookie getSessionCookie(Session session, String contextPath, boolean requestIsSecure)
|
||||
{
|
||||
if (isUsingCookies())
|
||||
{
|
||||
String sessionPath = getSessionPath();
|
||||
sessionPath = (sessionPath == null) ? contextPath : sessionPath;
|
||||
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
|
||||
return session.generateSetCookie((getSessionCookie() == null ? __DefaultSessionCookie : getSessionCookie()),
|
||||
getSessionDomain(),
|
||||
sessionPath,
|
||||
getMaxCookieAge(),
|
||||
isHttpOnly(),
|
||||
isSecureCookies() || (isSecureRequestOnly() && requestIsSecure),
|
||||
null,
|
||||
0,
|
||||
getSessionAttributes());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener for session-related events.
|
||||
*
|
||||
|
@ -507,43 +452,10 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Ne
|
|||
}
|
||||
|
||||
/**
|
||||
* Set up cookie configuration based on init params, if
|
||||
* the SessionCookieConfig has not been set.
|
||||
* Set up cookie configuration based on init params
|
||||
*/
|
||||
protected void configureCookies()
|
||||
{
|
||||
// Look for a session cookie name
|
||||
if (_servletContextHandlerContext != null)
|
||||
{
|
||||
ServletContext servletContext = _servletContextHandlerContext.getServletContext();
|
||||
String tmp = servletContext.getInitParameter(__SessionCookieProperty);
|
||||
if (tmp != null)
|
||||
setSessionCookie(tmp);
|
||||
|
||||
tmp = servletContext.getInitParameter(__SessionIdPathParameterNameProperty);
|
||||
if (tmp != null)
|
||||
setSessionIdPathParameterName(tmp);
|
||||
|
||||
// set up the max session cookie age if it isn't already
|
||||
if (getMaxCookieAge() == -1)
|
||||
{
|
||||
tmp = servletContext.getInitParameter(__MaxAgeProperty);
|
||||
if (tmp != null)
|
||||
setMaxCookieAge(Integer.parseInt(tmp.trim()));
|
||||
}
|
||||
|
||||
// set up the session domain if it isn't already
|
||||
if (getSessionDomain() == null)
|
||||
setSessionDomain(servletContext.getInitParameter(__SessionDomainProperty));
|
||||
|
||||
// set up the sessionPath if it isn't already
|
||||
if (getSessionPath() == null)
|
||||
setSessionPath(servletContext.getInitParameter(__SessionPathProperty));
|
||||
|
||||
tmp = servletContext.getInitParameter(__CheckRemoteSessionEncoding);
|
||||
if (tmp != null)
|
||||
setCheckingRemoteSessionIdEncoding(Boolean.parseBoolean(tmp));
|
||||
}
|
||||
}
|
||||
|
||||
public Session.APISession newSessionAPIWrapper(Session session)
|
||||
|
|
|
@ -132,7 +132,7 @@ public abstract class LoginAuthenticator implements Authenticator
|
|||
session.renewId(servletContextRequest);
|
||||
session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
|
||||
if (session.isSetCookieNeeded())
|
||||
Response.replaceCookie(response, session.getSessionManager().getSessionCookie(session, httpRequest.getContextPath(), httpRequest.isSecure()));
|
||||
Response.replaceCookie(response, session.getSessionManager().getSessionCookie(session, httpRequest.isSecure()));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("renew {}->{}", oldId, session.getId());
|
||||
return httpSession;
|
||||
|
|
|
@ -912,6 +912,7 @@ public class ServletContextHandlerTest
|
|||
_server.setHandler(contexts);
|
||||
|
||||
ServletContextHandler root = new ServletContextHandler(contexts, "/", ServletContextHandler.SESSIONS);
|
||||
root.getSessionHandler().setSessionDomain("testing");
|
||||
ListenerHolder initialListener = new ListenerHolder();
|
||||
initialListener.setListener(new InitialListener());
|
||||
root.getServletHandler().addListener(initialListener);
|
||||
|
@ -954,6 +955,7 @@ public class ServletContextHandlerTest
|
|||
|
||||
//test HttpSessionAttributeListener
|
||||
response = _connector.getResponse("GET /test?session=create HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, containsString("JSESSIONID"));
|
||||
String sessionid = response.substring(response.indexOf("JSESSIONID"), response.indexOf(";"));
|
||||
assertThat(response, Matchers.containsString("200 OK"));
|
||||
assertEquals(1, MySListener.creates);
|
||||
|
@ -2385,26 +2387,7 @@ public class ServletContextHandlerTest
|
|||
@Override
|
||||
public void addCookie(Cookie cookie)
|
||||
{
|
||||
// Let's set SameSite to STRICT
|
||||
// But we cannot use Servlet jakarta.servlet.http.Cookie.setComment(String) technique of old
|
||||
// as the Comment field is always null and is slated for removal.
|
||||
|
||||
// We'll use the Jetty HttpCookie to work around this.
|
||||
boolean httpOnly = false;
|
||||
String comment = null;
|
||||
HttpCookie.SameSite sameSite = HttpCookie.SameSite.STRICT;
|
||||
|
||||
addCookie(new HttpCookie(
|
||||
cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getDomain(),
|
||||
cookie.getPath(),
|
||||
cookie.getMaxAge(),
|
||||
httpOnly,
|
||||
cookie.getSecure(),
|
||||
comment,
|
||||
cookie.getVersion(),
|
||||
HttpCookie.SameSite.STRICT));
|
||||
addCookie(new HttpCookieFacade(cookie));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2412,19 +2395,8 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
// Let's force SameSite to STRICT
|
||||
Map<String, String> attrs = new HashMap<>(cookie.getAttributes());
|
||||
attrs.put("SameSite", "Strict");
|
||||
super.addCookie(new HttpCookie(
|
||||
cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getDomain(),
|
||||
cookie.getPath(),
|
||||
cookie.getMaxAge(),
|
||||
cookie.isHttpOnly(),
|
||||
cookie.isSecure(),
|
||||
cookie.getComment(),
|
||||
cookie.getVersion(),
|
||||
attrs
|
||||
));
|
||||
attrs.put(HttpCookie.SAME_SITE_ATTRIBUTE, HttpCookie.SameSite.STRICT.getAttributeValue());
|
||||
super.addCookie(HttpCookie.from(cookie.getName(), cookie.getValue(), attrs));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -484,7 +484,7 @@ public class SessionHandlerTest
|
|||
sessionCookieConfig.setAttribute("SameSite", "Strict");
|
||||
sessionCookieConfig.setAttribute("ham", "cheese");
|
||||
|
||||
HttpCookie cookie = mgr.getSessionCookie(session, "/bar", false);
|
||||
HttpCookie cookie = mgr.getSessionCookie(session, false);
|
||||
assertEquals("SPECIAL", cookie.getName());
|
||||
assertEquals("universe", cookie.getDomain());
|
||||
assertEquals("/foo", cookie.getPath());
|
||||
|
@ -492,8 +492,8 @@ public class SessionHandlerTest
|
|||
assertFalse(cookie.isSecure());
|
||||
assertEquals(99, cookie.getMaxAge());
|
||||
assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite());
|
||||
|
||||
String cookieStr = cookie.getRFC6265SetCookie();
|
||||
|
||||
String cookieStr = HttpCookie.getRFC6265SetCookie(cookie);
|
||||
assertThat(cookieStr, containsString("; SameSite=Strict; ham=cheese"));
|
||||
}
|
||||
|
||||
|
@ -517,32 +517,32 @@ public class SessionHandlerTest
|
|||
sessionCookieConfig.setSecure(true);
|
||||
|
||||
//sessionCookieConfig.secure == true, always mark cookie as secure, irrespective of if requestIsSecure
|
||||
HttpCookie cookie = mgr.getSessionCookie(session, "/foo", true);
|
||||
HttpCookie cookie = mgr.getSessionCookie(session, true);
|
||||
assertTrue(cookie.isSecure());
|
||||
//sessionCookieConfig.secure == true, always mark cookie as secure, irrespective of if requestIsSecure
|
||||
cookie = mgr.getSessionCookie(session, "/foo", false);
|
||||
cookie = mgr.getSessionCookie(session, false);
|
||||
assertTrue(cookie.isSecure());
|
||||
|
||||
//sessionCookieConfig.secure==false, setSecureRequestOnly==true, requestIsSecure==true
|
||||
//cookie should be secure: see SessionCookieConfig.setSecure() javadoc
|
||||
sessionCookieConfig.setSecure(false);
|
||||
cookie = mgr.getSessionCookie(session, "/foo", true);
|
||||
cookie = mgr.getSessionCookie(session, true);
|
||||
assertTrue(cookie.isSecure());
|
||||
|
||||
//sessionCookieConfig.secure=false, setSecureRequestOnly==true, requestIsSecure==false
|
||||
//cookie is not secure: see SessionCookieConfig.setSecure() javadoc
|
||||
cookie = mgr.getSessionCookie(session, "/foo", false);
|
||||
cookie = mgr.getSessionCookie(session, false);
|
||||
assertFalse(cookie.isSecure());
|
||||
|
||||
//sessionCookieConfig.secure=false, setSecureRequestOnly==false, requestIsSecure==false
|
||||
//cookie is not secure: not a secure request
|
||||
mgr.setSecureRequestOnly(false);
|
||||
cookie = mgr.getSessionCookie(session, "/foo", false);
|
||||
cookie = mgr.getSessionCookie(session, false);
|
||||
assertFalse(cookie.isSecure());
|
||||
|
||||
//sessionCookieConfig.secure=false, setSecureRequestOnly==false, requestIsSecure==true
|
||||
//cookie is not secure: not on secured requests and request is secure
|
||||
cookie = mgr.getSessionCookie(session, "/foo", true);
|
||||
cookie = mgr.getSessionCookie(session, true);
|
||||
assertFalse(cookie.isSecure());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,10 +101,9 @@ public class StandardDescriptorProcessorTest
|
|||
assertEquals("false", wac.getSessionHandler().getSessionCookieConfig().getAttribute("HttpOnly"));
|
||||
assertEquals(false, wac.getSessionHandler().isHttpOnly());
|
||||
|
||||
Map<String, String> attributes = wac.getSessionHandler().getSessionCookieConfig().getAttributes();
|
||||
|
||||
//SessionCookieConfig javadoc states that all setters must be also represented as attributes
|
||||
assertThat(wac.getSessionHandler().getSessionCookieConfig().getAttributes().keySet(),
|
||||
Map<String, String> attributes = wac.getSessionHandler().getSessionCookieConfig().getAttributes();
|
||||
assertThat(attributes.keySet(),
|
||||
containsInAnyOrder(Arrays.asList(
|
||||
equalToIgnoringCase("name"),
|
||||
equalToIgnoringCase("comment"),
|
||||
|
@ -117,9 +116,17 @@ public class StandardDescriptorProcessorTest
|
|||
equalToIgnoringCase("width"),
|
||||
equalToIgnoringCase("SameSite"))));
|
||||
|
||||
//test the attributes on SessionHandler do NOT contain the well-known ones of Name, Comment, Domain etc etc
|
||||
assertThat(wac.getSessionHandler().getSessionAttributes().keySet(),
|
||||
//test the attributes on SessionHandler do NOT contain the name
|
||||
Map<String, String> sessionAttributes = wac.getSessionHandler().getSessionAttributes();
|
||||
sessionAttributes.keySet().forEach(System.err::println);
|
||||
assertThat(sessionAttributes.keySet(),
|
||||
containsInAnyOrder(Arrays.asList(
|
||||
equalToIgnoringCase("comment"),
|
||||
equalToIgnoringCase("domain"),
|
||||
equalToIgnoringCase("path"),
|
||||
equalToIgnoringCase("max-age"),
|
||||
equalToIgnoringCase("secure"),
|
||||
equalToIgnoringCase("httponly"),
|
||||
equalToIgnoringCase("length"),
|
||||
equalToIgnoringCase("width"),
|
||||
equalToIgnoringCase("SameSite"))));
|
||||
|
|
|
@ -111,7 +111,7 @@ public class CookiesTest
|
|||
final String cookiePath = "/path";
|
||||
startServer((req, resp, cb) ->
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath);
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue, Map.of(org.eclipse.jetty.http.HttpCookie.DOMAIN_ATTRIBUTE, cookieDomain, org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, cookiePath));
|
||||
Response.addCookie(resp, cookie);
|
||||
return new WholeMessageEcho();
|
||||
});
|
||||
|
|
|
@ -94,7 +94,7 @@ public class Cookies extends CookieCutter
|
|||
return _cookies;
|
||||
|
||||
parseFields(_rawFields);
|
||||
_cookies = (Cookie[])_cookieList.toArray(new Cookie[_cookieList.size()]);
|
||||
_cookies = _cookieList.toArray(new Cookie[0]);
|
||||
_cookieList.clear();
|
||||
_parsed = true;
|
||||
return _cookies;
|
||||
|
|
|
@ -1293,7 +1293,7 @@ public class Request implements HttpServletRequest
|
|||
if (getRemoteUser() != null)
|
||||
session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
|
||||
if (session.isSetCookieNeeded())
|
||||
_channel.getResponse().replaceCookie(_sessionManager.getSessionCookie(session, getContextPath(), isSecure()));
|
||||
_channel.getResponse().replaceCookie(_sessionManager.getSessionCookie(session, isSecure()));
|
||||
|
||||
return httpSession.getId();
|
||||
}
|
||||
|
@ -1376,7 +1376,7 @@ public class Request implements HttpServletRequest
|
|||
if (_coreSession == null)
|
||||
throw new IllegalStateException("Create session failed");
|
||||
|
||||
HttpCookie cookie = _sessionManager.getSessionCookie(_coreSession, getContextPath(), isSecure());
|
||||
HttpCookie cookie = _sessionManager.getSessionCookie(_coreSession, isSecure());
|
||||
if (cookie != null)
|
||||
_channel.getResponse().replaceCookie(cookie);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Iterator;
|
|||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
|
@ -68,6 +69,18 @@ public class Response implements HttpServletResponse
|
|||
public static final int NO_CONTENT_LENGTH = -1;
|
||||
public static final int USE_KNOWN_CONTENT_LENGTH = -2;
|
||||
|
||||
/**
|
||||
* If this string is found within a cookie comment, then the cookie is HttpOnly
|
||||
**/
|
||||
private static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
|
||||
/**
|
||||
* These strings are used by to check for a SameSite specifier in a cookie comment
|
||||
**/
|
||||
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
|
||||
private static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
|
||||
private static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
|
||||
private static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
|
||||
|
||||
public enum OutputType
|
||||
{
|
||||
NONE, STREAM, WRITER
|
||||
|
@ -243,16 +256,7 @@ public class Response implements HttpServletResponse
|
|||
if (contextDefault == null)
|
||||
return cookie; //no default set
|
||||
|
||||
return new HttpCookie(cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getDomain(),
|
||||
cookie.getPath(),
|
||||
cookie.getMaxAge(),
|
||||
cookie.isHttpOnly(),
|
||||
cookie.isSecure(),
|
||||
cookie.getComment(),
|
||||
cookie.getVersion(),
|
||||
contextDefault);
|
||||
return HttpCookie.from(cookie, HttpCookie.SAME_SITE_ATTRIBUTE, contextDefault.getAttributeValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -264,23 +268,7 @@ public class Response implements HttpServletResponse
|
|||
if (StringUtil.isBlank(cookie.getName()))
|
||||
throw new IllegalArgumentException("Cookie.name cannot be blank/null");
|
||||
|
||||
String comment = cookie.getComment();
|
||||
// HttpOnly was supported as a comment in cookie flags before the java.net.HttpCookie implementation so need to check that
|
||||
boolean httpOnly = cookie.isHttpOnly() || HttpCookie.isHttpOnlyInComment(comment);
|
||||
SameSite sameSite = HttpCookie.getSameSiteFromComment(comment);
|
||||
comment = HttpCookie.getCommentWithoutAttributes(comment);
|
||||
|
||||
addCookie(new HttpCookie(
|
||||
cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getDomain(),
|
||||
cookie.getPath(),
|
||||
cookie.getMaxAge(),
|
||||
httpOnly,
|
||||
cookie.getSecure(),
|
||||
comment,
|
||||
cookie.getVersion(),
|
||||
sameSite));
|
||||
addCookie(new HttpCookieFacade(cookie));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1189,7 +1177,7 @@ public class Response implements HttpServletResponse
|
|||
SessionManager sessionManager = request.getSessionManager();
|
||||
if (sessionManager != null && httpSession instanceof Session.APISession apiSession)
|
||||
{
|
||||
HttpCookie cookie = sessionManager.getSessionCookie(apiSession.getCoreSession(), request.getContextPath(), request.isSecure());
|
||||
HttpCookie cookie = sessionManager.getSessionCookie(apiSession.getCoreSession(), request.isSecure());
|
||||
if (cookie != null)
|
||||
addCookie(cookie);
|
||||
}
|
||||
|
@ -1494,4 +1482,157 @@ public class Response implements HttpServletResponse
|
|||
return _supplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HttpCookieFacade implements HttpCookie
|
||||
{
|
||||
private final Cookie _cookie;
|
||||
private final String _comment;
|
||||
private final boolean _httpOnly;
|
||||
private final SameSite _sameSite;
|
||||
|
||||
public HttpCookieFacade(Cookie cookie)
|
||||
{
|
||||
_cookie = cookie;
|
||||
|
||||
String comment = cookie.getComment();
|
||||
|
||||
// HttpOnly was supported as a comment in cookie flags before the java.net.HttpCookie implementation so need to check that
|
||||
_httpOnly = cookie.isHttpOnly() || isHttpOnlyInComment(comment);
|
||||
_sameSite = getSameSiteFromComment(comment);
|
||||
_comment = getCommentWithoutAttributes(comment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment()
|
||||
{
|
||||
return _comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain()
|
||||
{
|
||||
return _cookie.getDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxAge()
|
||||
{
|
||||
return _cookie.getMaxAge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath()
|
||||
{
|
||||
return _cookie.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return _cookie.getSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _cookie.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue()
|
||||
{
|
||||
return _cookie.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion()
|
||||
{
|
||||
return _cookie.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SameSite getSameSite()
|
||||
{
|
||||
return _sameSite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHttpOnly()
|
||||
{
|
||||
return _httpOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes()
|
||||
{
|
||||
Map<String, String> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
attributes.put(COMMENT_ATTRIBUTE, _comment);
|
||||
attributes.put(DOMAIN_ATTRIBUTE, getDomain());
|
||||
if (_httpOnly)
|
||||
attributes.put(HTTP_ONLY_ATTRIBUTE, Boolean.TRUE.toString());
|
||||
if (_cookie.getMaxAge() >= 0)
|
||||
attributes.put(MAX_AGE_ATTRIBUTE, Long.toString(getMaxAge()));
|
||||
attributes.put(PATH_ATTRIBUTE, getPath());
|
||||
if (_sameSite != null)
|
||||
attributes.put(SAME_SITE_ATTRIBUTE, _sameSite.getAttributeValue());
|
||||
if (isSecure())
|
||||
attributes.put(SECURE_ATTRIBUTE, Boolean.TRUE.toString());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString()
|
||||
{
|
||||
return HttpCookie.asString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return HttpCookie.toString(this);
|
||||
}
|
||||
|
||||
static boolean isHttpOnlyInComment(String comment)
|
||||
{
|
||||
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
|
||||
}
|
||||
|
||||
static SameSite getSameSiteFromComment(String comment)
|
||||
{
|
||||
if (comment != null)
|
||||
{
|
||||
if (comment.contains(SAME_SITE_STRICT_COMMENT))
|
||||
{
|
||||
return SameSite.STRICT;
|
||||
}
|
||||
if (comment.contains(SAME_SITE_LAX_COMMENT))
|
||||
{
|
||||
return SameSite.LAX;
|
||||
}
|
||||
if (comment.contains(SAME_SITE_NONE_COMMENT))
|
||||
{
|
||||
return SameSite.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getCommentWithoutAttributes(String comment)
|
||||
{
|
||||
if (comment == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String strippedComment = comment.trim();
|
||||
|
||||
strippedComment = StringUtil.strip(strippedComment, HTTP_ONLY_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_NONE_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_LAX_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_STRICT_COMMENT);
|
||||
|
||||
return strippedComment.length() == 0 ? null : strippedComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.util.EnumSet;
|
|||
import java.util.Enumeration;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
|
@ -40,7 +39,6 @@ import jakarta.servlet.http.HttpSessionEvent;
|
|||
import jakarta.servlet.http.HttpSessionIdListener;
|
||||
import jakarta.servlet.http.HttpSessionListener;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpCookie.SameSite;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.session.AbstractSessionManager;
|
||||
import org.eclipse.jetty.session.Session;
|
||||
|
@ -48,7 +46,6 @@ import org.eclipse.jetty.session.SessionCache;
|
|||
import org.eclipse.jetty.session.SessionConfig;
|
||||
import org.eclipse.jetty.session.SessionIdManager;
|
||||
import org.eclipse.jetty.session.SessionManager;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -570,81 +567,6 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
|
|||
return super.resolveRequestedSessionId(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie.SameSite getSameSite()
|
||||
{
|
||||
return HttpCookie.getSameSiteFromComment(getSessionComment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Session cookie sameSite mode.
|
||||
*
|
||||
* @param sameSite The sameSite setting for Session cookies (or null for no sameSite setting)
|
||||
*/
|
||||
@Override
|
||||
public void setSameSite(HttpCookie.SameSite sameSite)
|
||||
{
|
||||
setSessionComment(HttpCookie.getCommentWithAttributes(getSessionComment(), false, sameSite));
|
||||
}
|
||||
|
||||
/**
|
||||
* A session cookie is marked as secure IFF any of the following conditions are true:
|
||||
* <ol>
|
||||
* <li>SessionCookieConfig.setSecure == true</li>
|
||||
* <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
|
||||
* </ol>
|
||||
* According to SessionCookieConfig javadoc, case 1 can be used when:
|
||||
* "... even though the request that initiated the session came over HTTP,
|
||||
* is to support a topology where the web container is front-ended by an
|
||||
* SSL offloading load balancer. In this case, the traffic between the client
|
||||
* and the load balancer will be over HTTPS, whereas the traffic between the
|
||||
* load balancer and the web container will be over HTTP."
|
||||
* <p>
|
||||
* For case 2, you can use _secureRequestOnly to determine if you want the
|
||||
* Servlet Spec 3.0 default behavior when SessionCookieConfig.setSecure==false,
|
||||
* which is:
|
||||
* <cite>
|
||||
* "they shall be marked as secure only if the request that initiated the
|
||||
* corresponding session was also secure"
|
||||
* </cite>
|
||||
* <p>
|
||||
* The default for _secureRequestOnly is true, which gives the above behavior. If
|
||||
* you set it to false, then a session cookie is NEVER marked as secure, even if
|
||||
* the initiating request was secure.
|
||||
*
|
||||
* @param session the session to which the cookie should refer.
|
||||
* @param contextPath the context to which the cookie should be linked.
|
||||
* The client will only send the cookie value when requesting resources under this path.
|
||||
* @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
|
||||
* @return if this <code>SessionManager</code> uses cookies, then this method will return a new
|
||||
* {@link HttpCookie cookie object} that should be set on the client in order to link future HTTP requests
|
||||
* with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
|
||||
*/
|
||||
@Override
|
||||
public HttpCookie getSessionCookie(Session session, String contextPath, boolean requestIsSecure)
|
||||
{
|
||||
if (isUsingCookies())
|
||||
{
|
||||
String sessionPath = getSessionPath();
|
||||
sessionPath = (sessionPath == null) ? contextPath : sessionPath;
|
||||
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
|
||||
SameSite sameSite = HttpCookie.getSameSiteFromComment(getSessionComment());
|
||||
Map<String, String> attributes = Collections.emptyMap();
|
||||
if (sameSite != null)
|
||||
attributes = Collections.singletonMap("SameSite", sameSite.getAttributeValue());
|
||||
return session.generateSetCookie((getSessionCookie() == null ? __DefaultSessionCookie : getSessionCookie()),
|
||||
getSessionDomain(),
|
||||
sessionPath,
|
||||
getMaxCookieAge(),
|
||||
isHttpOnly(),
|
||||
isSecureCookies() || (isSecureRequestOnly() && requestIsSecure),
|
||||
HttpCookie.getCommentWithoutAttributes(getSessionComment()),
|
||||
0,
|
||||
attributes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callSessionAttributeListeners(Session session, String name, Object old, Object value)
|
||||
{
|
||||
|
|
|
@ -1870,10 +1870,10 @@ public class RequestTest
|
|||
String uri = "http://host/foo/something";
|
||||
HttpChannel httpChannel = new HttpChannel(_context, new MockConnectionMetaData(_connector));
|
||||
Request request = new MockRequest(httpChannel, new HttpInput(httpChannel));
|
||||
request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(new HttpCookie("good", "thumbsup", 100), CookieCompliance.RFC6265));
|
||||
request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(new HttpCookie("bonza", "bewdy", 1), CookieCompliance.RFC6265));
|
||||
request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(new HttpCookie("bad", "thumbsdown", 0), CookieCompliance.RFC6265));
|
||||
request.getResponse().getHttpFields().add(new HttpField(HttpHeader.SET_COOKIE, new HttpCookie("ugly", "duckling", 100).getSetCookie(CookieCompliance.RFC6265)));
|
||||
request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(HttpCookie.from("good", "thumbsup", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(100))), CookieCompliance.RFC6265));
|
||||
request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(HttpCookie.from("bonza", "bewdy", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1))), CookieCompliance.RFC6265));
|
||||
request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(HttpCookie.from("bad", "thumbsdown", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0))), CookieCompliance.RFC6265));
|
||||
request.getResponse().getHttpFields().add(new HttpField(HttpHeader.SET_COOKIE, HttpCookie.getSetCookie(HttpCookie.from("ugly", "duckling", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(100))), CookieCompliance.RFC6265)));
|
||||
request.getResponse().getHttpFields().add(new HttpField(HttpHeader.SET_COOKIE, "flow=away; Max-Age=0; Secure; HttpOnly; SameSite=None"));
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
fields.add(HttpHeader.AUTHORIZATION, "Basic foo");
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Enumeration;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -1938,6 +1939,23 @@ public class ResponseTest
|
|||
assertNull(response.getHttpFields().get("Set-Cookie"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddCookieSameSiteByComment() throws Exception
|
||||
{
|
||||
_context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, HttpCookie.SameSite.STRICT);
|
||||
|
||||
Response response = getResponse();
|
||||
Cookie cookie = new Cookie("name", "value");
|
||||
cookie.setDomain("domain");
|
||||
cookie.setPath("/path");
|
||||
cookie.setSecure(true);
|
||||
cookie.setComment("comment__HTTP_ONLY____SAME_SITE_LAX__");
|
||||
|
||||
response.addCookie(cookie);
|
||||
String set = response.getHttpFields().get("Set-Cookie");
|
||||
assertEquals("name=value; Path=/path; Domain=domain; Secure; HttpOnly; SameSite=Lax", set);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddCookieSameSiteDefault() throws Exception
|
||||
{
|
||||
|
@ -2072,21 +2090,21 @@ public class ResponseTest
|
|||
{
|
||||
Response response = getResponse();
|
||||
|
||||
response.replaceCookie(new HttpCookie("Foo", "123456"));
|
||||
response.replaceCookie(new HttpCookie("Foo", "123456", "A", "/path"));
|
||||
response.replaceCookie(new HttpCookie("Foo", "123456", "B", "/path"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "123456"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "123456", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "A", HttpCookie.PATH_ATTRIBUTE, "/path")));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "123456", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "B", HttpCookie.PATH_ATTRIBUTE, "/path")));
|
||||
|
||||
response.replaceCookie(new HttpCookie("Bar", "123456"));
|
||||
response.replaceCookie(new HttpCookie("Bar", "123456", null, "/left"));
|
||||
response.replaceCookie(new HttpCookie("Bar", "123456", null, "/right"));
|
||||
response.replaceCookie(HttpCookie.from("Bar", "123456"));
|
||||
response.replaceCookie(HttpCookie.from("Bar", "123456", Map.of(HttpCookie.PATH_ATTRIBUTE, "/left")));
|
||||
response.replaceCookie(HttpCookie.from("Bar", "123456", Map.of(HttpCookie.PATH_ATTRIBUTE, "/right")));
|
||||
|
||||
response.replaceCookie(new HttpCookie("Bar", "value", null, "/right"));
|
||||
response.replaceCookie(new HttpCookie("Bar", "value", null, "/left"));
|
||||
response.replaceCookie(new HttpCookie("Bar", "value"));
|
||||
response.replaceCookie(HttpCookie.from("Bar", "value", Map.of(HttpCookie.PATH_ATTRIBUTE, "/right")));
|
||||
response.replaceCookie(HttpCookie.from("Bar", "value", Map.of(HttpCookie.PATH_ATTRIBUTE, "/left")));
|
||||
response.replaceCookie(HttpCookie.from("Bar", "value"));
|
||||
|
||||
response.replaceCookie(new HttpCookie("Foo", "value", "B", "/path"));
|
||||
response.replaceCookie(new HttpCookie("Foo", "value", "A", "/path"));
|
||||
response.replaceCookie(new HttpCookie("Foo", "value"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "B", HttpCookie.PATH_ATTRIBUTE, "/path")));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "A", HttpCookie.PATH_ATTRIBUTE, "/path")));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "value"));
|
||||
|
||||
String[] expected = new String[]{
|
||||
"Foo=value",
|
||||
|
@ -2107,11 +2125,11 @@ public class ResponseTest
|
|||
Response response = getResponse();
|
||||
_context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "LAX");
|
||||
//replace with no prior does an add
|
||||
response.replaceCookie(new HttpCookie("Foo", "123456"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "123456"));
|
||||
String set = response.getHttpFields().get("Set-Cookie");
|
||||
assertEquals("Foo=123456; SameSite=Lax", set);
|
||||
//check replacement
|
||||
response.replaceCookie(new HttpCookie("Foo", "other"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "other"));
|
||||
set = response.getHttpFields().get("Set-Cookie");
|
||||
assertEquals("Foo=other; SameSite=Lax", set);
|
||||
}
|
||||
|
@ -2122,21 +2140,21 @@ public class ResponseTest
|
|||
Response response = getResponse();
|
||||
|
||||
response.addHeader(HttpHeader.SET_COOKIE.asString(), "Foo=123456");
|
||||
response.replaceCookie(new HttpCookie("Foo", "value"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "value"));
|
||||
List<String> actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
|
||||
assertThat(actual, hasItems("Foo=value"));
|
||||
|
||||
response.setHeader(HttpHeader.SET_COOKIE, "Foo=123456; domain=Bah; Path=/path");
|
||||
response.replaceCookie(new HttpCookie("Foo", "other"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "other"));
|
||||
actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
|
||||
assertThat(actual, hasItems("Foo=123456; domain=Bah; Path=/path", "Foo=other"));
|
||||
|
||||
response.replaceCookie(new HttpCookie("Foo", "replaced", "Bah", "/path"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "replaced", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "Bah", HttpCookie.PATH_ATTRIBUTE, "/path")));
|
||||
actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
|
||||
assertThat(actual, hasItems("Foo=replaced; Path=/path; Domain=Bah", "Foo=other"));
|
||||
|
||||
response.setHeader(HttpHeader.SET_COOKIE, "Foo=123456; domain=Bah; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; Path=/path");
|
||||
response.replaceCookie(new HttpCookie("Foo", "replaced", "Bah", "/path"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "replaced", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "Bah", HttpCookie.PATH_ATTRIBUTE, "/path")));
|
||||
actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
|
||||
assertThat(actual, hasItems("Foo=replaced; Path=/path; Domain=Bah"));
|
||||
}
|
||||
|
@ -2148,7 +2166,7 @@ public class ResponseTest
|
|||
_context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "LAX");
|
||||
|
||||
response.addHeader(HttpHeader.SET_COOKIE.asString(), "Foo=123456");
|
||||
response.replaceCookie(new HttpCookie("Foo", "value"));
|
||||
response.replaceCookie(HttpCookie.from("Foo", "value"));
|
||||
String set = response.getHttpFields().get("Set-Cookie");
|
||||
assertEquals("Foo=value; SameSite=Lax", set);
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ public class SessionHandlerTest
|
|||
_sessionHandler.setSessionCookie("JSESSIONID");
|
||||
_sessionHandler.setUsingCookies(true);
|
||||
_sessionHandler.setUsingURLs(false);
|
||||
_sessionHandler.setSessionPath("/");
|
||||
contextHandler.setHandler(_sessionHandler);
|
||||
|
||||
_sessionHandler.setHandler(new AbstractHandler()
|
||||
|
@ -184,7 +185,7 @@ public class SessionHandlerTest
|
|||
//a default value on the context attribute org.eclipse.jetty.cookie.sameSiteDefault
|
||||
mgr.setSameSite(HttpCookie.SameSite.STRICT);
|
||||
|
||||
HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, "/bar", false);
|
||||
HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, false);
|
||||
assertEquals("SPECIAL", cookie.getName());
|
||||
assertEquals("universe", cookie.getDomain());
|
||||
assertEquals("/foo", cookie.getPath());
|
||||
|
@ -192,8 +193,8 @@ public class SessionHandlerTest
|
|||
assertFalse(cookie.isSecure());
|
||||
assertEquals(99, cookie.getMaxAge());
|
||||
assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite());
|
||||
|
||||
String cookieStr = cookie.getRFC6265SetCookie();
|
||||
|
||||
String cookieStr = HttpCookie.getRFC6265SetCookie(cookie);
|
||||
assertThat(cookieStr, containsString("; SameSite=Strict"));
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ public abstract class LoginAuthenticator implements Authenticator
|
|||
session.renewId(Request.getBaseRequest(request).getHttpChannel().getCoreRequest());
|
||||
session.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
|
||||
if (session.isSetCookieNeeded() && (response instanceof Response))
|
||||
((Response)response).replaceCookie(session.getSessionManager().getSessionCookie(session, request.getContextPath(), request.isSecure()));
|
||||
((Response)response).replaceCookie(session.getSessionManager().getSessionCookie(session, request.isSecure()));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("renew {}->{}", oldId, session.getId());
|
||||
}
|
||||
|
|
|
@ -891,6 +891,7 @@ public class ServletContextHandlerTest
|
|||
_server.setHandler(contexts);
|
||||
|
||||
ServletContextHandler root = new ServletContextHandler(contexts, "/", ServletContextHandler.SESSIONS);
|
||||
root.getSessionHandler().setSessionPath("/");
|
||||
ListenerHolder initialListener = new ListenerHolder();
|
||||
initialListener.setListener(new InitialListener());
|
||||
root.getServletHandler().addListener(initialListener);
|
||||
|
|
|
@ -111,7 +111,7 @@ public class CookiesTest
|
|||
final String cookiePath = "/path";
|
||||
startServer((req, resp, cb) ->
|
||||
{
|
||||
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath);
|
||||
org.eclipse.jetty.http.HttpCookie cookie = org.eclipse.jetty.http.HttpCookie.from(cookieName, cookieValue, Map.of(org.eclipse.jetty.http.HttpCookie.DOMAIN_ATTRIBUTE, cookieDomain, org.eclipse.jetty.http.HttpCookie.PATH_ATTRIBUTE, cookiePath));
|
||||
Response.addCookie(resp, cookie);
|
||||
return new WholeMessageEcho();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue