make auto-index better
authorespie <espie@openbsd.org>
Thu, 4 Jan 2024 18:17:47 +0000 (18:17 +0000)
committerespie <espie@openbsd.org>
Thu, 4 Jan 2024 18:17:47 +0000 (18:17 +0000)
- make it an actual table
- use "human readable sizes" for the file sizes
- add some decoration and javascript to be able to sort it per-column
(client side) (this means some extra column attribute)
- add glue to facilitate embedding js + css directly in the program
- add some graphical indication for directories
- should still validate as proper html everywhere (custom properties
need to be called data-* for this!)

Work with claudio@ and tb@, many thanks to claudio@ for some of the finer
points of css handling, and tb@ for some fine spaces fixes.

I've tried it with lynx as well, shows up correctly.

One big plus is that the size of columns work as utf-8, so you can expose
filenames without any problems (I've tried it with non-js text navigators
as well as firefox, chromium and friends)

And it looks slightly less yahoo ca. 1995.

It's still "one size fits all". If people object to the current look, adding
httpd.conf(5) properties to override the default css should be easy.

okay claudio@, tb@

usr.sbin/httpd/Makefile
usr.sbin/httpd/css.h.in [new file with mode: 0644]
usr.sbin/httpd/js.h.in [new file with mode: 0644]
usr.sbin/httpd/server_file.c
usr.sbin/httpd/toheader.sed [new file with mode: 0644]

index 3766675..4ddf615 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.30 2017/07/03 22:21:47 espie Exp $
+#      $OpenBSD: Makefile,v 1.31 2024/01/04 18:17:47 espie Exp $
 
 PROG=          httpd
 SRCS=          parse.y
@@ -12,11 +12,20 @@ MAN+=               patterns.7
 LDADD=         -levent -ltls -lssl -lcrypto -lutil
 DPADD=         ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL}
 #DEBUG=                -g -DDEBUG=3 -O0
-CFLAGS+=       -Wall -I${.CURDIR}
+CFLAGS+=       -Wall -I${.CURDIR} -I${.OBJDIR}
 CFLAGS+=       -Wstrict-prototypes -Wmissing-prototypes
 CFLAGS+=       -Wmissing-declarations
 CFLAGS+=       -Wshadow -Wpointer-arith
 CFLAGS+=       -Wsign-compare -Wcast-qual
 YFLAGS=
 
+.for h in css.h js.h
+$h: $h.in
+       sed -f ${.CURDIR}/toheader.sed <${.CURDIR}/$h.in >$@.tmp && mv $@.tmp $@
+.endfor
+
+server_file.o: css.h js.h
+
+CLEANFILES += css.h js.h
+
 .include <bsd.prog.mk>
diff --git a/usr.sbin/httpd/css.h.in b/usr.sbin/httpd/css.h.in
new file mode 100644 (file)
index 0000000..96ec89f
--- /dev/null
@@ -0,0 +1,34 @@
+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; }
+}
diff --git a/usr.sbin/httpd/js.h.in b/usr.sbin/httpd/js.h.in
new file mode 100644 (file)
index 0000000..8d0ea22
--- /dev/null
@@ -0,0 +1,18 @@
+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) );
+})))
index 582ff9c..61d9da7 100644 (file)
@@ -1,4 +1,4 @@
-/*     $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))
@@ -486,15 +489,16 @@ server_file_index(struct httpd *env, struct client *clt)
        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;
@@ -522,26 +526,22 @@ server_file_index(struct httpd *env, struct client *clt)
        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;
        }
@@ -569,7 +569,6 @@ server_file_index(struct httpd *env, struct client *clt)
                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;
@@ -584,20 +583,24 @@ server_file_index(struct httpd *env, struct client *clt)
                }
 
                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);
@@ -608,7 +611,9 @@ server_file_index(struct httpd *env, struct client *clt)
 
        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);
diff --git a/usr.sbin/httpd/toheader.sed b/usr.sbin/httpd/toheader.sed
new file mode 100644 (file)
index 0000000..37b1ec0
--- /dev/null
@@ -0,0 +1,10 @@
+# first line of input is the variable declaration, don't touch that
+2,$ {  
+# XXX beware of the order ! we have to quote \ and " before inserting \n"
+       s/\\/\\\\/g
+       s/"/\\"/g
+       s/^/    "/
+       s/$/\\n"/
+}
+# and append a ; at the end !
+$s/$/;/