Skip to content

Commit 527de1c

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

File tree

2 files changed

+136
-5
lines changed

2 files changed

+136
-5
lines changed

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

+124-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.ByteArrayOutputStream;
2020
import java.nio.charset.Charset;
2121
import java.nio.charset.StandardCharsets;
22+
import java.time.Instant;
23+
import java.time.format.DateTimeFormatter;
2224

2325
import org.springframework.lang.Nullable;
2426
import org.springframework.util.Assert;
@@ -45,16 +47,28 @@ public class ContentDisposition {
4547

4648
private final Long size;
4749

50+
private final Instant creationDate;
51+
52+
private final Instant modificationDate;
53+
54+
private final Instant readDate;
55+
4856

4957
/**
5058
* Private constructor. See static factory methods in this class.
5159
*/
52-
private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename, @Nullable Charset charset, @Nullable Long size) {
60+
private ContentDisposition(@Nullable String type, @Nullable String name,
61+
@Nullable String filename, @Nullable Charset charset, @Nullable Long size,
62+
@Nullable Instant creationDate, @Nullable Instant modificationDate, @Nullable Instant readDate) {
63+
5364
this.type = type;
5465
this.name = name;
5566
this.filename = filename;
5667
this.charset = charset;
5768
this.size = size;
69+
this.creationDate = creationDate;
70+
this.modificationDate = modificationDate;
71+
this.readDate = readDate;
5872
}
5973

6074

@@ -100,6 +114,30 @@ public Long getSize() {
100114
return this.size;
101115
}
102116

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

104142
/**
105143
* Return a builder for a {@code ContentDisposition}.
@@ -115,11 +153,12 @@ public static Builder builder(String type) {
115153
* Return an empty content disposition.
116154
*/
117155
public static ContentDisposition empty() {
118-
return new ContentDisposition(null, null, null, null, null);
156+
return new ContentDisposition(null, null, null, null, null, null, null, null);
119157
}
120158

121159
/**
122160
* Parse a {@literal Content-Disposition} header value as defined in RFC 2183.
161+
*
123162
* @param contentDisposition the {@literal Content-Disposition} header value
124163
* @return the parsed content disposition
125164
* @see #toString()
@@ -132,6 +171,9 @@ public static ContentDisposition parse(String contentDisposition) {
132171
String filename = null;
133172
Charset charset = null;
134173
Long size = null;
174+
Instant creationDate = null;
175+
Instant modificationDate = null;
176+
Instant readDate = null;
135177
for (int i = 1; i < parts.length; i++) {
136178
String part = parts[i];
137179
int eqIndex = part.indexOf('=');
@@ -155,12 +197,21 @@ else if (attribute.equals("filename") && (filename == null)) {
155197
else if (attribute.equals("size") ) {
156198
size = Long.parseLong(value);
157199
}
200+
else if (attribute.equals("creation-date")) {
201+
creationDate = DateTimeFormatter.RFC_1123_DATE_TIME.parse(value, Instant::from);
202+
}
203+
else if (attribute.equals("modification-date")) {
204+
modificationDate = DateTimeFormatter.RFC_1123_DATE_TIME.parse(value, Instant::from);
205+
}
206+
else if (attribute.equals("read-date")) {
207+
readDate = DateTimeFormatter.RFC_1123_DATE_TIME.parse(value, Instant::from);
208+
}
158209
}
159210
else {
160211
throw new IllegalArgumentException("Invalid content disposition format");
161212
}
162213
}
163-
return new ContentDisposition(type, name, filename, charset, size);
214+
return new ContentDisposition(type, name, filename, charset, size, creationDate, modificationDate, readDate);
164215
}
165216

166217
/**
@@ -229,7 +280,16 @@ public boolean equals(Object o) {
229280
if (charset != null ? !charset.equals(that.charset) : that.charset != null) {
230281
return false;
231282
}
232-
return size != null ? size.equals(that.size) : that.size == null;
283+
if (size != null ? !size.equals(that.size) : that.size != null) {
284+
return false;
285+
}
286+
if (creationDate != null ? !creationDate.equals(that.creationDate) : that.creationDate != null) {
287+
return false;
288+
}
289+
if (modificationDate != null ? !modificationDate.equals(that.modificationDate) : that.modificationDate != null) {
290+
return false;
291+
}
292+
return readDate != null ? readDate.equals(that.readDate) : that.readDate == null;
233293
}
234294

235295
@Override
@@ -239,6 +299,9 @@ public int hashCode() {
239299
result = 31 * result + (filename != null ? filename.hashCode() : 0);
240300
result = 31 * result + (charset != null ? charset.hashCode() : 0);
241301
result = 31 * result + (size != null ? size.hashCode() : 0);
302+
result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
303+
result = 31 * result + (modificationDate != null ? modificationDate.hashCode() : 0);
304+
result = 31 * result + (readDate != null ? readDate.hashCode() : 0);
242305
return result;
243306
}
244307

@@ -267,6 +330,21 @@ public String toString() {
267330
builder.append("; size=");
268331
builder.append(this.size);
269332
}
333+
if (this.creationDate != null) {
334+
builder.append("; creation-date=\"");
335+
builder.append(DateTimeFormatter.RFC_1123_DATE_TIME.format(this.creationDate));
336+
builder.append('\"');
337+
}
338+
if (this.modificationDate != null) {
339+
builder.append("; modification-date=\"");
340+
builder.append(DateTimeFormatter.RFC_1123_DATE_TIME.format(this.modificationDate));
341+
builder.append('\"');
342+
}
343+
if (this.readDate != null) {
344+
builder.append("; read-date=\"");
345+
builder.append(DateTimeFormatter.RFC_1123_DATE_TIME.format(this.readDate));
346+
builder.append('\"');
347+
}
270348
return builder.toString();
271349
}
272350

@@ -333,6 +411,21 @@ public interface Builder {
333411
*/
334412
Builder size(Long size);
335413

414+
/**
415+
* Set the value of the {@literal creation-date} parameter.
416+
*/
417+
Builder creationDate(Instant creationDate);
418+
419+
/**
420+
* Set the value of the {@literal modification-date} parameter.
421+
*/
422+
Builder modificationDate(Instant modificationDate);
423+
424+
/**
425+
* Set the value of the {@literal read-date} parameter.
426+
*/
427+
Builder readDate(Instant readDate);
428+
336429
/**
337430
* Build the content disposition
338431
*/
@@ -352,6 +445,13 @@ private static class BuilderImpl implements Builder {
352445

353446
private Long size;
354447

448+
private Instant creationDate;
449+
450+
private Instant modificationDate;
451+
452+
private Instant readDate;
453+
454+
355455
public BuilderImpl(String type) {
356456
Assert.hasText(type, "'type' must not be not empty");
357457
this.type = type;
@@ -382,9 +482,28 @@ public Builder size(Long size) {
382482
return this;
383483
}
384484

485+
@Override
486+
public Builder creationDate(Instant creationDate) {
487+
this.creationDate = creationDate;
488+
return this;
489+
}
490+
491+
@Override
492+
public Builder modificationDate(Instant modificationDate) {
493+
this.modificationDate = modificationDate;
494+
return this;
495+
}
496+
497+
@Override
498+
public Builder readDate(Instant readDate) {
499+
this.readDate = readDate;
500+
return this;
501+
}
502+
385503
@Override
386504
public ContentDisposition build() {
387-
return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size);
505+
return new ContentDisposition(this.type, this.name, this.filename, this.charset,
506+
this.size, this.creationDate, this.modificationDate, this.readDate);
388507
}
389508
}
390509

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

+12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Method;
2020
import java.nio.charset.Charset;
2121
import java.nio.charset.StandardCharsets;
22+
import java.time.Instant;
2223

2324
import static org.junit.Assert.assertEquals;
2425
import org.junit.Test;
@@ -76,6 +77,17 @@ public void parseInvalidParameter() {
7677
ContentDisposition.parse("foo;bar");
7778
}
7879

80+
@Test
81+
public void parseDates() {
82+
ContentDisposition disposition = ContentDisposition
83+
.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\"");
84+
assertEquals(ContentDisposition.builder("attachment")
85+
.creationDate(Instant.parse("2007-02-12T15:15:30.00Z"))
86+
.modificationDate(Instant.parse("2007-02-13T15:15:30.00Z"))
87+
.readDate(Instant.parse("2007-02-14T15:15:30.00Z"))
88+
.build(), disposition);
89+
}
90+
7991
@Test
8092
public void headerValue() {
8193
ContentDisposition disposition = ContentDisposition.builder("form-data")

0 commit comments

Comments
 (0)