src/server/safs/service.c

changeset 596
dc7cfde0f3bc
parent 584
f3ddd6dc8e7b
child 597
ca54033c7db1
--- a/src/server/safs/service.c	Fri May 30 12:18:15 2025 +0200
+++ b/src/server/safs/service.c	Fri May 30 14:11:56 2025 +0200
@@ -40,6 +40,7 @@
 
 #include "../util/strbuf.h"
 #include <cx/string.h>
+#include <cx/linked_list.h>
 #include <cx/printf.h>
 
 #include <errno.h>
@@ -715,14 +716,114 @@
     return http_handle_websocket(sn, rq, &ws);
 }
 
+
+// TODO: maybe move these util functions to another file or library
+static char* util_size_str2(const CxAllocator *a, WSBool iscollection, uint64_t contentlength, uint64_t dimension, int precision) {
+    char *str = cxMalloc(a, 16);
+    uint64_t size = contentlength;
+
+    if(iscollection) {
+        str[0] = '\0'; // currently no information for collections
+    } else if(dimension < 0x400) {
+        snprintf(str, 16, "%" PRIu64 " bytes", size);
+    } else if(dimension < 0x100000) {
+        float s = (float)size/0x400;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0x2800 && diff != 0) {
+            // size < 10 KiB
+            snprintf(str, 16, "%.*f KiB", precision, s);
+        } else {
+            snprintf(str, 16, "%.0f KiB", s);
+        }
+    } else if(dimension < 0x40000000) {
+        float s = (float)size/0x100000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0xa00000 && diff != 0) {
+            // size < 10 MiB
+            snprintf(str, 16, "%.*f MiB", precision, s);
+        } else {
+            size /= 0x100000;
+            snprintf(str, 16, "%.0f MiB", s);
+        }
+    } else if(dimension < 0x1000000000ULL) {
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0x280000000 && diff != 0) {
+            // size < 10 GiB
+            snprintf(str, 16, "%.*f GiB", precision, s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f GiB", s);
+        }
+    } else {
+        size /= 1024;
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(dimension < 0x280000000 && diff != 0) {
+            // size < 10 TiB
+            snprintf(str, 16, "%.*f TiB", precision, s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f TiB", s);
+        }
+    }
+    return str;
+}
+
+static char* util_size_str(const CxAllocator *a, WSBool iscollection, uint64_t contentlength) {
+    return util_size_str2(a, iscollection, contentlength, contentlength, 1);
+}
+
+static char* util_date_str(const CxAllocator *a, time_t tm) {
+    struct tm t;
+    struct tm n;
+    time_t now = time(NULL);
+#ifdef _WIN32
+    memcpy(&t, localtime(&tm), sizeof(struct tm));
+    memcpy(&n, localtime(&now), sizeof(struct tm));
+#else
+    localtime_r(&tm, &t);
+    localtime_r(&now, &n);
+#endif /* _WIN32 */
+    char *str = cxMalloc(a, 16);
+    if(t.tm_year == n.tm_year) {
+        strftime(str, 16, "%b %d %H:%M", &t);
+    } else {
+        strftime(str, 16, "%b %d  %Y", &t);
+    }
+    return str;
+}
+
+static int cmp_file_type_name(IndexEntry *a, IndexEntry *b) {
+    if(a->isdir != b->isdir) {
+        return a->isdir ? -1 : 1;
+    }
+    return strcmp(a->name, b->name);
+}
+
 int service_index(pblock *pb, Session *sn, Request *rq) {
     //printf("service_index\n");
+    const CxAllocator *a = pool_allocator(sn->pool);
 
     char *path = pblock_findkeyval(pb_key_path, rq->vars);
     char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
 
-    cxstring r_uri = cx_str(uri);
-
     // open the file
     VFSContext *vfs = vfs_request_context(sn, rq);
     VFS_DIR dir = vfs_opendir(vfs, path);
@@ -733,41 +834,102 @@
     sbuf_t *out = sbuf_new(1024); // output buffer
 
     // write html header
-    sbuf_puts(out, "<html>\n<head>\n<title>Index of ");
+    sbuf_puts(out, "<!DOCTYPE html>\n<html>\n<head>\n<title>Index of ");
     sbuf_puts(out, uri);
-    sbuf_puts(out, "</title>\n</head><body>\n<h1>Index of ");
+    sbuf_puts(out, "</title>\n");
+    sbuf_puts(out, "<style>\n");
+    sbuf_puts(out, "th { text-align: left; }\n");
+    sbuf_puts(out, "td { padding-right: 1em; }\n");
+    sbuf_puts(out, "</style>\n");
+    sbuf_puts(out, "</head><body>\n<h1>Index of ");
     sbuf_puts(out, uri);
     sbuf_puts(out, "</h1><hr>\n\n");
-
-    // list directory
-    VFS_ENTRY f;
-    while(vfs_readdir(dir, &f)) {
-        cxstring filename = cx_str(f.name);
-
-        sbuf_puts(out, "<a href=\"");
-        sbuf_append(out, r_uri);
-        sbuf_append(out, filename);
-        sbuf_puts(out, "\">");
-        sbuf_append(out, filename);
-        sbuf_puts(out, "</a><br>\n");
+    
+    CxList *files = cxLinkedListCreate(a, (cx_compare_func)cmp_file_type_name, sizeof(IndexEntry));
+    if(!files) {
+        return REQ_ABORTED;
     }
 
-    sbuf_puts(out, "\n</body>\n</html>\n");
+    // read directory at store entries in the files list
+    int ret = REQ_PROCEED;
+    VFS_ENTRY f;
+    while(vfs_readdir_stat(dir, &f)) {
+        IndexEntry entry;
+        entry.name = pool_strdup(sn->pool, f.name);
+        if(!entry.name) {
+            ret = REQ_ABORTED;
+            break;
+        }
+        if(f.stat_errno == 0) {
+            entry.isdir = S_ISDIR(f.stat.st_mode);
+            entry.size = (size_t)f.stat.st_size;
+            entry.size_str = util_size_str(a, entry.isdir, entry.size);
+            entry.lastmodified = util_date_str(a, f.stat.st_mtime);
+        } else {
+            entry.isdir = 0;
+            entry.lastmodified = NULL;
+            entry.size_str = NULL;
+            entry.size = 0;
+        }
+        
+        if(cxListAdd(files, &entry)) {
+            ret = REQ_ABORTED;
+            break;
+        }
+    }
+    
+    // generate html output
+    sbuf_puts(out, "<table>\n<tr><th>Type</th><th>Name</th><th>Size</th><th>Last Modified</th></tr>\n");
+    cxListSort(files);
+    CxIterator i = cxListIterator(files);
+    cx_foreach(IndexEntry *, entry, i) {
+        sbuf_puts(out, "<tr>\n");
+        
+        sbuf_puts(out, "<td>");
+        sbuf_puts(out, entry->isdir ? "[DIR]" : "[FILE]");
+        sbuf_puts(out, "</td>");
+        
+        sbuf_puts(out, "<td>");
+        sbuf_puts(out, "<a href=\"");
+        sbuf_puts(out, entry->name);
+        sbuf_puts(out, "\">");
+        sbuf_puts(out, entry->name);
+        sbuf_puts(out, "</a>");
+        sbuf_puts(out, "</td>");
+        
+        sbuf_puts(out, "<td>");
+        if(entry->size_str) {
+            sbuf_puts(out, entry->size_str);
+        }
+        sbuf_puts(out, "</td>");
+        
+        sbuf_puts(out, "<td>");
+        if(entry->size_str) {
+            sbuf_puts(out, entry->lastmodified);
+        }
+        sbuf_puts(out, "</td>");
+        
+        sbuf_puts(out, "</tr>\n");
+    }
+    
+    sbuf_puts(out, "</table>\n</body>\n</html>\n");
 
     // send stuff to client
-    pblock_removekey(pb_key_content_type, rq->srvhdrs);
-    pblock_kvinsert(pb_key_content_type, "text/html", 9, rq->srvhdrs);
-    pblock_nninsert("content-length", out->length, rq->srvhdrs);
-    protocol_status(sn, rq, 200, NULL);
-    http_start_response(sn, rq);
+    if(ret == REQ_PROCEED) {
+        pblock_removekey(pb_key_content_type, rq->srvhdrs);
+        pblock_kvinsert(pb_key_content_type, "text/html; charset=utf-8", 24, rq->srvhdrs);
+        pblock_nninsert("content-length", out->length, rq->srvhdrs);
+        protocol_status(sn, rq, 200, NULL);
+        http_start_response(sn, rq);
 
-    net_write(sn->csd, out->ptr, out->length);
+        net_write(sn->csd, out->ptr, out->length);
+    }
 
     // close
     vfs_closedir(dir);
     sbuf_free(out);
 
-    return REQ_PROCEED;
+    return ret;
 }
 
 int send_options(pblock *pb, Session *sn, Request *rq) {

mercurial