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 1028c19fd2b..32c718814b2 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,9 @@ import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME; */ public final class ContentDisposition { + private final static Pattern BASE64_ENCODED_PATTERN = + Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?B\\?([+/0-9a-zA-Z]+=*)\\?="); + private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT = "Invalid header field parameter format (as defined in RFC 5987)"; @@ -139,8 +142,9 @@ public final class ContentDisposition { } /** - * Return the value of the {@literal filename} parameter (or the value of the - * {@literal filename*} one decoded as defined in the RFC 5987), or {@code null} if not defined. + * Return the value of the {@literal filename} parameter, possibly decoded + * from BASE64 encoding based on RFC 2047, or of the {@literal filename*} + * parameter, possibly decoded as defined in the RFC 5987. */ @Nullable public String getFilename() { @@ -323,7 +327,6 @@ public final class ContentDisposition { return new ContentDisposition("", null, null, null, null, null, null, null); } - private final static Pattern BASE64_ENCODED_PATTERN = Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?B\\?([+/0-9a-zA-Z]+=*)\\?="); /** * Parse a {@literal Content-Disposition} header value as defined in RFC 2183. * @param contentDisposition the {@literal Content-Disposition} header value @@ -366,12 +369,18 @@ public final class ContentDisposition { } } else if (attribute.equals("filename") && (filename == null)) { - final Matcher matcher = BASE64_ENCODED_PATTERN.matcher(value); - if (matcher.find()) { - String charsetName = matcher.group(1); - String encoded = matcher.group(2); - filename = new String(Base64.getDecoder().decode(encoded), Charset.forName(charsetName)); - } else { + if (value.startsWith("=?") ) { + Matcher matcher = BASE64_ENCODED_PATTERN.matcher(value); + if (matcher.find()) { + String match1 = matcher.group(1); + String match2 = matcher.group(2); + filename = new String(Base64.getDecoder().decode(match2), Charset.forName(match1)); + } + else { + filename = value; + } + } + 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 50c0d06f982..690eda7f550 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,16 +80,16 @@ class ContentDispositionTests { .build()); } - @Test - void parseBase64EncodedUTF8Filename() { - assertThat(parse("attachment; filename=\"=?UTF-8?B?5pel5pys6KqeLmNzdg==?=\"").getFilename()) - .isEqualTo("日本語.csv"); + @Test // gh-26463 + void parseBase64EncodedFilename() { + String input = "attachment; filename=\"=?UTF-8?B?5pel5pys6KqeLmNzdg==?=\""; + assertThat(parse(input).getFilename()).isEqualTo("日本語.csv"); } - @Test + @Test // gh-26463 void parseBase64EncodedShiftJISFilename() { - assertThat(parse("attachment; filename=\"=?SHIFT_JIS?B?k/qWe4zqLmNzdg==?=\"").getFilename()) - .isEqualTo("日本語.csv"); + String input = "attachment; filename=\"=?SHIFT_JIS?B?k/qWe4zqLmNzdg==?=\""; + assertThat(parse(input).getFilename()).isEqualTo("日本語.csv"); } @Test