From 89849e50d18d9b942a657d8db1277765e1981b3a Mon Sep 17 00:00:00 2001 From: xjusko Date: Tue, 3 Sep 2024 17:13:24 +0200 Subject: [PATCH] [UNDERTOW-2404] Add default sorting by type and name in directory listing view; enable clickable name and size column headers for custom sorting --- .../handlers/resource/DirectoryUtils.java | 174 ++++++++++++++---- .../file/FileHandlerIndexTestCase.java | 5 +- 2 files changed, 140 insertions(+), 39 deletions(-) diff --git a/core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java b/core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java index 9fd3212090..cfdc0523f7 100644 --- a/core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java +++ b/core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java @@ -39,8 +39,14 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -109,32 +115,72 @@ public static StringBuilder renderDirectoryListing(final HttpServerExchange exch path += "/"; } - String relative = null; + String relative = determineRelativePath(exchange, path); + + String sortColumn = "name"; + String currentSortOrder = "asc"; + if (exchange != null) { - final Map context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT); - if (context != null) { - final PathPrefixPredicate.PathPrefixMatchRecord trans = (PathPrefixMatchRecord) context - .get(PathPrefixPredicate.PREFIX_MATCH_RECORD); - if (trans != null) { - if (trans.isOverWritten()) { - relative = trans.getPrefix(); - if (!relative.endsWith("/") && !path.startsWith("/")) { - relative += "/"; - } - } - } + if (exchange.getQueryParameters().get("sort") != null) { + sortColumn = exchange.getQueryParameters().get("sort").getFirst(); + } + if (exchange.getQueryParameters().get("order") != null) { + currentSortOrder = exchange.getQueryParameters().get("order").getFirst(); } } - StringBuilder builder = new StringBuilder(); - builder.append("\n\n\n") - .append("\n\n"); - builder.append("\n\n\n"); - builder.append("\n") - .append("\n\n") - .append("\n\n\n\n"); + String newSortOrder = "asc".equals(currentSortOrder) ? "desc" : "asc"; + String sortUrl = relative == null ? path : relative + path; + + StringBuilder builder = buildDirectoryListingTable(sortUrl, sortColumn, newSortOrder); int state = 0; + String parent = getParentPath(path, state); + + int i = 0; + if (parent != null) { + i++; + appendParentDirectory(resource, builder, relative, parent); + } + + List directories = new ArrayList<>(); + List files = new ArrayList<>(); + separateDirectoriesAndFiles(resource, directories, files); + + Comparator comparator = getComparator(sortColumn, currentSortOrder); + directories.sort(comparator); + files.sort(comparator); + + appendDirectories(directories, builder, i, sortUrl); + appendFiles(files, builder, i, sortUrl); + + builder.append("\n
Directory Listing - ").append(relative == null ? path : relative + path).append("
NameLast ModifiedSize
Powered by Undertow
\n\n"); + + return builder; + + } + + private static String formatLastModified(Date lastModified) { + if (lastModified == null) { + return "-"; + } + ZonedDateTime lastModifiedTime = ZonedDateTime.ofInstant( + lastModified.toInstant(), + ZoneId.systemDefault() + ); + DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) + .withLocale(Locale.getDefault()); + + return formatter.format(lastModifiedTime); + } + + private static void appendParentDirectory(Resource resource, StringBuilder builder, String relative, String parent) { + builder.append("[..]"); + builder.append(formatLastModified(resource.getLastModified())) + .append("--\n"); + } + + private static String getParentPath(String path, int state) { String parent = null; if(path.length() > 1) { for (int i = path.length() - 1; i >= 0; i--) { @@ -154,33 +200,89 @@ public static StringBuilder renderDirectoryListing(final HttpServerExchange exch parent = "/"; } } + return parent; + } - SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US); - int i = 0; - if (parent != null) { - i++; - builder.append("[..]"); - builder.append(format.format((resource.getLastModified() == null ? new Date(0L) : resource.getLastModified()))) + private static void appendFiles(List files, StringBuilder builder, int i, String sortUrl) { + for (Resource entry : files) { + builder.append("") + .append(entry.getName()).append("") + .append(formatLastModified(entry.getLastModified())) + .append(""); + formatSize(builder, entry.getContentLength()); + builder.append("\n"); + } + } + + private static void appendDirectories(List directories, StringBuilder builder, int i, String sortUrl) { + for (Resource entry : directories) { + builder.append("") + .append(entry.getName()).append("") + .append(formatLastModified(entry.getLastModified())) .append("--\n"); } + } + private static Comparator getComparator(String sortColumn, String currentSortOrder) { + Comparator comparator; + if ("lastModified".equals(sortColumn)) { + comparator = Comparator.comparing( + entry -> (entry.getLastModified() == null) ? new Date(0L) : entry.getLastModified() + ); + } else { + comparator = Comparator.comparing(Resource::getName); + } + + if ("desc".equals(currentSortOrder)) { + comparator = comparator.reversed(); + } + return comparator; + } + + private static void separateDirectoriesAndFiles(Resource resource, List directories, List files) { for (Resource entry : resource.list()) { - builder.append("").append(entry.getName()).append(""); - builder.append(format.format((entry.getLastModified() == null) ? new Date(0L) : entry.getLastModified())) - .append(""); if (entry.isDirectory()) { - builder.append("--"); + directories.add(entry); } else { - formatSize(builder, entry.getContentLength()); + files.add(entry); } - builder.append("\n"); } - builder.append("\n\n\n"); + } + private static StringBuilder buildDirectoryListingTable(String sortUrl, String sortColumn, String newSortOrder) { + StringBuilder builder = new StringBuilder(); + builder.append("\n\n\n") + .append("\n\n"); + builder.append("\n\n\n"); + builder.append("\n") + .append("") + .append("") + .append("") + .append("\n\n"); + builder.append("\n\n\n\n"); return builder; + } + private static String determineRelativePath(HttpServerExchange exchange, String path) { + String relative = null; + if (exchange != null) { + final Map context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT); + if (context != null) { + final PathPrefixMatchRecord trans = (PathPrefixMatchRecord) context + .get(PathPrefixPredicate.PREFIX_MATCH_RECORD); + if (trans != null) { + if (trans.isOverWritten()) { + relative = trans.getPrefix(); + if (!relative.endsWith("/") && !path.startsWith("/")) { + relative += "/"; + } + } + } + } + } + return relative; } public static void renderDirectoryListing(HttpServerExchange exchange, Resource resource) { diff --git a/core/src/test/java/io/undertow/server/handlers/file/FileHandlerIndexTestCase.java b/core/src/test/java/io/undertow/server/handlers/file/FileHandlerIndexTestCase.java index 101aea2138..3de1f1ab9f 100644 --- a/core/src/test/java/io/undertow/server/handlers/file/FileHandlerIndexTestCase.java +++ b/core/src/test/java/io/undertow/server/handlers/file/FileHandlerIndexTestCase.java @@ -24,7 +24,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Locale; import io.undertow.server.handlers.CanonicalPathHandler; @@ -103,9 +102,9 @@ public void testDirectoryIndex() throws IOException, URISyntaxException { Assert.assertEquals("text/html; charset=UTF-8", headers[0].getValue()); Assert.assertTrue(response, response.contains("page.html")); Assert.assertTrue(response, response.contains("tmp2")); - // All invalid symlinks have their date set to epoch + // All invalid symlinks have their date set to "-" SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US); - Assert.assertTrue(response, response.contains(format.format((new Date(0L))))); + Assert.assertTrue(response, response.contains("-")); } finally { client.getConnectionManager().shutdown(); if (badSymlink != null) {
Directory Listing - ").append(sortUrl).append("
NameLast ModifiedSize
Powered by Undertow