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 8516e898e952..aa7c03580df2 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -599,7 +599,7 @@ private static String encodeQuotedPrintableFilename(String filename, Charset cha } private static boolean isPrintable(byte c) { - return (c >= '!' && c <= '<') || (c >= '>' && c <= '~'); + return (c >= '!' && c <= '<') || (c >= '@' && c <= '~') || c == '>'; } private static String encodeQuotedPairs(String 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 ac4a83154304..e46a361a2620 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -325,4 +325,20 @@ void parseFormatted() { assertThat(parsed.toString()).isEqualTo(cd.toString()); } + @Test // gh-30252 + void parseFormattedWithQuestionMark() { + String filename = "filename with ?问号.txt"; + ContentDisposition cd = ContentDisposition.attachment() + .filename(filename, StandardCharsets.UTF_8) + .build(); + String[] parts = cd.toString().split("; "); + + String quotedPrintableFilename = parts[0] + "; " + parts[1]; + assertThat(ContentDisposition.parse(quotedPrintableFilename).getFilename()) + .isEqualTo(filename); + + String rfc5987Filename = parts[0] + "; " + parts[2]; + assertThat(ContentDisposition.parse(rfc5987Filename).getFilename()) + .isEqualTo(filename); + } }