From 97909f225819b2297c1036a8a9108c0582edcf90 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Mon, 29 May 2017 22:23:33 +0200 Subject: [PATCH] Support date properties in Content-Disposition HTTP header Issue: SPR-15555 --- .../http/ContentDisposition.java | 139 +++++++++++++++++- .../http/ContentDispositionTests.java | 22 +++ 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java index 6879ceae2e..a9f8774b18 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -19,6 +19,8 @@ package org.springframework.http; import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -26,6 +28,7 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static java.nio.charset.StandardCharsets.*; +import static java.time.format.DateTimeFormatter.*; /** * Represent the Content-Disposition type and parameters as defined in RFC 2183. @@ -47,18 +50,26 @@ public class ContentDisposition { private final Long size; + private final ZonedDateTime creationDate; + + private final ZonedDateTime modificationDate; + + private final ZonedDateTime readDate; + /** * Private constructor. See static factory methods in this class. */ - private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename, - @Nullable Charset charset, @Nullable Long size) { - + private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename, @Nullable Charset charset, @Nullable Long size, + @Nullable ZonedDateTime creationDate, @Nullable ZonedDateTime modificationDate, @Nullable ZonedDateTime readDate) { this.type = type; this.name = name; this.filename = filename; this.charset = charset; this.size = size; + this.creationDate = creationDate; + this.modificationDate = modificationDate; + this.readDate = readDate; } @@ -104,6 +115,30 @@ public class ContentDisposition { return this.size; } + /** + * Return the value of the {@literal creation-date} parameter, or {@code null} if not defined. + */ + @Nullable + public ZonedDateTime getCreationDate() { + return this.creationDate; + } + + /** + * Return the value of the {@literal modification-date} parameter, or {@code null} if not defined. + */ + @Nullable + public ZonedDateTime getModificationDate() { + return this.modificationDate; + } + + /** + * Return the value of the {@literal read-date} parameter, or {@code null} if not defined. + */ + @Nullable + public ZonedDateTime getReadDate() { + return this.readDate; + } + /** * Return a builder for a {@code ContentDisposition}. @@ -119,11 +154,12 @@ public class ContentDisposition { * Return an empty content disposition. */ public static ContentDisposition empty() { - return new ContentDisposition("", null, null, null, null); + return new ContentDisposition("", null, null, null, null, null, null, null); } /** * Parse a {@literal Content-Disposition} header value as defined in RFC 2183. + * * @param contentDisposition the {@literal Content-Disposition} header value * @return the parsed content disposition * @see #toString() @@ -136,6 +172,9 @@ public class ContentDisposition { String filename = null; Charset charset = null; Long size = null; + ZonedDateTime creationDate = null; + ZonedDateTime modificationDate = null; + ZonedDateTime readDate = null; for (int i = 1; i < parts.length; i++) { String part = parts[i]; int eqIndex = part.indexOf('='); @@ -159,12 +198,36 @@ public class ContentDisposition { else if (attribute.equals("size") ) { size = Long.parseLong(value); } + else if (attribute.equals("creation-date")) { + try { + creationDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME); + } + catch (DateTimeParseException ex) { + // ignore + } + } + else if (attribute.equals("modification-date")) { + try { + modificationDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME); + } + catch (DateTimeParseException ex) { + // ignore + } + } + else if (attribute.equals("read-date")) { + try { + readDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME); + } + catch (DateTimeParseException ex) { + // ignore + } + } } else { throw new IllegalArgumentException("Invalid content disposition format"); } } - return new ContentDisposition(type, name, filename, charset, size); + return new ContentDisposition(type, name, filename, charset, size, creationDate, modificationDate, readDate); } /** @@ -225,7 +288,10 @@ public class ContentDisposition { ObjectUtils.nullSafeEquals(this.name, otherCd.name) && ObjectUtils.nullSafeEquals(this.filename, otherCd.filename) && ObjectUtils.nullSafeEquals(this.charset, otherCd.charset) && - ObjectUtils.nullSafeEquals(this.size, otherCd.size)); + ObjectUtils.nullSafeEquals(this.size, otherCd.size) && + ObjectUtils.nullSafeEquals(this.creationDate, otherCd.creationDate)&& + ObjectUtils.nullSafeEquals(this.modificationDate, otherCd.modificationDate)&& + ObjectUtils.nullSafeEquals(this.readDate, otherCd.readDate)); } @Override @@ -235,6 +301,9 @@ public class ContentDisposition { result = 31 * result + ObjectUtils.nullSafeHashCode(this.filename); result = 31 * result + ObjectUtils.nullSafeHashCode(this.charset); result = 31 * result + ObjectUtils.nullSafeHashCode(this.size); + result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0); + result = 31 * result + (modificationDate != null ? modificationDate.hashCode() : 0); + result = 31 * result + (readDate != null ? readDate.hashCode() : 0); return result; } @@ -266,6 +335,21 @@ public class ContentDisposition { sb.append("; size="); sb.append(this.size); } + if (this.creationDate != null) { + sb.append("; creation-date=\""); + sb.append(RFC_1123_DATE_TIME.format(this.creationDate)); + sb.append('\"'); + } + if (this.modificationDate != null) { + sb.append("; modification-date=\""); + sb.append(RFC_1123_DATE_TIME.format(this.modificationDate)); + sb.append('\"'); + } + if (this.readDate != null) { + sb.append("; read-date=\""); + sb.append(RFC_1123_DATE_TIME.format(this.readDate)); + sb.append('\"'); + } return sb.toString(); } @@ -332,6 +416,21 @@ public class ContentDisposition { */ Builder size(Long size); + /** + * Set the value of the {@literal creation-date} parameter. + */ + Builder creationDate(ZonedDateTime creationDate); + + /** + * Set the value of the {@literal modification-date} parameter. + */ + Builder modificationDate(ZonedDateTime modificationDate); + + /** + * Set the value of the {@literal read-date} parameter. + */ + Builder readDate(ZonedDateTime readDate); + /** * Build the content disposition */ @@ -351,6 +450,13 @@ public class ContentDisposition { private Long size; + private ZonedDateTime creationDate; + + private ZonedDateTime modificationDate; + + private ZonedDateTime readDate; + + public BuilderImpl(String type) { Assert.hasText(type, "'type' must not be not empty"); this.type = type; @@ -381,9 +487,28 @@ public class ContentDisposition { return this; } + @Override + public Builder creationDate(ZonedDateTime creationDate) { + this.creationDate = creationDate; + return this; + } + + @Override + public Builder modificationDate(ZonedDateTime modificationDate) { + this.modificationDate = modificationDate; + return this; + } + + @Override + public Builder readDate(ZonedDateTime readDate) { + this.readDate = readDate; + return this; + } + @Override public ContentDisposition build() { - return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size); + return new ContentDisposition(this.type, this.name, this.filename, this.charset, + this.size, this.creationDate, this.modificationDate, this.readDate); } } diff --git a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java index 0d24061c05..359e2ac689 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -19,6 +19,8 @@ package org.springframework.http; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -76,6 +78,26 @@ public class ContentDispositionTests { ContentDisposition.parse("foo;bar"); } + @Test + public void parseDates() { + ContentDisposition disposition = ContentDisposition + .parse("attachment; creation-date=\"Mon, 12 Feb 2007 10:15:30 -0500\"; modification-date=\"Tue, 13 Feb 2007 10:15:30 -0500\"; read-date=\"Wed, 14 Feb 2007 10:15:30 -0500\""); + assertEquals(ContentDisposition.builder("attachment") + .creationDate(ZonedDateTime.parse("Mon, 12 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME)) + .modificationDate(ZonedDateTime.parse("Tue, 13 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME)) + .readDate(ZonedDateTime.parse("Wed, 14 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME)) + .build(), disposition); + } + + @Test + public void parseInvalidDates() { + ContentDisposition disposition = ContentDisposition + .parse("attachment; creation-date=\"-1\"; modification-date=\"-1\"; read-date=\"Wed, 14 Feb 2007 10:15:30 -0500\""); + assertEquals(ContentDisposition.builder("attachment") + .readDate(ZonedDateTime.parse("Wed, 14 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME)) + .build(), disposition); + } + @Test public void headerValue() { ContentDisposition disposition = ContentDisposition.builder("form-data")