Restore check for jar root existence (now via getEntryName/getJarEntry)

Closes gh-34607
This commit is contained in:
Juergen Hoeller 2025-03-17 19:20:41 +01:00
parent 5b6abe4c13
commit 760376c318
3 changed files with 30 additions and 20 deletions

View File

@ -56,6 +56,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
// Try a URL connection content-length header // Try a URL connection content-length header
URLConnection con = url.openConnection(); URLConnection con = url.openConnection();
customizeConnection(con); customizeConnection(con);
HttpURLConnection httpCon = (con instanceof HttpURLConnection huc ? huc : null); HttpURLConnection httpCon = (con instanceof HttpURLConnection huc ? huc : null);
if (httpCon != null) { if (httpCon != null) {
httpCon.setRequestMethod("HEAD"); httpCon.setRequestMethod("HEAD");
@ -81,12 +82,16 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
} }
} }
} }
// Check content-length entry but not for JarURLConnection where
// this would open the jar file but effectively never close it -> if (con instanceof JarURLConnection jarCon) {
// for jar entries, always fall back to stream existence instead. // For JarURLConnection, do not check content-length but rather the
if (!(con instanceof JarURLConnection) && con.getContentLengthLong() > 0) { // existence of the entry (or the jar root in case of no entryName).
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
}
else if (con.getContentLengthLong() > 0) {
return true; return true;
} }
if (httpCon != null) { if (httpCon != null) {
// No HTTP OK status, and no content-length header: give up // No HTTP OK status, and no content-length header: give up
httpCon.disconnect(); httpCon.disconnect();
@ -346,8 +351,8 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
*/ */
protected void customizeConnection(URLConnection con) throws IOException { protected void customizeConnection(URLConnection con) throws IOException {
ResourceUtils.useCachesIfNecessary(con); ResourceUtils.useCachesIfNecessary(con);
if (con instanceof HttpURLConnection httpConn) { if (con instanceof HttpURLConnection httpCon) {
customizeConnection(httpConn); customizeConnection(httpCon);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -234,8 +234,8 @@ public class UrlResource extends AbstractFileResolvingResource {
} }
catch (IOException ex) { catch (IOException ex) {
// Close the HTTP connection (if applicable). // Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection httpConn) { if (con instanceof HttpURLConnection httpCon) {
httpConn.disconnect(); httpCon.disconnect();
} }
throw ex; throw ex;
} }

View File

@ -51,8 +51,10 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.FileSystemUtils; import org.springframework.util.FileSystemUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -133,6 +135,7 @@ class PathMatchingResourcePatternResolverTests {
assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt"); assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt");
} }
@Nested @Nested
class WithHashtagsInTheirFilenames { class WithHashtagsInTheirFilenames {
@ -299,6 +302,7 @@ class PathMatchingResourcePatternResolverTests {
} }
} }
@Nested @Nested
class ClassPathManifestEntries { class ClassPathManifestEntries {
@ -313,8 +317,8 @@ class PathMatchingResourcePatternResolverTests {
writeApplicationJar(this.temp.resolve("app.jar")); writeApplicationJar(this.temp.resolve("app.jar"));
String java = ProcessHandle.current().info().command().get(); String java = ProcessHandle.current().info().command().get();
Process process = new ProcessBuilder(java, "-jar", "app.jar") Process process = new ProcessBuilder(java, "-jar", "app.jar")
.directory(this.temp.toFile()) .directory(this.temp.toFile())
.start(); .start();
assertThat(process.waitFor()).isZero(); assertThat(process.waitFor()).isZero();
String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8); String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8);
assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt"); assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt");
@ -328,6 +332,8 @@ class PathMatchingResourcePatternResolverTests {
StreamUtils.copy("test", StandardCharsets.UTF_8, jar); StreamUtils.copy("test", StandardCharsets.UTF_8, jar);
jar.closeEntry(); 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 void writeApplicationJar(Path path) throws Exception { private void writeApplicationJar(Path path) throws Exception {
@ -338,8 +344,7 @@ class PathMatchingResourcePatternResolverTests {
mainAttributes.put(Name.MANIFEST_VERSION, "1.0"); mainAttributes.put(Name.MANIFEST_VERSION, "1.0");
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) { try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) {
String appClassResource = ClassUtils.convertClassNameToResourcePath( String appClassResource = ClassUtils.convertClassNameToResourcePath(
ClassPathManifestEntriesTestApplication.class.getName()) ClassPathManifestEntriesTestApplication.class.getName()) + ClassUtils.CLASS_FILE_SUFFIX;
+ ClassUtils.CLASS_FILE_SUFFIX;
String folder = ""; String folder = "";
for (String name : appClassResource.split("/")) { for (String name : appClassResource.split("/")) {
if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) { if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) {
@ -356,18 +361,19 @@ class PathMatchingResourcePatternResolverTests {
} }
} }
} }
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 { private String buildSpringClassPath() throws Exception {
return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") +
+ copyClasses(LogFactory.class, "commons-logging"); copyClasses(LogFactory.class, "commons-logging");
} }
private String copyClasses(Class<?> sourceClass, String destinationName) private String copyClasses(Class<?> sourceClass, String destinationName) throws URISyntaxException, IOException {
throws URISyntaxException, IOException {
Path destination = this.temp.resolve(destinationName); Path destination = this.temp.resolve(destinationName);
String resourcePath = ClassUtils.convertClassNameToResourcePath(sourceClass.getName()) String resourcePath = ClassUtils.convertClassNameToResourcePath(
+ ClassUtils.CLASS_FILE_SUFFIX; sourceClass.getName()) + ClassUtils.CLASS_FILE_SUFFIX;
URL resource = getClass().getClassLoader().getResource(resourcePath); URL resource = getClass().getClassLoader().getResource(resourcePath);
URL url = new URL(resource.toString().replace(resourcePath, "")); URL url = new URL(resource.toString().replace(resourcePath, ""));
URLConnection connection = url.openConnection(); URLConnection connection = url.openConnection();
@ -393,7 +399,6 @@ class PathMatchingResourcePatternResolverTests {
} }
return destinationName + "/ "; return destinationName + "/ ";
} }
} }