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 522a60038cb4f2f87f2403a62475bd67afa8500f..a7565f180e7b1fe2e2d1b9caf251daf352f2e7bf 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 @@ -16,6 +16,9 @@ package org.springframework.mock.web; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + import javax.servlet.http.Cookie; import org.springframework.lang.Nullable; @@ -35,6 +38,9 @@ public class MockCookie extends Cookie { private static final long serialVersionUID = 4312531139502726325L; + @Nullable + private ZonedDateTime expires; + @Nullable private String sameSite; @@ -49,6 +55,20 @@ public class MockCookie extends Cookie { super(name, value); } + /** + * Add the "Expires" attribute to the cookie. + */ + public void setExpires(@Nullable ZonedDateTime expires) { + this.expires = expires; + } + + /** + * Return the "Expires" attribute, or {@code null} if not set. + */ + @Nullable + public ZonedDateTime getExpires() { + return this.expires; + } /** * Add the "SameSite" attribute to the cookie. @@ -94,6 +114,10 @@ public class MockCookie extends Cookie { else if (StringUtils.startsWithIgnoreCase(attribute, "Max-Age")) { cookie.setMaxAge(Integer.parseInt(extractAttributeValue(attribute, setCookieHeader))); } + else if (StringUtils.startsWithIgnoreCase(attribute, "Expires")) { + cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader), + DateTimeFormatter.RFC_1123_DATE_TIME)); + } else if (StringUtils.startsWithIgnoreCase(attribute, "Path")) { cookie.setPath(extractAttributeValue(attribute, setCookieHeader)); } diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 3d0a3ca54eb9cd728be8155d1f2391e86bc06e55..7f0f826d7d083c50daea8ba2dd02df837879f673 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -27,6 +27,7 @@ import java.nio.charset.Charset; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -374,9 +375,14 @@ public class MockHttpServletResponse implements HttpServletResponse { if (maxAge >= 0) { buf.append("; Max-Age=").append(maxAge); buf.append("; Expires="); - HttpHeaders headers = new HttpHeaders(); - headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0); - buf.append(headers.getFirst(HttpHeaders.EXPIRES)); + if (cookie instanceof MockCookie && ((MockCookie) cookie).getExpires() != null) { + buf.append(((MockCookie) cookie).getExpires().format(DateTimeFormatter.RFC_1123_DATE_TIME)); + } + else { + HttpHeaders headers = new HttpHeaders(); + headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0); + buf.append(headers.getFirst(HttpHeaders.EXPIRES)); + } } if (cookie.getSecure()) { 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 0883956d0a5dfc3e442f873827c02b4503a82703..436fdff3875119482a8d256e6282a5585b936a56 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,9 @@ package org.springframework.mock.web; import org.junit.jupiter.api.Test; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -62,8 +65,8 @@ class MockCookieTests { @Test void parseHeaderWithAttributes() { - MockCookie cookie = MockCookie.parse( - "SESSION=123; Domain=example.com; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax"); + MockCookie cookie = MockCookie.parse("SESSION=123; Domain=example.com; Max-Age=60; " + + "Expires=Tue, 8 Oct 2019 19:50:00 GMT; Path=/; Secure; HttpOnly; SameSite=Lax"); assertCookie(cookie, "SESSION", "123"); assertThat(cookie.getDomain()).isEqualTo("example.com"); @@ -71,6 +74,8 @@ class MockCookieTests { assertThat(cookie.getPath()).isEqualTo("/"); assertThat(cookie.getSecure()).isTrue(); assertThat(cookie.isHttpOnly()).isTrue(); + assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME)); assertThat(cookie.getSameSite()).isEqualTo("Lax"); } @@ -104,8 +109,8 @@ class MockCookieTests { @Test void parseHeaderWithAttributesCaseSensitivity() { - MockCookie cookie = MockCookie.parse( - "SESSION=123; domain=example.com; max-age=60; path=/; secure; httponly; samesite=Lax"); + MockCookie cookie = MockCookie.parse("SESSION=123; domain=example.com; max-age=60; " + + "expires=Tue, 8 Oct 2019 19:50:00 GMT; path=/; secure; httponly; samesite=Lax"); assertCookie(cookie, "SESSION", "123"); assertThat(cookie.getDomain()).isEqualTo("example.com"); @@ -113,6 +118,8 @@ class MockCookieTests { assertThat(cookie.getPath()).isEqualTo("/"); assertThat(cookie.getSecure()).isTrue(); assertThat(cookie.isHttpOnly()).isTrue(); + assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME)); assertThat(cookie.getSameSite()).isEqualTo("Lax"); } diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java index ea2797096a2576f903101a29a52afe183764bbdb..98e200b7f4975e7bba113ac3aa7e3748ed5c6a2b 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java @@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * @author Sam Brannen * @author Brian Clozel * @author Sebastien Deleuze + * @author Vedran Pavic * @since 19.02.2006 */ class MockHttpServletResponseTests { @@ -362,6 +363,14 @@ class MockHttpServletResponseTests { assertCookieValues("123", "999"); } + @Test + void addCookieHeaderWithExpires() { + String cookieValue = "SESSION=123; Path=/; Max-Age=100; Expires=Tue, 8 Oct 2019 19:50:00 GMT; Secure; " + + "HttpOnly; SameSite=Lax"; + response.addHeader(HttpHeaders.SET_COOKIE, cookieValue); + assertThat(response.getHeader(HttpHeaders.SET_COOKIE)).isEqualTo(cookieValue); + } + @Test void addCookie() { MockCookie mockCookie = new MockCookie("SESSION", "123"); diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockCookie.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockCookie.java index b7ac261942e8f31c34a55d5f97dfe1adee849d02..9b6ba9448554dbb280b3fd2e99f3d2bfd795ee3b 100644 --- a/spring-web/src/test/java/org/springframework/mock/web/test/MockCookie.java +++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockCookie.java @@ -16,6 +16,9 @@ package org.springframework.mock.web.test; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + import javax.servlet.http.Cookie; import org.springframework.lang.Nullable; @@ -35,6 +38,9 @@ public class MockCookie extends Cookie { private static final long serialVersionUID = 4312531139502726325L; + @Nullable + private ZonedDateTime expires; + @Nullable private String sameSite; @@ -49,6 +55,20 @@ public class MockCookie extends Cookie { super(name, value); } + /** + * Add the "Expires" attribute to the cookie. + */ + public void setExpires(@Nullable ZonedDateTime expires) { + this.expires = expires; + } + + /** + * Return the "Expires" attribute, or {@code null} if not set. + */ + @Nullable + public ZonedDateTime getExpires() { + return this.expires; + } /** * Add the "SameSite" attribute to the cookie. @@ -94,6 +114,10 @@ public class MockCookie extends Cookie { else if (StringUtils.startsWithIgnoreCase(attribute, "Max-Age")) { cookie.setMaxAge(Integer.parseInt(extractAttributeValue(attribute, setCookieHeader))); } + else if (StringUtils.startsWithIgnoreCase(attribute, "Expires")) { + cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader), + DateTimeFormatter.RFC_1123_DATE_TIME)); + } else if (StringUtils.startsWithIgnoreCase(attribute, "Path")) { cookie.setPath(extractAttributeValue(attribute, setCookieHeader)); } diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java index c9bc689e590bdb0d1122114ecbbefe757baf8396..397a0606e1868f1c0df9d98dce79789639859e7e 100644 --- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java +++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java @@ -27,6 +27,7 @@ import java.nio.charset.Charset; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -374,9 +375,14 @@ public class MockHttpServletResponse implements HttpServletResponse { if (maxAge >= 0) { buf.append("; Max-Age=").append(maxAge); buf.append("; Expires="); - HttpHeaders headers = new HttpHeaders(); - headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0); - buf.append(headers.getFirst(HttpHeaders.EXPIRES)); + if (cookie instanceof MockCookie && ((MockCookie) cookie).getExpires() != null) { + buf.append(((MockCookie) cookie).getExpires().format(DateTimeFormatter.RFC_1123_DATE_TIME)); + } + else { + HttpHeaders headers = new HttpHeaders(); + headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0); + buf.append(headers.getFirst(HttpHeaders.EXPIRES)); + } } if (cookie.getSecure()) {