Jetty 12 - New HTTP Cookie interface (#9205)

Convert HttpCookie to an interface.
This commit is contained in:
Greg Wilkins 2023-01-31 10:02:28 +11:00 committed by GitHub
parent b2bd8b969c
commit 50a88187fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 870 additions and 1030 deletions

View File

@ -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
{

View File

@ -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

View File

@ -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));
}
}
};
}

View File

@ -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());
}
}

View File

@ -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__"));
}
}

View File

@ -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);
}
};

View File

@ -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 &amp;&amp; _secureRequestOnly==true &amp;&amp; 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.

View File

@ -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;
}

View File

@ -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;

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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()

View File

@ -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));
}
}

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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 &amp;&amp; _secureRequestOnly==true &amp;&amp; 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)

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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"))));

View File

@ -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();
});

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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 &amp;&amp; _secureRequestOnly==true &amp;&amp; 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)
{

View File

@ -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");

View File

@ -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);
}

View File

@ -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"));
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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();
});