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 4d480116c39c..c50ffc715a3c 100644
--- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
+++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
@@ -222,7 +222,7 @@ public String toString() {
if (this.filename != null) {
if (this.charset == null || StandardCharsets.US_ASCII.equals(this.charset)) {
sb.append("; filename=\"");
- sb.append(this.filename).append('\"');
+ sb.append(escapeQuotationsInFilename(this.filename)).append('\"');
}
else {
sb.append("; filename*=");
@@ -428,6 +428,23 @@ private static boolean isRFC5987AttrChar(byte c) {
c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
}
+ private static String escapeQuotationsInFilename(String filename) {
+ if (filename.indexOf('"') == -1 && filename.indexOf('\\') == -1) {
+ return filename;
+ }
+ boolean escaped = false;
+ StringBuilder sb = new StringBuilder();
+ for (char c : filename.toCharArray()) {
+ sb.append((c == '"' && !escaped) ? "\\\"" : c);
+ escaped = (!escaped && c == '\\');
+ }
+ // Remove backslash at the end..
+ if (escaped) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
/**
* Encode the given header field param as describe in RFC 5987.
* @param input the header field param
@@ -437,13 +454,10 @@ private static boolean isRFC5987AttrChar(byte c) {
* @see RFC 5987
*/
private static String encodeFilename(String input, Charset charset) {
- Assert.notNull(input, "Input String should not be null");
- Assert.notNull(charset, "Charset should not be null");
- if (StandardCharsets.US_ASCII.equals(charset)) {
- return input;
- }
- Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
- "Charset should be UTF-8 or ISO-8859-1");
+ Assert.notNull(input, "`input` is required");
+ Assert.notNull(charset, "`charset` is required");
+ Assert.isTrue(!StandardCharsets.US_ASCII.equals(charset), "ASCII does not require encoding");
+ Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Only UTF-8 and ISO-8859-1 supported.");
byte[] source = input.getBytes(charset);
int len = source.length;
StringBuilder sb = new StringBuilder(len << 1);
@@ -578,25 +592,13 @@ public Builder name(String name) {
@Override
public Builder filename(String filename) {
Assert.hasText(filename, "No filename");
- this.filename = escapeQuotationMarks(filename);
+ this.filename = filename;
return this;
}
- private static String escapeQuotationMarks(String filename) {
- if (filename.indexOf('"') == -1) {
- return filename;
- }
- boolean escaped = false;
- StringBuilder sb = new StringBuilder();
- for (char c : filename.toCharArray()) {
- sb.append((c == '"' && !escaped) ? "\\\"" : c);
- escaped = (!escaped && c == '\\');
- }
- return sb.toString();
- }
-
@Override
public Builder filename(String filename, Charset charset) {
+ Assert.hasText(filename, "No filename");
this.filename = filename;
this.charset = charset;
return this;
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 c14753ab844f..c63492780787 100644
--- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java
+++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java
@@ -222,10 +222,15 @@ public void formatWithEncodedFilenameUsingUsAscii() {
@Test // gh-24220
public void formatWithFilenameWithQuotes() {
- BiConsumer tester = (input, output) ->
+ BiConsumer tester = (input, output) -> {
+
assertThat(builder("form-data").filename(input).build().toString())
.isEqualTo("form-data; filename=\"" + output + "\"");
+ assertThat(builder("form-data").filename(input, StandardCharsets.US_ASCII).build().toString())
+ .isEqualTo("form-data; filename=\"" + output + "\"");
+ };
+
String filename = "\"foo.txt";
tester.accept(filename, "\\" + filename);
@@ -243,6 +248,10 @@ public void formatWithFilenameWithQuotes() {
tester.accept("\"\"foo.txt", "\\\"\\\"foo.txt");
tester.accept("\"\"\"foo.txt", "\\\"\\\"\\\"foo.txt");
+
+ tester.accept("foo.txt\\", "foo.txt");
+ tester.accept("foo.txt\\\\", "foo.txt\\\\");
+ tester.accept("foo.txt\\\\\\", "foo.txt\\\\");
}
@Test