summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Douillet <vincent@vdouillet.fr>2024-09-09 13:52:35 +0200
committerVincent Douillet <vincent@vdouillet.fr>2024-09-10 12:01:05 +0200
commit4e921ffa31fdca2425d326efda1c6d9ad190359a (patch)
tree8d0fe130bd26dedb5c0754249734608217bbd6b9
parentd7edafe299d44a0bec9d29e2b3a4ca853ffa1a80 (diff)
upload: handle form submission
-rw-r--r--README.md27
-rw-r--r--browse.c4
-rw-r--r--browse.h11
-rw-r--r--config.c4
-rw-r--r--download.c2
-rw-r--r--file.c6
-rw-r--r--file.h6
-rw-r--r--main.c2
-rw-r--r--template/upload_head.html2
-rw-r--r--test.c39
-rw-r--r--upload.c139
11 files changed, 191 insertions, 51 deletions
diff --git a/README.md b/README.md
index a140bc0..26597bf 100644
--- a/README.md
+++ b/README.md
@@ -32,16 +32,19 @@ By default on OpenBSD, the `slowcgi(8)` daemon allows a timeout of 2 minutes for
## Sample httpd.conf
server "server" {
- listen on * port 80
- connection timeout 540
- location "/vault/*" {
- root "/cgi-bin/vault"
- fastcgi param VAULT_DATA_DIR "/vault-data"
- request strip 1
- }
- location "/static/*" {
- root "/vault-static"
- request strip 1
- gzip-static
- }
+ listen on * port 80
+ connection {
+ # allow up to 512M uploads
+ max request body 536870912
+ }
+ location "/vault/*" {
+ root "/cgi-bin/vault"
+ fastcgi param VAULT_DATA_DIR "/vault-data"
+ request strip 1
+ }
+ location "/static/*" {
+ root "/vault-static"
+ request strip 1
+ gzip-static
+ }
}
diff --git a/browse.c b/browse.c
index ce76290..b233c8b 100644
--- a/browse.c
+++ b/browse.c
@@ -57,7 +57,7 @@ SLIST_HEAD(, file) list;
/*
* browse url = r->pname / BROWSE_URL / file->path
*/
-static size_t
+size_t
build_browse_url(struct kreq * r, struct file * file)
{
char action_url[PATH_MAX];
@@ -95,7 +95,7 @@ build_file_list(struct kreq * r, const struct file * request_dir)
struct file *last_file;
request_dir_len = file_get_data_path(request_dir, request_dir_path,
- PATH_MAX);
+ PATH_MAX, NULL);
if (request_dir_len == 0) {
kutil_warn(r, NULL,
"browse: Unable to build request dir path: %s",
diff --git a/browse.h b/browse.h
index 68d8c45..144eada 100644
--- a/browse.h
+++ b/browse.h
@@ -33,9 +33,20 @@
#include <kcgi.h>
+#include "file.h"
+
#define BROWSE_URL "browse"
/*
+ * Build the URL to browse to a directory and assign 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_browse_url(struct kreq *, struct file *);
+
+/*
* Print a browse page that allows to browse through the folder hierarchy.
* The KCGI request is required.
*/
diff --git a/config.c b/config.c
index 4401d31..6e4061e 100644
--- a/config.c
+++ b/config.c
@@ -37,10 +37,10 @@ extern char **environ;
char *
config_data_dir()
{
- char *key;
+ char *key, **var;
key = "VAULT_DATA_DIR";
- for (char **var = environ; *var; var++) {
+ for (var = environ; *var; var++) {
if (strncmp(key, *var, strlen(key)) == 0) {
if (strlen(*var) <= strlen(key))
return NULL;
diff --git a/download.c b/download.c
index 15da06d..c18dcc1 100644
--- a/download.c
+++ b/download.c
@@ -129,7 +129,7 @@ download(struct kreq * r)
};
goto end;
}
- path_size = file_get_data_path(f, file_path, PATH_MAX);
+ 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,
diff --git a/file.c b/file.c
index 239f548..24fb557 100644
--- a/file.c
+++ b/file.c
@@ -145,7 +145,8 @@ file_get_basename(const struct file * f)
}
size_t
-file_get_data_path(const struct file * f, char *data_path, size_t data_path_len)
+file_get_data_path(const struct file * f, char *data_path, size_t data_path_len,
+ char *append)
{
char *data_dir;
size_t path_len;
@@ -155,7 +156,8 @@ file_get_data_path(const struct file * f, char *data_path, size_t data_path_len)
return 0;
/* check if it is a directory */
- path_len = url_build(data_path, data_path_len, data_dir, f->path, NULL);
+ path_len = url_build(data_path, data_path_len, data_dir, f->path,
+ append, NULL);
if (path_len == 0 || path_len >= PATH_MAX)
return 0;
diff --git a/file.h b/file.h
index 38ca06e..f6aac76 100644
--- a/file.h
+++ b/file.h
@@ -69,9 +69,11 @@ char *
file_get_basename(const struct file *);
/*
- * Build the data path (on the filesystem) for a file.
+ * Build the data path (on the filesystem) for a file and optionally append
+ * another component to the path. If no additional component is needed, it
+ * should be NULL.
*/
size_t
-file_get_data_path(const struct file *, char *, size_t);
+file_get_data_path(const struct file *, char *, size_t, char *);
#endif /* FILE_H */
diff --git a/main.c b/main.c
index 137ac14..430bc6a 100644
--- a/main.c
+++ b/main.c
@@ -74,7 +74,7 @@ main(void)
http_exit(NULL, KHTTP_500, "Data dir not configured");
/* A bit of security cannot hurt */
- if (-1 == unveil(data_dir, "r")
+ if (-1 == unveil(data_dir, "rwc")
|| -1 == unveil(TEMPLATE_DIR, "r")
|| -1 == unveil(NULL, NULL))
http_exit(&r, KHTTP_500, "Unveil failed: %s", strerror(errno));
diff --git a/template/upload_head.html b/template/upload_head.html
index fbdb209..542bc78 100644
--- a/template/upload_head.html
+++ b/template/upload_head.html
@@ -1,6 +1,6 @@
<h1><i class="fa-regular fa-folder-open"></i> Uploading</h1>
<p>to <pre>@@path@@</pre></p>
-<form action="@@submit_url@@" method="post">
+<form action="@@submit_url@@" method="post" enctype="multipart/form-data">
<div>
<label for="file">File(s):</label>
<input type="file" name="file" id="file" multiple />
diff --git a/test.c b/test.c
index 390cbc3..b3cf87a 100644
--- a/test.c
+++ b/test.c
@@ -1,7 +1,9 @@
#include <stdio.h>
+#include <string.h>
#include "browse.h"
#include "http.h"
+#include "upload.h"
/* minunit, see https://jera.com/techinfo/jtns/jtn002 */
#define mu_assert(message, test) do { if (!(test)) return message; } while (0)
@@ -27,7 +29,6 @@ test_browse_invalid_traversal()
/* ...should return an error */
mu_assert("error, browse allowed invalid traversal!",
ret.code >= KHTTP_400);
-
return 0;
}
@@ -47,7 +48,42 @@ test_browse_path_too_long()
mu_assert("error, browse allowed invalid traversal!",
ret.code >= KHTTP_400);
+ return 0;
+}
+
+static char *
+test_upload_post()
+{
+ char *file_content;
+ struct kreq r;
+ struct http_ret ret;
+ struct kpair fields[2];
+
+ file_content = "text file";
+
+ fields[0] = (struct kpair) {
+ .key = "file",
+ .file = "bob.txt",
+ .val = file_content,
+ .valsz = strlen(file_content)
+ };
+ fields[1] = (struct kpair) {
+ .key = "file",
+ .file = "alice.txt",
+ .val = file_content,
+ .valsz = strlen(file_content)
+ };
+
+ r = (struct kreq) {
+ .pname = "/vault",
+ .path = "",
+ .method = KMETHOD_POST,
+ .fieldsz = 2,
+ .fields = fields
+ };
+ ret = upload(&r);
+ mu_assert("error, POST upload failed!", ret.code <= KHTTP_400);
return 0;
}
@@ -56,6 +92,7 @@ all_tests()
{
mu_run_test(test_browse_invalid_traversal);
mu_run_test(test_browse_path_too_long);
+ mu_run_test(test_upload_post);
return 0;
}
diff --git a/upload.c b/upload.c
index fef628e..765e694 100644
--- a/upload.c
+++ b/upload.c
@@ -34,6 +34,7 @@
#include <limits.h>
#include <string.h>
+#include "browse.h"
#include "cgi.h"
#include "http.h"
#include "template.h"
@@ -115,56 +116,42 @@ header_template_callback(size_t index, void *arg)
return 1;
}
-struct http_ret
-upload(struct kreq * r)
+/*
+ * GET request, we print upload form
+ */
+void
+upload_get(struct kreq * r, struct http_ret * ret, struct file * file)
{
- struct file *file;
- struct http_ret ret;
struct khtmlreq html;
struct ktemplate template;
struct page_template *tmpl;
struct template_data data;
- /* initialize vars and return structure for success */
- file = NULL;
tmpl = NULL;
- ret = (struct http_ret) {
- KHTTP_200, ""
- };
- /* build file and submit url */
- file = file_new(r->path);
- if (file == NULL) {
- ret = (struct http_ret) {
- KHTTP_404,
- "upload: Unable to build data file"
- };
- goto end;
- }
+ /* action url is form submit url */
if (build_upload_url(r, file) == 0) {
- ret = (struct http_ret) {
+ *ret = (struct http_ret) {
KHTTP_500,
- "upload: Can't build upload url"
+ "upload: Can't build upload url"
};
goto end;
}
-
/* read template */
tmpl = page_template_new(UPLOAD_URL);
if (tmpl == NULL) {
- ret = (struct http_ret) {
+ *ret = (struct http_ret) {
KHTTP_500,
- "upload: Unable to read template"
+ "upload: Unable to read template"
};
goto end;
}
-
+ /* print upload form */
http_open(r, KHTTP_200, r->mime);
K_OK(khttp_puts(r, tmpl->header), r);
K_OK(khtml_open(&html, r, 0), r);
- /* print upload form */
template.key = header_template_keys;
template.keysz = 2;
template.cb = header_template_callback;
@@ -180,9 +167,107 @@ upload(struct kreq * r)
K_OK(khtml_close(&html), r);
- /* free allocated memory */
end:
- file_free(file);
page_template_free(tmpl);
+}
+
+/*
+ * POST request, we handle upload form
+ */
+void
+upload_post(struct kreq * r, struct http_ret * ret, struct file * f)
+{
+ char path[PATH_MAX];
+ FILE *to_write;
+ size_t path_len, i;
+
+ /* 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"
+ };
+ return;
+ }
+
+ /*
+ * data should have been validated prior to handling the request, so
+ * here we should have an array of fields named "file", each field
+ * corresponding to one uploaded file
+ */
+ for (i = 0; i < r->fieldsz; i++) {
+ 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"
+ };
+ return;
+ }
+ to_write = fopen(path, "wb");
+ if (to_write == NULL) {
+ *ret = (struct http_ret) {
+ 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"
+ };
+ return;
+ }
+ if (fclose(to_write) != 0) {
+ *ret = (struct http_ret) {
+ KHTTP_500,
+ "upload: error while closing file"
+ };
+ return;
+ }
+ }
+
+ /* 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
+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"
+ };
+ goto end;
+ }
+ /* print form or handle submission according to HTTP method */
+ if (r->method == KMETHOD_POST)
+ upload_post(r, &ret, file);
+ else
+ upload_get(r, &ret, file);
+
+end:
+ file_free(file);
return ret;
}