Skip to content

Commit 3814f12

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 ceb881a commit 3814f12

File tree

6 files changed

+87
-11
lines changed

6 files changed

+87
-11
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
@@ -26,6 +26,7 @@
2626
import java.text.DateFormat;
2727
import java.text.ParseException;
2828
import java.text.SimpleDateFormat;
29+
import java.time.format.DateTimeFormatter;
2930
import java.util.ArrayList;
3031
import java.util.Collection;
3132
import java.util.Collections;
@@ -345,9 +346,14 @@ private String getCookieHeader(Cookie cookie) {
345346
if (maxAge >= 0) {
346347
buf.append("; Max-Age=").append(maxAge);
347348
buf.append("; Expires=");
348-
HttpHeaders headers = new HttpHeaders();
349-
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
350-
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
349+
if (cookie instanceof MockCookie && ((MockCookie) cookie).getExpires() != null) {
350+
buf.append(((MockCookie) cookie).getExpires().format(DateTimeFormatter.RFC_1123_DATE_TIME));
351+
}
352+
else {
353+
HttpHeaders headers = new HttpHeaders();
354+
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
355+
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
356+
}
351357
}
352358

353359
if (cookie.getSecure()) {

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import org.junit.Test;
2121
import org.junit.rules.ExpectedException;
2222

23+
import java.time.ZonedDateTime;
24+
import java.time.format.DateTimeFormatter;
25+
2326
import static org.junit.Assert.*;
2427

2528
/**
@@ -67,15 +70,17 @@ public void parseHeaderWithoutAttributes() {
6770

6871
@Test
6972
public void parseHeaderWithAttributes() {
70-
MockCookie cookie = MockCookie.parse(
71-
"SESSION=123; Domain=example.com; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax");
73+
MockCookie cookie = MockCookie.parse("SESSION=123; Domain=example.com; Max-Age=60; " +
74+
"Expires=Tue, 8 Oct 2019 19:50:00 GMT; Path=/; Secure; HttpOnly; SameSite=Lax");
7275

7376
assertCookie(cookie, "SESSION", "123");
7477
assertEquals("example.com", cookie.getDomain());
7578
assertEquals(60, cookie.getMaxAge());
7679
assertEquals("/", cookie.getPath());
7780
assertTrue(cookie.getSecure());
7881
assertTrue(cookie.isHttpOnly());
82+
assertEquals(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT",
83+
DateTimeFormatter.RFC_1123_DATE_TIME), cookie.getExpires());
7984
assertEquals("Lax", cookie.getSameSite());
8085
}
8186

@@ -109,15 +114,17 @@ public void parseInvalidAttribute() {
109114

110115
@Test
111116
public void parseHeaderWithAttributesCaseSensitivity() {
112-
MockCookie cookie = MockCookie.parse(
113-
"SESSION=123; domain=example.com; max-age=60; path=/; secure; httponly; samesite=Lax");
114-
117+
MockCookie cookie = MockCookie.parse("SESSION=123; domain=example.com; max-age=60; " +
118+
"expires=Tue, 8 Oct 2019 19:50:00 GMT; path=/; secure; httponly; samesite=Lax");
119+
115120
assertCookie(cookie, "SESSION", "123");
116121
assertEquals("example.com", cookie.getDomain());
117122
assertEquals(60, cookie.getMaxAge());
118123
assertEquals("/", cookie.getPath());
119124
assertTrue(cookie.getSecure());
120125
assertTrue(cookie.isHttpOnly());
126+
assertEquals(ZonedDateTime.parse("Tue, 8 Oct 2019 19:50:00 GMT",
127+
DateTimeFormatter.RFC_1123_DATE_TIME), cookie.getExpires());
121128
assertEquals("Lax", cookie.getSameSite());
122129
}
123130

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
@@ -39,6 +39,7 @@
3939
* @author Rob Winch
4040
* @author Sam Brannen
4141
* @author Brian Clozel
42+
* @author Vedran Pavic
4243
* @since 19.02.2006
4344
*/
4445
public class MockHttpServletResponseTests {
@@ -351,6 +352,14 @@ public void addCookieHeader() {
351352
assertCookieValues("123", "999");
352353
}
353354

355+
@Test
356+
public void addCookieHeaderWithExpires() {
357+
String cookieValue = "SESSION=123; Path=/; Max-Age=100; Expires=Tue, 8 Oct 2019 19:50:00 GMT; Secure; " +
358+
"HttpOnly; SameSite=Lax";
359+
response.addHeader(HttpHeaders.SET_COOKIE, cookieValue);
360+
assertEquals(cookieValue, response.getHeader(HttpHeaders.SET_COOKIE));
361+
}
362+
354363
@Test
355364
public void addCookie() {
356365
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
@@ -26,6 +26,7 @@
2626
import java.text.DateFormat;
2727
import java.text.ParseException;
2828
import java.text.SimpleDateFormat;
29+
import java.time.format.DateTimeFormatter;
2930
import java.util.ArrayList;
3031
import java.util.Collection;
3132
import java.util.Collections;
@@ -345,9 +346,14 @@ private String getCookieHeader(Cookie cookie) {
345346
if (maxAge >= 0) {
346347
buf.append("; Max-Age=").append(maxAge);
347348
buf.append("; Expires=");
348-
HttpHeaders headers = new HttpHeaders();
349-
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
350-
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
349+
if (cookie instanceof MockCookie && ((MockCookie) cookie).getExpires() != null) {
350+
buf.append(((MockCookie) cookie).getExpires().format(DateTimeFormatter.RFC_1123_DATE_TIME));
351+
}
352+
else {
353+
HttpHeaders headers = new HttpHeaders();
354+
headers.setExpires(maxAge > 0 ? System.currentTimeMillis() + 1000L * maxAge : 0);
355+
buf.append(headers.getFirst(HttpHeaders.EXPIRES));
356+
}
351357
}
352358

353359
if (cookie.getSecure()) {

0 commit comments

Comments
 (0)