Skip to content

Commit 4f308ec

Browse files
authored
Merge pull request #1660 from xjusko/UNDERTOW-2404
[UNDERTOW-2404] Add default sorting by type and name in directory lis…
2 parents 3dc476d + 89849e5 commit 4f308ec

File tree

2 files changed

+140
-39
lines changed

2 files changed

+140
-39
lines changed

core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java

Lines changed: 138 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@
3939
import java.nio.charset.StandardCharsets;
4040
import java.security.MessageDigest;
4141
import java.security.NoSuchAlgorithmException;
42-
import java.text.SimpleDateFormat;
42+
import java.time.ZoneId;
43+
import java.time.ZonedDateTime;
44+
import java.time.format.DateTimeFormatter;
45+
import java.time.format.FormatStyle;
46+
import java.util.ArrayList;
47+
import java.util.Comparator;
4348
import java.util.Date;
49+
import java.util.List;
4450
import java.util.Locale;
4551
import java.util.Map;
4652

@@ -109,32 +115,72 @@ public static StringBuilder renderDirectoryListing(final HttpServerExchange exch
109115
path += "/";
110116
}
111117

112-
String relative = null;
118+
String relative = determineRelativePath(exchange, path);
119+
120+
String sortColumn = "name";
121+
String currentSortOrder = "asc";
122+
113123
if (exchange != null) {
114-
final Map<String, Object> context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT);
115-
if (context != null) {
116-
final PathPrefixPredicate.PathPrefixMatchRecord trans = (PathPrefixMatchRecord) context
117-
.get(PathPrefixPredicate.PREFIX_MATCH_RECORD);
118-
if (trans != null) {
119-
if (trans.isOverWritten()) {
120-
relative = trans.getPrefix();
121-
if (!relative.endsWith("/") && !path.startsWith("/")) {
122-
relative += "/";
123-
}
124-
}
125-
}
124+
if (exchange.getQueryParameters().get("sort") != null) {
125+
sortColumn = exchange.getQueryParameters().get("sort").getFirst();
126+
}
127+
if (exchange.getQueryParameters().get("order") != null) {
128+
currentSortOrder = exchange.getQueryParameters().get("order").getFirst();
126129
}
127130
}
128131

129-
StringBuilder builder = new StringBuilder();
130-
builder.append("<html>\n<head>\n<script src='").append(relative == null ? path : relative + path).append("?js'></script>\n")
131-
.append("<link rel='stylesheet' type='text/css' href='").append(relative == null ? path : relative + path).append("?css' />\n</head>\n");
132-
builder.append("<body onresize='growit()' onload='growit()'>\n<table id='thetable'>\n<thead>\n");
133-
builder.append("<tr><th class='loc' colspan='3'>Directory Listing - ").append(relative == null ? path : relative + path).append("</th></tr>\n")
134-
.append("<tr><th class='label offset'>Name</th><th class='label'>Last Modified</th><th class='label'>Size</th></tr>\n</thead>\n")
135-
.append("<tfoot>\n<tr><th class=\"loc footer\" colspan=\"3\">Powered by Undertow</th></tr>\n</tfoot>\n<tbody>\n");
132+
String newSortOrder = "asc".equals(currentSortOrder) ? "desc" : "asc";
133+
String sortUrl = relative == null ? path : relative + path;
134+
135+
StringBuilder builder = buildDirectoryListingTable(sortUrl, sortColumn, newSortOrder);
136136

137137
int state = 0;
138+
String parent = getParentPath(path, state);
139+
140+
int i = 0;
141+
if (parent != null) {
142+
i++;
143+
appendParentDirectory(resource, builder, relative, parent);
144+
}
145+
146+
List<Resource> directories = new ArrayList<>();
147+
List<Resource> files = new ArrayList<>();
148+
separateDirectoriesAndFiles(resource, directories, files);
149+
150+
Comparator<Resource> comparator = getComparator(sortColumn, currentSortOrder);
151+
directories.sort(comparator);
152+
files.sort(comparator);
153+
154+
appendDirectories(directories, builder, i, sortUrl);
155+
appendFiles(files, builder, i, sortUrl);
156+
157+
builder.append("</tbody>\n</table>\n</body>\n</html>");
158+
159+
return builder;
160+
161+
}
162+
163+
private static String formatLastModified(Date lastModified) {
164+
if (lastModified == null) {
165+
return "-";
166+
}
167+
ZonedDateTime lastModifiedTime = ZonedDateTime.ofInstant(
168+
lastModified.toInstant(),
169+
ZoneId.systemDefault()
170+
);
171+
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
172+
.withLocale(Locale.getDefault());
173+
174+
return formatter.format(lastModifiedTime);
175+
}
176+
177+
private static void appendParentDirectory(Resource resource, StringBuilder builder, String relative, String parent) {
178+
builder.append("<tr class='odd'><td><a class='icon up' href='").append(relative == null ? parent : relative + parent).append(parent.endsWith("/") ? "" : "/").append("'>[..]</a></td><td>");
179+
builder.append(formatLastModified(resource.getLastModified()))
180+
.append("</td><td>--</td></tr>\n");
181+
}
182+
183+
private static String getParentPath(String path, int state) {
138184
String parent = null;
139185
if(path.length() > 1) {
140186
for (int i = path.length() - 1; i >= 0; i--) {
@@ -154,33 +200,89 @@ public static StringBuilder renderDirectoryListing(final HttpServerExchange exch
154200
parent = "/";
155201
}
156202
}
203+
return parent;
204+
}
157205

158-
SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US);
159-
int i = 0;
160-
if (parent != null) {
161-
i++;
162-
builder.append("<tr class='odd'><td><a class='icon up' href='").append(relative == null ? parent : relative + parent).append(parent.endsWith("/") ? "" : "/").append("'>[..]</a></td><td>");
163-
builder.append(format.format((resource.getLastModified() == null ? new Date(0L) : resource.getLastModified())))
206+
private static void appendFiles(List<Resource> files, StringBuilder builder, int i, String sortUrl) {
207+
for (Resource entry : files) {
208+
builder.append("<tr class='").append((++i & 1) == 1 ? "odd" : "even").append("'><td><a class='icon file' href='")
209+
.append(sortUrl).append(entry.getName()).append("'>")
210+
.append(entry.getName()).append("</a></td><td>")
211+
.append(formatLastModified(entry.getLastModified()))
212+
.append("</td><td>");
213+
formatSize(builder, entry.getContentLength());
214+
builder.append("</td></tr>\n");
215+
}
216+
}
217+
218+
private static void appendDirectories(List<Resource> directories, StringBuilder builder, int i, String sortUrl) {
219+
for (Resource entry : directories) {
220+
builder.append("<tr class='").append((++i & 1) == 1 ? "odd" : "even").append("'><td><a class='icon dir' href='")
221+
.append(sortUrl).append(entry.getName()).append("/'>")
222+
.append(entry.getName()).append("</a></td><td>")
223+
.append(formatLastModified(entry.getLastModified()))
164224
.append("</td><td>--</td></tr>\n");
165225
}
226+
}
166227

228+
private static Comparator<Resource> getComparator(String sortColumn, String currentSortOrder) {
229+
Comparator<Resource> comparator;
230+
if ("lastModified".equals(sortColumn)) {
231+
comparator = Comparator.comparing(
232+
entry -> (entry.getLastModified() == null) ? new Date(0L) : entry.getLastModified()
233+
);
234+
} else {
235+
comparator = Comparator.comparing(Resource::getName);
236+
}
237+
238+
if ("desc".equals(currentSortOrder)) {
239+
comparator = comparator.reversed();
240+
}
241+
return comparator;
242+
}
243+
244+
private static void separateDirectoriesAndFiles(Resource resource, List<Resource> directories, List<Resource> files) {
167245
for (Resource entry : resource.list()) {
168-
builder.append("<tr class='").append((++i & 1) == 1 ? "odd" : "even").append("'><td><a class='icon ");
169-
builder.append(entry.isDirectory() ? "dir" : "file");
170-
builder.append("' href='").append(relative == null ? path : relative + path).append(entry.getName()).append(entry.isDirectory() ? "/" : "").append("'>").append(entry.getName()).append("</a></td><td>");
171-
builder.append(format.format((entry.getLastModified() == null) ? new Date(0L) : entry.getLastModified()))
172-
.append("</td><td>");
173246
if (entry.isDirectory()) {
174-
builder.append("--");
247+
directories.add(entry);
175248
} else {
176-
formatSize(builder, entry.getContentLength());
249+
files.add(entry);
177250
}
178-
builder.append("</td></tr>\n");
179251
}
180-
builder.append("</tbody>\n</table>\n</body>\n</html>");
252+
}
181253

254+
private static StringBuilder buildDirectoryListingTable(String sortUrl, String sortColumn, String newSortOrder) {
255+
StringBuilder builder = new StringBuilder();
256+
builder.append("<html>\n<head>\n<script src='").append(sortUrl).append("?js'></script>\n")
257+
.append("<link rel='stylesheet' type='text/css' href='").append(sortUrl).append("?css' />\n</head>\n");
258+
builder.append("<body onresize='growit()' onload='growit()'>\n<table id='thetable'>\n<thead>\n");
259+
builder.append("<tr><th class='loc' colspan='3'>Directory Listing - ").append(sortUrl).append("</th></tr>\n")
260+
.append("<tr>")
261+
.append("<th class='label offset'><a href='").append(sortUrl).append("?sort=name&order=").append("name".equals(sortColumn) ? newSortOrder : "asc").append("'>Name</a></th>")
262+
.append("<th class='label'><a href='").append(sortUrl).append("?sort=lastModified&order=").append("lastModified".equals(sortColumn) ? newSortOrder : "asc").append("'>Last Modified</a></th>")
263+
.append("<th class='label'>Size</th></tr>\n</thead>\n");
264+
builder.append("<tfoot>\n<tr><th class=\"loc footer\" colspan=\"3\">Powered by Undertow</th></tr>\n</tfoot>\n<tbody>\n");
182265
return builder;
266+
}
183267

268+
private static String determineRelativePath(HttpServerExchange exchange, String path) {
269+
String relative = null;
270+
if (exchange != null) {
271+
final Map<String, Object> context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT);
272+
if (context != null) {
273+
final PathPrefixMatchRecord trans = (PathPrefixMatchRecord) context
274+
.get(PathPrefixPredicate.PREFIX_MATCH_RECORD);
275+
if (trans != null) {
276+
if (trans.isOverWritten()) {
277+
relative = trans.getPrefix();
278+
if (!relative.endsWith("/") && !path.startsWith("/")) {
279+
relative += "/";
280+
}
281+
}
282+
}
283+
}
284+
}
285+
return relative;
184286
}
185287

186288
public static void renderDirectoryListing(HttpServerExchange exchange, Resource resource) {

core/src/test/java/io/undertow/server/handlers/file/FileHandlerIndexTestCase.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.nio.file.Path;
2525
import java.nio.file.Paths;
2626
import java.text.SimpleDateFormat;
27-
import java.util.Date;
2827
import java.util.Locale;
2928

3029
import io.undertow.server.handlers.CanonicalPathHandler;
@@ -103,9 +102,9 @@ public void testDirectoryIndex() throws IOException, URISyntaxException {
103102
Assert.assertEquals("text/html; charset=UTF-8", headers[0].getValue());
104103
Assert.assertTrue(response, response.contains("page.html"));
105104
Assert.assertTrue(response, response.contains("tmp2"));
106-
// All invalid symlinks have their date set to epoch
105+
// All invalid symlinks have their date set to "-"
107106
SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US);
108-
Assert.assertTrue(response, response.contains(format.format((new Date(0L)))));
107+
Assert.assertTrue(response, response.contains("-"));
109108
} finally {
110109
client.getConnectionManager().shutdown();
111110
if (badSymlink != null) {

0 commit comments

Comments
 (0)