diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceListing.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceListing.java index f0c41e487274..88609c1b01fe 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceListing.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceListing.java @@ -13,14 +13,17 @@ package org.eclipse.jetty.server; -import java.nio.file.Path; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.StringUtil; @@ -56,7 +59,9 @@ public static String getAsHTML(Resource resource, String base, boolean parent, S if (base == null || !resource.isDirectory()) return null; - List listing = new ArrayList<>(resource.list().stream().map(URIUtil::encodePath).map(resource::resolve).toList()); + List listing = resource.list().stream() + .filter(distinctBy(Resource::getFileName)) + .collect(Collectors.toCollection(ArrayList::new)); boolean sortOrderAscending = true; String sortColumn = "N"; // name (or "M" for Last Modified, or "S" for Size) @@ -208,14 +213,11 @@ public static String getAsHTML(Resource resource, String base, boolean parent, S for (Resource item : listing) { // Listings always return non-composite Resource entries - Path filePath = item.getPath(); - if (filePath == null) - continue; // skip, can't represent this in a listing anyway. - - String name = filePath.getFileName().toString(); + String name = item.getFileName(); if (StringUtil.isBlank(name)) - continue; + continue; // a resource either not backed by a filename (eg: MemoryResource), or has no filename (eg: a segment-less root "/") + // Ensure name has a slash if it's a directory if (item.isDirectory() && !name.endsWith("/")) name += URIUtil.SLASH; @@ -252,6 +254,13 @@ public static String getAsHTML(Resource resource, String base, boolean parent, S return buf.toString(); } + /* TODO: see if we can use {@link Collectors#groupingBy} */ + private static Predicate distinctBy(Function keyExtractor) + { + HashSet map = new HashSet<>(); + return t -> map.add(keyExtractor.apply(t)); + } + /** * Encode any characters that could break the URI string in an HREF. * Such as ">Link diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java index 6351944265d9..32b2d42e8f51 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java @@ -179,7 +179,7 @@ public void testUncacheable() throws Exception @Override public boolean isCacheable(Resource resource) { - return super.isCacheable(resource) && resource.getName().indexOf("2.txt") < 0; + return super.isCacheable(resource) && !resource.getFileName().equals("2.txt"); } }; diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java index 9739e7442f1f..eba897579675 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java @@ -195,6 +195,51 @@ public static boolean isArchive(URI uri) return (ext.equals(".jar") || ext.equals(".war") || ext.equals(".zip")); } + /** + * Test if Path is any supported Java Library Archive type (suitable to use as a library in a classpath/classloader) + * + * @param path the path to test + * @return true if path is a file, and an extension of {@code .jar}, or {@code .zip} + * @see #getExtension(Path) + */ + public static boolean isLibArchive(Path path) + { + String ext = getExtension(path); + if (ext == null) + return false; + return (ext.equals(".jar") || ext.equals(".zip")); + } + + /** + * Test if filename is any supported Java Library Archive type (suitable to use as a library in a classpath/classloader) + * + * @param filename the filename to test + * @return true if path is a file and name ends with {@code .jar}, or {@code .zip} + * @see #getExtension(String) + */ + public static boolean isLibArchive(String filename) + { + String ext = getExtension(filename); + if (ext == null) + return false; + return (ext.equals(".jar") || ext.equals(".zip")); + } + + /** + * Test if URI is any supported Java Library Archive type (suitable to use as a library in a classpath/classloader) + * + * @param uri the URI to test + * @return true if the URI has a path that seems to point to a ({@code .jar}, or {@code .zip}). + * @see #getExtension(URI) + */ + public static boolean isLibArchive(URI uri) + { + String ext = getExtension(uri); + if (ext == null) + return false; + return (ext.equals(".jar") || ext.equals(".zip")); + } + /** * Predicate to select all class files * @@ -365,7 +410,7 @@ public static boolean isNotModuleInfoClass(Path path) * Is the path a TLD File * * @param path the path to test. - * @return True if a .war file. + * @return True if a .tld file. */ public static boolean isTld(Path path) { @@ -387,6 +432,17 @@ public static boolean isWebArchive(Path path) return ".war".equals(getExtension(path)); } + /** + * Is the path a Web Archive File (not directory) + * + * @param uri the uri to test. + * @return True if a .war file. + */ + public static boolean isWebArchive(URI uri) + { + return ".war".equals(getExtension(uri)); + } + /** * Is the filename a WAR file. * @@ -419,4 +475,26 @@ public static boolean isXml(String filename) { return ".xml".equals(getExtension(filename)); } + + /** + * Is the Path a file that ends in ZIP? + * + * @param path the path to test + * @return true if a .zip, false otherwise + */ + public static boolean isZip(Path path) + { + return ".zip".equals(getExtension(path)); + } + + /** + * Is the Path a file that ends in ZIP? + * + * @param filename the filename to test + * @return true if a .zip, false otherwise + */ + public static boolean isZip(String filename) + { + return ".zip".equals(getExtension(filename)); + } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 415855a5d41a..8c65a4821c5a 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -1922,7 +1922,7 @@ public static List split(String str) { listStream .filter(Files::isRegularFile) - .filter(FileID::isArchive) + .filter(FileID::isLibArchive) .sorted(Comparator.naturalOrder()) .forEach(path -> uris.add(toJarFileUri(path.toUri()))); } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java index f82fb8b5f09f..103782732e80 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MemoryResource.java @@ -23,6 +23,8 @@ import java.nio.channels.ReadableByteChannel; import java.nio.file.Path; import java.time.Instant; +import java.util.Collection; +import java.util.List; import java.util.Objects; import org.eclipse.jetty.util.IO; @@ -73,7 +75,22 @@ public URI getURI() @Override public String getName() { - return getPath().toAbsolutePath().toString(); + Path p = getPath(); + if (p == null) + return null; + return p.toAbsolutePath().toString(); + } + + @Override + public String getFileName() + { + Path p = getPath(); + if (p == null) + return null; + Path fn = p.getFileName(); + if (fn == null) + return ""; // no segments, so no filename + return fn.toString(); } @Override @@ -106,6 +123,18 @@ public boolean exists() return true; } + @Override + public List list() + { + return List.of(); // empty + } + + @Override + public Collection getAllResources() + { + return List.of(); // empty + } + @Override public String toString() { diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java index c317fd00bd6f..1aa83541ef1d 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/MountedPathResource.java @@ -44,4 +44,16 @@ public Path getContainerPath() { return containerUri == null ? null : Path.of(containerUri); } + + @Override + public String getName() + { + Path abs = getPath(); + // If a "jar:file:" based path, we should normalize here, as the toAbsolutePath() does not resolve "/../" style segments in all cases + if ("jar".equalsIgnoreCase(abs.toUri().getScheme())) + abs = abs.normalize(); + // Get the absolute path + abs = abs.toAbsolutePath(); + return abs.toString(); + } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index 7e54edb4d583..ba294006b3a9 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -15,7 +15,7 @@ import java.io.IOException; import java.net.URI; -import java.nio.file.FileSystemNotFoundException; +import java.nio.file.DirectoryIteratorException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -23,7 +23,11 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.URIUtil; @@ -207,27 +211,31 @@ public static boolean isSameName(Path pathA, Path pathB) } PathResource(URI uri, boolean bypassAllowedSchemeCheck) + { + // normalize to referenced location, Paths.get() doesn't like "/bar/../foo/text.txt" style references + // and will return a Path that will not be found with `Files.exists()` or `Files.isDirectory()` + this(Paths.get(uri.normalize()), uri, bypassAllowedSchemeCheck); + } + + PathResource(Path path) + { + this(path, path.toUri(), true); + } + + PathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) { if (!uri.isAbsolute()) throw new IllegalArgumentException("not an absolute uri: " + uri); if (!bypassAllowedSchemeCheck && !ALLOWED_SCHEMES.contains(uri.getScheme())) throw new IllegalArgumentException("not an allowed scheme: " + uri); - try - { - // normalize to referenced location, Paths.get() doesn't like "/bar/../foo/text.txt" style references - // and will return a Path that will not be found with `Files.exists()` or `Files.isDirectory()` - this.path = Paths.get(uri.normalize()); - String uriString = uri.toString(); - if (Files.isDirectory(path) && !uriString.endsWith(URIUtil.SLASH)) - uri = URIUtil.correctFileURI(URI.create(uriString + URIUtil.SLASH)); - this.uri = uri; - this.alias = checkAliasPath(); - } - catch (FileSystemNotFoundException e) - { - throw new IllegalStateException("No FileSystem mounted for : " + uri, e); - } + String uriString = uri.toASCIIString(); + if (Files.isDirectory(path) && !uriString.endsWith(URIUtil.SLASH)) + uri = URIUtil.correctFileURI(URI.create(uriString + URIUtil.SLASH)); + + this.path = path; + this.uri = uri; + this.alias = checkAliasPath(); } @Override @@ -263,12 +271,41 @@ public Path getPath() return path; } + public List list() + { + if (!isDirectory()) + return List.of(); // empty + + try (Stream dirStream = Files.list(getPath())) + { + return dirStream.map(PathResource::new).collect(Collectors.toCollection(ArrayList::new)); + } + catch (DirectoryIteratorException e) + { + LOG.debug("Directory list failure", e); + } + catch (IOException e) + { + LOG.debug("Directory list access failure", e); + } + return List.of(); // empty + } + @Override public String getName() { return path.toAbsolutePath().toString(); } + @Override + public String getFileName() + { + Path fn = path.getFileName(); + if (fn == null) // if path has no segments (eg "/") + return ""; + return fn.toString(); + } + @Override public URI getURI() { diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index 76ec2eaf4688..75075595749d 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -19,8 +19,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.channels.ReadableByteChannel; -import java.nio.file.DirectoryIteratorException; -import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -238,12 +236,21 @@ public long length() public abstract URI getURI(); /** - * The name of the resource. + * The full name of the resource. * - * @return the name of the resource + * @return the full name of the resource, or null if not backed by a Path */ public abstract String getName(); + /** + *

The file name of the resource.

+ * + *

This is the last segment of the path.

+ * + * @return the filename of the resource, or "" if there are no path segments (eg: path of "/"), or null if not backed by a Path + */ + public abstract String getFileName(); + /** * Creates a new input stream to the resource. * @@ -267,45 +274,16 @@ public ReadableByteChannel newReadableByteChannel() throws IOException } /** - * list of resource names contained in the given resource. - * Ordering is unspecified, so callers may wish to sort the return value to ensure deterministic behavior. - * Equivalent to {@link Files#newDirectoryStream(Path)} with parameter: {@link #getPath()} then iterating over the returned - * {@link DirectoryStream}, taking the {@link Path#getFileName()} of each iterated entry and appending a {@code /} to - * the file name if testing it with {@link Files#isDirectory(Path, LinkOption...)} returns true. + *

List of existing Resources contained in the given resource.

* - * @return a list of resource names contained in the given resource, or null if {@link DirectoryIteratorException} or - * {@link IOException} was thrown while building the filename list. - * Note: The resource names are not URL encoded. + *

Ordering is unspecified, so callers may wish to sort the return value to ensure deterministic behavior.

+ * + * @return a mutable list of resources contained in the tracked resource, + * or an empty immutable list if unable to build the list. */ - public List list() // TODO: should return Path's + public List list() { - if (!isDirectory()) - return null; - try (DirectoryStream dir = Files.newDirectoryStream(getPath())) - { - List entries = new ArrayList<>(); - for (Path entry : dir) - { - String name = entry.getFileName().toString(); - - if (Files.isDirectory(entry)) - { - name += "/"; - } - - entries.add(name); - } - return entries; - } - catch (DirectoryIteratorException e) - { - LOG.debug("Directory list failure", e); - } - catch (IOException e) - { - LOG.debug("Directory list access failure", e); - } - return null; + return List.of(); // empty } /** @@ -441,19 +419,12 @@ public Collection getAllResources() try { ArrayList deep = new ArrayList<>(); + for (Resource r: list()) { - List list = list(); - if (list != null) - { - for (String i : list) - { - Resource r = resolve(i); - if (r.isDirectory()) - deep.addAll(r.getAllResources()); - else - deep.add(r); - } - } + if (r.isDirectory()) + deep.addAll(r.getAllResources()); + else + deep.add(r); } return deep; } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollators.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollators.java index 3a8a8c432e3d..0f1d2ae4c7a7 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollators.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollators.java @@ -18,7 +18,6 @@ import java.util.Comparator; import java.util.Locale; -// TODO remove public class ResourceCollators { private static Comparator BY_NAME_ASCENDING = @@ -33,9 +32,24 @@ public int compare(Resource o1, Resource o2) } }; + private static Comparator BY_FILENAME_ASCENDING = + new Comparator<>() + { + private final Collator collator = Collator.getInstance(Locale.ENGLISH); + + @Override + public int compare(Resource o1, Resource o2) + { + return collator.compare(o1.getFileName(), o2.getFileName()); + } + }; + private static Comparator BY_NAME_DESCENDING = Collections.reverseOrder(BY_NAME_ASCENDING); + private static Comparator BY_FILENAME_DESCENDING = + Collections.reverseOrder(BY_FILENAME_ASCENDING); + private static Comparator BY_LAST_MODIFIED_ASCENDING = Comparator.comparing(Resource::lastModified); @@ -72,6 +86,18 @@ public static Comparator byName(boolean sortOrderAscending) } } + public static Comparator byFileName(boolean sortOrderAscending) + { + if (sortOrderAscending) + { + return BY_FILENAME_ASCENDING; + } + else + { + return BY_FILENAME_DESCENDING; + } + } + public static Comparator bySize(boolean sortOrderAscending) { if (sortOrderAscending) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java index be925cd0e2e9..3c93a4377679 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java @@ -22,8 +22,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -220,6 +218,20 @@ public String getName() return null; } + @Override + public String getFileName() + { + for (Resource r : _resources) + { + String filename = r.getFileName(); + if (filename != null) + { + return filename; + } + } + return null; + } + @Override public URI getURI() { @@ -267,22 +279,14 @@ public Iterator iterator() return _resources.iterator(); } - /** - * @return The list of resource names(merged) contained in the collection of resources. - */ @Override - public List list() + public List list() { - HashSet set = new HashSet<>(); + List result = new ArrayList<>(); for (Resource r : _resources) { - List list = r.list(); - if (list != null) - set.addAll(list); + result.addAll(r.list()); } - - ArrayList result = new ArrayList<>(set); - result.sort(Comparator.naturalOrder()); return result; } diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java index 4bf86975d8b5..5b589bc9b6b0 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java @@ -80,7 +80,6 @@ public static Stream basenameCases() @MethodSource("basenameCases") public void testGetBasename(String input, String expected) throws IOException { - Path outputJar = workDir.getEmptyPathDir().resolve("test.jar"); Map env = new HashMap<>(); env.put("create", "true"); @@ -230,12 +229,70 @@ public void testGetExtension(String input, String expected) throws IOException "jar:file:/home/user/project/with.jar/in/path/name", "file:/home/user/project/directory/", "file:/home/user/hello.ear", - "/home/user/hello.jar", - "/home/user/app.war" + "file:/opt/websites/webapps/company.war", // war files are not lib archives (the classes are not in the right place) + "/home/user/app.war", // not a absolute URI + "/home/user/hello.jar" + }) + public void testIsLibArchiveUriFalse(String rawUri) + { + assertFalse(FileID.isLibArchive(URI.create(rawUri)), "Should not be detected as a Lib Archive: " + rawUri); + } + + @ParameterizedTest + @ValueSource(strings = { + "file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar", + "jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar!/", + "jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar", + "file:/home/user/install/jetty-home-12.0.0.zip", + "jar:file:/home/user/.m2/repository/jakarta/servlet/jakarta.servlet-api/6.0.0/jakarta.servlet-api-6.0.0.jar!/META-INF/resources" + }) + public void testIsLibArchiveUriTrue(String rawUri) + { + assertTrue(FileID.isLibArchive(URI.create(rawUri)), "Should be detected as a Lib Archive: " + rawUri); + } + + @ParameterizedTest + @ValueSource(strings = { + "jar:file:/home/user/project/with.jar/in/path/name", + "/home/user/project/with.jar/in/path/name", + "/home/user/project/directory/", + "/home/user/hello.ear", + "/opt/websites/webapps/company.war", + "/home/user/app.war", + "/home/user/hello.tar.gz", + "webapp.war", + "name" + }) + public void testIsLibArchiveStringFalse(String str) + { + assertFalse(FileID.isLibArchive(str), "Should not be detected as a Lib Archive: " + str); + } + + @ParameterizedTest + @ValueSource(strings = { + "/home/user/.m2/repository/com/company/1.0/company-1.0.jar", + "company-1.0.jar", + "jar:file:/home/user/.m2/repository/com/company/1.0/company-1.0.jar", + "file:/home/user/install/jetty-home-12.0.0.zip", + "/home/user/install/jetty-home-12.0.0.zip", + "jetty-util-12.jar" + }) + public void testIsLibArchiveStringTrue(String str) + { + assertTrue(FileID.isLibArchive(str), "Should be detected as a Lib Archive: " + str); + } + + @ParameterizedTest + @ValueSource(strings = { + "jar:file:/home/user/project/with.jar/in/path/name", + "file:/home/user/project/directory/", + "file:/home/user/hello.ear", + "/home/user/app.war", // not a absolute URI + "/home/user/hello.jar" }) public void testIsArchiveUriFalse(String rawUri) { - assertFalse(FileID.isArchive(URI.create(rawUri)), "Should be detected as a JAR URI: " + rawUri); + assertFalse(FileID.isArchive(URI.create(rawUri)), "Should not be detected as an Archive: " + rawUri); } @ParameterizedTest @@ -249,7 +306,7 @@ public void testIsArchiveUriFalse(String rawUri) }) public void testIsArchiveUriTrue(String rawUri) { - assertTrue(FileID.isArchive(URI.create(rawUri)), "Should be detected as a JAR URI: " + rawUri); + assertTrue(FileID.isArchive(URI.create(rawUri)), "Should be detected as an Archive: " + rawUri); } @ParameterizedTest @@ -347,7 +404,7 @@ public void testIsMetaInfVersions(String input) throws IOException "cee.jar", "cee.zip" }) - public void testIsWebArchiveFalse(String input) throws IOException + public void testIsWebArchiveStringFalse(String input) throws IOException { assertFalse(FileID.isWebArchive(input), "isWebArchive((String) \"%s\")".formatted(input)); Path path = touchTestPath(input); @@ -363,7 +420,7 @@ public void testIsWebArchiveFalse(String input) throws IOException "ZED.WAR", "Zed.War" }) - public void testIsWebArchiveTrue(String input) throws IOException + public void testIsWebArchiveStringTrue(String input) throws IOException { assertTrue(FileID.isWebArchive(input), "isWebArchive((String) \"%s\")".formatted(input)); Path path = touchTestPath(input); diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java index 9a4cb6d8e8fc..6179b0b9b5d7 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java @@ -540,6 +540,10 @@ public void testAddPathNav() // java.net.URI will encode `\\` to `%5C` per URI rules actual = URIUtil.addPath(uri, "foo/..\\bar"); assertThat(actual.toASCIIString(), is("file:////c:/foo/..%5Cbar")); + + // Adding a "file" to a URI that ends in a "file", what should it do? + actual = URIUtil.addPath(URI.create("file:///opt/foo.txt"), "bar.dat"); + assertThat(actual.toASCIIString(), is("file:///opt/foo.txt/bar.dat")); } public static Stream ensureSafeEncodingSource() diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java index bfb7e6b7a30d..945126595e15 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java @@ -27,11 +27,11 @@ import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystemException; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.time.Instant; -import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.toolchain.test.FS; @@ -51,6 +51,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.endsWith; @@ -59,7 +60,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -159,7 +159,7 @@ public void testNonAbsoluteURI() throws Exception @Test public void testNotFileURI() { - assertThrows(IllegalStateException.class, + assertThrows(FileSystemNotFoundException.class, () -> ResourceFactory.root().newResource(new URI("https://www.eclipse.org/jetty/"))); } @@ -449,6 +449,18 @@ public void testName() throws Exception assertThat("base.name", base.getName(), is(expected)); } + @Test + public void testFileName() throws Exception + { + Path dir = workDir.getEmptyPathDir(); + Files.createDirectories(dir); + + String expected = dir.getFileName().toString(); + + Resource base = ResourceFactory.root().newResource(dir); + assertThat("base.filename", base.getFileName(), is(expected)); + } + @Test public void testInputStream() throws Exception { @@ -524,20 +536,18 @@ public void testList() throws Exception Files.createDirectories(dir.resolve("tick")); Files.createDirectories(dir.resolve("tock")); - List expected = new ArrayList<>(); - expected.add("foo"); - expected.add("bar"); - expected.add("tick/"); - expected.add("tock/"); + String[] expectedFileNames = { + "foo", + "bar", + "tick", + "tock" + }; Resource base = ResourceFactory.root().newResource(dir); - List actual = base.list(); + List listing = base.list(); + List actualFileNames = listing.stream().map(Resource::getFileName).toList(); - assertEquals(expected.size(), actual.size()); - for (String s : expected) - { - assertTrue(actual.contains(s)); - } + assertThat(actualFileNames, containsInAnyOrder(expectedFileNames)); } @Test diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/JarResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/JarResourceTest.java index 4f4bee7f86a2..37ab6d16db4b 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/JarResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/JarResourceTest.java @@ -15,12 +15,11 @@ import java.net.URI; import java.nio.file.ClosedFileSystemException; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.zip.ZipFile; import org.eclipse.jetty.toolchain.test.FS; @@ -31,7 +30,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -72,8 +70,8 @@ public void testJarFile() { Resource r = resourceFactory.newResource(uri); - Set entries = new HashSet<>(r.list()); - assertThat(entries, containsInAnyOrder("alphabet", "numbers", "subsubdir/")); + List entries = r.list().stream().map(Resource::getFileName).toList(); + assertThat(entries, containsInAnyOrder("alphabet", "numbers", "subsubdir")); Path extract = workDir.getPathFile("extract"); FS.ensureEmpty(extract); @@ -82,13 +80,13 @@ public void testJarFile() Resource e = resourceFactory.newResource(extract.toString()); - entries = new HashSet<>(e.list()); - assertThat(entries, containsInAnyOrder("alphabet", "numbers", "subsubdir/")); + entries = r.list().stream().map(Resource::getFileName).toList(); + assertThat(entries, containsInAnyOrder("alphabet", "numbers", "subsubdir")); s = "jar:" + testZip.toUri().toASCIIString() + "!/subdir/subsubdir/"; r = resourceFactory.newResource(s); - entries = new HashSet<>(r.list()); + entries = r.list().stream().map(Resource::getFileName).toList(); assertThat(entries, containsInAnyOrder("alphabet", "numbers")); Path extract2 = workDir.getPathFile("extract2"); @@ -98,7 +96,7 @@ public void testJarFile() e = resourceFactory.newResource(extract2.toString()); - entries = new HashSet<>(e.list()); + entries = r.list().stream().map(Resource::getFileName).toList(); assertThat(entries, containsInAnyOrder("alphabet", "numbers")); } } @@ -119,10 +117,10 @@ public void testJarFileUnMounted() throws Exception } @Test - public void testJarFileDeleted(@TempDir Path tempDir) throws Exception + public void testJarFileDeleted(WorkDir workDir) throws Exception { Path originalTestZip = MavenTestingUtils.getTestResourcePathFile("TestData/test.zip"); - Path testZip = Files.copy(originalTestZip, tempDir.resolve("test.zip")); + Path testZip = Files.copy(originalTestZip, workDir.getEmptyPathDir().resolve("test.zip")); String s = "jar:" + testZip.toUri().toASCIIString() + "!/subdir/"; URI uri = URI.create(s); Resource resource; @@ -131,16 +129,16 @@ public void testJarFileDeleted(@TempDir Path tempDir) throws Exception resource = resourceFactory.newResource(uri); assertTrue(resource.exists()); Files.delete(testZip); - assertThrows(IllegalStateException.class, () -> resource.resolve("alphabet")); + assertThrows(FileSystemNotFoundException.class, () -> resource.resolve("alphabet")); } assertThrows(ClosedFileSystemException.class, resource::exists); } @Test - public void testDumpAndSweep(@TempDir Path tempDir) throws Exception + public void testDumpAndSweep(WorkDir workDir) throws Exception { Path originalTestZip = MavenTestingUtils.getTestResourcePathFile("TestData/test.zip"); - Path testZip = Files.copy(originalTestZip, tempDir.resolve("test.zip")); + Path testZip = Files.copy(originalTestZip, workDir.getEmptyPathDir().resolve("test.zip")); String s = "jar:" + testZip.toUri().toASCIIString() + "!/subdir/"; URI uri = URI.create(s); try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable()) @@ -242,15 +240,15 @@ public void testJarFileResourceList() throws Exception assertThat("path /rez/ is a dir", rez.isDirectory(), is(true)); - List actual = rez.list(); + List actual = rez.list().stream().map(Resource::getFileName).toList(); String[] expected = new String[]{ "one", "aaa", "bbb", - "oddities/", - "another dir/", + "oddities", + "another dir", "ccc", - "deep/", + "deep", }; assertThat("Dir contents", actual, containsInAnyOrder(expected)); } @@ -272,7 +270,7 @@ public void testJarFileResourceListPreEncodedEntries() throws Exception assertThat("path /rez/oddities/ is a dir", rez.isDirectory(), is(true)); - List actual = rez.list(); + List actual = rez.list().stream().map(Resource::getFileName).toList(); String[] expected = new String[]{ ";", "#hashcode", @@ -298,7 +296,7 @@ public void testJarFileResourceListDirWithSpace() throws Exception assertThat("path /rez/another dir/ is a dir", anotherDir.isDirectory(), is(true)); - List actual = anotherDir.list(); + List actual = anotherDir.list().stream().map(Resource::getFileName).toList(); String[] expected = new String[]{ "a file.txt", "another file.txt", diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java index 309e3112c9f7..a0367c6e63bf 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java @@ -119,6 +119,9 @@ public void testNonDefaultFileSystemGetPath() Resource resource = resourceFactory.newResource(manifestPath); Path path = resource.getPath(); assertThat("Path should not be null even for non-default FileSystem", path, notNullValue()); + + assertThat("Resource.getName", resource.getName(), is(path.toAbsolutePath().toString())); + assertThat("Resource.getFileName", resource.getFileName(), is("MANIFEST.MF")); } } @@ -130,6 +133,9 @@ public void testDefaultFileSystemGetFile() Path path = resource.getPath(); assertThat("File for default FileSystem", path, is(exampleJar)); + + assertThat("Resource.getName", resource.getName(), is(exampleJar.toAbsolutePath().toString())); + assertThat("Resource.getFileName", resource.getFileName(), is("example.jar")); } @Test @@ -158,16 +164,22 @@ public void testJarFileIsAliasFile(WorkDir workDir) throws IOException // Resolve to name, but different case testText = archiveResource.resolve("/TEST.TXT"); assertFalse(testText.exists()); + assertThat("Resource.getName", testText.getName(), is("/TEST.TXT")); + assertThat("Resource.getFileName", testText.getFileName(), is("TEST.TXT")); // Resolve using path navigation testText = archiveResource.resolve("/foo/../test.txt"); assertTrue(testText.exists()); assertTrue(testText.isAlias()); + assertThat("Resource.getName", testText.getName(), is("/test.txt")); + assertThat("Resource.getFileName", testText.getFileName(), is("test.txt")); // Resolve using encoded characters testText = archiveResource.resolve("/test%2Etxt"); assertTrue(testText.exists()); assertFalse(testText.isAlias()); + assertThat("Resource.getName", testText.getName(), is("/test.txt")); + assertThat("Resource.getFileName", testText.getFileName(), is("test.txt")); } } diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java index f8913c28f8e1..056f23856734 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -35,13 +36,18 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(WorkDirExtension.class) @@ -66,6 +72,7 @@ public void afterEach() @Test public void testList() throws Exception { + Path testBaseDir = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource"); Path one = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/one"); Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two"); Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three"); @@ -75,9 +82,36 @@ public void testList() throws Exception resourceFactory.newResource(two), resourceFactory.newResource(three) ); - assertThat(rc.list(), contains("1.txt", "2.txt", "3.txt", "dir/")); - assertThat(rc.resolve("dir").list(), contains("1.txt", "2.txt", "3.txt")); - assertThat(rc.resolve("unknown").list(), nullValue()); + + Function relativizeToTestResources = (r) -> testBaseDir.toUri().relativize(r.getURI()).toASCIIString(); + + List listing = rc.list(); + List listingFilenames = listing.stream().map(relativizeToTestResources).toList(); + + String[] expected = new String[] { + "one/dir/", + "one/1.txt", + "two/2.txt", + "two/dir/", + "two/1.txt", + "three/3.txt", + "three/2.txt", + "three/dir/" + }; + + assertThat(listingFilenames, containsInAnyOrder(expected)); + + listingFilenames = rc.resolve("dir").list().stream().map(relativizeToTestResources).toList(); + + expected = new String[] { + "one/dir/1.txt", + "two/dir/2.txt", + "three/dir/3.txt" + }; + + assertThat(listingFilenames, containsInAnyOrder(expected)); + + assertThat(rc.resolve("unknown").list(), is(empty())); assertEquals(getContent(rc, "1.txt"), "1 - one"); assertEquals(getContent(rc, "2.txt"), "2 - two"); @@ -216,6 +250,39 @@ public void testIterable() assertThat(actual, contains(expected)); } + /** + * Demonstrate behavior of ResourceCollection.resolve() when dealing with + * conflicting names between Directories and Files. + */ + @ParameterizedTest + @ValueSource(strings = {"foo", "/foo", "foo/", "/foo/"}) + public void testResolveConflictDirAndFile(String input) throws IOException + { + Path base = workDir.getEmptyPathDir(); + Path dirA = base.resolve("dirA"); + FS.ensureDirExists(dirA); + Files.createDirectory(dirA.resolve("foo")); + + Path dirB = base.resolve("dirB"); + FS.ensureDirExists(dirB); + Files.createDirectory(dirB.resolve("foo")); + + Path dirC = base.resolve("dirC"); + FS.ensureDirExists(dirC); + Files.createFile(dirC.resolve("foo")); + + Resource rc = Resource.combine( + ResourceFactory.root().newResource(dirA), + ResourceFactory.root().newResource(dirB), + ResourceFactory.root().newResource(dirC) + ); + + Resource result = rc.resolve(input); + assertThat(result, instanceOf(PathResource.class)); + assertThat(result.getPath().toUri().toASCIIString(), endsWith("dirC/foo")); + assertFalse(result.isDirectory()); + } + @Test public void testUserSpaceConfigurationNoGlob() throws Exception { diff --git a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/OverlayManager.java b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/OverlayManager.java index e5a11be69ebb..b60d0ec0160c 100644 --- a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/OverlayManager.java +++ b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/OverlayManager.java @@ -133,12 +133,7 @@ protected Resource unpackOverlay(Overlay overlay) //Get the name of the overlayed war and unpack it to a dir of the //same name in the temporary directory - String name = overlay.getResource().getName(); - if (name.endsWith("!/")) - name = name.substring(0, name.length() - 2); - int i = name.lastIndexOf('/'); - if (i > 0) - name = name.substring(i + 1, name.length()); + String name = overlay.getResource().getFileName(); name = name.replace('.', '_'); File overlaysDir = new File(warPlugin.getProject().getBuild().getDirectory(), "jetty_overlays"); diff --git a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/SelectiveJarResource.java b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/SelectiveJarResource.java index a29badb933a9..5b34d86f68c8 100644 --- a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/SelectiveJarResource.java +++ b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/SelectiveJarResource.java @@ -128,6 +128,12 @@ public String getName() return _delegate.getName(); } + @Override + public String getFileName() + { + return _delegate.getFileName(); + } + @Override public void copyTo(Path directory) throws IOException { diff --git a/jetty-ee10/jetty-ee10-runner/src/main/java/org/eclipse/jetty/ee10/runner/Runner.java b/jetty-ee10/jetty-ee10-runner/src/main/java/org/eclipse/jetty/ee10/runner/Runner.java index 60a9369fac87..4fc86a324111 100644 --- a/jetty-ee10/jetty-ee10-runner/src/main/java/org/eclipse/jetty/ee10/runner/Runner.java +++ b/jetty-ee10/jetty-ee10-runner/src/main/java/org/eclipse/jetty/ee10/runner/Runner.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.ee10.runner; -import java.io.IOException; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URI; @@ -51,6 +50,7 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.util.FileID; import org.eclipse.jetty.util.RolloverFileOutputStream; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; @@ -106,32 +106,17 @@ public class Classpath { private List _classpath = new ArrayList<>(); - public void addJars(Resource lib) throws IOException + public void addJars(Resource lib) { if (lib == null || !lib.exists()) throw new IllegalStateException("No such lib: " + lib); - List list = lib.list(); - if (list == null) - return; - - for (String path : list) + for (Resource item: lib.list()) { - if (".".equals(path) || "..".equals(path)) - continue; - - Resource item = lib.resolve(path); if (item.isDirectory()) addJars(item); - else - { - String lowerCasePath = path.toLowerCase(Locale.ENGLISH); - if (lowerCasePath.endsWith(".jar") || - lowerCasePath.endsWith(".zip")) - { - _classpath.add(item.getURI()); - } - } + else if (FileID.isLibArchive(item.getFileName())) + _classpath.add(item.getURI()); } } diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java index 6c4ab667e8f0..2a7dc2af3214 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java @@ -830,22 +830,15 @@ public Set getResourcePaths(String path) { Resource resource = getResource(path); - if (resource != null && resource.exists()) - { - if (!path.endsWith(URIUtil.SLASH)) - path = path + URIUtil.SLASH; + if (!path.endsWith(URIUtil.SLASH)) + path = path + URIUtil.SLASH; - List l = resource.list(); - if (l != null) - { - HashSet set = new HashSet<>(); - for (int i = 0; i < l.size(); i++) - { - set.add(path + l.get(i)); - } - return set; - } + HashSet set = new HashSet<>(); + for (Resource item: resource.list()) + { + set.add(path + item.getFileName()); } + return set; } catch (Exception e) { @@ -2936,9 +2929,22 @@ public URL getResource(String path) throws MalformedURLException path = URIUtil.canonicalPath(path); if (path == null) return null; + + // Assumption is that the resource base has been properly setup. + // Spec requirement is that the WAR file is interrogated first. + // If a WAR file is mounted, or is extracted to a temp directory, + // then the first entry of the resource base must be the WAR file. Resource resource = ServletContextHandler.this.getResource(path); - if (resource != null && resource.exists()) - return resource.getURI().toURL(); + if (resource == null) + return null; + + for (Resource r: resource) + { + if (r.exists()) + return r.getURI().toURL(); + } + + // A Resource was returned, but did not exist return null; } diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java index bd1fd7e41567..ff3c6ccf768c 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java @@ -25,12 +25,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -44,6 +42,7 @@ import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.UriPatternPredicate; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollators; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.ResourceUriPatternPredicate; import org.slf4j.Logger; @@ -693,38 +692,15 @@ protected List findWebInfLibJars(WebAppContext context) throws Exception { Resource webInf = context.getWebInf(); - if (webInf == null || !webInf.exists()) + if (webInf == null) return null; - List jarResources = new ArrayList(); Resource webInfLib = webInf.resolve("/lib"); - if (webInfLib.exists() && webInfLib.isDirectory()) - { - List files = webInfLib.list(); - if (files != null) - { - files.sort(Comparator.naturalOrder()); - } - for (int f = 0; files != null && f < files.size(); f++) - { - try - { - Resource file = webInfLib.resolve(files.get(f)); - String fnlc = file.getName().toLowerCase(Locale.ENGLISH); - int dot = fnlc.lastIndexOf('.'); - String extension = (dot < 0 ? null : fnlc.substring(dot)); - if (extension != null && (extension.equals(".jar") || extension.equals(".zip"))) - { - jarResources.add(file); - } - } - catch (Exception ex) - { - LOG.warn("Unable to load WEB-INF file {}", files.get(f), ex); - } - } - } - return jarResources; + + return webInfLib.list().stream() + .filter((lib) -> FileID.isLibArchive(lib.getFileName())) + .sorted(ResourceCollators.byName(true)) + .collect(Collectors.toList()); } /** @@ -793,6 +769,6 @@ protected List findExtraClasspathDirs(WebAppContext context) private boolean isFileSupported(Resource resource) { - return FileID.isArchive(resource.getURI()); + return FileID.isLibArchive(resource.getURI()); } } diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java index 220ed0135ffd..21994a3e089a 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java @@ -1444,23 +1444,22 @@ public URL getResource(String path) throws MalformedURLException if (path == null) return null; + // Assumption is that the resource base has been properly setup. + // Spec requirement is that the WAR file is interrogated first. + // If a WAR file is mounted, or is extracted to a temp directory, + // then the first entry of the resource base must be the WAR file. Resource resource = WebAppContext.this.getResource(path); - if (resource == null || !resource.exists()) + if (resource == null) return null; - // Should we go to the original war? - if (resource.isDirectory() && resource instanceof ResourceCollection && !WebAppContext.this.isExtractWAR()) + for (Resource r: resource) { - List resources = ((ResourceCollection)resource).getResources(); - for (int i = resources.size(); i-- > 0; ) - { - Resource r = resources.get(i); - if (r.getName().startsWith("jar:file")) - return r.getURI().toURL(); - } + if (r.exists()) + return r.getURI().toURL(); } - return resource.getURI().toURL(); + // A Resource was returned, but did not exist + return null; } } diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/OrderingTest.java b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/OrderingTest.java index 32a9e02a6d64..9388af43970e 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/OrderingTest.java +++ b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/OrderingTest.java @@ -84,6 +84,12 @@ public String getName() return _name; } + @Override + public String getFileName() + { + return null; + } + @Override public URI getURI() { @@ -107,12 +113,6 @@ public long length() { return 0; } - - @Override - public List list() - { - return null; - } } @BeforeEach diff --git a/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/OverlayManager.java b/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/OverlayManager.java index 472521efbd1b..a549dfe47ab8 100644 --- a/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/OverlayManager.java +++ b/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/OverlayManager.java @@ -133,12 +133,7 @@ protected Resource unpackOverlay(Overlay overlay) //Get the name of the overlayed war and unpack it to a dir of the //same name in the temporary directory - String name = overlay.getResource().getName(); - if (name.endsWith("!/")) - name = name.substring(0, name.length() - 2); - int i = name.lastIndexOf('/'); - if (i > 0) - name = name.substring(i + 1, name.length()); + String name = overlay.getResource().getFileName(); name = name.replace('.', '_'); File overlaysDir = new File(warPlugin.getProject().getBuild().getDirectory(), "jetty_overlays"); diff --git a/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/SelectiveJarResource.java b/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/SelectiveJarResource.java index 47cf0798c622..6bdb979e67fc 100644 --- a/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/SelectiveJarResource.java +++ b/jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9/maven/plugin/SelectiveJarResource.java @@ -128,6 +128,12 @@ public String getName() return _delegate.getName(); } + @Override + public String getFileName() + { + return _delegate.getFileName(); + } + @Override public void copyTo(Path directory) throws IOException { diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java index 3a5f46abc86a..d1beb4569589 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java @@ -1463,22 +1463,19 @@ public Set getResourcePaths(String path) { Resource resource = getResource(path); - if (resource != null && resource.exists()) - { - if (!path.endsWith(URIUtil.SLASH)) - path = path + URIUtil.SLASH; + if (!path.endsWith(URIUtil.SLASH)) + path = path + URIUtil.SLASH; + + HashSet set = new HashSet<>(); - List l = resource.list(); - if (l != null) + for (Resource r: resource) + { + for (Resource item: r.list()) { - HashSet set = new HashSet<>(); - for (int i = 0; i < l.size(); i++) - { - set.add(path + l.get(i)); - } - return set; + set.add(path + item.getFileName()); } } + return set; } catch (Exception e) { diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java index 45e878f0cadf..ad433476bf57 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java @@ -25,12 +25,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -44,6 +42,7 @@ import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.UriPatternPredicate; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollators; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.ResourceUriPatternPredicate; import org.slf4j.Logger; @@ -696,35 +695,12 @@ protected List findWebInfLibJars(WebAppContext context) if (webInf == null || !webInf.exists()) return null; - List jarResources = new ArrayList(); Resource webInfLib = webInf.resolve("/lib"); - if (webInfLib.exists() && webInfLib.isDirectory()) - { - List files = webInfLib.list(); - if (files != null) - { - files.sort(Comparator.naturalOrder()); - } - for (int f = 0; files != null && f < files.size(); f++) - { - try - { - Resource file = webInfLib.resolve(files.get(f)); - String fnlc = file.getName().toLowerCase(Locale.ENGLISH); - int dot = fnlc.lastIndexOf('.'); - String extension = (dot < 0 ? null : fnlc.substring(dot)); - if (extension != null && (extension.equals(".jar") || extension.equals(".zip"))) - { - jarResources.add(file); - } - } - catch (Exception ex) - { - LOG.warn("Unable to load WEB-INF file {}", files.get(f), ex); - } - } - } - return jarResources; + + return webInfLib.list().stream() + .filter((lib) -> FileID.isLibArchive(lib.getFileName())) + .sorted(ResourceCollators.byName(true)) + .collect(Collectors.toList()); } /** @@ -794,6 +770,6 @@ protected List findExtraClasspathDirs(WebAppContext context) private boolean isFileSupported(Resource resource) { - return FileID.isArchive(resource.getURI()); + return FileID.isLibArchive(resource.getURI()); } } diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java index cbfdc3f45a7e..06a35533ca1d 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java @@ -1433,23 +1433,22 @@ public URL getResource(String path) throws MalformedURLException if (path == null) return null; + // Assumption is that the resource base has been properly setup. + // Spec requirement is that the WAR file is interrogated first. + // If a WAR file is mounted, or is extracted to a temp directory, + // then the first entry of the resource base must be the WAR file. Resource resource = WebAppContext.this.getResource(path); - if (resource == null || !resource.exists()) + if (resource == null) return null; - // Should we go to the original war? - if (resource.isDirectory() && resource instanceof ResourceCollection && !WebAppContext.this.isExtractWAR()) + for (Resource r: resource) { - List resources = ((ResourceCollection)resource).getResources(); - for (int i = resources.size(); i-- > 0; ) - { - Resource r = resources.get(i); - if (r.getName().startsWith("jar:file")) - return r.getURI().toURL(); - } + if (r.exists()) + return r.getURI().toURL(); } - return resource.getURI().toURL(); + // A Resource was returned, but did not exist + return null; } @Override diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/OrderingTest.java b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/OrderingTest.java index 4dbd47785335..b88fdc5479c7 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/OrderingTest.java +++ b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/OrderingTest.java @@ -84,6 +84,12 @@ public String getName() return _name; } + @Override + public String getFileName() + { + return null; + } + @Override public URI getURI() { @@ -107,12 +113,6 @@ public long length() { return 0; } - - @Override - public List list() - { - return null; - } } @BeforeEach