Skip to content

Commit 9b20876

Browse files
vpavicsbrannen
authored andcommitted
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
1 parent 2482209 commit 9b20876

File tree

6 files changed

+86
-10
lines changed

6 files changed

+86
-10
lines changed

spring-test/src/main/java/org/springframework/mock/web/MockCookie.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.mock.web;
1818

19+
import java.time.ZonedDateTime;
20+
import java.time.format.DateTimeFormatter;
21+
1922
import javax.servlet.http.Cookie;
2023

2124
import org.springframework.lang.Nullable;
@@ -35,6 +38,9 @@ public class MockCookie extends Cookie {
3538
private static final long serialVersionUID = 4312531139502726325L;
3639

3740

41+
@Nullable
42+
private ZonedDateTime expires;
43+
3844
@Nullable
3945
private String sameSite;
4046

@@ -49,6 +55,20 @@ public MockCookie(String name, String value) {
4955
super(name, value);
5056
}
5157

58+
/**
59+
* Add the "Expires" attribute to the cookie.
60+
*/
61+
public void setExpires(@Nullable ZonedDateTime expires) {
62+
this.expires = expires;
63+
}
64+
65+
/**
66+
* Return the "Expires" attribute, or {@code null} if not set.
67+
*/
68+
@Nullable
69+
public ZonedDateTime getExpires() {
70+
return this.expires;
71+
}
5272

5373
/**
5474
* Add the "SameSite" attribute to the cookie.
@@ -94,6 +114,10 @@ public static MockCookie parse(String setCookieHeader) {
94114
else if (StringUtils.startsWithIgnoreCase(attribute, "Max-Age")) {
95115
cookie.setMaxAge(Integer.parseInt(extractAttributeValue(attribute, setCookieHeader)));
96116
}
117+
else if (StringUtils.startsWithIgnoreCase(attribute, "Expires")) {
118+
cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader),
119+
DateTimeFormatter.RFC_1123_DATE_TIME));
120+
}
97121
else if (StringUtils.startsWithIgnoreCase(attribute, "Path")) {
98122
cookie.setPath(extractAttributeValue(attribute, setCookieHeader));
99123
}

spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.text.DateFormat;
2828
import java.text.ParseException;
2929
import java.text.SimpleDateFormat;
30+
import java.time.format.DateTimeFormatter;
3031
import java.util.ArrayList;
3132
import java.util.Collection;
3233
import java.util.Collections;
@@ -374,9 +375,14 @@ private String getCookieHeader(Cookie cookie) {
374375
if (maxAge >= 0) {
375376
buf.append("; Max-Age=").append(maxAge);
376377
buf.append("; Expires=");
377-
HttpHeaders headers = new HttpHeaders();
378-
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
379-
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
378+
if (cookie instanceof MockCookie && ((MockCookie) cookie).getExpires() != null) {
379+
buf.append(((MockCookie) cookie).getExpires().format(DateTimeFormatter.RFC_1123_DATE_TIME));
380+
}
381+
else {
382+
HttpHeaders headers = new HttpHeaders();
383+
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
384+
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
385+
}
380386
}
381387

382388
if (cookie.getSecure()) {

spring-test/src/test/java/org/springframework/mock/web/MockCookieTests.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import java.time.ZonedDateTime;
22+
import java.time.format.DateTimeFormatter;
23+
2124
import static org.assertj.core.api.Assertions.assertThat;
2225
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
2326

@@ -62,15 +65,17 @@ void parseHeaderWithoutAttributes() {
6265

6366
@Test
6467
void parseHeaderWithAttributes() {
65-
MockCookie cookie = MockCookie.parse(
66-
"SESSION=123; Domain=example.com; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax");
68+
MockCookie cookie = MockCookie.parse("SESSION=123; Domain=example.com; Max-Age=60; " +
69+
"Expires=Tue, 8 Oct 2019 19:50:00 GMT; Path=/; Secure; HttpOnly; SameSite=Lax");
6770

6871
assertCookie(cookie, "SESSION", "123");
6972
assertThat(cookie.getDomain()).isEqualTo("example.com");
7073
assertThat(cookie.getMaxAge()).isEqualTo(60);
7174
assertThat(cookie.getPath()).isEqualTo("/");
7275
assertThat(cookie.getSecure()).isTrue();
7376
assertThat(cookie.isHttpOnly()).isTrue();
77+
assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT",
78+
DateTimeFormatter.RFC_1123_DATE_TIME));
7479
assertThat(cookie.getSameSite()).isEqualTo("Lax");
7580
}
7681

@@ -104,15 +109,17 @@ void parseInvalidAttribute() {
104109

105110
@Test
106111
void parseHeaderWithAttributesCaseSensitivity() {
107-
MockCookie cookie = MockCookie.parse(
108-
"SESSION=123; domain=example.com; max-age=60; path=/; secure; httponly; samesite=Lax");
112+
MockCookie cookie = MockCookie.parse("SESSION=123; domain=example.com; max-age=60; " +
113+
"expires=Tue, 8 Oct 2019 19:50:00 GMT; path=/; secure; httponly; samesite=Lax");
109114

110115
assertCookie(cookie, "SESSION", "123");
111116
assertThat(cookie.getDomain()).isEqualTo("example.com");
112117
assertThat(cookie.getMaxAge()).isEqualTo(60);
113118
assertThat(cookie.getPath()).isEqualTo("/");
114119
assertThat(cookie.getSecure()).isTrue();
115120
assertThat(cookie.isHttpOnly()).isTrue();
121+
assertThat(cookie.getExpires()).isEqualTo(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT",
122+
DateTimeFormatter.RFC_1123_DATE_TIME));
116123
assertThat(cookie.getSameSite()).isEqualTo("Lax");
117124
}
118125

spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @author Sam Brannen
4343
* @author Brian Clozel
4444
* @author Sebastien Deleuze
45+
* @author Vedran Pavic
4546
* @since 19.02.2006
4647
*/
4748
class MockHttpServletResponseTests {
@@ -362,6 +363,14 @@ void addCookieHeader() {
362363
assertCookieValues("123", "999");
363364
}
364365

366+
@Test
367+
void addCookieHeaderWithExpires() {
368+
String cookieValue = "SESSION=123; Path=/; Max-Age=100; Expires=Tue, 8 Oct 2019 19:50:00 GMT; Secure; " +
369+
"HttpOnly; SameSite=Lax";
370+
response.addHeader(HttpHeaders.SET_COOKIE, cookieValue);
371+
assertThat(response.getHeader(HttpHeaders.SET_COOKIE)).isEqualTo(cookieValue);
372+
}
373+
365374
@Test
366375
void addCookie() {
367376
MockCookie mockCookie = new MockCookie("SESSION", "123");

spring-web/src/test/java/org/springframework/mock/web/test/MockCookie.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.mock.web.test;
1818

19+
import java.time.ZonedDateTime;
20+
import java.time.format.DateTimeFormatter;
21+
1922
import javax.servlet.http.Cookie;
2023

2124
import org.springframework.lang.Nullable;
@@ -35,6 +38,9 @@ public class MockCookie extends Cookie {
3538
private static final long serialVersionUID = 4312531139502726325L;
3639

3740

41+
@Nullable
42+
private ZonedDateTime expires;
43+
3844
@Nullable
3945
private String sameSite;
4046

@@ -49,6 +55,20 @@ public MockCookie(String name, String value) {
4955
super(name, value);
5056
}
5157

58+
/**
59+
* Add the "Expires" attribute to the cookie.
60+
*/
61+
public void setExpires(@Nullable ZonedDateTime expires) {
62+
this.expires = expires;
63+
}
64+
65+
/**
66+
* Return the "Expires" attribute, or {@code null} if not set.
67+
*/
68+
@Nullable
69+
public ZonedDateTime getExpires() {
70+
return this.expires;
71+
}
5272

5373
/**
5474
* Add the "SameSite" attribute to the cookie.
@@ -94,6 +114,10 @@ public static MockCookie parse(String setCookieHeader) {
94114
else if (StringUtils.startsWithIgnoreCase(attribute, "Max-Age")) {
95115
cookie.setMaxAge(Integer.parseInt(extractAttributeValue(attribute, setCookieHeader)));
96116
}
117+
else if (StringUtils.startsWithIgnoreCase(attribute, "Expires")) {
118+
cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader),
119+
DateTimeFormatter.RFC_1123_DATE_TIME));
120+
}
97121
else if (StringUtils.startsWithIgnoreCase(attribute, "Path")) {
98122
cookie.setPath(extractAttributeValue(attribute, setCookieHeader));
99123
}

spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.text.DateFormat;
2828
import java.text.ParseException;
2929
import java.text.SimpleDateFormat;
30+
import java.time.format.DateTimeFormatter;
3031
import java.util.ArrayList;
3132
import java.util.Collection;
3233
import java.util.Collections;
@@ -374,9 +375,14 @@ private String getCookieHeader(Cookie cookie) {
374375
if (maxAge >= 0) {
375376
buf.append("; Max-Age=").append(maxAge);
376377
buf.append("; Expires=");
377-
HttpHeaders headers = new HttpHeaders();
378-
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
379-
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
378+
if (cookie instanceof MockCookie && ((MockCookie) cookie).getExpires() != null) {
379+
buf.append(((MockCookie) cookie).getExpires().format(DateTimeFormatter.RFC_1123_DATE_TIME));
380+
}
381+
else {
382+
HttpHeaders headers = new HttpHeaders();
383+
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
384+
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
385+
}
380386
}
381387

382388
if (cookie.getSecure()) {

0 commit comments

Comments
 (0)