Add Partitioned cookie attribute support for servers
This commit adds support for the "Partitioned" cookie attribute in WebFlux servers and the related testing infrastructure. Note, Undertow does not support this feature at the moment. Closes gh-31454
This commit is contained in:
@@ -47,6 +47,8 @@ public final class ResponseCookie extends HttpCookie {
|
||||
|
||||
private final boolean httpOnly;
|
||||
|
||||
private final boolean partitioned;
|
||||
|
||||
@Nullable
|
||||
private final String sameSite;
|
||||
|
||||
@@ -55,7 +57,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
* Private constructor. See {@link #from(String, String)}.
|
||||
*/
|
||||
private ResponseCookie(String name, @Nullable String value, Duration maxAge, @Nullable String domain,
|
||||
@Nullable String path, boolean secure, boolean httpOnly, @Nullable String sameSite) {
|
||||
@Nullable String path, boolean secure, boolean httpOnly, boolean partitioned, @Nullable String sameSite) {
|
||||
|
||||
super(name, value);
|
||||
Assert.notNull(maxAge, "Max age must not be null");
|
||||
@@ -65,6 +67,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
this.path = path;
|
||||
this.secure = secure;
|
||||
this.httpOnly = httpOnly;
|
||||
this.partitioned = partitioned;
|
||||
this.sameSite = sameSite;
|
||||
|
||||
Rfc6265Utils.validateCookieName(name);
|
||||
@@ -116,6 +119,15 @@ public final class ResponseCookie extends HttpCookie {
|
||||
return this.httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the cookie has the "Partitioned" attribute.
|
||||
* @since 6.2
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1">The Partitioned attribute spec</a>
|
||||
*/
|
||||
public boolean isPartitioned() {
|
||||
return this.partitioned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cookie "SameSite" attribute, or {@code null} if not set.
|
||||
* <p>This limits the scope of the cookie such that it will only be attached to
|
||||
@@ -139,6 +151,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
.path(this.path)
|
||||
.secure(this.secure)
|
||||
.httpOnly(this.httpOnly)
|
||||
.partitioned(this.partitioned)
|
||||
.sameSite(this.sameSite);
|
||||
}
|
||||
|
||||
@@ -180,6 +193,9 @@ public final class ResponseCookie extends HttpCookie {
|
||||
if (this.httpOnly) {
|
||||
sb.append("; HttpOnly");
|
||||
}
|
||||
if (this.partitioned) {
|
||||
sb.append("; Partitioned");
|
||||
}
|
||||
if (StringUtils.hasText(this.sameSite)) {
|
||||
sb.append("; SameSite=").append(this.sameSite);
|
||||
}
|
||||
@@ -272,6 +288,13 @@ public final class ResponseCookie extends HttpCookie {
|
||||
*/
|
||||
ResponseCookieBuilder httpOnly(boolean httpOnly);
|
||||
|
||||
/**
|
||||
* Add the "Partitioned" attribute to the cookie.
|
||||
* @since 6.2
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1">The Partitioned attribute spec</a>
|
||||
*/
|
||||
ResponseCookieBuilder partitioned(boolean partitioned);
|
||||
|
||||
/**
|
||||
* Add the "SameSite" attribute to the cookie.
|
||||
* <p>This limits the scope of the cookie such that it will only be
|
||||
@@ -397,6 +420,8 @@ public final class ResponseCookie extends HttpCookie {
|
||||
|
||||
private boolean httpOnly;
|
||||
|
||||
private boolean partitioned;
|
||||
|
||||
@Nullable
|
||||
private String sameSite;
|
||||
|
||||
@@ -461,6 +486,12 @@ public final class ResponseCookie extends HttpCookie {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder partitioned(boolean partitioned) {
|
||||
this.partitioned = partitioned;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder sameSite(@Nullable String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
@@ -470,7 +501,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
@Override
|
||||
public ResponseCookie build() {
|
||||
return new ResponseCookie(this.name, this.value, this.maxAge,
|
||||
this.domain, this.path, this.secure, this.httpOnly, this.sameSite);
|
||||
this.domain, this.path, this.secure, this.httpOnly, this.partitioned, this.sameSite);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ class ReactorNetty2ServerHttpResponse extends AbstractServerHttpResponse impleme
|
||||
for (ResponseCookie httpCookie : getCookies().get(name)) {
|
||||
Long maxAge = (!httpCookie.getMaxAge().isNegative()) ? httpCookie.getMaxAge().getSeconds() : null;
|
||||
HttpSetCookie.SameSite sameSite = (httpCookie.getSameSite() != null) ? HttpSetCookie.SameSite.valueOf(httpCookie.getSameSite()) : null;
|
||||
// TODO: support Partitioned attribute when available in Netty 5 API
|
||||
DefaultHttpSetCookie cookie = new DefaultHttpSetCookie(name, httpCookie.getValue(), httpCookie.getPath(),
|
||||
httpCookie.getDomain(), null, maxAge, sameSite, false, httpCookie.isSecure(), httpCookie.isHttpOnly());
|
||||
this.response.addCookie(cookie);
|
||||
|
||||
@@ -120,6 +120,7 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze
|
||||
}
|
||||
cookie.setSecure(httpCookie.isSecure());
|
||||
cookie.setHttpOnly(httpCookie.isHttpOnly());
|
||||
cookie.setPartitioned(httpCookie.isPartitioned());
|
||||
if (httpCookie.getSameSite() != null) {
|
||||
cookie.setSameSite(CookieHeaderNames.SameSite.valueOf(httpCookie.getSameSite()));
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Adapt {@link ServerHttpResponse} to the Servlet {@link HttpServletResponse}.
|
||||
@@ -49,6 +50,8 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
|
||||
|
||||
private static final boolean IS_SERVLET61 = ReflectionUtils.findField(HttpServletResponse.class, "SC_PERMANENT_REDIRECT") != null;
|
||||
|
||||
private final HttpServletResponse response;
|
||||
|
||||
private final ServletOutputStream outputStream;
|
||||
@@ -181,6 +184,14 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
|
||||
}
|
||||
cookie.setSecure(httpCookie.isSecure());
|
||||
cookie.setHttpOnly(httpCookie.isHttpOnly());
|
||||
if (httpCookie.isPartitioned()) {
|
||||
if (IS_SERVLET61) {
|
||||
cookie.setAttribute("Partitioned", "");
|
||||
}
|
||||
else {
|
||||
cookie.setAttribute("Partitioned", "true");
|
||||
}
|
||||
}
|
||||
this.response.addCookie(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ class UndertowServerHttpResponse extends AbstractListenerServerHttpResponse impl
|
||||
}
|
||||
cookie.setSecure(httpCookie.isSecure());
|
||||
cookie.setHttpOnly(httpCookie.isHttpOnly());
|
||||
// TODO: add "Partitioned" attribute when Undertow supports it
|
||||
cookie.setSameSiteMode(httpCookie.getSameSite());
|
||||
this.exchange.setResponseCookie(cookie);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user