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 255a075a58a1..225fd3751e2e 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -19,12 +19,15 @@ 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; 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. @@ -45,16 +48,28 @@ 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; } @@ -100,6 +115,30 @@ public Long getSize() { 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}. @@ -115,11 +154,12 @@ public static Builder builder(String type) { * Return an empty content disposition. */ public static ContentDisposition empty() { - return new ContentDisposition(null, null, null, null, null); + return new ContentDisposition(null, 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() @@ -132,6 +172,9 @@ public static ContentDisposition parse(String 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('='); @@ -155,12 +198,36 @@ else if (attribute.equals("filename") && (filename == null)) { 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); } /** @@ -229,7 +296,16 @@ public boolean equals(Object o) { if (charset != null ? !charset.equals(that.charset) : that.charset != null) { return false; } - return size != null ? size.equals(that.size) : that.size == null; + if (size != null ? !size.equals(that.size) : that.size != null) { + return false; + } + if (creationDate != null ? !creationDate.equals(that.creationDate) : that.creationDate != null) { + return false; + } + if (modificationDate != null ? !modificationDate.equals(that.modificationDate) : that.modificationDate != null) { + return false; + } + return readDate != null ? readDate.equals(that.readDate) : that.readDate == null; } @Override @@ -239,6 +315,9 @@ public int hashCode() { result = 31 * result + (filename != null ? filename.hashCode() : 0); result = 31 * result + (charset != null ? charset.hashCode() : 0); result = 31 * result + (size != null ? size.hashCode() : 0); + 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; } @@ -267,6 +346,21 @@ public String toString() { builder.append("; size="); builder.append(this.size); } + if (this.creationDate != null) { + builder.append("; creation-date=\""); + builder.append(RFC_1123_DATE_TIME.format(this.creationDate)); + builder.append('\"'); + } + if (this.modificationDate != null) { + builder.append("; modification-date=\""); + builder.append(RFC_1123_DATE_TIME.format(this.modificationDate)); + builder.append('\"'); + } + if (this.readDate != null) { + builder.append("; read-date=\""); + builder.append(RFC_1123_DATE_TIME.format(this.readDate)); + builder.append('\"'); + } return builder.toString(); } @@ -333,6 +427,21 @@ public interface Builder { */ 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 */ @@ -352,6 +461,13 @@ private static class BuilderImpl implements Builder { 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; @@ -382,9 +498,28 @@ public Builder size(Long size) { 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 0d24061c0504..359e2ac68969 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 @@ 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 void parseInvalidParameter() { 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")