From 425c311d3cb871a22b21b9a469c6bfe9181dc104 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 14 Jun 2018 13:07:17 -0400 Subject: [PATCH] Correctly set maxAge and expires in ResponseCookie Issue: SPR-16940 --- .../org/springframework/http/HttpHeaders.java | 9 ++- .../springframework/http/ResponseCookie.java | 10 ++- .../http/ResponseCookieTests.java | 72 +++++++++++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 9300d53276..c33cbedfb4 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1218,9 +1218,14 @@ public class HttpHeaders implements MultiValueMap, Serializable * @see #setZonedDateTime(String, ZonedDateTime) */ public void setDate(String headerName, long date) { + set(headerName, formatDate(date)); + } + + // Package private: also used in ResponseCookie.. + static String formatDate(long date) { Instant instant = Instant.ofEpochMilli(date); - ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, GMT); - set(headerName, DATE_FORMATTERS[0].format(zonedDateTime)); + ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT); + return DATE_FORMATTERS[0].format(time); } /** diff --git a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java index b459e3a147..533e4c7bb9 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java @@ -139,12 +139,10 @@ public final class ResponseCookie extends HttpCookie { sb.append("; Domain=").append(this.domain); } if (!this.maxAge.isNegative()) { - sb.append("; Max-Age=").append(this.maxAge); + sb.append("; Max-Age=").append(this.maxAge.getSeconds()); sb.append("; Expires="); - HttpHeaders headers = new HttpHeaders(); - long seconds = this.maxAge.getSeconds(); - headers.setExpires(seconds > 0 ? System.currentTimeMillis() + seconds : 0); - sb.append(headers.getFirst(HttpHeaders.EXPIRES)); + long millis = this.maxAge.getSeconds() > 0 ? System.currentTimeMillis() + this.maxAge.toMillis() : 0; + sb.append(HttpHeaders.formatDate(millis)); } if (this.secure) { @@ -241,7 +239,7 @@ public final class ResponseCookie extends HttpCookie { ResponseCookieBuilder maxAge(Duration maxAge); /** - * Set the cookie "Max-Age" attribute in seconds. + * Variant of {@link #maxAge(Duration)} accepting a value in seconds. */ ResponseCookieBuilder maxAge(long maxAgeSeconds); diff --git a/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java new file mode 100644 index 0000000000..d088d96b1f --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Unit tests for {@link ResponseCookie}. + * @author Rossen Stoyanchev + */ +public class ResponseCookieTests { + + @Test + public void defaultValues() { + assertEquals("id=1fWa", ResponseCookie.from("id", "1fWa").build().toString()); + } + + @Test + public void httpOnlyStrictSecureWithDomainAndPath() { + assertEquals("id=1fWa; Path=/projects; Domain=spring.io; Secure; HttpOnly", + ResponseCookie.from("id", "1fWa").domain("spring.io").path("/projects") + .httpOnly(true).secure(true).build().toString()); + } + + @Test + public void maxAge() { + + Duration maxAge = Duration.ofDays(365); + String expires = HttpHeaders.formatDate(System.currentTimeMillis() + maxAge.toMillis()); + expires = expires.substring(0, expires.indexOf(":") + 1); + + assertThat(ResponseCookie.from("id", "1fWa").maxAge(maxAge).build().toString(), allOf( + startsWith("id=1fWa; Max-Age=31536000; Expires=" + expires), + endsWith(" GMT"))); + + assertThat(ResponseCookie.from("id", "1fWa").maxAge(maxAge.getSeconds()).build().toString(), allOf( + startsWith("id=1fWa; Max-Age=31536000; Expires=" + expires), + endsWith(" GMT"))); + } + + @Test + public void maxAge0() { + assertEquals("id=1fWa; Max-Age=0; Expires=Thu, 1 Jan 1970 00:00:00 GMT", + ResponseCookie.from("id", "1fWa").maxAge(Duration.ofSeconds(0)).build().toString()); + + assertEquals("id=1fWa; Max-Age=0; Expires=Thu, 1 Jan 1970 00:00:00 GMT", + ResponseCookie.from("id", "1fWa").maxAge(0).build().toString()); + } + +}