summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--README.md8
-rw-r--r--browse.c67
-rw-r--r--browse.h5
-rw-r--r--config.h2
-rw-r--r--create.c238
-rw-r--r--create.h54
-rw-r--r--delete.c77
-rw-r--r--delete.h2
-rw-r--r--download.c46
-rw-r--r--download.h2
-rw-r--r--http.c7
-rw-r--r--http.h8
-rw-r--r--main.c48
-rw-r--r--mime.c1
-rw-r--r--template/browse_head.html5
-rw-r--r--template/create_head.html17
-rw-r--r--template/head.html3
-rw-r--r--test.c224
-rw-r--r--upload.c66
-rw-r--r--upload.h2
21 files changed, 599 insertions, 288 deletions
diff --git a/Makefile b/Makefile
index 5ccb6a0..d3de998 100644
--- a/Makefile
+++ b/Makefile
@@ -47,10 +47,10 @@ all: main
.c.o:
$(CC) -o $@ -c $< $(CFLAGS)
-main: str.o config.o mime.o url.o template.o file.o http.o delete.o upload.o download.o browse.o main.o
+main: str.o config.o mime.o url.o template.o file.o http.o delete.o upload.o download.o create.o browse.o main.o
$(CC) -o $@ $> $(LDFLAGS)
-test: str.o config.o mime.o url.o template.o file.o http.o delete.o upload.o download.o browse.o test.o
+test: str.o config.o mime.o url.o template.o file.o http.o delete.o upload.o download.o create.o browse.o test.o
$(CC) -o $@ $> $(LDFLAGS)
env: env.o
@@ -68,6 +68,7 @@ install:
install -D -o www -g www -m 0440 template/browse_item.html /var/www/usr/share/vault/template/browse_item.html
install -D -o www -g www -m 0440 template/upload_head.html /var/www/usr/share/vault/template/upload_head.html
install -D -o www -g www -m 0440 template/delete_head.html /var/www/usr/share/vault/template/delete_head.html
+ install -D -o www -g www -m 0440 template/create_head.html /var/www/usr/share/vault/template/create_head.html
install -D -o www -g www -m 0440 fontawesome-6.5.1/css/fontawesome.css /var/www/vault-static/fontawesome-6.5.1/css/fontawesome.css
gzip -fk /var/www/vault-static/fontawesome-6.5.1/css/fontawesome.css
install -D -o www -g www -m 0440 fontawesome-6.5.1/css/solid.css /var/www/vault-static/fontawesome-6.5.1/css/solid.css
diff --git a/README.md b/README.md
index 26597bf..8b85103 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
# Vault
+Note: This software is WIP. There may be bugs or unintended behavior.
+
Vault is an opinionated web-based file manager. It is built in C for OpenBSD around the CGI standard, following these main principles:
-* __secure__: a great amount of time has been devoted to avoid undefined behaviour and security issues
* __simple__: browse, create and delete folders; download and upload files. JavaScript support is optional on the client side and its absence should be handled gracefully. Everything should work from a terminal-based web browser
* __fast__: with the server running on an ARM SBC, there should not be any noticeable delay when loading a page
@@ -21,9 +22,8 @@ Vault comes with a Makefile:
The vault binary will be installed as `/var/www/cgi-bin/vault`. Static resources will be installed in `/var/www/vault-static` and should be served from `/static`. You need to configure your web server accordingly, see below for a sample file. If the log file does not exist, you need to create it. Here is how to do it for a default installation:
- # mkdir -p /var/www/var/log
- # touch /var/www/var/log/vault.log
- # chown www /var/www/var/log/vault.log
+ # touch /var/www/logs/vault.log
+ # chown www /var/www/logs/vault.log
By default on OpenBSD, the `slowcgi(8)` daemon allows a timeout of 2 minutes for CGI programs. This might not be enough if you want to allow users to download large files. This timeout can be increased by changing the `slowcgi(8)` parameters in `/etc/rc.conf.local`, for example to allow up to 10 minutes :
diff --git a/browse.c b/browse.c
index a230fe0..28e2b78 100644
--- a/browse.c
+++ b/browse.c
@@ -41,6 +41,7 @@
#include "browse.h"
#include "cgi.h"
#include "config.h"
+#include "create.h"
#include "download.h"
#include "file.h"
#include "http.h"
@@ -164,7 +165,7 @@ struct template_data {
struct file *f;
};
-static const char *const header_template_keys[] = {"upload_url"};
+static const char *const header_template_keys[] = {"upload_url", "create_url"};
static int
header_template_callback(size_t index, void *arg)
@@ -185,7 +186,21 @@ header_template_callback(size_t index, void *arg)
switch (index) {
case 0:
/* upload_url */
- K_OK(khtml_puts(html, data->f->action_url), r);
+ if (build_upload_url(r, data->f) > 0) {
+ K_OK(khtml_puts(html, data->f->action_url), r);
+ } else
+ kutil_warnx(r, NULL,
+ "failed to build upload URL for path %s",
+ data->f->path);
+ break;
+ case 1:
+ /* create_url */
+ if (build_create_url(r, data->f) > 0) {
+ K_OK(khtml_puts(html, data->f->action_url), r);
+ } else
+ kutil_warnx(r, NULL,
+ "failed to build create URL for path %s",
+ data->f->path);
break;
default:
kutil_warnx(r, NULL,
@@ -241,7 +256,11 @@ file_template_callback(size_t index, void *arg)
K_OK(khtml_puts(html, data->f->action_url), r);
break;
case 3:
- /* size, print as human readable */
+ /* size, only for files, print as human readable */
+ if (data->f->is_dir) {
+ break;
+ }
+
file_size = data->f->size;
i = 0;
while (file_size > 1024 && i < 3) {
@@ -275,12 +294,11 @@ file_template_callback(size_t index, void *arg)
return 1;
}
-struct http_ret
+void
browse(struct kreq * r)
{
struct file *file;
struct page_template *tmpl;
- struct http_ret ret;
struct khtmlreq html;
struct ktemplate template;
struct template_data data;
@@ -289,53 +307,26 @@ browse(struct kreq * r)
file = NULL;
tmpl = NULL;
- /* initialize return structure for success */
- ret = (struct http_ret) {
- KHTTP_200, ""
- };
-
/* list requested directory content */
file = file_new(r->path);
if (file == NULL) {
- ret = (struct http_ret) {
- KHTTP_404,
- "browse: Unable to build data file"
- };
+ http_exit(r, KHTTP_404, "browse: Unable to build data file");
goto end;
}
if (!file->is_dir) {
- ret = (struct http_ret) {
- KHTTP_404,
- "browse: Invalid data file"
- };
- goto end;
- }
- if (build_upload_url(r, file) == 0) {
- ret = (struct http_ret) {
- KHTTP_500,
- "browse: Can't build upload url"
- };
+ http_exit(r, KHTTP_404, "browse: Invalid data file");
goto end;
}
-
if (build_file_list(r, file) < 0) {
- ret = (struct http_ret) {
- KHTTP_500,
- "browse: Unable to build file list"
- };
+ http_exit(r, KHTTP_500, "browse: Unable to build file list");
goto end;
}
-
/* read template */
tmpl = page_template_new(BROWSE_URL);
if (tmpl == NULL) {
- ret = (struct http_ret) {
- KHTTP_500,
- "browse: Unable to read template"
- };
+ http_exit(r, KHTTP_500, "browse: Unable to read template");
goto end;
}
-
/* we have all the data we need, we can start to write output page */
http_open(r, KHTTP_200, KMIME_TEXT_HTML);
@@ -345,7 +336,7 @@ browse(struct kreq * r)
/* print browse header with action buttons for current dir */
template.key = header_template_keys;
- template.keysz = 1;
+ template.keysz = 2;
template.cb = header_template_callback;
template.arg = &data;
data.r = r;
@@ -383,6 +374,4 @@ end:
file_free(file);
file = NULL;
}
-
- return ret;
}
diff --git a/browse.h b/browse.h
index 144eada..bcfd928 100644
--- a/browse.h
+++ b/browse.h
@@ -43,13 +43,12 @@
* All parameters are required.
* Returns the length of the created URL, or 0 in case of failure.
*/
-size_t
-build_browse_url(struct kreq *, struct file *);
+size_t build_browse_url(struct kreq *, struct file *);
/*
* Print a browse page that allows to browse through the folder hierarchy.
* The KCGI request is required.
*/
-struct http_ret browse(struct kreq *);
+void browse(struct kreq *);
#endif /* BROWSE_H */
diff --git a/config.h b/config.h
index edb494b..4920d42 100644
--- a/config.h
+++ b/config.h
@@ -36,7 +36,7 @@
* otherwise it will quit with an error.
*/
#ifndef LOG_FILE
-#define LOG_FILE "/var/log/vault.log"
+#define LOG_FILE "/logs/vault.log"
#endif
/* The directory that contains the HTML template files */
diff --git a/create.c b/create.c
new file mode 100644
index 0000000..e5a98b0
--- /dev/null
+++ b/create.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2025, Vincent Douillet <vincent@vdouillet.fr>
+ *
+ * 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.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * 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 <stdlib.h>
+#include <kcgi.h>
+#include <kcgihtml.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "browse.h"
+#include "cgi.h"
+#include "http.h"
+#include "template.h"
+#include "create.h"
+#include "url.h"
+
+/*
+ * create url = r->pname / CREATE_URL / file->path
+ */
+size_t
+build_create_url(const struct kreq * r, struct file * file)
+{
+ char action_url[PATH_MAX];
+ size_t action_url_len;
+
+ if (!file->is_dir)
+ return 0;
+
+ action_url_len = url_build(action_url, PATH_MAX, r->pname, CREATE_URL,
+ file->path, NULL);
+ if (action_url_len == 0 || action_url_len >= PATH_MAX) {
+ kutil_warn(r, NULL,
+ "create: action URL overflow: %s", action_url);
+ return 0;
+ }
+ file->action_url = strndup(action_url, action_url_len);
+ if (file->action_url == NULL) {
+ kutil_warn(r, NULL,
+ "create: unable to allocate file url buffer: %s",
+ action_url);
+ return 0;
+ }
+ return action_url_len;
+}
+
+/*
+ * data required in the template functions
+ */
+struct template_data {
+ struct kreq *r;
+ struct khtmlreq *html;
+ struct file *f;
+};
+
+static const char *const header_template_keys[] = {"path", "submit_url"};
+
+static int
+header_template_callback(size_t index, void *arg)
+{
+ struct kreq *r;
+ struct khtmlreq *html;
+ struct template_data *data;
+
+ if (arg == NULL) {
+ kutil_warn(NULL, NULL,
+ "Invalid data for create header template");
+ return 0;
+ }
+ data = arg;
+ r = data->r;
+ html = data->html;
+
+ switch (index) {
+ case 0:
+ /* path */
+ K_OK(khtml_puts(html, data->f->path), r);
+ break;
+ case 1:
+ /* submit_url */
+ K_OK(khtml_puts(html, data->f->action_url), r);
+ break;
+ default:
+ kutil_warnx(r, NULL,
+ "Invalid key index for create header template: %zd",
+ index);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * GET request, we print create form
+ */
+void
+create_get(struct kreq * r, struct file * file)
+{
+ struct khtmlreq html;
+ struct ktemplate template;
+ struct page_template *tmpl;
+ struct template_data data;
+
+ tmpl = NULL;
+
+ /* action url is form submit url */
+ if (build_create_url(r, file) == 0) {
+ http_exit(r, KHTTP_500, "create: Can't build create url");
+ goto end;
+ }
+ /* read template */
+ tmpl = page_template_new(CREATE_URL);
+ if (tmpl == NULL) {
+ http_exit(r, KHTTP_500, "create: Unable to read template");
+ goto end;
+ }
+ /* print create form */
+ http_open(r, KHTTP_200, KMIME_TEXT_HTML);
+
+ K_OK(khttp_puts(r, tmpl->header), r);
+ K_OK(khtml_open(&html, r, 0), r);
+
+ template.key = header_template_keys;
+ template.keysz = 2;
+ template.cb = header_template_callback;
+ template.arg = &data;
+ data.r = r;
+ data.html = &html;
+ data.f = file;
+ K_OK(khttp_template_buf(r, &template, tmpl->page_header,
+ strlen(tmpl->page_header)), r);
+
+ K_OK(khttp_puts(r, tmpl->page_footer), r);
+ K_OK(khttp_puts(r, tmpl->footer), r);
+
+ K_OK(khtml_close(&html), r);
+
+end:
+ page_template_free(tmpl);
+}
+
+/*
+ * POST request, we handle create form
+ */
+void
+create_post(struct kreq * r, struct file * f)
+{
+ char path[PATH_MAX];
+ size_t path_len, i;
+ struct kpair *name;
+
+ /* prepare action url for return redirection in case of success */
+ if (build_browse_url(r, f) == 0) {
+ http_exit(r, KHTTP_500, "create: can't build return url");
+ return;
+ }
+ /* validate input data */
+ if (r->fieldsz != 1) {
+ http_exit(r, KHTTP_400, "create: missing folder name");
+ return;
+ }
+ name = &(r->fields[0]);
+ /*
+ * TODO name->val == NULL is not enough, need better sanitization
+ * for example, some chars are not allowed in a folder name
+ * an empty non-NULL string is also not ok
+ */
+ if (strncmp(name->key, "name", 4) || name->val == NULL) {
+ http_exit(r, KHTTP_400, "create: invalide folder name");
+ return;
+ }
+ path_len = file_get_data_path(f, path, PATH_MAX, name->val);
+ if (path_len == 0 || path_len >= PATH_MAX) {
+ http_exit(r, KHTTP_500,
+ "create: can't build file path");
+ return;
+ }
+ /* TODO check if folder already exists */
+ if (mkdir(path, 0755)) {
+ http_exit(r, KHTTP_500,
+ "create: can't create directory");
+ return;
+ }
+
+ /* on success, redirect to the new folder */
+ khttp_head(r, kresps[KRESP_LOCATION], "%s", f->action_url);
+ http_open(r, KHTTP_303, r->mime);
+}
+
+void
+create(struct kreq * r)
+{
+ struct file *file;
+
+ file = NULL;
+
+ /* build file corresponding to request (ie. create) path */
+ file = file_new(r->path);
+ if (file == NULL) {
+ http_exit(r, KHTTP_404, "create: Unable to build data file");
+ goto end;
+ }
+ /* print form or handle submission according to HTTP method */
+ if (r->method == KMETHOD_POST)
+ create_post(r, file);
+ else
+ create_get(r, file);
+
+end:
+ file_free(file);
+}
diff --git a/create.h b/create.h
new file mode 100644
index 0000000..1626a63
--- /dev/null
+++ b/create.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2025, Vincent Douillet <vincent@vdouillet.fr>
+ *
+ * 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.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * 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.
+ */
+
+#ifndef CREATE_H
+#define CREATE_H
+
+#include <kcgi.h>
+
+#include "file.h"
+
+#define CREATE_URL "create"
+
+/*
+ * Build the URL to create a folder and assigns it to the file's action_url.
+ * All parameters are required
+ * Returns the length of the created URL, or 0 in case of failure.
+ */
+size_t
+build_create_url(const struct kreq *, struct file *);
+
+/*
+ * Print a create form (http GET) or handle a create form submission (http
+ * POST).
+ */
+void create(struct kreq *);
+
+#endif /* CREATE_H */
diff --git a/delete.c b/delete.c
index c6c1449..367b937 100644
--- a/delete.c
+++ b/delete.c
@@ -122,7 +122,7 @@ header_template_callback(size_t index, void *arg)
* GET request, we print delete form
*/
void
-delete_get(struct kreq * r, struct http_ret * ret, struct file * file)
+delete_get(struct kreq * r, struct file * file)
{
struct khtmlreq html;
struct ktemplate template;
@@ -133,19 +133,13 @@ delete_get(struct kreq * r, struct http_ret * ret, struct file * file)
/* action url is form submit url */
if (build_delete_url(r, file) == 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: Can't build delete url"
- };
+ http_exit(r, KHTTP_500, "delete: Can't build delete url");
goto end;
}
/* read template */
tmpl = page_template_new(DELETE_URL);
if (tmpl == NULL) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: Unable to read template"
- };
+ http_exit(r, KHTTP_500, "delete: Unable to read template");
goto end;
}
/* print delete form */
@@ -177,7 +171,7 @@ end:
* POST request, we handle delete form
*/
void
-delete_post(struct kreq * r, struct http_ret * ret, struct file * f)
+delete_post(struct kreq * r, struct file * f)
{
char path[PATH_MAX];
char *paths[2];
@@ -193,32 +187,22 @@ delete_post(struct kreq * r, struct http_ret * ret, struct file * f)
/* prepare action url for return redirection in case of success */
parent = file_get_parent(f);
if (parent == NULL || build_browse_url(r, parent) == 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: can't build return url"
- };
+ http_exit(r, KHTTP_500, "delete: can't build return url");
goto end;
}
-
/* build file path on disk */
if (file_get_data_path(f, path, sizeof(path), NULL) <= 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: can't build file data path"
- };
+ http_exit(r, KHTTP_500, "delete: can't build file data path");
goto end;
}
-
if (f->is_dir) {
/* don't follow symlinks */
fts_opts = 0x0 | FTS_PHYSICAL;
paths[0] = path;
paths[1] = NULL;
if ((fts = fts_open(paths, fts_opts, NULL)) == NULL) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: can't open file hierarchy"
- };
+ http_exit(r, KHTTP_500,
+ "delete: can't open file hierarchy");
goto end;
}
/* file and directory delete loop */
@@ -227,28 +211,22 @@ delete_post(struct kreq * r, struct http_ret * ret, struct file * f)
/* halt on fts errors */
if (fi == FTS_DNR || fi == FTS_ERR || fi == FTS_NS) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: fts_read error"
- };
+ http_exit(r, KHTTP_500,
+ "delete: fts_read error");
goto end;
}
/* delete regular files and empty folders */
else if ((fi == FTS_DP || fi == FTS_F) &&
remove(ftsent->fts_path) < 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: failed to delete file"
- };
+ http_exit(r, KHTTP_500,
+ "delete: failed to delete file");
goto end;
}
}
} else {
if (remove(path) < 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "delete: failed to delete file"
- };
+ http_exit(r, KHTTP_500,
+ "delete: failed to delete file");
goto end;
}
}
@@ -257,10 +235,6 @@ delete_post(struct kreq * r, struct http_ret * ret, struct file * f)
khttp_head(r, kresps[KRESP_LOCATION], "%s", parent->action_url);
http_open(r, KHTTP_303, r->mime);
- *ret = (struct http_ret) {
- KHTTP_303,
- ""
- };
end:
if (parent != NULL)
file_free(parent);
@@ -268,28 +242,21 @@ end:
fts_close(fts);
}
-
-struct http_ret
+void
del(struct kreq * r)
{
char *path;
size_t suffix_len;
struct file *file;
- struct http_ret ret;
file = NULL;
- ret = (struct http_ret) {
- KHTTP_200, ""
- };
/* build file corresponding to request (ie. delete) path */
suffix_len = strlen(r->suffix);
if (suffix_len > 0) {
if (str_concat(&path, r->path, ".", r->suffix, NULL) <= 0) {
- ret = (struct http_ret) {
- KHTTP_404,
- "delete: Unable to build file path"
- };
+ http_exit(r, KHTTP_404,
+ "delete: Unable to build file path");
goto end;
}
} else {
@@ -298,21 +265,17 @@ del(struct kreq * r)
file = file_new(path);
if (file == NULL) {
- ret = (struct http_ret) {
- KHTTP_404,
- "delete: Unable to build data file"
- };
+ http_exit(r, KHTTP_404, "delete: Unable to build data file");
goto end;
}
/* print form or handle submission according to HTTP method */
if (r->method == KMETHOD_POST)
- delete_post(r, &ret, file);
+ delete_post(r, file);
else
- delete_get(r, &ret, file);
+ delete_get(r, file);
end:
if (suffix_len > 0)
free(path);
file_free(file);
- return ret;
}
diff --git a/delete.h b/delete.h
index 8d499bb..ece3088 100644
--- a/delete.h
+++ b/delete.h
@@ -49,6 +49,6 @@ build_delete_url(const struct kreq *, struct file *);
* Print a delete form (http GET) or handle a delete form submission (http
* POST).
*/
-struct http_ret del(struct kreq *);
+void del(struct kreq *);
#endif /* DELETE_H */
diff --git a/download.c b/download.c
index 1153a41..5dffd03 100644
--- a/download.c
+++ b/download.c
@@ -76,7 +76,7 @@ build_download_url(const struct kreq * r, struct file * file)
return action_url_len;
}
-struct http_ret
+void
download(struct kreq * r)
{
void *buffer;
@@ -85,22 +85,16 @@ download(struct kreq * r)
char file_path[PATH_MAX];
char *path;
size_t path_size, suffix_len;
- struct http_ret ret;
- /* initialize empty file and return struct to success */
+ /* initialize empty file to success */
f = NULL;
- ret = (struct http_ret) {
- KHTTP_200, ""
- };
/* build requested file path, with suffix or without */
suffix_len = strlen(r->suffix);
if (suffix_len > 0) {
if (str_concat(&path, r->path, ".", r->suffix, NULL) <= 0) {
- ret = (struct http_ret) {
- KHTTP_414,
- "download: unable to build requested file path"
- };
+ http_exit(r, KHTTP_414,
+ "download: unable to build requested file path");
goto end;
}
} else {
@@ -109,54 +103,37 @@ download(struct kreq * r)
/* build file metadata */
f = file_new(path);
if (f == NULL) {
- ret = (struct http_ret) {
- KHTTP_404,
- "download: file metadata failure"
- };
+ http_exit(r, KHTTP_404, "download: file metadata failure");
goto end;
}
/* we do not support downloading folders */
if (f->is_dir) {
- ret = (struct http_ret) {
- KHTTP_400,
- "download: can't download folder"
- };
+ http_exit(r, KHTTP_400, "download: can't download folder");
goto end;
}
/* memory map the file */
path_size = file_get_data_path(f, file_path, PATH_MAX, NULL);
if (path_size == 0 || path_size >= PATH_MAX) {
- ret = (struct http_ret) {
- KHTTP_404,
- "download: unable to build file path"
- };
+ http_exit(r, KHTTP_404, "download: unable to build file path");
goto end;
}
fd = open(file_path, O_RDONLY);
if (fd < 0) {
- ret = (struct http_ret) {
- KHTTP_404,
- "download: unable to open file"
- };
+ http_exit(r, KHTTP_404, "download: unable to open file");
goto end;
}
/* mmap does not work with empty file: st_size = 0 */
if (f->size > 0) {
buffer = mmap(NULL, f->size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buffer == MAP_FAILED) {
- ret = (struct http_ret) {
- KHTTP_500,
- "download: mmap failed"
- };
+ http_exit(r, KHTTP_500, "download: mmap failed");
goto end;
}
}
/* write the file */
if (!http_open_file(r, KHTTP_200, f)) {
- ret = (struct http_ret) {
- KHTTP_500,
- "download: filename url encoding failed"
- };
+ http_exit(r, KHTTP_500,
+ "download: filename url encoding failed");
goto end;
}
if (f->size > 0)
@@ -169,5 +146,4 @@ end:
if (f != NULL && f->size > 0)
munmap(buffer, f->size);
file_free(f);
- return ret;
}
diff --git a/download.h b/download.h
index f602bd7..0282cda 100644
--- a/download.h
+++ b/download.h
@@ -49,6 +49,6 @@ build_download_url(const struct kreq *, struct file *);
* Return a requested file.
* The KCGI request is required.
*/
-struct http_ret download(struct kreq *);
+void download(struct kreq *);
#endif /* DOWNLOAD_H */
diff --git a/http.c b/http.c
index 4dbeff2..b28565e 100644
--- a/http.c
+++ b/http.c
@@ -53,7 +53,12 @@ http_open_file(struct kreq * r, enum khttp code, const struct file * f)
return false;
khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[code]);
- khttp_head(r, kresps[KRESP_CONTENT_DISPOSITION],
+
+ /* no content-disposition for pictures & videos (open them in browser) */
+ if (f->mime != MIME_GIF && f->mime != MIME_JPG && f->mime != MIME_MP4 &&
+ f->mime != MIME_PNG && f->mime != MIME_SVG && f->mime != MIME_WEBM &&
+ f->mime != MIME_WEBP)
+ khttp_head(r, kresps[KRESP_CONTENT_DISPOSITION],
"attachment;filename=\"%s\"", filename);
free(filename);
khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", mime_str(f->mime));
diff --git a/http.h b/http.h
index 71a5d61..198e6b0 100644
--- a/http.h
+++ b/http.h
@@ -36,14 +36,6 @@
#include "file.h"
/*
- * Return structure for HTTP action methods
- */
-struct http_ret {
- enum khttp code;
- char *message;
-};
-
-/*
* Initialize headers and start the document body with the provided http code.
* All parameters are required.
*/
diff --git a/main.c b/main.c
index 8042a00..71ae864 100644
--- a/main.c
+++ b/main.c
@@ -36,6 +36,7 @@
#include "browse.h"
#include "cgi.h"
#include "config.h"
+#include "create.h"
#include "delete.h"
#include "download.h"
#include "http.h"
@@ -46,6 +47,7 @@ enum page {
PAGE_DOWNLOAD,
PAGE_UPLOAD,
PAGE_DELETE,
+ PAGE_CREATE,
PAGE__MAX
};
@@ -54,6 +56,7 @@ static const char *const pages[PAGE__MAX] = {
DOWNLOAD_URL,
UPLOAD_URL,
DELETE_URL,
+ CREATE_URL,
};
int
@@ -62,60 +65,75 @@ main(void)
char *data_dir;
enum kcgi_err parse_err;
struct kreq r;
- struct http_ret ret;
- if (kutil_openlog(LOG_FILE) == 0)
+ if (kutil_openlog(LOG_FILE) == 0) {
http_exit(NULL, KHTTP_500, "Unable to open %s", LOG_FILE);
+ return EXIT_SUCCESS;
+ }
parse_err = khttp_parse(&r, NULL, 0, pages, PAGE__MAX, PAGE_BROWSE);
- if (parse_err != KCGI_OK)
+ if (parse_err != KCGI_OK) {
http_exit(NULL, KHTTP_500, "Unable to parse request: %s",
kcgi_strerror(parse_err));
+ return EXIT_SUCCESS;
+ }
data_dir = config_data_dir();
- if (data_dir == NULL)
+ if (data_dir == NULL) {
http_exit(NULL, KHTTP_500, "Data dir not configured");
+ goto end;
+ }
/* A bit of security cannot hurt */
if (-1 == unveil(data_dir, "rwc")
|| -1 == unveil(TEMPLATE_DIR, "r")
- || -1 == unveil(NULL, NULL))
+ || -1 == unveil(NULL, NULL)) {
http_exit(&r, KHTTP_500, "Unveil failed: %s", strerror(errno));
- if (-1 == pledge("stdio rpath wpath cpath", NULL))
+ goto end;
+ }
+ if (-1 == pledge("stdio rpath wpath cpath", NULL)) {
http_exit(&r, KHTTP_500, "Pledge failed: %s", strerror(errno));
+ goto end;
+ }
/*
* Make sure basic request parameters are as expected : GET or POST,
* valid page and HTML document
*/
- if (r.method != KMETHOD_GET && r.method != KMETHOD_POST)
+ if (r.method != KMETHOD_GET && r.method != KMETHOD_POST) {
http_exit(&r, KHTTP_405, NULL);
- if (r.page == PAGE__MAX)
+ goto end;
+ }
+ if (r.page == PAGE__MAX) {
http_exit(&r, KHTTP_404, NULL);
+ goto end;
+ }
switch (r.page) {
case PAGE_BROWSE:
if (r.mime != KMIME_TEXT_HTML)
http_exit(&r, KHTTP_406, NULL); /* Not Acceptable */
- ret = browse(&r);
+ browse(&r);
break;
case PAGE_DOWNLOAD:
- ret = download(&r);
+ download(&r);
break;
case PAGE_UPLOAD:
- ret = upload(&r);
+ upload(&r);
break;
case PAGE_DELETE:
- ret = del(&r);
+ del(&r);
+ break;
+ case PAGE_CREATE:
+ create(&r);
break;
default:
http_exit(&r, KHTTP_404, NULL);
+ goto end;
}
+end:
khttp_free(&r);
- if (ret.code >= KHTTP_400)
- http_exit(&r, ret.code, ret.message);
-
return EXIT_SUCCESS;
}
diff --git a/mime.c b/mime.c
index 813ea25..2157b9e 100644
--- a/mime.c
+++ b/mime.c
@@ -132,7 +132,6 @@ mime_from_ext(const char *ext)
return MIME_BIN;
}
-
const char *
mime_str(enum mime mime)
{
diff --git a/template/browse_head.html b/template/browse_head.html
index 6775012..8b53948 100644
--- a/template/browse_head.html
+++ b/template/browse_head.html
@@ -1,5 +1,8 @@
<h1><i class="fa-regular fa-folder-open"></i> Browsing</h1>
-<a href="@@upload_url@@">Upload here</a>
+<ul id="action-list">
+ <li><a href="@@upload_url@@">Upload here</a></li>
+ <li><a href="@@create_url@@">Create folder here</a></li>
+</ul>
<hr />
<table id="file-list">
<thead>
diff --git a/template/create_head.html b/template/create_head.html
new file mode 100644
index 0000000..5b273ce
--- /dev/null
+++ b/template/create_head.html
@@ -0,0 +1,17 @@
+<h1><i class="fa-regular fa-folder-open"></i> New folder</h1>
+<p>in</p>
+<pre>@@path@@</pre>
+<hr />
+<div>
+<form action="@@submit_url@@" method="post" enctype="multipart/form-data">
+ <div>
+ <label for="name">Folder name:</label>
+ </div>
+ <div>
+ <input type="text" name="name" id="name" />
+ </div>
+ <div>
+ <input type="submit" value="Create" />
+ </div>
+</form>
+</div>
diff --git a/template/head.html b/template/head.html
index a36c475..85f28e3 100644
--- a/template/head.html
+++ b/template/head.html
@@ -15,6 +15,9 @@ body {
font:1.2em/1.62 sans-serif;
}
a:visited { color:blue; }
+ul#action-list { padding:0; }
+ul#action-list li { display:inline; }
+ul#action-list li:not(:last-child):after { content:' |'; }
table#file-list {
width:100%;
border-collapse:collapse;
diff --git a/test.c b/test.c
index 3738ced..b47a56d 100644
--- a/test.c
+++ b/test.c
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "browse.h"
@@ -18,41 +19,163 @@
int tests_run;
static char *
-test_browse_invalid_traversal()
+test_build_browse_url()
{
+ size_t s;
+ struct file f;
struct kreq r;
- struct http_ret ret;
- /* attempt to traverse out of DATA_DIR... */
+ /* nominal case */
r = (struct kreq) {
- .path = "..",
+ .pname = "vault",
+ .path = "Alice/Bob",
.suffix = "",
- .mime = KMIME_TEXT_HTML
};
- ret = browse(&r);
+ f = (struct file) {
+ .is_dir = true,
+ .path = "Alice/Bob",
+ .action_url = NULL
+ };
+ s = build_browse_url(&r, &f);
+ mu_assert("failed to build browse url", s > 0 && f.action_url != NULL);
+
+ /* refuse to browse files */
+ if (f.action_url != NULL)
+ free(f.action_url);
+ f.action_url = NULL;
+ f.is_dir = false;
+ s = build_browse_url(&r, &f);
+ mu_assert("allowed to browse a file", s <= 0 && f.action_url == NULL);
+
+ return 0;
+}
+
+static char *
+test_build_download_url()
+{
+ size_t s;
+ struct file f;
+ struct kreq r;
+
+ /* nominal case */
+ r = (struct kreq) {
+ .pname = "vault",
+ .path = "Alice/Bob",
+ .suffix = "txt",
+ };
+ f = (struct file) {
+ .is_dir = false,
+ .path = "Alice/Bob.txt",
+ .action_url = NULL
+ };
+ s = build_download_url(&r, &f);
+ mu_assert("failed to build download url",
+ s > 0 && f.action_url != NULL);
+
+ /* refuse to download directories */
+ if (f.action_url != NULL)
+ free(f.action_url);
+ f.action_url = NULL;
+ f.is_dir = true;
+ s = build_download_url(&r, &f);
+ mu_assert("allowed to download a directory",
+ s <= 0 && f.action_url == NULL);
+
+ return 0;
+}
+
+static char *
+test_build_upload_url()
+{
+ size_t s;
+ struct file f;
+ struct kreq r;
+
+ /* nominal case */
+ r = (struct kreq) {
+ .pname = "vault",
+ .path = "Alice/Bob",
+ .suffix = "",
+ };
+ f = (struct file) {
+ .is_dir = true,
+ .path = "Alice/Bob",
+ .action_url = NULL
+ };
+ s = build_upload_url(&r, &f);
+ mu_assert("failed to build upload url", s > 0 && f.action_url != NULL);
+
+ /* refuse to upload to a file */
+ if (f.action_url != NULL)
+ free(f.action_url);
+ f.action_url = NULL;
+ f.is_dir = false;
+ s = build_upload_url(&r, &f);
+ mu_assert("allowed to upload to a file",
+ s <= 0 && f.action_url == NULL);
- /* ...should return an error */
- mu_assert("error, browse allowed invalid traversal!",
- ret.code >= KHTTP_400);
return 0;
}
static char *
-test_browse_path_too_long()
+test_build_delete_url()
{
+ size_t s;
+ struct file f;
struct kreq r;
- struct http_ret ret;
- /* a lengthy path should cause URL overflow... */
+ /* nominal case */
r = (struct kreq) {
- .path = "this/is/a/very/very/lengthy/path/that/should/overflow/the/maximum/allowed/path/length/of/PATH_MAX/well/of/course/this/limit/is/platform/dependent",
+ .pname = "vault",
+ .path = "Alice/Bob",
.suffix = "",
- .mime = KMIME_TEXT_HTML
};
- ret = browse(&r);
+ f = (struct file) {
+ .is_dir = true,
+ .path = "Alice/Bob",
+ .action_url = NULL
+ };
+ s = build_delete_url(&r, &f);
+ mu_assert("failed to build delete url", s > 0 && f.action_url != NULL);
+
+ /* allow also to delete a file */
+ if (f.action_url != NULL)
+ free(f.action_url);
+ f.action_url = NULL;
+ f.is_dir = false;
+ s = build_delete_url(&r, &f);
+ mu_assert("refused to delete a file", s > 0 && f.action_url != NULL);
- mu_assert("error, browse allowed invalid traversal!",
- ret.code >= KHTTP_400);
+ return 0;
+}
+
+static char *
+test_url_build()
+{
+ char dst[BUF_SZ], *expected;
+ size_t dst_len;
+
+ expected = "/vault/browse";
+ dst_len = url_build(dst, BUF_SZ, "/vault/browse", "", NULL);
+ mu_assert("test_url_build failed", dst_len > 0 && dst_len < BUF_SZ);
+ mu_assert("test_url_build failed", strncmp(expected, dst, BUF_SZ) == 0);
+
+ expected = "/vault/browse/hello/world";
+ dst_len = url_build(dst, BUF_SZ, "/vault/browse", "hello", "world",
+ NULL);
+ mu_assert("test_url_build failed", dst_len > 0 && dst_len < BUF_SZ);
+ mu_assert("test_url_build failed", strncmp(expected, dst, BUF_SZ) == 0);
+
+ expected = "/vault/browse";
+ dst_len = url_build(dst, BUF_SZ, "/vault/", "browse/", NULL);
+ mu_assert("test_url_build failed", dst_len > 0 && dst_len < BUF_SZ);
+ mu_assert("test_url_build failed", strncmp(expected, dst, BUF_SZ) == 0);
+
+ dst_len = url_build(dst, BUF_SZ, "", "", NULL);
+ mu_assert("test_url_build failed", dst_len == 0);
+
+ dst_len = url_build(dst, BUF_SZ, NULL);
+ mu_assert("test_url_build failed", dst_len == 0);
return 0;
}
@@ -61,7 +184,6 @@ test_upload_post()
{
char *file_content;
struct kreq r;
- struct http_ret ret;
struct kpair fields[2];
file_content = "text file";
@@ -86,9 +208,7 @@ test_upload_post()
.fieldsz = 2,
.fields = fields
};
- ret = upload(&r);
-
- mu_assert("error, POST upload failed!", ret.code <= KHTTP_400);
+ upload(&r);
return 0;
}
@@ -96,16 +216,12 @@ static char *
test_download()
{
struct kreq r;
- struct http_ret ret;
-
r = (struct kreq) {
.pname = "/vault",
.path = "a",
.suffix = "txt",
};
- ret = download(&r);
-
- mu_assert("error, download failed!", ret.code <= KHTTP_400);
+ download(&r);
return 0;
}
@@ -113,17 +229,13 @@ static char *
test_delete_get()
{
struct kreq r;
- struct http_ret ret;
-
r = (struct kreq) {
.pname = "/vault",
.path = "a",
.suffix = "txt",
.method = KMETHOD_GET,
};
- ret = del(&r);
-
- mu_assert("error, GET delete failed!", ret.code <= KHTTP_400);
+ del(&r);
return 0;
}
@@ -131,60 +243,34 @@ static char *
test_delete_post()
{
struct kreq r;
- struct http_ret ret;
-
r = (struct kreq) {
.pname = "/vault",
.path = "Folder",
.suffix = "",
.method = KMETHOD_POST,
};
- ret = del(&r);
-
- mu_assert("error, POST delete failed!", ret.code <= KHTTP_400);
- return 0;
-}
-
-static char *
-test_url_build()
-{
- char dst[BUF_SZ], *expected;
- size_t dst_len;
-
- expected = "/vault/browse";
- dst_len = url_build(dst, BUF_SZ, "/vault/browse", "", NULL);
- mu_assert("test_url_build failed", dst_len > 0 && dst_len < BUF_SZ);
- mu_assert("test_url_build failed", strncmp(expected, dst, BUF_SZ) == 0);
-
- expected = "/vault/browse/hello/world";
- dst_len = url_build(dst, BUF_SZ, "/vault/browse", "hello", "world",
- NULL);
- mu_assert("test_url_build failed", dst_len > 0 && dst_len < BUF_SZ);
- mu_assert("test_url_build failed", strncmp(expected, dst, BUF_SZ) == 0);
-
- expected = "/vault/browse";
- dst_len = url_build(dst, BUF_SZ, "/vault/", "browse/", NULL);
- mu_assert("test_url_build failed", dst_len > 0 && dst_len < BUF_SZ);
- mu_assert("test_url_build failed", strncmp(expected, dst, BUF_SZ) == 0);
-
- dst_len = url_build(dst, BUF_SZ, "", "", NULL);
- mu_assert("test_url_build failed", dst_len == 0);
-
- dst_len = url_build(dst, BUF_SZ, NULL);
- mu_assert("test_url_build failed", dst_len == 0);
+ del(&r);
return 0;
}
static char *
all_tests()
{
- mu_run_test(test_browse_invalid_traversal);
- mu_run_test(test_browse_path_too_long);
- mu_run_test(test_url_build);
+ /* the following tests are for debug only */
+ /*
mu_run_test(test_download);
mu_run_test(test_upload_post);
mu_run_test(test_delete_get);
mu_run_test(test_delete_post);
+ */
+
+ /* url building */
+ mu_run_test(test_url_build);
+ mu_run_test(test_build_browse_url);
+ mu_run_test(test_build_download_url);
+ mu_run_test(test_build_upload_url);
+ mu_run_test(test_build_delete_url);
+
return 0;
}
diff --git a/upload.c b/upload.c
index 56cf2c4..d9b38cb 100644
--- a/upload.c
+++ b/upload.c
@@ -121,7 +121,7 @@ header_template_callback(size_t index, void *arg)
* GET request, we print upload form
*/
void
-upload_get(struct kreq * r, struct http_ret * ret, struct file * file)
+upload_get(struct kreq * r, struct file * file)
{
struct khtmlreq html;
struct ktemplate template;
@@ -132,19 +132,13 @@ upload_get(struct kreq * r, struct http_ret * ret, struct file * file)
/* action url is form submit url */
if (build_upload_url(r, file) == 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: Can't build upload url"
- };
+ http_exit(r, KHTTP_500, "upload: Can't build upload url");
goto end;
}
/* read template */
tmpl = page_template_new(UPLOAD_URL);
if (tmpl == NULL) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: Unable to read template"
- };
+ http_exit(r, KHTTP_500, "upload: Unable to read template");
goto end;
}
/* print upload form */
@@ -176,7 +170,7 @@ end:
* POST request, we handle upload form
*/
void
-upload_post(struct kreq * r, struct http_ret * ret, struct file * f)
+upload_post(struct kreq * r, struct file * f)
{
char path[PATH_MAX];
FILE *to_write;
@@ -184,13 +178,9 @@ upload_post(struct kreq * r, struct http_ret * ret, struct file * f)
/* prepare action url for return redirection in case of success */
if (build_browse_url(r, f) == 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: can't build return url"
- };
+ http_exit(r, KHTTP_500, "upload: can't build return url");
return;
}
-
/*
* data should have been validated prior to handling the request, so
* here we should have an array of fields named "file", each field
@@ -200,33 +190,25 @@ upload_post(struct kreq * r, struct http_ret * ret, struct file * f)
struct kpair *upl = &(r->fields[i]);
path_len = file_get_data_path(f, path, PATH_MAX, upl->file);
if (path_len == 0 || path_len >= PATH_MAX) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: can't build file path"
- };
+ http_exit(r, KHTTP_500,
+ "upload: can't build file path");
return;
}
to_write = fopen(path, "wb");
if (to_write == NULL) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: can't open file for writing"
- };
+ http_exit(r, KHTTP_500,
+ "upload: can't open file for writing");
return;
}
if (fwrite(upl->val, sizeof(char), upl->valsz, to_write)
!= upl->valsz) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: error while writing file"
- };
+ http_exit(r, KHTTP_500,
+ "upload: error while writing file");
return;
}
if (fclose(to_write) != 0) {
- *ret = (struct http_ret) {
- KHTTP_500,
- "upload: error while closing file"
- };
+ http_exit(r, KHTTP_500,
+ "upload: error while closing file");
return;
}
}
@@ -234,41 +216,27 @@ upload_post(struct kreq * r, struct http_ret * ret, struct file * f)
/* on success, redirect to the directory where new files were created */
khttp_head(r, kresps[KRESP_LOCATION], "%s", f->action_url);
http_open(r, KHTTP_303, r->mime);
-
- *ret = (struct http_ret) {
- KHTTP_303,
- ""
- };
}
-
-struct http_ret
+void
upload(struct kreq * r)
{
struct file *file;
- struct http_ret ret;
file = NULL;
- ret = (struct http_ret) {
- KHTTP_200, ""
- };
/* build file corresponding to request (ie. upload) path */
file = file_new(r->path);
if (file == NULL) {
- ret = (struct http_ret) {
- KHTTP_404,
- "upload: Unable to build data file"
- };
+ http_exit(r, KHTTP_404, "upload: Unable to build data file");
goto end;
}
/* print form or handle submission according to HTTP method */
if (r->method == KMETHOD_POST)
- upload_post(r, &ret, file);
+ upload_post(r, file);
else
- upload_get(r, &ret, file);
+ upload_get(r, file);
end:
file_free(file);
- return ret;
}
diff --git a/upload.h b/upload.h
index 526a628..4ec23a6 100644
--- a/upload.h
+++ b/upload.h
@@ -49,6 +49,6 @@ build_upload_url(const struct kreq *, struct file *);
* Print an upload form (http GET) or handle an upload form submission (http
* POST).
*/
-struct http_ret upload(struct kreq *);
+void upload(struct kreq *);
#endif /* UPLOAD_H */