Skip to content

Commit

Permalink
Issue #8474 - Resource.list and Resource.getFileName work
Browse files Browse the repository at this point in the history
* Introduce Resource.getFileName and use it
* Correct signature of Resource.list and use it

Signed-off-by: Joakim Erdfelt <[email protected]>
  • Loading branch information
joakime committed Sep 13, 2022
1 parent 0d08f37 commit 237f2f4
Show file tree
Hide file tree
Showing 28 changed files with 428 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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;
Expand Down Expand Up @@ -56,7 +59,7 @@ public static String getAsHTML(Resource resource, String base, boolean parent, S
if (base == null || !resource.isDirectory())
return null;

List<Resource> listing = new ArrayList<>(resource.list().stream().map(URIUtil::encodePath).map(resource::resolve).toList());
List<Resource> listing = resource.list().stream().filter(distinctBy(Resource::getFileName)).collect(Collectors.toList());

boolean sortOrderAscending = true;
String sortColumn = "N"; // name (or "M" for Last Modified, or "S" for Size)
Expand Down Expand Up @@ -208,14 +211,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;

Expand Down Expand Up @@ -252,6 +252,12 @@ public static String getAsHTML(Resource resource, String base, boolean parent, S
return buf.toString();
}

private static <T> Predicate<T> distinctBy(Function<? super T, Object> keyExtractor)
{
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

/**
* Encode any characters that could break the URI string in an HREF.
* Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,48 +151,48 @@ public static boolean hasNamedPathSegment(Path path, String segmentName)
}

/**
* Test if Path is any supported Java Archive type (ends in {@code .jar}, {@code .war}, or {@code .zip}).
* Test if Path is any supported Java Archive type (ends in {@code .jar}, or {@code .zip}).
*
* @param path the path to test
* @return true if path is a file, and an extension of {@code .jar}, {@code .war}, or {@code .zip}
* @return true if path is a file, and an extension of {@code .jar}, or {@code .zip}
* @see #getExtension(Path)
*/
public static boolean isArchive(Path path)
{
String ext = getExtension(path);
if (ext == null)
return false;
return (ext.equals(".jar") || ext.equals(".war") || ext.equals(".zip"));
return (ext.equals(".jar") || ext.equals(".zip"));
}

/**
* Test if filename is any supported Java Archive type (ends in {@code .jar}, {@code .war}, or {@code .zip}).
* Test if filename is any supported Java Archive type (ends in {@code .jar}, or {@code .zip}).
*
* @param filename the filename to test
* @return true if path is a file and name ends with {@code .jar}, {@code .war}, or {@code .zip}
* @return true if path is a file and name ends with {@code .jar}, or {@code .zip}
* @see #getExtension(String)
*/
public static boolean isArchive(String filename)
{
String ext = getExtension(filename);
if (ext == null)
return false;
return (ext.equals(".jar") || ext.equals(".war") || ext.equals(".zip"));
return (ext.equals(".jar") || ext.equals(".zip"));
}

/**
* Test if URI is any supported Java Archive type.
*
* @param uri the URI to test
* @return true if the URI has a path that seems to point to a ({@code .jar}, {@code .war}, or {@code .zip}).
* @return true if the URI has a path that seems to point to a ({@code .jar}, or {@code .zip}).
* @see #isArchive(String)
*/
public static boolean isArchive(URI uri)
{
String ext = getExtension(uri);
if (ext == null)
return false;
return (ext.equals(".jar") || ext.equals(".war") || ext.equals(".zip"));
return (ext.equals(".jar") || ext.equals(".zip"));
}

/**
Expand Down Expand Up @@ -365,7 +365,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)
{
Expand All @@ -387,6 +387,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.
*
Expand Down Expand Up @@ -419,4 +430,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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1963,7 +1963,7 @@ public static URI toJarFileUri(URI uri)
Objects.requireNonNull(uri, "URI");
String scheme = Objects.requireNonNull(uri.getScheme(), "URI scheme");

if (!FileID.isArchive(uri))
if (!FileID.isArchive(uri) && !FileID.isWebArchive(uri))
return uri;

boolean hasInternalReference = uri.getRawSchemeSpecificPart().indexOf("!/") > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -106,6 +123,18 @@ public boolean exists()
return true;
}

@Override
public List<Resource> list()
{
return List.of(); // empty
}

@Override
public Collection<Resource> getAllResources()
{
return List.of(); // empty
}

@Override
public String toString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@

import java.io.IOException;
import java.net.URI;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
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 org.eclipse.jetty.util.Index;
Expand Down Expand Up @@ -206,6 +211,13 @@ public static boolean isSameName(Path pathA, Path pathB)
this(uri, false);
}

PathResource(Path path)
{
this.path = path;
this.uri = path.toUri();
this.alias = checkAliasPath();
}

PathResource(URI uri, boolean bypassAllowedSchemeCheck)
{
if (!uri.isAbsolute())
Expand Down Expand Up @@ -263,10 +275,67 @@ public Path getPath()
return path;
}

/**
* List of Resources contained in the given resource.
*
* <p>
* Ordering is unspecified, so callers may wish to sort the return value to ensure deterministic behavior.
* </p>
*
* 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.
*
* @return a list of resource contained in the tracked resources, or empty list if an exception was thrown while building the list.
*/
public List<Resource> list()
{
if (!isDirectory())
return List.of(); // empty

try (DirectoryStream<Path> dir = Files.newDirectoryStream(getPath()))
{
List<Resource> entries = new ArrayList<>();
for (Path entry : dir)
{
Path fn = entry.getFileName();
if (fn == null) // we have a no-segment path (eg: "/")
entries.add(this);
else
entries.add(new PathResource(entry));
}
return entries;
}
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();
Path abs = path;
// If a "jar:file:" based path, we should normalize here, as the toAbsolutePath() does not resolve "/../" style segments in all cases
if ("jar".equalsIgnoreCase(path.toUri().getScheme()))
abs = path.normalize();
// Get the absolute path
abs = abs.toAbsolutePath();
return abs.toString();
}

@Override
public String getFileName()
{
Path fn = path.getFileName();
if (fn == null) // if path has no segments (eg "/")
return "";
return fn.toString();
}

@Override
Expand Down
Loading

0 comments on commit 237f2f4

Please sign in to comment.