--- /dev/null
+static const char *css =
+body {
+ background-color: white;
+ color: black;
+ font-family: sans-serif;
+}
+table {
+ border-collapse: collapse;
+ border: 1px solid;
+}
+tr.sort th {
+ border-bottom: 1px solid;
+ font-weight: normal;
+ text-decoration: underline;
+ cursor: pointer;
+}
+tr.sort th.sorted { font-weight: bold; }
+tr.sort th::after { content: "\a0\2195"; }
+tr.dir td:nth-child(2n+1) {
+ font-weight: bold;
+ font-style: italic;
+}
+td, th { padding: 2pt 2em; }
+td:first-child, th:first-child { padding-left: 5pt; }
+td:last-child, th:last-child { padding-right: 5pt; }
+td:nth-child(n+2) { text-align: end; }
+thead { text-align: left; }
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #1E1F21;
+ color: #EEEFF1;
+ }
+ a { color: #BAD7FF; }
+}
--- /dev/null
+static const char *js =
+const rowValue = (tr, idx) => tr.children[idx].getAttribute('data-o') || tr.children[idx].innerText || tr.children[idx].textContent;
+
+const compare = (idx, asc) => (a, b) => ((v1, v2) =>
+ v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
+ )(rowValue(asc ? a : b, idx), rowValue(asc ? b : a, idx));
+
+// set up the listener
+document.querySelectorAll('tr.sort th').forEach(th => th.addEventListener('click', (() => {
+ const table = th.closest('table');
+ // make the sorted column bold
+ table.querySelectorAll('tr.sort th').forEach(th2 =>
+ th2.className = th2 == th ? 'sorted' : 'unsorted');
+ const body = table.querySelector('tbody');
+ Array.from(body.querySelectorAll('tr'))
+ .sort(compare(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
+ .forEach(tr => body.appendChild(tr) );
+})))
-/* $OpenBSD: server_file.c,v 1.76 2023/12/28 18:05:32 espie Exp $ */
+/* $OpenBSD: server_file.c,v 1.77 2024/01/04 18:17:47 espie Exp $ */
/*
* Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org>
#include <dirent.h>
#include <time.h>
#include <event.h>
+#include <util.h>
#include "httpd.h"
#include "http.h"
+#include "css.h"
+#include "js.h"
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
struct http_descriptor *desc = clt->clt_descreq;
struct server_config *srv_conf = clt->clt_srv_conf;
struct dirent **namelist, *dp;
- int namesize, i, ret, fd = -1, namewidth, skip;
+ int namesize, i, ret, fd = -1, skip;
int code = 500;
struct evbuffer *evb = NULL;
struct media_type *media;
- const char *stripped, *style;
+ const char *stripped;
char *escapeduri, *escapedhtml, *escapedpath;
struct tm tm;
struct stat st;
time_t t, dir_mtime;
+ char human_size[FMT_SCALED_STRSIZE];
if ((ret = server_file_method(clt)) != 0) {
code = ret;
if ((escapedpath = escape_html(desc->http_path)) == NULL)
goto abort;
- /* A CSS stylesheet allows minimal customization by the user */
- style = "body { background-color: white; color: black; font-family: "
- "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"
- "@media (prefers-color-scheme: dark) {\n"
- "body { background-color: #1E1F21; color: #EEEFF1; }\n"
- "a { color: #BAD7FF; }\n}";
-
/* Generate simple HTML index document */
if (evbuffer_add_printf(evb,
"<!DOCTYPE html>\n"
- "<html>\n"
+ "<html lang=\"en\">\n"
"<head>\n"
"<meta charset=\"utf-8\">\n"
"<title>Index of %s</title>\n"
- "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "<style><!--\n%s--></style>\n"
"</head>\n"
"<body>\n"
"<h1>Index of %s</h1>\n"
- "<hr>\n<pre>\n",
- escapedpath, style, escapedpath) == -1) {
+ "<table><thead>\n"
+ "<tr class=\"sort\"><th class=\"sorted\">Name</th>\n"
+ " <th>Date</th><th>Size</th></tr>\n"
+ "</thead><tbody>\n",
+ escapedpath, css, escapedpath) == -1) {
free(escapedpath);
goto abort;
}
t = subst.st_mtime;
localtime_r(&t, &tm);
strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
- namewidth = 51 - strlen(dp->d_name);
if ((escapeduri = url_encode(dp->d_name)) == NULL) {
skip = 1;
}
if (S_ISDIR(subst.st_mode)) {
- namewidth -= 1; /* trailing slash */
if (evbuffer_add_printf(evb,
- "<a href=\"%s%s/\">%s/</a>%*s%s%20s\n",
+ "<tr class=\"dir\">"
+ "<td><a href=\"%s%s/\">%s/</a></td>\n"
+ " <td data-o=\"%lld\">%s</td><td>%s</td></tr>\n",
strchr(escapeduri, ':') != NULL ? "./" : "",
- escapeduri, escapedhtml,
- MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1)
+ escapeduri, escapedhtml,
+ (long long)t, tmstr, "-") == -1)
skip = 1;
} else if (S_ISREG(subst.st_mode)) {
- if (evbuffer_add_printf(evb,
- "<a href=\"%s%s\">%s</a>%*s%s%20llu\n",
+ if ((fmt_scaled(subst.st_size, human_size) != 0) ||
+ (evbuffer_add_printf(evb,
+ "<tr><td><a href=\"%s%s\">%s</a></td>\n"
+ " <td data-o=\"%lld\">%s</td>"
+ "<td data-o=\"%llu\">%s</td></tr>\n",
strchr(escapeduri, ':') != NULL ? "./" : "",
escapeduri, escapedhtml,
- MAXIMUM(namewidth, 0), " ",
- tmstr, subst.st_size) == -1)
+ (long long)t, tmstr,
+ subst.st_size, human_size) == -1))
skip = 1;
}
free(escapeduri);
if (skip ||
evbuffer_add_printf(evb,
- "</pre>\n<hr>\n</body>\n</html>\n") == -1)
+ "</tbody></table>\n<script>\n"
+ "%s\n"
+ "</script>\n</body>\n</html>\n", js) == -1)
goto abort;
close(fd);