From 281736f14eba93ea7bfe67ae7f349a338660ed71 Mon Sep 17 00:00:00 2001 From: Justin Tay Date: Sat, 1 Apr 2023 06:54:49 +0800 Subject: [PATCH] Update MockCookie to use Servlet 6.0 APIs and semantics for "attributes" Closes gh-30263 --- .../springframework/mock/web/MockCookie.java | 31 +++++---- .../mock/web/MockCookieTests.java | 65 +++++++++++++++++++ 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java b/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java index ca28a9aa9763..fc9c7e784b26 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockCookie.java @@ -39,16 +39,14 @@ @SuppressWarnings("removal") public class MockCookie extends Cookie { - private static final long serialVersionUID = 4312531139502726325L; + private static final long serialVersionUID = 1198809317225300389L; + private static final String SAME_SITE = "SameSite"; + private static final String EXPIRES = "Expires"; @Nullable private ZonedDateTime expires; - @Nullable - private String sameSite; - - /** * Construct a new {@link MockCookie} with the supplied name and value. * @param name the name @@ -64,7 +62,7 @@ public MockCookie(String name, String value) { * @since 5.1.11 */ public void setExpires(@Nullable ZonedDateTime expires) { - this.expires = expires; + setAttribute(EXPIRES, expires != null ? expires.format(DateTimeFormatter.RFC_1123_DATE_TIME) : null); } /** @@ -85,7 +83,7 @@ public ZonedDateTime getExpires() { * @see RFC6265 bis */ public void setSameSite(@Nullable String sameSite) { - this.sameSite = sameSite; + setAttribute(SAME_SITE, sameSite); } /** @@ -94,10 +92,9 @@ public void setSameSite(@Nullable String sameSite) { */ @Nullable public String getSameSite() { - return this.sameSite; + return getAttribute(SAME_SITE); } - /** * Factory method that parses the value of the supplied "Set-Cookie" header. * @param setCookieHeader the "Set-Cookie" value; never {@code null} or empty @@ -122,7 +119,7 @@ public static MockCookie parse(String setCookieHeader) { else if (StringUtils.startsWithIgnoreCase(attribute, "Max-Age")) { cookie.setMaxAge(Integer.parseInt(extractAttributeValue(attribute, setCookieHeader))); } - else if (StringUtils.startsWithIgnoreCase(attribute, "Expires")) { + else if (StringUtils.startsWithIgnoreCase(attribute, EXPIRES)) { try { cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader), DateTimeFormatter.RFC_1123_DATE_TIME)); @@ -140,7 +137,7 @@ else if (StringUtils.startsWithIgnoreCase(attribute, "Secure")) { else if (StringUtils.startsWithIgnoreCase(attribute, "HttpOnly")) { cookie.setHttpOnly(true); } - else if (StringUtils.startsWithIgnoreCase(attribute, "SameSite")) { + else if (StringUtils.startsWithIgnoreCase(attribute, SAME_SITE)) { cookie.setSameSite(extractAttributeValue(attribute, setCookieHeader)); } else if (StringUtils.startsWithIgnoreCase(attribute, "Comment")) { @@ -157,6 +154,14 @@ private static String extractAttributeValue(String attribute, String header) { return nameAndValue[1]; } + @Override + public void setAttribute(String name, @Nullable String value) { + if(EXPIRES.equalsIgnoreCase(name)) { + this.expires = value != null ? ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME) : null; + } + super.setAttribute(name, value); + } + @Override public String toString() { return new ToStringCreator(this) @@ -168,9 +173,9 @@ public String toString() { .append("Comment", getComment()) .append("Secure", getSecure()) .append("HttpOnly", isHttpOnly()) - .append("SameSite", this.sameSite) + .append(SAME_SITE, getSameSite()) .append("Max-Age", getMaxAge()) - .append("Expires", (this.expires != null ? + .append(EXPIRES, (this.expires != null ? DateTimeFormatter.RFC_1123_DATE_TIME.format(this.expires) : null)) .toString(); } diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java index 9c14a4f4bf32..9783cb57cfec 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java @@ -18,6 +18,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -25,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Unit tests for {@link MockCookie}. @@ -136,4 +138,67 @@ void parseHeaderWithAttributesCaseSensitivity() { assertThat(cookie.getSameSite()).isEqualTo("Lax"); } + @Test + void setSameSiteShouldSetAttribute() { + MockCookie cookie = new MockCookie("SESSION", "123"); + cookie.setSameSite("Strict"); + + assertThat(cookie.getAttribute("samesite")).isEqualTo("Strict"); + } + + @Test + void setExpiresShouldSetAttribute() { + MockCookie cookie = new MockCookie("SESSION", "123"); + cookie.setExpires(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME)); + + assertThat(cookie.getAttribute("expires")).isEqualTo("Tue, 8 Oct 2019 19:50:00 GMT"); + } + + @Test + void setSameSiteNullShouldClear() { + MockCookie cookie = new MockCookie("SESSION", "123"); + cookie.setSameSite("Strict"); + assertThat(cookie.getSameSite()).isEqualTo("Strict"); + + cookie.setSameSite(null); + assertThat(cookie.getSameSite()).isNull(); + assertThat(cookie.getAttribute("samesite")).isNull(); + } + + @Test + void setExpiresNullShouldClear() { + MockCookie cookie = new MockCookie("SESSION", "123"); + cookie.setExpires(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME)); + assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME)); + + cookie.setExpires(null); + assertThat(cookie.getExpires()).isNull(); + assertThat(cookie.getAttribute("expires")).isNull(); + } + + @Test + void setAttributeSameSiteShouldSetSameSite() { + MockCookie cookie = new MockCookie("SESSION", "123"); + cookie.setAttribute("samesite", "Lax"); + + assertThat(cookie.getSameSite()).isEqualTo("Lax"); + } + + @Test + void setAttributeExpiresShouldSetExpires() { + MockCookie cookie = new MockCookie("SESSION", "123"); + cookie.setAttribute("expires", "Tue, 8 Oct 2019 19:50:00 GMT"); + + assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME)); + } + + @Test + void setInvalidAttributeExpiresShouldThrow() { + MockCookie cookie = new MockCookie("SESSION", "123"); + assertThatThrownBy(() -> cookie.setAttribute("expires", "12345")).isInstanceOf(DateTimeParseException.class); + } }