Avoid int overflow in expiration calculations in MockMvc and FlashMap

FlashMap.startExpirationPeriod(int) and MockMvcWebConnection's cookie
handling both multiplied an int number of seconds by 1000 without
widening to long. Above 2_147_483 seconds (about 24.9 days) the
multiplication overflows to a negative offset, so the computed
expiration time lands in the past.

For FlashMap, a flash map configured through
AbstractFlashMapManager.setFlashMapTimeout(int) with a large timeout is
then treated as expired immediately. For MockMvcWebConnection, a cookie
with a large max-age is removed from the CookieManager instead of being
stored.

This applies the same widening already used for this pattern in
gh-25613.

Closes gh-37208

Signed-off-by: kogun <akogun@gmail.com>
This commit is contained in:
kogun
2026-08-31 17:05:20 +02:00
committed by Sam Brannen
parent b823aed17c
commit dcc24a2325
4 changed files with 31 additions and 2 deletions
@@ -183,7 +183,7 @@ public final class MockMvcWebConnection implements WebConnection {
private static Cookie createCookie(jakarta.servlet.http.Cookie cookie) {
Date expires = null;
if (cookie.getMaxAge() > -1) {
expires = new Date(System.currentTimeMillis() + cookie.getMaxAge() * 1000);
expires = new Date(System.currentTimeMillis() + cookie.getMaxAge() * 1000L);
}
BasicClientCookie result = new BasicClientCookie(cookie.getName(), cookie.getValue());
result.setDomain(cookie.getDomain());
@@ -109,6 +109,17 @@ class MockMvcWebClientBuilderTests {
assertThat(getResponse(client, "http://localhost/").getContentAsString()).isEqualTo("NA");
}
@Test
void cookieWithLargeMaxAgeIsStored() throws Exception {
this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build();
WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build();
assertThat(getResponse(client, "http://localhost/").getContentAsString()).isEqualTo("NA");
assertThat(postResponse(client, "http://localhost/long-lived", "cookie=foo")
.getContentAsString()).isEqualTo("Set");
assertThat(getResponse(client, "http://localhost/").getContentAsString()).isEqualTo("foo");
}
private void assertMockMvcUsed(WebClient client, String url) throws Exception {
assertThat(getResponse(client, url).getContentAsString()).isEqualTo("mvc");
}
@@ -162,6 +173,15 @@ class MockMvcWebClientBuilderTests {
return "Set";
}
@PostMapping(path = "/long-lived", produces = "text/plain")
String setLongLivedCookie(@RequestParam String cookie, HttpServletResponse response) {
jakarta.servlet.http.Cookie longLived = new jakarta.servlet.http.Cookie(COOKIE_NAME, cookie);
longLived.setMaxAge(Integer.MAX_VALUE);
longLived.setPath("/");
response.addCookie(longLived);
return "Set";
}
@DeleteMapping(path = "/", produces = "text/plain")
String deleteCookie(HttpServletResponse response) {
jakarta.servlet.http.Cookie cookie = new jakarta.servlet.http.Cookie(COOKIE_NAME, "");
@@ -112,7 +112,7 @@ public final class FlashMap extends HashMap<String, Object> implements Comparabl
* @param timeToLive the number of seconds before expiration
*/
public void startExpirationPeriod(int timeToLive) {
this.expirationTime = System.currentTimeMillis() + timeToLive * 1000;
this.expirationTime = System.currentTimeMillis() + timeToLive * 1000L;
}
/**
@@ -50,6 +50,15 @@ class FlashMapTests {
assertThat(flashMap.isExpired()).isFalse();
}
@Test
void notExpiredWithLargeTimeToLive() {
FlashMap flashMap = new FlashMap();
flashMap.startExpirationPeriod(Integer.MAX_VALUE);
assertThat(flashMap.getExpirationTime()).isGreaterThan(System.currentTimeMillis());
assertThat(flashMap.isExpired()).isFalse();
}
@Test
void compareTo() {
FlashMap flashMap1 = new FlashMap();