Skip to content

Commit 97909f2

Browse files
committed
Support date properties in Content-Disposition HTTP header
Issue: SPR-15555
1 parent e0e6736 commit 97909f2

File tree

2 files changed

+154
-7
lines changed

2 files changed

+154
-7
lines changed

spring-web/src/main/java/org/springframework/http/ContentDisposition.java

+132-7
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
import java.io.ByteArrayOutputStream;
2020
import java.nio.charset.Charset;
2121
import java.nio.charset.StandardCharsets;
22+
import java.time.ZonedDateTime;
23+
import java.time.format.DateTimeParseException;
2224

2325
import org.springframework.lang.Nullable;
2426
import org.springframework.util.Assert;
2527
import org.springframework.util.ObjectUtils;
2628
import org.springframework.util.StringUtils;
2729

2830
import static java.nio.charset.StandardCharsets.*;
31+
import static java.time.format.DateTimeFormatter.*;
2932

3033
/**
3134
* Represent the Content-Disposition type and parameters as defined in RFC 2183.
@@ -47,18 +50,26 @@ public class ContentDisposition {
4750

4851
private final Long size;
4952

53+
private final ZonedDateTime creationDate;
54+
55+
private final ZonedDateTime modificationDate;
56+
57+
private final ZonedDateTime readDate;
58+
5059

5160
/**
5261
* Private constructor. See static factory methods in this class.
5362
*/
54-
private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename,
55-
@Nullable Charset charset, @Nullable Long size) {
56-
63+
private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename, @Nullable Charset charset, @Nullable Long size,
64+
@Nullable ZonedDateTime creationDate, @Nullable ZonedDateTime modificationDate, @Nullable ZonedDateTime readDate) {
5765
this.type = type;
5866
this.name = name;
5967
this.filename = filename;
6068
this.charset = charset;
6169
this.size = size;
70+
this.creationDate = creationDate;
71+
this.modificationDate = modificationDate;
72+
this.readDate = readDate;
6273
}
6374

6475

@@ -104,6 +115,30 @@ public Long getSize() {
104115
return this.size;
105116
}
106117

118+
/**
119+
* Return the value of the {@literal creation-date} parameter, or {@code null} if not defined.
120+
*/
121+
@Nullable
122+
public ZonedDateTime getCreationDate() {
123+
return this.creationDate;
124+
}
125+
126+
/**
127+
* Return the value of the {@literal modification-date} parameter, or {@code null} if not defined.
128+
*/
129+
@Nullable
130+
public ZonedDateTime getModificationDate() {
131+
return this.modificationDate;
132+
}
133+
134+
/**
135+
* Return the value of the {@literal read-date} parameter, or {@code null} if not defined.
136+
*/
137+
@Nullable
138+
public ZonedDateTime getReadDate() {
139+
return this.readDate;
140+
}
141+
107142

108143
/**
109144
* Return a builder for a {@code ContentDisposition}.
@@ -119,11 +154,12 @@ public static Builder builder(String type) {
119154
* Return an empty content disposition.
120155
*/
121156
public static ContentDisposition empty() {
122-
return new ContentDisposition("", null, null, null, null);
157+
return new ContentDisposition("", null, null, null, null, null, null, null);
123158
}
124159

125160
/**
126161
* Parse a {@literal Content-Disposition} header value as defined in RFC 2183.
162+
*
127163
* @param contentDisposition the {@literal Content-Disposition} header value
128164
* @return the parsed content disposition
129165
* @see #toString()
@@ -136,6 +172,9 @@ public static ContentDisposition parse(String contentDisposition) {
136172
String filename = null;
137173
Charset charset = null;
138174
Long size = null;
175+
ZonedDateTime creationDate = null;
176+
ZonedDateTime modificationDate = null;
177+
ZonedDateTime readDate = null;
139178
for (int i = 1; i < parts.length; i++) {
140179
String part = parts[i];
141180
int eqIndex = part.indexOf('=');
@@ -159,12 +198,36 @@ else if (attribute.equals("filename") && (filename == null)) {
159198
else if (attribute.equals("size") ) {
160199
size = Long.parseLong(value);
161200
}
201+
else if (attribute.equals("creation-date")) {
202+
try {
203+
creationDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME);
204+
}
205+
catch (DateTimeParseException ex) {
206+
// ignore
207+
}
208+
}
209+
else if (attribute.equals("modification-date")) {
210+
try {
211+
modificationDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME);
212+
}
213+
catch (DateTimeParseException ex) {
214+
// ignore
215+
}
216+
}
217+
else if (attribute.equals("read-date")) {
218+
try {
219+
readDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME);
220+
}
221+
catch (DateTimeParseException ex) {
222+
// ignore
223+
}
224+
}
162225
}
163226
else {
164227
throw new IllegalArgumentException("Invalid content disposition format");
165228
}
166229
}
167-
return new ContentDisposition(type, name, filename, charset, size);
230+
return new ContentDisposition(type, name, filename, charset, size, creationDate, modificationDate, readDate);
168231
}
169232

170233
/**
@@ -225,7 +288,10 @@ public boolean equals(Object other) {
225288
ObjectUtils.nullSafeEquals(this.name, otherCd.name) &&
226289
ObjectUtils.nullSafeEquals(this.filename, otherCd.filename) &&
227290
ObjectUtils.nullSafeEquals(this.charset, otherCd.charset) &&
228-
ObjectUtils.nullSafeEquals(this.size, otherCd.size));
291+
ObjectUtils.nullSafeEquals(this.size, otherCd.size) &&
292+
ObjectUtils.nullSafeEquals(this.creationDate, otherCd.creationDate)&&
293+
ObjectUtils.nullSafeEquals(this.modificationDate, otherCd.modificationDate)&&
294+
ObjectUtils.nullSafeEquals(this.readDate, otherCd.readDate));
229295
}
230296

231297
@Override
@@ -235,6 +301,9 @@ public int hashCode() {
235301
result = 31 * result + ObjectUtils.nullSafeHashCode(this.filename);
236302
result = 31 * result + ObjectUtils.nullSafeHashCode(this.charset);
237303
result = 31 * result + ObjectUtils.nullSafeHashCode(this.size);
304+
result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
305+
result = 31 * result + (modificationDate != null ? modificationDate.hashCode() : 0);
306+
result = 31 * result + (readDate != null ? readDate.hashCode() : 0);
238307
return result;
239308
}
240309

@@ -266,6 +335,21 @@ public String toString() {
266335
sb.append("; size=");
267336
sb.append(this.size);
268337
}
338+
if (this.creationDate != null) {
339+
sb.append("; creation-date=\"");
340+
sb.append(RFC_1123_DATE_TIME.format(this.creationDate));
341+
sb.append('\"');
342+
}
343+
if (this.modificationDate != null) {
344+
sb.append("; modification-date=\"");
345+
sb.append(RFC_1123_DATE_TIME.format(this.modificationDate));
346+
sb.append('\"');
347+
}
348+
if (this.readDate != null) {
349+
sb.append("; read-date=\"");
350+
sb.append(RFC_1123_DATE_TIME.format(this.readDate));
351+
sb.append('\"');
352+
}
269353
return sb.toString();
270354
}
271355

@@ -332,6 +416,21 @@ public interface Builder {
332416
*/
333417
Builder size(Long size);
334418

419+
/**
420+
* Set the value of the {@literal creation-date} parameter.
421+
*/
422+
Builder creationDate(ZonedDateTime creationDate);
423+
424+
/**
425+
* Set the value of the {@literal modification-date} parameter.
426+
*/
427+
Builder modificationDate(ZonedDateTime modificationDate);
428+
429+
/**
430+
* Set the value of the {@literal read-date} parameter.
431+
*/
432+
Builder readDate(ZonedDateTime readDate);
433+
335434
/**
336435
* Build the content disposition
337436
*/
@@ -351,6 +450,13 @@ private static class BuilderImpl implements Builder {
351450

352451
private Long size;
353452

453+
private ZonedDateTime creationDate;
454+
455+
private ZonedDateTime modificationDate;
456+
457+
private ZonedDateTime readDate;
458+
459+
354460
public BuilderImpl(String type) {
355461
Assert.hasText(type, "'type' must not be not empty");
356462
this.type = type;
@@ -381,9 +487,28 @@ public Builder size(Long size) {
381487
return this;
382488
}
383489

490+
@Override
491+
public Builder creationDate(ZonedDateTime creationDate) {
492+
this.creationDate = creationDate;
493+
return this;
494+
}
495+
496+
@Override
497+
public Builder modificationDate(ZonedDateTime modificationDate) {
498+
this.modificationDate = modificationDate;
499+
return this;
500+
}
501+
502+
@Override
503+
public Builder readDate(ZonedDateTime readDate) {
504+
this.readDate = readDate;
505+
return this;
506+
}
507+
384508
@Override
385509
public ContentDisposition build() {
386-
return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size);
510+
return new ContentDisposition(this.type, this.name, this.filename, this.charset,
511+
this.size, this.creationDate, this.modificationDate, this.readDate);
387512
}
388513
}
389514

spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.lang.reflect.Method;
2020
import java.nio.charset.Charset;
2121
import java.nio.charset.StandardCharsets;
22+
import java.time.ZonedDateTime;
23+
import java.time.format.DateTimeFormatter;
2224

2325
import static org.junit.Assert.assertEquals;
2426
import org.junit.Test;
@@ -76,6 +78,26 @@ public void parseInvalidParameter() {
7678
ContentDisposition.parse("foo;bar");
7779
}
7880

81+
@Test
82+
public void parseDates() {
83+
ContentDisposition disposition = ContentDisposition
84+
.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\"");
85+
assertEquals(ContentDisposition.builder("attachment")
86+
.creationDate(ZonedDateTime.parse("Mon, 12 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME))
87+
.modificationDate(ZonedDateTime.parse("Tue, 13 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME))
88+
.readDate(ZonedDateTime.parse("Wed, 14 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME))
89+
.build(), disposition);
90+
}
91+
92+
@Test
93+
public void parseInvalidDates() {
94+
ContentDisposition disposition = ContentDisposition
95+
.parse("attachment; creation-date=\"-1\"; modification-date=\"-1\"; read-date=\"Wed, 14 Feb 2007 10:15:30 -0500\"");
96+
assertEquals(ContentDisposition.builder("attachment")
97+
.readDate(ZonedDateTime.parse("Wed, 14 Feb 2007 10:15:30 -0500", DateTimeFormatter.RFC_1123_DATE_TIME))
98+
.build(), disposition);
99+
}
100+
79101
@Test
80102
public void headerValue() {
81103
ContentDisposition disposition = ContentDisposition.builder("form-data")

0 commit comments

Comments
 (0)