diff options
-rw-r--r-- | README.md | 27 | ||||
-rw-r--r-- | browse.c | 4 | ||||
-rw-r--r-- | browse.h | 11 | ||||
-rw-r--r-- | config.c | 4 | ||||
-rw-r--r-- | download.c | 2 | ||||
-rw-r--r-- | file.c | 6 | ||||
-rw-r--r-- | file.h | 6 | ||||
-rw-r--r-- | main.c | 2 | ||||
-rw-r--r-- | template/upload_head.html | 2 | ||||
-rw-r--r-- | test.c | 39 | ||||
-rw-r--r-- | upload.c | 139 |
11 files changed, 191 insertions, 51 deletions
@@ -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 + } } @@ -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", @@ -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. */ @@ -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; @@ -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, @@ -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; @@ -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 */ @@ -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 /> @@ -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; } @@ -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; } |