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 6db7d90f6a..0a80807ecf 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -48,6 +48,18 @@ public final class ContentDisposition { private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT = "Invalid header field parameter format (as defined in RFC 5987)"; + /** + * The {@literal attachment} content-disposition type. + */ + private static final String ATTACHMENT = "attachment"; + /** + * The {@literal form-data} content-disposition type. + */ + private static final String FORM_DATA = "form-data"; + /** + * The {@literal inline} content-disposition type. + */ + private static final String INLINE = "inline"; @Nullable private final String type; @@ -174,6 +186,29 @@ public final class ContentDisposition { return this.readDate; } + /** + * Return whether the {@link #getType() type} is {@literal attachment}. + * @since 5.3 + */ + public boolean isAttachment() { + return ATTACHMENT.equals(this.type); + } + + /** + * Return whether the {@link #getType() type} is {@literal form-data}. + * @since 5.3 + */ + public boolean isFormData() { + return FORM_DATA.equals(this.type); + } + + /** + * Return whether the {@link #getType() type} is {@literal inline}. + * @since 5.3 + */ + public boolean isInline() { + return INLINE.equals(this.type); + } @Override public boolean equals(@Nullable Object other) { @@ -264,6 +299,36 @@ public final class ContentDisposition { return new BuilderImpl(type); } + /** + * Return a builder for a {@code ContentDisposition} with + * the {@link #ATTACHMENT attachment} type. + * @return the builder + * @since 5.3 + */ + public static Builder attachment() { + return builder(ATTACHMENT); + } + + /** + * Return a builder for a {@code ContentDisposition} with + * the {@link #FORM_DATA form-data} type. + * @return the builder + * @since 5.3 + */ + public static Builder formData() { + return builder(FORM_DATA); + } + + /** + * Return a builder for a {@code ContentDisposition} with + * the {@link #INLINE inline} type. + * @return the builder + * @since 5.3 + */ + public static Builder inline() { + return builder(INLINE); + } + /** * Return an empty content disposition. */ diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 01e403e17f..54e07c9eca 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -860,7 +860,7 @@ public class HttpHeaders implements MultiValueMap, Serializable */ public void setContentDispositionFormData(String name, @Nullable String filename) { Assert.notNull(name, "Name must not be null"); - ContentDisposition.Builder disposition = ContentDisposition.builder("form-data").name(name); + ContentDisposition.Builder disposition = ContentDisposition.formData().name(name); if (StringUtils.hasText(filename)) { disposition.filename(filename); } 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 f320e6675d..e2fb0aa6e1 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -25,7 +25,8 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.springframework.http.ContentDisposition.builder; +import static org.springframework.http.ContentDisposition.attachment; +import static org.springframework.http.ContentDisposition.formData; /** * Unit tests for {@link ContentDisposition} @@ -41,7 +42,7 @@ class ContentDispositionTests { @SuppressWarnings("deprecation") void parse() { assertThat(parse("form-data; name=\"foo\"; filename=\"foo.txt\"; size=123")) - .isEqualTo(builder("form-data") + .isEqualTo(formData() .name("foo") .filename("foo.txt") .size(123L) @@ -51,7 +52,7 @@ class ContentDispositionTests { @Test void parseFilenameUnquoted() { assertThat(parse("form-data; filename=unquoted")) - .isEqualTo(builder("form-data") + .isEqualTo(formData() .filename("unquoted") .build()); } @@ -59,7 +60,7 @@ class ContentDispositionTests { @Test // SPR-16091 void parseFilenameWithSemicolon() { assertThat(parse("attachment; filename=\"filename with ; semicolon.txt\"")) - .isEqualTo(builder("attachment") + .isEqualTo(attachment() .filename("filename with ; semicolon.txt") .build()); } @@ -67,7 +68,7 @@ class ContentDispositionTests { @Test void parseEncodedFilename() { assertThat(parse("form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt")) - .isEqualTo(builder("form-data") + .isEqualTo(formData() .name("name") .filename("中文.txt", StandardCharsets.UTF_8) .build()); @@ -76,7 +77,7 @@ class ContentDispositionTests { @Test // gh-24112 void parseEncodedFilenameWithPaddedCharset() { assertThat(parse("attachment; filename*= UTF-8''some-file.zip")) - .isEqualTo(builder("attachment") + .isEqualTo(attachment() .filename("some-file.zip", StandardCharsets.UTF_8) .build()); } @@ -84,7 +85,7 @@ class ContentDispositionTests { @Test void parseEncodedFilenameWithoutCharset() { assertThat(parse("form-data; name=\"name\"; filename*=test.txt")) - .isEqualTo(builder("form-data") + .isEqualTo(formData() .name("name") .filename("test.txt") .build()); @@ -111,7 +112,7 @@ class ContentDispositionTests { BiConsumer tester = (description, filename) -> assertThat(parse("form-data; name=\"file\"; filename=\"" + filename + "\"; size=123")) .as(description) - .isEqualTo(builder("form-data").name("file").filename(filename).size(123L).build()); + .isEqualTo(formData().name("file").filename(filename).size(123L).build()); tester.accept("Escaped quotes should be ignored", "\\\"The Twilight Zone\\\".txt"); @@ -130,7 +131,7 @@ class ContentDispositionTests { @SuppressWarnings("deprecation") void parseWithExtraSemicolons() { assertThat(parse("form-data; name=\"foo\";; ; filename=\"foo.txt\"; size=123")) - .isEqualTo(builder("form-data") + .isEqualTo(formData() .name("foo") .filename("foo.txt") .size(123L) @@ -149,7 +150,7 @@ class ContentDispositionTests { "creation-date=\"" + creationTime.format(formatter) + "\"; " + "modification-date=\"" + modificationTime.format(formatter) + "\"; " + "read-date=\"" + readTime.format(formatter) + "\"")).isEqualTo( - builder("attachment") + attachment() .creationDate(creationTime) .modificationDate(modificationTime) .readDate(readTime) @@ -166,7 +167,7 @@ class ContentDispositionTests { "creation-date=\"-1\"; " + "modification-date=\"-1\"; " + "read-date=\"" + readTime.format(formatter) + "\"")).isEqualTo( - builder("attachment") + attachment() .readDate(readTime) .build()); } @@ -195,7 +196,7 @@ class ContentDispositionTests { @SuppressWarnings("deprecation") void format() { assertThat( - builder("form-data") + formData() .name("foo") .filename("foo.txt") .size(123L) @@ -206,7 +207,7 @@ class ContentDispositionTests { @Test void formatWithEncodedFilename() { assertThat( - builder("form-data") + formData() .name("name") .filename("中文.txt", StandardCharsets.UTF_8) .build().toString()) @@ -216,7 +217,7 @@ class ContentDispositionTests { @Test void formatWithEncodedFilenameUsingUsAscii() { assertThat( - builder("form-data") + formData() .name("name") .filename("test.txt", StandardCharsets.US_ASCII) .build() @@ -229,10 +230,10 @@ class ContentDispositionTests { BiConsumer tester = (input, output) -> { - assertThat(builder("form-data").filename(input).build().toString()) + assertThat(formData().filename(input).build().toString()) .isEqualTo("form-data; filename=\"" + output + "\""); - assertThat(builder("form-data").filename(input, StandardCharsets.US_ASCII).build().toString()) + assertThat(formData().filename(input, StandardCharsets.US_ASCII).build().toString()) .isEqualTo("form-data; filename=\"" + output + "\""); }; @@ -262,7 +263,7 @@ class ContentDispositionTests { @Test void formatWithEncodedFilenameUsingInvalidCharset() { assertThatIllegalArgumentException().isThrownBy(() -> - builder("form-data") + formData() .name("name") .filename("test.txt", StandardCharsets.UTF_16) .build() diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java index a36415ff00..0bad35ed16 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -384,7 +384,7 @@ public class HttpHeadersTests { assertThat(disposition).isNotNull(); assertThat(headers.getContentDisposition()).as("Invalid Content-Disposition header").isEqualTo(ContentDisposition.empty()); - disposition = ContentDisposition.builder("attachment").name("foo").filename("foo.txt").size(123L).build(); + disposition = ContentDisposition.attachment().name("foo").filename("foo.txt").size(123L).build(); headers.setContentDisposition(disposition); assertThat(headers.getContentDisposition()).as("Invalid Content-Disposition header").isEqualTo(disposition); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageReaderTests.java index 1f076897b1..151eae3288 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageReaderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageReaderTests.java @@ -48,7 +48,7 @@ public class ResourceHttpMessageReaderTests extends AbstractLeakCheckingTests { String body = "Test resource content"; ContentDisposition contentDisposition = - ContentDisposition.builder("attachment").name("file").filename(filename).build(); + ContentDisposition.attachment().name("file").filename(filename).build(); MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK); response.getHeaders().setContentType(MediaType.TEXT_PLAIN); diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java index 5a05d431c7..3c98fe538d 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java @@ -67,7 +67,7 @@ public class ResourceHttpMessageConverterTests { MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG); inputMessage.getHeaders().setContentDisposition( - ContentDisposition.builder("attachment").filename("yourlogo.jpg").build()); + ContentDisposition.attachment().filename("yourlogo.jpg").build()); Resource actualResource = converter.read(Resource.class, inputMessage); assertThat(FileCopyUtils.copyToByteArray(actualResource.getInputStream())).isEqualTo(body); assertThat(actualResource.getFilename()).isEqualTo("yourlogo.jpg"); @@ -79,7 +79,7 @@ public class ResourceHttpMessageConverterTests { MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG); inputMessage.getHeaders().setContentDisposition( - ContentDisposition.builder("attachment").filename("yourlogo.jpg").build()); + ContentDisposition.attachment().filename("yourlogo.jpg").build()); inputMessage.getHeaders().setContentLength(123); Resource actualResource = converter.read(InputStreamResource.class, inputMessage); assertThat(actualResource).isInstanceOf(InputStreamResource.class);