From 3814f12b67088db3e6c9da61809415b405b709ef Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Tue, 29 Oct 2019 13:45:20 +0100 Subject: [PATCH] Preserve expires attribute in MockCookie At present, MockCookie doesn't preserve expires attribute. This has a consequence that a cookie value set using MockHttpServletResponse#addHeader containing an expires attribute will not match the cookie value obtained from MockHttpServletResponse#getHeader, since the expires attribute will get calculated based on current time. This commit enhances MockCookie to preserve the expires attribute. Closes gh-23769 --- .../springframework/mock/web/MockCookie.java | 24 +++++++++++++++++++ .../mock/web/MockHttpServletResponse.java | 12 +++++++--- .../mock/web/MockCookieTests.java | 17 +++++++++---- .../web/MockHttpServletResponseTests.java | 9 +++++++ .../mock/web/test/MockCookie.java | 24 +++++++++++++++++++ .../web/test/MockHttpServletResponse.java | 12 +++++++--- 6 files changed, 87 insertions(+), 11 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 522a60038cb..a7565f180e7 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 87b7c28db12..b933ba14bf3 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 @@ -26,6 +26,7 @@ import java.io.Writer; 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; @@ -345,9 +346,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 3180369f0a4..3253c86d329 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 @@ -20,6 +20,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + import static org.junit.Assert.*; /** @@ -67,8 +70,8 @@ public class MockCookieTests { @Test public 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"); assertEquals("example.com", cookie.getDomain()); @@ -76,6 +79,8 @@ public class MockCookieTests { assertEquals("/", cookie.getPath()); assertTrue(cookie.getSecure()); assertTrue(cookie.isHttpOnly()); + assertEquals(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME), cookie.getExpires()); assertEquals("Lax", cookie.getSameSite()); } @@ -109,15 +114,17 @@ public class MockCookieTests { @Test public 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"); assertEquals("example.com", cookie.getDomain()); assertEquals(60, cookie.getMaxAge()); assertEquals("/", cookie.getPath()); assertTrue(cookie.getSecure()); assertTrue(cookie.isHttpOnly()); + assertEquals(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT", + DateTimeFormatter.RFC_1123_DATE_TIME), cookie.getExpires()); assertEquals("Lax", cookie.getSameSite()); } 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 7494203e8de..4218a79dc27 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 @@ -39,6 +39,7 @@ import static org.junit.Assert.*; * @author Rob Winch * @author Sam Brannen * @author Brian Clozel + * @author Vedran Pavic * @since 19.02.2006 */ public class MockHttpServletResponseTests { @@ -351,6 +352,14 @@ public class MockHttpServletResponseTests { assertCookieValues("123", "999"); } + @Test + public 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); + assertEquals(cookieValue, response.getHeader(HttpHeaders.SET_COOKIE)); + } + @Test public 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 b7ac261942e..9b6ba944855 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 caa5209052c..c4ce94effd8 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 @@ -26,6 +26,7 @@ import java.io.Writer; 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; @@ -345,9 +346,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()) {