Sun, 23 Nov 2025 13:48:29 +0100
respect DirectoryIndex location setting in the service_index SAF
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2013 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <sys/file.h> #include <sys/stat.h> #include "service.h" #include "objecttype.h" #include "../util/io.h" #include "../util/pblock.h" #include "../util/util.h" #include "../daemon/protocol.h" #include "../daemon/vfs.h" #include "../daemon/httprequest.h" #include "../util/strbuf.h" #include <cx/string.h> #include <cx/linked_list.h> #include <cx/printf.h> #include <errno.h> /* * prepares servicing a file * * adds content-length header * * return the opened file */ SYS_FILE prepare_service_file(Session *sn, Request *rq, VFSContext *vfs, struct stat *s, int *ret) { char *path = pblock_findkeyval(pb_key_path, rq->vars); // open the file SYS_FILE fd = vfs_open(vfs, path, O_RDONLY); if(!fd) { // vfs_open sets http status code *ret = REQ_ABORTED; return NULL; } // get stat if(vfs_fstat(vfs, fd, s) != 0) { //perror("prepare_service_file: stat"); protocol_status(sn, rq, 500, NULL); *ret = REQ_ABORTED; return NULL; } // check if the file is a directory if(S_ISDIR(s->st_mode)) { pblock_removekey(pb_key_content_type, rq->srvhdrs); char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); size_t urilen = strlen(uri); if(urilen > 0 && uri[urilen-1] != '/') { pblock_nvinsert("content-length", "0", rq->srvhdrs); char *location = pool_malloc(sn->pool, urilen + 2); memcpy(location, uri, urilen); location[urilen] = '/'; location[urilen+1] = '\0'; pblock_kvinsert(pb_key_location, location, urilen + 1, rq->srvhdrs); protocol_status(sn, rq, 302, NULL); http_start_response(sn, rq); *ret = REQ_PROCEED; } else { // set content-type to "internal/directory" and ret to REQ_NOACTION // maybe a SAF will respond to that pblock_kvinsert( pb_key_content_type, OBJTYPE_INTERNAL_DIRECTORY, sizeof(OBJTYPE_INTERNAL_DIRECTORY)-1, rq->srvhdrs); *ret = REQ_NOACTION; } vfs_close(fd); return NULL; } // sets last-modified, content-length and checks conditions const char *etag = vfs_getetag(fd); // optionally, get etag from file if(http_set_finfo_etag(sn, rq, s, etag) != REQ_PROCEED) { vfs_close(fd); *ret = REQ_ABORTED; return NULL; } // TODO: check if vfs can seek pblock_kvinsert(pb_key_accept_ranges, "bytes", 5, rq->srvhdrs); // everything ok for now protocol_status(sn, rq, 200, NULL); return fd; } static void free_range(Session *sn, HttpRange *range) { HttpRange *elm = range; while(elm) { HttpRange *next = elm->next; pool_free(sn->pool, elm); elm = next; } } static HttpRange* parse_range(Session *sn, char *header, int *status) { *status = PROTOCOL_OK; cxstring range = cx_strtrim(cx_str(header)); if(!cx_strprefix(range, (cxstring)CX_STR("bytes="))) { // unknown range unit - ignore range header return NULL; } // get byte-range-set range = cx_strsubs(range, 6); if(range.length < 1) { return NULL; } HttpRange *range_list = NULL; HttpRange *last = NULL; off_t begin = -1; int start = 0; int hasbegin = 0; for(int i=0;i<=range.length;i++) { char c = range.ptr[i]; if(c == '-') { cxstring num = cx_strsubsl(range, start, i-start); if(num.length == 0) { // empty string before '-' is legal hasbegin = 1; begin = -1; start = i+1; continue; } char *end; errno = 0; long long n = strtoll(num.ptr, &end, 10); if(errno == 0 && end == range.ptr + i && n >= 0) { begin = n; hasbegin = 1; start = i+1; } else { // syntax error free_range(sn, range_list); return NULL; } } else if(c == ',' || c == '\0') { cxstring num = cx_strsubsl(range, start, i-start); if(hasbegin) { long long n; if(num.length == 0) { // empty string after '-' is legal n = -1; } else { char *end; errno = 0; n = strtoll(num.ptr, &end, 10); if(errno != 0 || end != range.ptr + i || n < 0) { // syntax error free_range(sn, range_list); return NULL; } } if(!(begin < 0 && n < 0)) { // range: begin - n HttpRange *rangeelm = pool_malloc(sn->pool, sizeof(HttpRange)); if(!rangeelm) { free_range(sn, range_list); *status = PROTOCOL_SERVER_ERROR; return NULL; } rangeelm->begin = begin; rangeelm->end = n; rangeelm->next = NULL; if(!last) { range_list = rangeelm; last = rangeelm; } else { last->next = rangeelm; last = rangeelm; } hasbegin = 0; start = i+1; continue; } } // syntax error free_range(sn, range_list); return NULL; } } return range_list; } static int validate_range(HttpRange *range, struct stat *finfo, int *status) { off_t max_len = finfo->st_size; while(range) { if(range->begin > 0 && range->end > 0) { if(range->end < range->begin) { *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; return 0; } } if(range->begin >= max_len) { *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; return 0; } if(range->end >= max_len) { *status = PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE; return 0; } range = range->next; } // TODO: check for Denial-of-Service Attacks return 1; } /* * translates a HttpRange element to a begin offset and a length * the HttpRange must be validated */ static void range2off(HttpRange *range, off_t filelen, off_t *begin, off_t *length) { if(range->begin < 0) { // bytes=-a *begin = filelen - range->end; *length = range->end; } else if(range->end < 0) { // bytes=a- *begin = range->begin; *length = filelen - range->begin; } else { // bytes=a-b *begin = range->begin; *length = range->end + 1 - range->begin; } } #define SF_MAX_LEN 0x8000000 static int send_range(Session *sn, SYS_FILE fd, off_t offset, off_t length, char *header, int headerlen) { off_t remaining = length; sendfiledata sfd; sfd.fd = fd; sfd.header = header; sfd.hlen = headerlen; sfd.trailer = NULL; sfd.tlen = 0; while(remaining > 0) { size_t sflen = remaining < SF_MAX_LEN ? remaining : SF_MAX_LEN; sfd.offset = offset; sfd.len = sflen; ssize_t r = net_sendfile(sn->csd, &sfd); if(r < 0) { return -1; } sfd.header = NULL; // make sure the header is only sent once offset += r; remaining -= r; } return 0; } static void send_range_cleanup(AsyncSendRange *asr) { WSBool error = asr->error; Session *sn = asr->sn; Request *rq = asr->rq; pool_handle_t *pool = asr->sn->pool; vfs_close(asr->in); pool_free(pool, asr->aio->buf); pool_free(pool, asr->aio); pool_free(pool, asr->readev); pool_free(pool, asr->writeev); pool_free(pool, asr); int ret = REQ_PROCEED; if(error) { rq->rq_attr.keep_alive = 0; ret = REQ_ABORTED; } // return to nsapi loop nsapi_function_return(sn, rq, ret); } static int send_buf( SYS_NETFD out, char *restrict buf, size_t len, size_t *restrict pos) { while(*pos < len) { ssize_t w = net_write(out, buf + *pos, len - *pos); if(w <= 0) { return -1; } *pos += w; } return 0; } static int send_bytes(AsyncSendRange *asr, WSBool *completed) { *completed = FALSE; if(asr->header) { if(send_buf(asr->out, asr->header, asr->headerlen, &asr->headerpos)) { if(net_errno(asr->out) == EAGAIN) { return 0; } else { asr->error = TRUE; return 1; } } if(asr->headerpos >= asr->headerlen) { asr->header = NULL; } } if(send_buf(asr->out, asr->aio->buf, asr->aio->result, &asr->wpos)) { if(net_errno(asr->out) == EAGAIN) { return 0; } else { asr->error = TRUE; return 1; } } if(!asr->read_complete) { // write completed => new asynchronous read asr->aio->offset += asr->aio->result; size_t length = asr->end - asr->offset; asr->aio->nbytes = AIO_BUF_SIZE < length ? AIO_BUF_SIZE : length; asr->read_inprogress = TRUE; if(system_aio_read(asr->aio)) { asr->error = TRUE; return 1; } } *completed = TRUE; return 0; } static int send_range_readevent(EventHandler *ev, Event *event) { AsyncSendRange *asr = event->cookie; asr->read_inprogress = FALSE; asr->wpos = 0; asr->offset += asr->aio->result; if(asr->error || asr->aio->result < 0) { return 0; } int ret = 1; if(asr->aio->result == 0 || asr->offset >= asr->end) { asr->read_complete = TRUE; ret = 0; } WSBool completed; if(send_bytes(asr, &completed)) { return 0; } if(!completed && !asr->write_inprogress) { asr->write_inprogress = TRUE; if(event_pollout(ev, asr->out, asr->writeev)) { asr->error = TRUE; return 0; } } return ret; } static int send_range_writeevent(EventHandler *ev, Event *event) { AsyncSendRange *asr = event->cookie; if(asr->error) { return 1; } WSBool completed; if(send_bytes(asr, &completed)) { return 1; } if(completed) { return 0; } return 1; } static int send_range_aio_finish(EventHandler *ev, Event *event) { AsyncSendRange *asr = event->cookie; if(!asr->write_inprogress) { send_range_cleanup(asr); } asr->read_inprogress = FALSE; return 0; } static int send_range_poll_finish(EventHandler *ev, Event *event) { AsyncSendRange *asr = event->cookie; if(!asr->read_inprogress) { send_range_cleanup(asr); } asr->write_inprogress = FALSE; return 0; } static int send_range_aio(Session *sn, Request *rq, SYS_FILE fd, off_t offset, off_t length, char *header, int headerlen) { net_setnonblock(sn->csd, TRUE); // try to send the header ssize_t hw = net_write(sn->csd, header, headerlen); if(hw < 0) { if(net_errno(sn->csd) == EAGAIN) { hw = 0; } else { return REQ_ABORTED; } } AsyncSendRange *asr = pool_malloc(sn->pool, sizeof(AsyncSendRange)); asr->sn = sn; asr->rq = rq; asr->in = fd; asr->out = sn->csd; asr->offset = offset; asr->end = offset + length; //asr->length = length; asr->pos = offset; asr->read_complete = FALSE; asr->read_inprogress = FALSE; asr->write_inprogress = FALSE; asr->error = FALSE; if(hw == headerlen) { asr->header = NULL; asr->headerlen = 0; asr->headerpos = 0; } else { asr->header = header; asr->headerlen = headerlen; asr->headerpos = hw; } Event *readev = pool_malloc(sn->pool, sizeof(Event)); ZERO(readev, sizeof(Event)); readev->cookie = asr; readev->fn = send_range_readevent; readev->finish = send_range_aio_finish; Event *writeev = pool_malloc(sn->pool, sizeof(Event)); ZERO(writeev, sizeof(Event)); writeev->cookie = asr; writeev->fn = send_range_writeevent; writeev->finish = send_range_poll_finish; asr->readev = readev; asr->writeev = writeev; aiocb_s *aio = pool_malloc(sn->pool, sizeof(aiocb_s)); aio->buf = pool_malloc(sn->pool, AIO_BUF_SIZE); aio->nbytes = AIO_BUF_SIZE < length ? AIO_BUF_SIZE : length; aio->filedes = fd; aio->offset = offset; aio->evhandler = sn->ev; aio->event = readev; asr->aio = aio; asr->wpos = 0; asr->read_inprogress = TRUE; if(system_aio_read(aio)) { send_range_cleanup(asr); return REQ_ABORTED; } asr->read_inprogress = TRUE; return REQ_PROCESSING; } struct multi_range_elm { cxmutstr header; off_t offset; off_t length; }; static int send_multi_range(Session *sn, Request *rq, SYS_FILE fd, off_t filelen, HttpRange *range) { CxAllocator *a = pool_allocator(sn->pool); pb_param *content_type = pblock_remove("content-type", rq->srvhdrs); char sep[64]; int seplen = util_mime_separator(sep); cxmutstr newct = cx_asprintf_a(a, "multipart/byteranges; boundary=%s", sep+4); pblock_kvinsert( pb_key_content_type, newct.ptr, newct.length, rq->srvhdrs); cxFree(a, newct.ptr); // calculate content-length off_t response_len = 0; int nrange = 0; HttpRange *rangeelm = range; while(rangeelm) { nrange++; rangeelm = rangeelm->next; } struct multi_range_elm *r = pool_calloc(sn->pool, nrange, sizeof(struct multi_range_elm)); rangeelm = range; int i=0; while(rangeelm) { range2off(rangeelm, filelen, &(r[i].offset), &(r[i].length)); r[i].header = cx_asprintf_a( a, "%s\r\nContent-Type: %s\r\nContent-Range: bytes %lld-%lld/%lld\r\n\r\n", sep, content_type->value, (long long)r[i].offset, (long long)r[i].offset+r[i].length - 1, (long long)filelen); response_len += r[i].header.length + r[i].length; rangeelm = rangeelm->next; i++; } response_len += seplen + 4; // trailer: sep + '--' + CRLF // finally, set the content-length header pblock_kllinsert( pb_key_content_length, (long long)response_len, rq->srvhdrs); // and start the response http_start_response(sn, rq); rangeelm = range; i = 0; while(rangeelm) { if(send_range(sn, fd, r[i].offset, r[i].length, r[i].header.ptr, r[i].header.length)) { // TODO: error } rangeelm = rangeelm->next; i++; } net_printf(sn->csd, "%s--\r\n", sep); pool_free(sn->pool, r); return 0; } int send_file(pblock *pb, Session *sn, Request *rq) { int ret = REQ_NOACTION; struct stat s; VFSContext *vfs = vfs_request_context(sn, rq); SYS_FILE fd = prepare_service_file(sn, rq, vfs, &s, &ret); if(!fd) { // if an error occurs, prepare_service_file sets the http status code // in case fd is a directory and the uri already ends with an trailing // '/', ret is set to REQ_NOACTION return ret; } // get and validate range header char *range_header = pblock_findkeyval(pb_key_range, rq->headers); HttpRange *range = NULL; if(range_header) { log_ereport(LOG_DEBUG, "send_file: range: %s", range_header); int status; range = parse_range(sn, range_header, &status); if(status != PROTOCOL_OK) { protocol_status(sn, rq, status, NULL); vfs_close(fd); return REQ_ABORTED; } if(!validate_range(range, &s, &status)) { protocol_status(sn, rq, status, NULL); free_range(sn, range); vfs_close(fd); return REQ_ABORTED; } } int single_range = 1; off_t offset; off_t length; if(range) { protocol_status(sn, rq, 206, NULL); pblock_removekey(pb_key_content_length, rq->srvhdrs); if(range->next) { single_range = 0; } else { range2off(range, s.st_size, &offset, &length); pblock_kllinsert( pb_key_content_length, (long long)length, rq->srvhdrs); cxmutstr content_range = cx_asprintf( "%lld-%lld/%lld", (long long)offset, (long long)offset+length - 1, (long long)s.st_size); pblock_kvinsert( pb_key_content_range, content_range.ptr, content_range.length, rq->srvhdrs); free(content_range.ptr); } } else { offset = 0; length = s.st_size; } if(single_range) { // send response header http_start_response(sn, rq); // send content // TODO: fix: send_range_aio is unstable #96 //ret = send_range_aio(sn, rq, fd, offset, length, NULL, 0); //if(ret == REQ_PROCESSING) { // return ret; //} if(send_range(sn, fd, offset, length, NULL, 0)) { // TODO: error } } else { ret = send_multi_range(sn, rq, fd, s.st_size, range); // TODO: error } // cleanup vfs_close(fd); free_range(sn, range); return ret; } int service_hello(pblock *pb, Session *sn, Request *rq) { pblock_removekey(pb_key_content_type, rq->srvhdrs); pblock_nvinsert("content-type", "text/plain", rq->srvhdrs); pblock_nninsert("content-length", 13, rq->srvhdrs); protocol_status(sn, rq, 200, NULL); http_start_response(sn, rq); net_write(sn->csd, "Hello World!\n", 13); return REQ_PROCEED; } static int ws_msghandler(WebSocket *ws, WSMessage *msg) { if(msg->type == 1) { printf("Message(text): %.*s\n", (int)msg->length, msg->data); websocket_send_text(ws->userdata, "hello", 5); } else { printf("Message: opcode: %d | length: %d\n", msg->type, (int)msg->length); } return 0; } int service_ws_hello(pblock *pb, Session *sn, Request *rq) { WebSocket ws; ZERO(&ws, sizeof(WebSocket)); ws.userdata = sn->csd; ws.on_message = ws_msghandler; 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; } int ret = strcasecmp(a->name, b->name); if(ret != 0) { return ret; } return strcmp(a->name, b->name); } int service_index(pblock *pb, Session *sn, Request *rq) { NSAPIRequest *req = (NSAPIRequest*)rq; if(req->location && req->location->set_dirindex && !req->location->dirindex) { return REQ_NOACTION; } //printf("service_index\n"); const CxAllocator *a = pool_allocator(sn->pool); char *path = pblock_findkeyval(pb_key_path, rq->vars); cxstring uri = cx_str(pblock_findkeyval(pb_key_uri, rq->reqpb)); if(uri.length == 0) { return REQ_ABORTED; } if(uri.ptr[uri.length-1] != '/') { cxmutstr newuri = cx_strcat_a(a, 2, uri, CX_STR("/")); uri = cx_strcast(newuri); } // params char *useemojis = pblock_findval("use-emojis", pb); char *diricon = pblock_findval("dir-icon", pb); char *fileicon = pblock_findval("file-icon", pb); char *hidden = pblock_findval("show-hidden", pb); WSBool show_hidden = TRUE; char *dir_str = "<dir>"; char *file_str = "<file>"; if(hidden) { show_hidden = util_getboolean(hidden, FALSE); } if(useemojis && util_getboolean(useemojis, FALSE)) { dir_str = "📁"; file_str = "📄"; } else { if(diricon) { dir_str = cx_asprintf_a(a, "<img src=\"%s\" alt=\"directory\" border=\"0\"/>", diricon).ptr; } if(fileicon) { file_str = cx_asprintf_a(a, "<img src=\"%s\" alt=\"directory\" border=\"0\"/>", fileicon).ptr; } } // open the file VFSContext *vfs = vfs_request_context(sn, rq); VFS_DIR dir = vfs_opendir(vfs, path); if(!dir) { return REQ_ABORTED; } CxList *files = cxLinkedListCreate(a, (cx_compare_func)cmp_file_type_name, sizeof(IndexEntry)); if(!files) { vfs_closedir(dir); return REQ_ABORTED; } sbuf_t *out = sbuf_new(1024); // output buffer // write html header sbuf_puts(out, "<!DOCTYPE html>\n<html>\n<head>\n<title>Index of "); sbuf_append(out, uri); sbuf_puts(out, "</title>\n"); sbuf_puts(out, "<style>\n"); sbuf_puts(out, "body { font-family: sans; }\n"); sbuf_puts(out, "h1 { font-size: 1.1em; }\n"); sbuf_puts(out, "th { text-align: left; }\n"); sbuf_puts(out, "td { padding-right: 2em; }\n"); sbuf_puts(out, "a { text-decoration: none; }\n"); sbuf_puts(out, ".type { padding-right: 0em; }\n"); sbuf_puts(out, ".path { background-color: #e0e0e0; border-radius: 0.5em; padding: 0.25em 0.75em 0.25em 0.75em; }"); sbuf_puts(out, "</style>\n"); sbuf_puts(out, "</head><body><h1>"); if(uri.length > 0) { sbuf_puts(out, "<span class=\"path\"><a href=\"/\">/</a></span>\n"); size_t start = 1; for(size_t i=1;i<uri.length;i++) { if(uri.ptr[i] == '/') { sbuf_puts(out, "<span class=\"path\"><a href=\""); sbuf_write(out, uri.ptr, i); sbuf_puts(out, "\">"); sbuf_write(out, uri.ptr+start, i-start); sbuf_puts(out, "</a></span>\n"); start = i+1; } } } sbuf_puts(out, "</h1><hr>"); // read directory at store entries in the files list int ret = REQ_PROCEED; VFS_ENTRY f; while(vfs_readdir_stat(dir, &f)) { if(!show_hidden && f.name[0] == '.') { continue; } 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 colspan=\"2\">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>"); sbuf_puts(out, "<td class=\"type\">"); sbuf_puts(out, entry->isdir ? dir_str : file_str); sbuf_puts(out, "</td>"); sbuf_puts(out, "<td>"); sbuf_puts(out, "<a href=\""); sbuf_append(out, uri); 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) { char buf[64]; snprintf(buf, 64, "<span title=\"%zu bytes\">", entry->size); sbuf_puts(out, buf); sbuf_puts(out, entry->size_str); sbuf_puts(out, "</span>"); } 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 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); } // close vfs_closedir(dir); sbuf_free(out); return ret; } int send_options(pblock *pb, Session *sn, Request *rq) { char *allow = "HEAD, GET, PUT, DELETE, TRACE, OPTIONS, MOVE, COPY, " "PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, ACL, REPORT"; char *dav = "1,2,access-control"; pblock_removekey(pb_key_content_type, rq->srvhdrs); pblock_nvinsert("allow", allow, rq->srvhdrs); pblock_nvinsert("dav", dav, rq->srvhdrs); pblock_nninsert("content-length", 0, rq->srvhdrs); protocol_status(sn, rq, 204, NULL); http_start_response(sn, rq); return REQ_PROCEED; }