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 ee883bbe775..c254df91e7d 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -53,7 +53,7 @@ public final class ContentDisposition { Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?B\\?([+/0-9a-zA-Z]+=*)\\?="); private final static Pattern QUOTED_PRINTABLE_ENCODED_PATTERN = - Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?Q\\?(\\p{Print}+)\\?="); + Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?Q\\?([!->@-~]+)\\?="); // Printable ASCII other than "?" or SPACE private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT = "Invalid header field parameter format (as defined in RFC 5987)"; @@ -375,22 +375,29 @@ public final class ContentDisposition { if (value.startsWith("=?") ) { Matcher matcher = BASE64_ENCODED_PATTERN.matcher(value); if (matcher.find()) { - charset = Charset.forName(matcher.group(1)); - String encodedValue = matcher.group(2); - StringBuilder sb = new StringBuilder(new String(Base64.getDecoder().decode(encodedValue), charset)); - while (matcher.find()){ + Base64.Decoder decoder = Base64.getDecoder(); + StringBuilder builder = new StringBuilder(); + do { charset = Charset.forName(matcher.group(1)); - encodedValue = matcher.group(2); - sb.append(new String(Base64.getDecoder().decode(encodedValue), charset)); + byte[] decoded = decoder.decode(matcher.group(2)); + builder.append(new String(decoded, charset)); } - filename = sb.toString(); + while (matcher.find()); + + filename = builder.toString(); } else { matcher = QUOTED_PRINTABLE_ENCODED_PATTERN.matcher(value); if (matcher.find()) { - charset = Charset.forName(matcher.group(1)); - String encodedValue = matcher.group(2); - filename = decodeQuotedPrintableFilename(encodedValue, charset); + StringBuilder builder = new StringBuilder(); + do { + charset = Charset.forName(matcher.group(1)); + String decoded = decodeQuotedPrintableFilename(matcher.group(2), charset); + builder.append(decoded); + } + while (matcher.find()); + + filename = builder.toString(); } else { filename = value; 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 25bcd8d7fc8..9c6610550c4 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -87,10 +87,11 @@ class ContentDispositionTests { } @Test - void parseBase64EncodedFilenameHasMoreSegments() { - /* https://datatracker.ietf.org/doc/html/rfc2047#section-2 - * An 'encoded-word' may not be more than 75 characters long */ - String input = "attachment; filename=\"=?utf-8?B?U3ByaW5n5qGG5p625Li65Z+65LqOSmF2YeeahOeOsOS7o+S8geS4muW6lA==?= =?utf-8?B?55So56iL5bqP5o+Q5L6b5LqG5YWo6Z2i55qE57yW56iL5ZKM6YWN572u5qih?= =?utf-8?B?5Z6LLnR4dA==?=\""; + void parseBase64EncodedFilenameMultipleSegments() { + String input = + "attachment; filename=\"=?utf-8?B?U3ByaW5n5qGG5p625Li65Z+65LqOSmF2YeeahOeOsOS7o+S8geS4muW6lA==?= " + + "=?utf-8?B?55So56iL5bqP5o+Q5L6b5LqG5YWo6Z2i55qE57yW56iL5ZKM6YWN572u5qih?= " + + "=?utf-8?B?5Z6LLnR4dA==?=\""; assertThat(parse(input).getFilename()).isEqualTo("Spring框架为基于Java的现代企业应用程序提供了全面的编程和配置模型.txt"); } @@ -106,6 +107,18 @@ class ContentDispositionTests { assertThat(parse(input).getFilename()).isEqualTo("日本語.csv"); } + @Test + void parseQuotedPrintableFilenameMultipleSegments() { + String input = + "attachment; filename=\"=?utf-8?Q?Spring=E6=A1=86=E6=9E=B6=E4=B8=BA=E5=9F=BA=E4=BA=8E?=" + + "=?utf-8?Q?Java=E7=9A=84=E7=8E=B0=E4=BB=A3=E4=BC=81=E4=B8=9A=E5=BA=94?=" + + "=?utf-8?Q?=E7=94=A8=E7=A8=8B=E5=BA=8F=E6=8F=90=E4=BE=9B=E4=BA=86=E5=85=A8?=" + + "=?utf-8?Q?=E9=9D=A2=E7=9A=84=E7=BC=96=E7=A8=8B=E5=92=8C=E9=85=8D=E7=BD=AE?=" + + "=?utf-8?Q?=E6=A8=A1=E5=9E=8B.txt?=\""; + assertThat(parse(input).getFilename()).isEqualTo("Spring框架为基于Java的现代企业应用程序提供了全面的编程和配置模型.txt"); + + } + @Test void parseQuotedPrintableShiftJISFilename() { String input = "attachment; filename=\"=?SHIFT_JIS?Q?=93=FA=96{=8C=EA.csv?=\"";