From 0aa9d9d535d2939074d4975e3c2bae22d6a20935 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 5 Oct 2022 15:43:40 +0200 Subject: [PATCH] Return URL-decoded file name from UrlResource#getFilename() Prior to this commit, UrlResource#getFilename() returned the filename of the resource URL-encoded which is in contrast to what FileSystemResource#getFilename() returns for an equivalent resource. In addition, users most likely expect that a filename returned from a method defined in the Resource interface is unencoded. This commit therefore revises UrlResource#getFilename() so that it always returns the filename URL-decoded. Closes gh-29261 --- .../org/springframework/core/io/Resource.java | 5 +++-- .../springframework/core/io/UrlResource.java | 9 +++++++-- .../springframework/core/io/ResourceTests.java | 18 +++++++++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java index 1070232d48d6..740d5719314f 100644 --- a/spring-core/src/main/java/org/springframework/core/io/Resource.java +++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java @@ -158,10 +158,11 @@ default ReadableByteChannel readableChannel() throws IOException { Resource createRelative(String relativePath) throws IOException; /** - * Determine a filename for this resource, i.e. typically the last - * part of the path: for example, "myfile.txt". + * Determine the filename for this resource — typically the last + * part of the path — for example, {@code "myfile.txt"}. *

Returns {@code null} if this type of resource does not * have a filename. + *

Implementations are encouraged to return the filename unencoded. */ @Nullable String getFilename(); diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java index bcb559652f7d..72e43ae4efd9 100644 --- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java @@ -26,6 +26,8 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -316,12 +318,15 @@ protected URL createRelativeURL(String relativePath) throws MalformedURLExceptio } /** - * This implementation returns the name of the file that this URL refers to. + * This implementation returns the URL-decoded name of the file that this URL + * refers to. * @see java.net.URL#getPath() + * @see java.net.URLDecoder#decode(String, java.nio.charset.Charset) */ @Override public String getFilename() { - return StringUtils.getFilename(getCleanedUrl().getPath()); + String filename = StringUtils.getFilename(getCleanedUrl().getPath()); + return URLDecoder.decode(filename, StandardCharsets.UTF_8); } /** diff --git a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java index b0ce104fef4e..7549db9a6339 100644 --- a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java @@ -24,6 +24,7 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.ByteBuffer; @@ -59,7 +60,6 @@ */ class ResourceTests { - @ParameterizedTest(name = "{index}: {0}") @MethodSource("resource") void resourceIsValid(Resource resource) throws Exception { @@ -272,6 +272,22 @@ void filenameIsExtractedFromFilePath() throws Exception { assertThat(new UrlResource("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt"); } + @Test + void filenameContainingHashTagIsExtractedFromFilePathUnencoded() throws Exception { + String unencodedPath = "/dir/test#1.txt"; + String encodedPath = "/dir/test%231.txt"; + + URI uri = new URI("file", unencodedPath, null); + URL url = uri.toURL(); + assertThat(uri.getPath()).isEqualTo(unencodedPath); + assertThat(uri.getRawPath()).isEqualTo(encodedPath); + assertThat(url.getPath()).isEqualTo(encodedPath); + + UrlResource urlResource = new UrlResource(url); + assertThat(urlResource.getURI().getPath()).isEqualTo(unencodedPath); + assertThat(urlResource.getFilename()).isEqualTo("test#1.txt"); + } + @Test void factoryMethodsProduceEqualResources() throws Exception { Resource resource1 = new UrlResource("file:core/io/Resource.class");