From ca3440cb42881ab6fe209f65b2eeed1b1cfa9984 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 6 Nov 2019 22:23:20 +0100 Subject: [PATCH] Re-enable support for invalid Expires attributes in MockCookie Changes introduced in commit 9b2087618bba6cd546066e2f27deb166f6870a64 caused a regression for Cookie support in MockHttpServletResponse. Specifically, an Expires attribute that cannot be parsed using `ZonedDateTime.parse()` now results in an exception; whereas, previously an entry such as `Expires=0` was allowed. This commit fixes this issue in MockCookie by catching and ignoring any DateTimeException thrown while attempting to parse an Expires attribute. Closes gh-23911 --- .../springframework/mock/web/MockCookie.java | 11 ++++++-- .../mock/web/MockCookieTests.java | 24 +++++++++++++--- .../web/MockHttpServletResponseTests.java | 28 +++++++++++++++++++ .../mock/web/test/MockCookie.java | 11 ++++++-- 4 files changed, 66 insertions(+), 8 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 17c3f2c027c..5f67f51177c 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,7 @@ package org.springframework.mock.web; +import java.time.DateTimeException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; * * @author Vedran Pavic * @author Juergen Hoeller + * @author Sam Brannen * @since 5.1 */ public class MockCookie extends Cookie { @@ -119,8 +121,13 @@ public class MockCookie extends Cookie { 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)); + try { + cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader), + DateTimeFormatter.RFC_1123_DATE_TIME)); + } + catch (DateTimeException ex) { + // ignore invalid date formats + } } else if (StringUtils.startsWithIgnoreCase(attribute, "Path")) { cookie.setPath(extractAttributeValue(attribute, setCookieHeader)); 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 3253c86d329..d846031a1f5 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 @@ -16,13 +16,13 @@ package org.springframework.mock.web; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + 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.*; /** @@ -84,6 +84,22 @@ public class MockCookieTests { assertEquals("Lax", cookie.getSameSite()); } + @Test + public void parseHeaderWithZeroExpiresAttribute() { + MockCookie cookie = MockCookie.parse("SESSION=123; Expires=0"); + + assertCookie(cookie, "SESSION", "123"); + assertNull(cookie.getExpires()); + } + + @Test + public void parseHeaderWithBogusExpiresAttribute() { + MockCookie cookie = MockCookie.parse("SESSION=123; Expires=bogus"); + + assertCookie(cookie, "SESSION", "123"); + assertNull(cookie.getExpires()); + } + private void assertCookie(MockCookie cookie, String name, String value) { assertEquals(name, cookie.getName()); assertEquals(value, cookie.getValue()); @@ -116,7 +132,7 @@ public class MockCookieTests { public void parseHeaderWithAttributesCaseSensitivity() { 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()); 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 70f6f002a04..b7a0df5dbc7 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 @@ -351,6 +351,20 @@ public class MockHttpServletResponseTests { assertEquals(cookieValue, response.getHeader(HttpHeaders.SET_COOKIE)); } + /** + * @since 5.1.12 + */ + @Test + public void setCookieHeaderWithZeroExpiresAttribute() { + String cookieValue = "SESSION=123; Path=/; Max-Age=100; Expires=0"; + response.setHeader(HttpHeaders.SET_COOKIE, cookieValue); + assertNumCookies(1); + String header = response.getHeader(HttpHeaders.SET_COOKIE); + assertNotEquals(cookieValue, header); + // We don't assert the actual Expires value since it is based on the current time. + assertTrue(header.startsWith("SESSION=123; Path=/; Max-Age=100; Expires=")); + } + @Test public void addCookieHeader() { response.addHeader(HttpHeaders.SET_COOKIE, "SESSION=123; Path=/; Secure; HttpOnly; SameSite=Lax"); @@ -375,6 +389,20 @@ public class MockHttpServletResponseTests { assertEquals(cookieValue, response.getHeader(HttpHeaders.SET_COOKIE)); } + /** + * @since 5.1.12 + */ + @Test + public void addCookieHeaderWithZeroExpiresAttribute() { + String cookieValue = "SESSION=123; Path=/; Max-Age=100; Expires=0"; + response.addHeader(HttpHeaders.SET_COOKIE, cookieValue); + assertNumCookies(1); + String header = response.getHeader(HttpHeaders.SET_COOKIE); + assertNotEquals(cookieValue, header); + // We don't assert the actual Expires value since it is based on the current time. + assertTrue(header.startsWith("SESSION=123; Path=/; Max-Age=100; Expires=")); + } + @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 3bea81f01d4..408e2ecefdd 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,7 @@ package org.springframework.mock.web.test; +import java.time.DateTimeException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; * * @author Vedran Pavic * @author Juergen Hoeller + * @author Sam Brannen * @since 5.1 */ public class MockCookie extends Cookie { @@ -119,8 +121,13 @@ public class MockCookie extends Cookie { 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)); + try { + cookie.setExpires(ZonedDateTime.parse(extractAttributeValue(attribute, setCookieHeader), + DateTimeFormatter.RFC_1123_DATE_TIME)); + } + catch (DateTimeException ex) { + // ignore invalid date formats + } } else if (StringUtils.startsWithIgnoreCase(attribute, "Path")) { cookie.setPath(extractAttributeValue(attribute, setCookieHeader));