Better integrate ResponseCookie in CookieLocaleResolver
Improve ResponseCookie to allow an existing instance to be mutated and also to set the cookie value through the builder. This allows CookieLocaleResolver to avoid duplicating all the fields of ResponseCookie and to have only a ResponseCookie field instead. Closes gh-28779
This commit is contained in:
parent
075fccca94
commit
e6c2d44646
|
|
@ -54,7 +54,7 @@ public final class ResponseCookie extends HttpCookie {
|
|||
/**
|
||||
* Private constructor. See {@link #from(String, String)}.
|
||||
*/
|
||||
private ResponseCookie(String name, String value, Duration maxAge, @Nullable String domain,
|
||||
private ResponseCookie(String name, @Nullable String value, Duration maxAge, @Nullable String domain,
|
||||
@Nullable String path, boolean secure, boolean httpOnly, @Nullable String sameSite) {
|
||||
|
||||
super(name, value);
|
||||
|
|
@ -128,6 +128,19 @@ public final class ResponseCookie extends HttpCookie {
|
|||
return this.sameSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder pre-populated with values from {@code "this"} instance.
|
||||
* @since 6.0
|
||||
*/
|
||||
public ResponseCookieBuilder mutate() {
|
||||
return new DefaultResponseCookieBuilder(getName(), getValue(), false)
|
||||
.maxAge(this.maxAge)
|
||||
.domain(this.domain)
|
||||
.path(this.path)
|
||||
.secure(this.secure)
|
||||
.httpOnly(this.httpOnly)
|
||||
.sameSite(this.sameSite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
|
|
@ -179,6 +192,18 @@ public final class ResponseCookie extends HttpCookie {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method to obtain a builder for a server-defined cookie, given its
|
||||
* name only, and where the value as well as other attributes can be set
|
||||
* later via builder methods.
|
||||
* @param name the cookie name
|
||||
* @return a builder to create the cookie with
|
||||
* @since 6.0
|
||||
*/
|
||||
public static ResponseCookieBuilder from(final String name) {
|
||||
return new DefaultResponseCookieBuilder(name, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to obtain a builder for a server-defined cookie that starts
|
||||
* with a name-value pair and may also include attributes.
|
||||
|
|
@ -187,7 +212,7 @@ public final class ResponseCookie extends HttpCookie {
|
|||
* @return a builder to create the cookie with
|
||||
*/
|
||||
public static ResponseCookieBuilder from(final String name, final String value) {
|
||||
return from(name, value, false);
|
||||
return new DefaultResponseCookieBuilder(name, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -201,90 +226,7 @@ public final class ResponseCookie extends HttpCookie {
|
|||
* @since 5.2.5
|
||||
*/
|
||||
public static ResponseCookieBuilder fromClientResponse(final String name, final String value) {
|
||||
return from(name, value, true);
|
||||
}
|
||||
|
||||
|
||||
private static ResponseCookieBuilder from(final String name, final String value, boolean lenient) {
|
||||
|
||||
return new ResponseCookieBuilder() {
|
||||
|
||||
private Duration maxAge = Duration.ofSeconds(-1);
|
||||
|
||||
@Nullable
|
||||
private String domain;
|
||||
|
||||
@Nullable
|
||||
private String path;
|
||||
|
||||
private boolean secure;
|
||||
|
||||
private boolean httpOnly;
|
||||
|
||||
@Nullable
|
||||
private String sameSite;
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder maxAge(Duration maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder maxAge(long maxAgeSeconds) {
|
||||
this.maxAge = maxAgeSeconds >= 0 ? Duration.ofSeconds(maxAgeSeconds) : Duration.ofSeconds(-1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder domain(@Nullable String domain) {
|
||||
this.domain = initDomain(domain);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String initDomain(@Nullable String domain) {
|
||||
if (lenient && StringUtils.hasLength(domain)) {
|
||||
String str = domain.trim();
|
||||
if (str.startsWith("\"") && str.endsWith("\"")) {
|
||||
if (str.substring(1, str.length() - 1).trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder path(@Nullable String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder secure(boolean secure) {
|
||||
this.secure = secure;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder httpOnly(boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder sameSite(@Nullable String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookie build() {
|
||||
return new ResponseCookie(name, value, this.maxAge, this.domain, this.path,
|
||||
this.secure, this.httpOnly, this.sameSite);
|
||||
}
|
||||
};
|
||||
return new DefaultResponseCookieBuilder(name, value, true);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -293,6 +235,12 @@ public final class ResponseCookie extends HttpCookie {
|
|||
*/
|
||||
public interface ResponseCookieBuilder {
|
||||
|
||||
/**
|
||||
* Set the cookie value.
|
||||
* @since 6.0
|
||||
*/
|
||||
ResponseCookieBuilder value(@Nullable String value);
|
||||
|
||||
/**
|
||||
* Set the cookie "Max-Age" attribute.
|
||||
*
|
||||
|
|
@ -429,4 +377,106 @@ public final class ResponseCookie extends HttpCookie {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ResponseCookieBuilder}.
|
||||
*/
|
||||
private static class DefaultResponseCookieBuilder implements ResponseCookieBuilder {
|
||||
|
||||
private final String name;
|
||||
|
||||
@Nullable
|
||||
private String value;
|
||||
|
||||
private final boolean lenient;
|
||||
|
||||
private Duration maxAge = Duration.ofSeconds(-1);
|
||||
|
||||
@Nullable
|
||||
private String domain;
|
||||
|
||||
@Nullable
|
||||
private String path;
|
||||
|
||||
private boolean secure;
|
||||
|
||||
private boolean httpOnly;
|
||||
|
||||
@Nullable
|
||||
private String sameSite;
|
||||
|
||||
public DefaultResponseCookieBuilder(String name, @Nullable String value, boolean lenient) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder value(@Nullable String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder maxAge(Duration maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder maxAge(long maxAgeSeconds) {
|
||||
this.maxAge = (maxAgeSeconds >= 0 ? Duration.ofSeconds(maxAgeSeconds) : Duration.ofSeconds(-1));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder domain(@Nullable String domain) {
|
||||
this.domain = initDomain(domain);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String initDomain(@Nullable String domain) {
|
||||
if (this.lenient && StringUtils.hasLength(domain)) {
|
||||
String str = domain.trim();
|
||||
if (str.startsWith("\"") && str.endsWith("\"")) {
|
||||
if (str.substring(1, str.length() - 1).trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder path(@Nullable String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder secure(boolean secure) {
|
||||
this.secure = secure;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder httpOnly(boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookieBuilder sameSite(@Nullable String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseCookie build() {
|
||||
return new ResponseCookie(this.name, this.value, this.maxAge,
|
||||
this.domain, this.path, this.secure, this.httpOnly, this.sameSite);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,21 +91,7 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
private static final Log logger = LogFactory.getLog(CookieLocaleResolver.class);
|
||||
|
||||
|
||||
private String cookieName;
|
||||
|
||||
private Duration cookieMaxAge = Duration.ofSeconds(-1);
|
||||
|
||||
@Nullable
|
||||
private String cookiePath = "/";
|
||||
|
||||
@Nullable
|
||||
private String cookieDomain;
|
||||
|
||||
private boolean cookieSecure;
|
||||
|
||||
private boolean cookieHttpOnly;
|
||||
|
||||
private String cookieSameSite = "Lax";
|
||||
private ResponseCookie cookie;
|
||||
|
||||
private boolean languageTagCompliant = true;
|
||||
|
||||
|
|
@ -124,8 +110,8 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
* @since 6.0
|
||||
*/
|
||||
public CookieLocaleResolver(String cookieName) {
|
||||
Assert.notNull(cookieName, "cookieName must not be null");
|
||||
this.cookieName = cookieName;
|
||||
Assert.notNull(cookieName, "'cookieName' must not be null");
|
||||
this.cookie = ResponseCookie.from(cookieName).path("/").sameSite("Lax").build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -144,7 +130,15 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
@Deprecated
|
||||
public void setCookieName(String cookieName) {
|
||||
Assert.notNull(cookieName, "cookieName must not be null");
|
||||
this.cookieName = cookieName;
|
||||
this.cookie = ResponseCookie.from(cookieName)
|
||||
.maxAge(this.cookie.getMaxAge())
|
||||
.domain(this.cookie.getDomain())
|
||||
.path(this.cookie.getPath())
|
||||
.secure(this.cookie.isSecure())
|
||||
.httpOnly(this.cookie.isHttpOnly())
|
||||
.sameSite(this.cookie.getSameSite())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -155,8 +149,8 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
* @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#maxAge(Duration)
|
||||
*/
|
||||
public void setCookieMaxAge(Duration cookieMaxAge) {
|
||||
Assert.notNull(cookieMaxAge, "cookieMaxAge must not be null");
|
||||
this.cookieMaxAge = cookieMaxAge;
|
||||
Assert.notNull(cookieMaxAge, "'cookieMaxAge' must not be null");
|
||||
this.cookie = this.cookie.mutate().maxAge(cookieMaxAge).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -174,7 +168,7 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
* @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#path(String)
|
||||
*/
|
||||
public void setCookiePath(@Nullable String cookiePath) {
|
||||
this.cookiePath = cookiePath;
|
||||
this.cookie = this.cookie.mutate().path(cookiePath).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -182,7 +176,7 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
* @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#domain(String)
|
||||
*/
|
||||
public void setCookieDomain(@Nullable String cookieDomain) {
|
||||
this.cookieDomain = cookieDomain;
|
||||
this.cookie = this.cookie.mutate().domain(cookieDomain).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -190,7 +184,7 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
* @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#secure(boolean)
|
||||
*/
|
||||
public void setCookieSecure(boolean cookieSecure) {
|
||||
this.cookieSecure = cookieSecure;
|
||||
this.cookie = this.cookie.mutate().secure(cookieSecure).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -198,17 +192,18 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
* @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#httpOnly(boolean)
|
||||
*/
|
||||
public void setCookieHttpOnly(boolean cookieHttpOnly) {
|
||||
this.cookieHttpOnly = cookieHttpOnly;
|
||||
this.cookie = this.cookie.mutate().httpOnly(cookieHttpOnly).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "SameSite" attribute to the cookie.
|
||||
* <p>By default, this is set to {@code "Lax"}.
|
||||
* @since 6.0
|
||||
* @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#sameSite(String)
|
||||
*/
|
||||
public void setCookieSameSite(String cookieSameSite) {
|
||||
Assert.notNull(cookieSameSite, "cookieSameSite must not be null");
|
||||
this.cookieSameSite = cookieSameSite;
|
||||
this.cookie = this.cookie.mutate().sameSite(cookieSameSite).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -320,7 +315,7 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
TimeZone timeZone = null;
|
||||
|
||||
// Retrieve and parse cookie value.
|
||||
Cookie cookie = WebUtils.getCookie(request, this.cookieName);
|
||||
Cookie cookie = WebUtils.getCookie(request, this.cookie.getName());
|
||||
if (cookie != null) {
|
||||
String value = cookie.getValue();
|
||||
String localePart = value;
|
||||
|
|
@ -344,12 +339,12 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
if (isRejectInvalidCookies() &&
|
||||
request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
|
||||
throw new IllegalStateException("Encountered invalid locale cookie '" +
|
||||
this.cookieName + "': [" + value + "] due to: " + ex.getMessage());
|
||||
this.cookie.getName() + "': [" + value + "] due to: " + ex.getMessage());
|
||||
}
|
||||
else {
|
||||
// Lenient handling (e.g. error dispatch): ignore locale/timezone parse exceptions
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Ignoring invalid locale cookie '" + this.cookieName +
|
||||
logger.debug("Ignoring invalid locale cookie '" + this.cookie.getName() +
|
||||
"': [" + value + "] due to: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -374,40 +369,20 @@ public class CookieLocaleResolver extends AbstractLocaleContextResolver {
|
|||
Assert.notNull(response, "HttpServletResponse is required for CookieLocaleResolver");
|
||||
|
||||
Locale locale = null;
|
||||
TimeZone timeZone = null;
|
||||
ResponseCookie cookie;
|
||||
TimeZone zone = null;
|
||||
if (localeContext != null) {
|
||||
locale = localeContext.getLocale();
|
||||
if (localeContext instanceof TimeZoneAwareLocaleContext timeZoneAwareLocaleContext) {
|
||||
timeZone = timeZoneAwareLocaleContext.getTimeZone();
|
||||
zone = timeZoneAwareLocaleContext.getTimeZone();
|
||||
}
|
||||
cookie = ResponseCookie.from(this.cookieName,
|
||||
(locale != null ? toLocaleValue(locale) : "-") +
|
||||
(timeZone != null ? '/' + timeZone.getID() : ""))
|
||||
.maxAge(this.cookieMaxAge)
|
||||
.path(this.cookiePath)
|
||||
.domain(this.cookieDomain)
|
||||
.secure(this.cookieSecure)
|
||||
.httpOnly(this.cookieHttpOnly)
|
||||
.sameSite(this.cookieSameSite)
|
||||
.build();
|
||||
String value = (locale != null ? toLocaleValue(locale) : "-") + (zone != null ? '/' + zone.getID() : "");
|
||||
this.cookie = this.cookie.mutate().value(value).build();
|
||||
}
|
||||
else {
|
||||
// a cookie with empty value and max age 0
|
||||
cookie = ResponseCookie.from(this.cookieName, "")
|
||||
.maxAge(Duration.ZERO)
|
||||
.path(this.cookiePath)
|
||||
.domain(this.cookieDomain)
|
||||
.secure(this.cookieSecure)
|
||||
.httpOnly(this.cookieHttpOnly)
|
||||
.sameSite(this.cookieSameSite)
|
||||
.build();
|
||||
}
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, this.cookie.toString());
|
||||
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
|
||||
(locale != null ? locale : this.defaultLocaleFunction.apply(request)));
|
||||
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
|
||||
(timeZone != null ? timeZone : this.defaultTimeZoneFunction.apply(request)));
|
||||
(zone != null ? zone : this.defaultTimeZoneFunction.apply(request)));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue