From 5e79657d4b4871bbba156892692b15b795ddb2d6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 23 Nov 2016 13:02:23 +0000 Subject: [PATCH] Treat URLs for same file in nested archive and from jar root as equal Consider the following two URLs: jar:file:/test.jar!/BOOT-INF/classes!/foo.txt jar:file:/test.jar!/BOOT-INF/classes/foo.txt They both reference the same foo.txt file in the BOOT-INF/classes directory of test.jar, however the first URL does so via the nested BOOT-INF/classes archive. Previously, this difference in the URLs would lead to PathMatchingResourcePatternResolver returning two resources for foo.txt when asked to find all resources matching the pattern classpath*:/**/*.txt. This commit updates our Handler that is used for jar: URLs to consider the two URLs above to be equivalent such that url1 is equal to url2 and the two urls will produce the same hash code. Closes gh-7449 --- .../boot/loader/jar/Handler.java | 34 ++++++++++++++++++- .../boot/loader/jar/HandlerTests.java | 17 ++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java index 2b7a05670b..7d801bcbe4 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java @@ -196,6 +196,30 @@ public class Handler extends URLStreamHandler { setURL(context, JAR_PROTOCOL, null, -1, null, null, file, null, null); } + @Override + protected int hashCode(URL u) { + int result = 0; + String protocol = u.getProtocol(); + if (protocol != null) { + result += protocol.hashCode(); + } + String file = u.getFile(); + int separatorIndex = file.indexOf(SEPARATOR); + if (separatorIndex == -1) { + return result + file.hashCode(); + } + String fileWithoutEntry = file.substring(0, separatorIndex); + try { + result += new URL(fileWithoutEntry).hashCode(); + } + catch (MalformedURLException ex) { + result += fileWithoutEntry.hashCode(); + } + String entry = canonicalize(file.substring(separatorIndex + 2)); + result += entry.hashCode(); + return result; + } + @Override protected boolean sameFile(URL u1, URL u2) { if (!u1.getProtocol().equals("jar") || !u2.getProtocol().equals("jar")) { @@ -209,7 +233,11 @@ public class Handler extends URLStreamHandler { String nested1 = u1.getFile().substring(separator1 + SEPARATOR.length()); String nested2 = u2.getFile().substring(separator2 + SEPARATOR.length()); if (!nested1.equals(nested2)) { - return false; + String canonical1 = canonicalize(nested1); + String canonical2 = canonicalize(nested2); + if (!canonical1.equals(canonical2)) { + return false; + } } String root1 = u1.getFile().substring(0, separator1); String root2 = u2.getFile().substring(0, separator2); @@ -222,6 +250,10 @@ public class Handler extends URLStreamHandler { return super.sameFile(u1, u2); } + private String canonicalize(String path) { + return path.replace(SEPARATOR, "/"); + } + public JarFile getRootJarFileFromUrl(URL url) throws IOException { String spec = url.getFile(); int separatorIndex = spec.indexOf(SEPARATOR); diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java index 47862459a3..2fc34b35b1 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java @@ -119,6 +119,23 @@ public class HandlerTests { new URL("jar:file:/the/path/to/the/first.jar!/content.txt"))).isTrue(); } + @Test + public void sameFileReturnsTrueForUrlsThatReferenceSameFileViaNestedArchiveAndFromRootOfJar() + throws MalformedURLException { + assertThat(this.handler.sameFile( + new URL("jar:file:/test.jar!/BOOT-INF/classes!/foo.txt"), + new URL("jar:file:/test.jar!/BOOT-INF/classes/foo.txt"))).isTrue(); + } + + @Test + public void hashcodesAreEqualForUrlsThatReferenceSameFileViaNestedArchiveAndFromRootOfJar() + throws MalformedURLException { + assertThat(this.handler + .hashCode(new URL("jar:file:/test.jar!/BOOT-INF/classes!/foo.txt"))) + .isEqualTo(this.handler.hashCode( + new URL("jar:file:/test.jar!/BOOT-INF/classes/foo.txt"))); + } + private URL createUrl(String file) throws MalformedURLException { return new URL("jar", null, -1, file, this.handler); }