Skip to content

Commit 7ceeace

Browse files
committed
Support date properties in Content-Disposition HTTP header
Issue: SPR-15555
1 parent 90df7dd commit 7ceeace

File tree

2 files changed

+162
-5
lines changed

2 files changed

+162
-5
lines changed

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

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
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.StringUtils;
2628

2729
import static java.nio.charset.StandardCharsets.*;
30+
import static java.time.format.DateTimeFormatter.*;
2831

2932
/**
3033
* Represent the content disposition type and parameters as defined in RFC 2183.
@@ -45,16 +48,28 @@ public class ContentDisposition {
4548

4649
private final Long size;
4750

51+
private final ZonedDateTime creationDate;
52+
53+
private final ZonedDateTime modificationDate;
54+
55+
private final ZonedDateTime readDate;
56+
4857

4958
/**
5059
* Private constructor. See static factory methods in this class.
5160
*/
52-
private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename, @Nullable Charset charset, @Nullable Long size) {
61+
private ContentDisposition(@Nullable String type, @Nullable String name,
62+
@Nullable String filename, @Nullable Charset charset, @Nullable Long size,
63+
@Nullable ZonedDateTime creationDate, @Nullable ZonedDateTime modificationDate, @Nullable ZonedDateTime readDate) {
64+
5365
this.type = type;
5466
this.name = name;
5567
this.filename = filename;
5668
this.charset = charset;
5769
this.size = size;
70+
this.creationDate = creationDate;
71+
this.modificationDate = modificationDate;
72+
this.readDate = readDate;
5873
}
5974

6075

@@ -100,6 +115,30 @@ public Long getSize() {
100115
return this.size;
101116
}
102117

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+
103142

104143
/**
105144
* Return a builder for a {@code ContentDisposition}.
@@ -115,11 +154,12 @@ public static Builder builder(String type) {
115154
* Return an empty content disposition.
116155
*/
117156
public static ContentDisposition empty() {
118-
return new ContentDisposition(null, null, null, null, null);
157+
return new ContentDisposition(null, null, null, null, null, null, null, null);
119158
}
120159

121160
/**
122161
* Parse a {@literal Content-Disposition} header value as defined in RFC 2183.
162+
*
123163
* @param contentDisposition the {@literal Content-Disposition} header value
124164
* @return the parsed content disposition
125165
* @see #toString()
@@ -132,6 +172,9 @@ public static ContentDisposition parse(String contentDisposition) {
132172
String filename = null;
133173
Charset charset = null;
134174
Long size = null;
175+
ZonedDateTime creationDate = null;
176+
ZonedDateTime modificationDate = null;
177+
ZonedDateTime readDate = null;
135178
for (int i = 1; i < parts.length; i++) {
136179
String part = parts[i];
137180
int eqIndex = part.indexOf('=');
@@ -155,12 +198,36 @@ else if (attribute.equals("filename") && (filename == null)) {
155198
else if (attribute.equals("size") ) {
156199
size = Long.parseLong(value);
157200
}
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+
}
158225
}
159226
else {
160227
throw new IllegalArgumentException("Invalid content disposition format");
161228
}
162229
}
163-
return new ContentDisposition(type, name, filename, charset, size);
230+
return new ContentDisposition(type, name, filename, charset, size, creationDate, modificationDate, readDate);
164231
}
165232

166233
/**
@@ -229,7 +296,16 @@ public boolean equals(Object o) {
229296
if (charset != null ? !charset.equals(that.charset) : that.charset != null) {
230297
return false;
231298
}
232-
return size != null ? size.equals(that.size) : that.size == null;
299+
if (size != null ? !size.equals(that.size) : that.size != null) {
300+
return false;
301+
}
302+
if (creationDate != null ? !creationDate.equals(that.creationDate) : that.creationDate != null) {
303+
return false;
304+
}
305+
if (modificationDate != null ? !modificationDate.equals(that.modificationDate) : that.modificationDate != null) {
306+
return false;
307+
}
308+
return readDate != null ? readDate.equals(that.readDate) : that.readDate == null;
233309
}
234310

235311
@Override
@@ -239,6 +315,9 @@ public int hashCode() {
239315
result = 31 * result + (filename != null ? filename.hashCode() : 0);
240316
result = 31 * result + (charset != null ? charset.hashCode() : 0);
241317
result = 31 * result + (size != null ? size.hashCode() : 0);
318+
result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
319+
result = 31 * result + (modificationDate != null ? modificationDate.hashCode() : 0);
320+
result = 31 * result + (readDate != null ? readDate.hashCode() : 0);
242321
return result;
243322
}
244323

@@ -267,6 +346,21 @@ public String toString() {
267346
builder.append("; size=");
268347
builder.append(this.size);
269348
}
349+
if (this.creationDate != null) {
350+
builder.append("; creation-date=\"");
351+
builder.append(RFC_1123_DATE_TIME.format(this.creationDate));
352+
builder.append('\"');
353+
}
354+
if (this.modificationDate != null) {
355+
builder.append("; modification-date=\"");
356+
builder.append(RFC_1123_DATE_TIME.format(this.modificationDate));
357+
builder.append('\"');
358+
}
359+
if (this.readDate != null) {
360+
builder.append("; read-date=\"");
361+
builder.append(RFC_1123_DATE_TIME.format(this.readDate));
362+
builder.append('\"');
363+
}
270364
return builder.toString();
271365
}
272366

@@ -333,6 +427,21 @@ public interface Builder {
333427
*/
334428
Builder size(Long size);
335429

430+
/**
431+
* Set the value of the {@literal creation-date} parameter.
432+
*/
433+
Builder creationDate(ZonedDateTime creationDate);
434+
435+
/**
436+
* Set the value of the {@literal modification-date} parameter.
437+
*/
438+
Builder modificationDate(ZonedDateTime modificationDate);
439+
440+
/**
441+
* Set the value of the {@literal read-date} parameter.
442+
*/
443+
Builder readDate(ZonedDateTime readDate);
444+
336445
/**
337446
* Build the content disposition
338447
*/
@@ -352,6 +461,13 @@ private static class BuilderImpl implements Builder {
352461

353462
private Long size;
354463

464+
private ZonedDateTime creationDate;
465+
466+
private ZonedDateTime modificationDate;
467+
468+
private ZonedDateTime readDate;
469+
470+
355471
public BuilderImpl(String type) {
356472
Assert.hasText(type, "'type' must not be not empty");
357473
this.type = type;
@@ -382,9 +498,28 @@ public Builder size(Long size) {
382498
return this;
383499
}
384500

501+
@Override
502+
public Builder creationDate(ZonedDateTime creationDate) {
503+
this.creationDate = creationDate;
504+
return this;
505+
}
506+
507+
@Override
508+
public Builder modificationDate(ZonedDateTime modificationDate) {
509+
this.modificationDate = modificationDate;
510+
return this;
511+
}
512+
513+
@Override
514+
public Builder readDate(ZonedDateTime readDate) {
515+
this.readDate = readDate;
516+
return this;
517+
}
518+
385519
@Override
386520
public ContentDisposition build() {
387-
return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size);
521+
return new ContentDisposition(this.type, this.name, this.filename, this.charset,
522+
this.size, this.creationDate, this.modificationDate, this.readDate);
388523
}
389524
}
390525

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

Lines changed: 22 additions & 0 deletions
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)