diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 0b45256ea3f3..a2085d1ad64a 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -630,8 +630,15 @@ private Set getClassPathManifestEntriesFromJar(File jar) // See jdk.internal.loader.URLClassPath.JarLoader.tryResolveFile(URL, String) continue; } - File candidate = new File(parent, path); - if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) { + + // Handle absolute paths correctly - don't use parent for absolute paths + File pathFile = new File(path); + File candidate = pathFile.isAbsolute() ? pathFile : new File(parent, path); + + // For relative paths, enforce security check (must be under parent) + // For absolute paths, just verify file exists (matching JVM behavior) + if (candidate.isFile() && + (pathFile.isAbsolute() || candidate.getCanonicalPath().contains(parent.getCanonicalPath()))) { manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches)); } } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java index a5943124de45..e0390b6c7b03 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java @@ -337,6 +337,21 @@ void javaDashJarFindsClassPathManifestEntries() throws Exception { assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt"); } + @Test + void javaDashJarFindsAbsoluteClassPathManifestEntries() throws Exception { + Path assetJar = this.temp.resolve("dependency").resolve("asset.jar"); + Files.createDirectories(assetJar.getParent()); + writeAssetJar(assetJar); + writeApplicationJarWithAbsolutePath(this.temp.resolve("app.jar"), assetJar); + String java = ProcessHandle.current().info().command().get(); + Process process = new ProcessBuilder(java, "-jar", "app.jar") + .directory(this.temp.toFile()) + .start(); + assertThat(process.waitFor()).isZero(); + String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8); + assertThat(result.replace("\\", "/")).contains("!!!!").contains("asset.jar!/assets/file.txt"); + } + private void writeAssetJar(Path path) throws Exception { try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()))) { jar.putNextEntry(new ZipEntry("assets/")); @@ -392,6 +407,35 @@ private void writeApplicationJar(Path path) throws Exception { assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue(); } + private void writeApplicationJarWithAbsolutePath(Path path, Path assetJar) throws Exception { + Manifest manifest = new Manifest(); + Attributes mainAttributes = manifest.getMainAttributes(); + mainAttributes.put(Name.CLASS_PATH, buildSpringClassPath() + assetJar.toAbsolutePath()); + mainAttributes.put(Name.MAIN_CLASS, ClassPathManifestEntriesTestApplication.class.getName()); + mainAttributes.put(Name.MANIFEST_VERSION, "1.0"); + try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) { + String appClassResource = ClassUtils.convertClassNameToResourcePath( + ClassPathManifestEntriesTestApplication.class.getName()) + ClassUtils.CLASS_FILE_SUFFIX; + String folder = ""; + for (String name : appClassResource.split("/")) { + if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) { + folder += name + "/"; + jar.putNextEntry(new ZipEntry(folder)); + jar.closeEntry(); + } + else { + jar.putNextEntry(new ZipEntry(folder + name)); + try (InputStream in = getClass().getResourceAsStream(name)) { + in.transferTo(jar); + } + jar.closeEntry(); + } + } + } + assertThat(new FileSystemResource(path).exists()).isTrue(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue(); + } + private String buildSpringClassPath() throws Exception { return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") + copyClasses(LogFactory.class, "commons-logging");