diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | browse.c | 4 | ||||
-rw-r--r-- | download.c | 99 | ||||
-rw-r--r-- | file.c | 41 | ||||
-rw-r--r-- | file.h | 20 | ||||
-rw-r--r-- | http.c | 12 | ||||
-rw-r--r-- | http.h | 15 | ||||
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | mime.c | 139 | ||||
-rw-r--r-- | mime.h | 85 | ||||
-rw-r--r-- | url.c | 11 | ||||
-rw-r--r-- | url.h | 3 | ||||
-rw-r--r-- | util.c | 18 | ||||
-rw-r--r-- | util.h | 14 |
15 files changed, 365 insertions, 106 deletions
@@ -46,7 +46,7 @@ all: main .c.o: $(CC) -o $@ -c $< $(CFLAGS) -main: util.o url.o http.o file.o download.o browse.o main.o +main: util.o mime.o url.o file.o http.o download.o browse.o main.o $(CC) -o $@ $> $(LDFLAGS) env: env.o @@ -24,3 +24,7 @@ The vault binary will be installed as `/var/www/cgi-bin/vault`. You need to conf # mkdir -p /var/www/var/log # touch /var/www/var/log/vault.log # chown www /var/www/var/log/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 : + + slowcgi_flags="-t 600" @@ -71,7 +71,7 @@ build_browse_url(struct kreq * r, struct file * file) "browse: action URL overflow: %s", action_url); return 0; } - file->action_url = v_strcpy(action_url, action_url_len); + file->action_url = strndup(action_url, action_url_len); if (file->action_url == NULL) { kutil_warn(r, NULL, "browse: unable to allocate file url buffer: %s", @@ -188,7 +188,7 @@ browse(struct kreq * r) struct template_data data; /* check that the requested URL can be safely processed */ - if (!check_request_path(r->path)) + if (!check_request_path(r->path, r->suffix)) http_exit(r, KHTTP_400, "browse: Invalid request path"); /* list requested directory content */ @@ -33,6 +33,8 @@ #include <fcntl.h> #include <limits.h> +#include <stdlib.h> +#include <string.h> #include <unistd.h> #include "cgi.h" @@ -63,7 +65,7 @@ build_download_url(struct kreq * r, struct file * file) "download: action URL overflow: %s", action_url); return 0; } - file->action_url = v_strcpy(action_url, action_url_len); + file->action_url = strndup(action_url, action_url_len); if (file->action_url == NULL) { kutil_warn(r, NULL, "download: unable to allocate file url buffer: %s", @@ -73,50 +75,95 @@ build_download_url(struct kreq * r, struct file * file) return action_url_len; } +static char * +strsplit(char * str, char c) { + char *right, *result; + size_t right_len; + + right = strrchr(str, c); + if(!right || right == str) + return NULL; + + // move right part to new buffer + right_len = strlen(right); + result = malloc(sizeof(char) * right_len); + if(result == NULL) + return NULL; + + if(strlcpy(result, right + 1, right_len) >= right_len) { + free(result); + return NULL; + } + + // remove right part from src buffer + *right = '\0'; + + return result; +} + void download(struct kreq * r) { + char *file_name; void *buffer; - struct stat st; - int st_ret, fd; - char file_path[PATH_MAX]; + struct file* f; + int fd; + char file_path[PATH_MAX], request_path[PATH_MAX]; size_t path_size; /* check that the requested URL can be safely processed */ - if (!check_request_path(r->path)) - http_exit(r, KHTTP_400, "download: Invalid request path"); + if (strlen(r->path) == 0 || !check_request_path(r->path, r->suffix)) + http_exit(r, KHTTP_400, "download: invalid request path"); /* build requested file path */ - path_size = url_build(file_path, PATH_MAX, DATA_DIR, r->path, + if(strlen(r->suffix) > 0) { + /* request with suffix */ + if(snprintf(request_path, sizeof(request_path), "%s.%s", r->path, r->suffix) + >= (int) sizeof(request_path)) + http_exit(r, KHTTP_414, NULL); + } + else { + if(snprintf(request_path, sizeof(request_path), "%s", r->path) >= (int) sizeof(request_path)) + http_exit(r, KHTTP_414, NULL); + } + + path_size = url_build(file_path, PATH_MAX, DATA_DIR, request_path, NULL); if (path_size == 0) - http_exit(r, KHTTP_404, "download: Unable to build file path"); + http_exit(r, KHTTP_404, "download: unable to build file path"); if (path_size >= PATH_MAX) http_exit(r, KHTTP_414, NULL); - /* check that it is a file */ - + /* build file metadata */ + file_name = strsplit(request_path, '/'); + if(file_name == NULL) + f = file_new("/", 1, request_path, strlen(request_path)); /* ROOT file */ + else { + f = file_new(request_path, strlen(request_path), file_name, + strlen(file_name)); + free(file_name); /* copied in file_new */ + } + if(f == NULL) + http_exit(r, KHTTP_404, "download: file metadata failure"); /* memory map the file */ fd = open(file_path, O_RDONLY); - if (fd < 0) - http_exit(r, KHTTP_404, "download: Unable to open file"); - st_ret = fstat(fd, &st); - if (st_ret != 0) - http_exit(r, KHTTP_404, "download: Unable to open file"); - - /* TODO mmap does not work with empty file: st_size = 0 */ - buffer = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (buffer == MAP_FAILED) - http_exit(r, KHTTP_500, "download: mmap failed"); + if(fd < 0) + http_exit(r, KHTTP_404, "download: unable to open file"); + /* 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) + http_exit(r, KHTTP_500, "download: mmap failed"); + } /* write the file */ - /* TODO proper filename & MIME type detection */ - K_OK(khttp_head(r, kresps[KRESP_CONTENT_DISPOSITION], "attachment; filename=\"File.txt\""), r); - http_open(r, KHTTP_200, KMIME_TEXT_PLAIN); - K_OK(khttp_write(r, (const char *) buffer, st.st_size), r); + http_open_file(r, KHTTP_200, f); + if(f->size > 0) + K_OK(khttp_write(r, (const char *) buffer, f->size), r); /* cleanup */ - munmap(buffer, st.st_size); - close(fd); + if(f->size > 0) + munmap(buffer, f->size); + free(f); } @@ -36,6 +36,7 @@ #include "config.h" #include "file.h" +#include "mime.h" #include "url.h" #include "util.h" @@ -61,29 +62,17 @@ file_free(struct file * file) } static bool -ext_cmp(char *ext, char **ext_list) -{ - int i; - - for (i = 0; ext_list[i] != NULL; i++) { - if (strcmp(ext, ext_list[i]) == 0) - return true; - } - - return false; -} - -static bool -find_type(struct file * f) +fill_metadata(struct file * f) { char *ext; - char *video_ext[] = {"mp4", "mkv", "avi", NULL}; - char *audio_ext[] = {"mp3", "flac", "aac", NULL}; - char *text_ext[] = {"txt", "doc", "docx", "odt", NULL}; char path[PATH_MAX]; struct stat sb; size_t path_len; + /* default mime */ + f->mime = MIME__MAX; + + /* check if it is a directory */ path_len = url_build(path, PATH_MAX, DATA_DIR, f->path, f->name, NULL); if (path_len == 0 || path_len >= PATH_MAX) return false; @@ -91,22 +80,16 @@ find_type(struct file * f) if (stat(path, &sb) != 0) return false; + f->size = sb.st_size; f->is_dir = S_ISDIR(sb.st_mode); + /* find mime from file extension */ ext = strrchr(f->name, '.'); if (!ext || ext == f->name) { - f->type = OTHER; return true; } ext++; - if (ext_cmp(ext, audio_ext)) - f->type = AUDIO; - else if (ext_cmp(ext, video_ext)) - f->type = VIDEO; - else if (ext_cmp(ext, text_ext)) - f->type = TEXT; - else - f->type = OTHER; + f->mime = mime_from_ext(ext); return true; } @@ -120,17 +103,17 @@ file_new(char *dir, size_t dir_len, char *name, size_t name_len) if (file == NULL) return NULL; - file->path = v_strcpy(dir, dir_len); + file->path = strndup(dir, dir_len); if (file->path == NULL) { file_free(file); return NULL; } - file->name = v_strcpy(name, name_len); + file->name = strndup(name, name_len); if (file->name == NULL) { file_free(file); return NULL; } - if (!find_type(file)) { + if (!fill_metadata(file)) { file_free(file); return NULL; } @@ -35,23 +35,19 @@ #include <stdbool.h> -enum file_type { - AUDIO, - VIDEO, - TEXT, - OTHER -}; +#include "mime.h" /* * a file */ struct file { - char *path; /* relative to DATA_DIR */ - char *name; - char *action_url; /* action URL for HTML page */ - bool is_dir; - enum file_type type; SLIST_ENTRY(file) files; + size_t size; + enum mime mime; + char *path; /* relative to DATA_DIR */ + char *name; + char *action_url; /* action URL for HTML page */ + bool is_dir; }; /* @@ -65,6 +61,6 @@ file_free(struct file *); * Build a new file */ struct file * -file_new(char *dir, size_t dir_len, char *name, size_t name_len); +file_new(char *, size_t, char *, size_t); #endif /* FILE_H */ @@ -33,6 +33,18 @@ #include <stdlib.h> #include "http.h" +#include "mime.h" + +void +http_open_file(struct kreq * r, enum khttp code, struct file * f) +{ + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[code]); + khttp_head(r, kresps[KRESP_CONTENT_DISPOSITION], + "attachment;filename=\"%s\"", f->name); + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", mime_str(f->mime)); + khttp_head(r, kresps[KRESP_CONTENT_LENGTH], "%zu", f->size); + khttp_body_compress(r, 0); /* file is not compressed */ +} void http_open(struct kreq * r, enum khttp code, enum kmime mime) @@ -33,11 +33,21 @@ #include <kcgi.h> +#include "file.h" + +/* + * Initialize headers and start the document body with the provided http code. + * All parameters are required. + */ +void +http_open_file(struct kreq *, enum khttp, struct file *); + /* * Initialize headers and start the document body with the provided http code. * All parameters are required. */ -void http_open(struct kreq * r, enum khttp code, enum kmime mime); +void +http_open(struct kreq *, enum khttp, enum kmime); /* * Send a plain text error response. @@ -48,6 +58,7 @@ void http_open(struct kreq * r, enum khttp code, enum kmime mime); * * This function exits with EXIT_FAILURE. It never returns. */ -void http_exit(struct kreq *, enum khttp, char *,...); +void +http_exit(struct kreq *, enum khttp, char *,...); #endif /* HTTP_H */ @@ -78,13 +78,13 @@ main(void) */ if (r.method != KMETHOD_GET && r.method != KMETHOD_POST) http_exit(&r, KHTTP_405, NULL); - if (r.mime != KMIME_TEXT_HTML) - http_exit(&r, KHTTP_406, NULL); /* Not Acceptable */ if (r.page == PAGE__MAX) http_exit(&r, KHTTP_404, NULL); switch (r.page) { case PAGE_BROWSE: + if (r.mime != KMIME_TEXT_HTML) + http_exit(&r, KHTTP_406, NULL); /* Not Acceptable */ browse(&r); break; case PAGE_DOWNLOAD: @@ -0,0 +1,139 @@ +/* + * Copyright 2023, 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 <stddef.h> +#include <strings.h> + +#include "mime.h" + +struct mime_map { + char* str; + enum mime mime; +}; + +struct mime_map mime_ext_list[] = { + { "aac", MIME_AAC }, + { "avi", MIME_AVI }, + { "bin", MIME_BIN }, + { "bz2", MIME_BZ2 }, + { "csv", MIME_CSV }, + { "doc", MIME_DOC }, + { "docx", MIME_DOCX }, + { "epub", MIME_EPUB }, + { "gz", MIME_GZ }, + { "gif", MIME_GIF }, + { "html", MIME_HTML }, + { "jar", MIME_JAR }, + { "jpg", MIME_JPG }, + { "jpeg", MIME_JPG }, + { "log", MIME_TXT }, + { "mkv", MIME_MKV }, + { "mp3", MIME_MP3 }, + { "mp4", MIME_MP4 }, + { "odp", MIME_ODP }, + { "ods", MIME_ODS }, + { "odt", MIME_ODT }, + { "png", MIME_PNG }, + { "pdf", MIME_PDF }, + { "ppt", MIME_PPT }, + { "pptx", MIME_PPTX }, + { "tar", MIME_TAR }, + { "rar", MIME_RAR }, + { "sh", MIME_SH }, + { "svg", MIME_SVG }, + { "svg", MIME_SVG }, + { "txt", MIME_TXT }, + { "webm", MIME_WEBM }, + { "webp", MIME_WEBP }, + { "xls", MIME_XLS }, + { "xlsx", MIME_XLSX }, + { "xml", MIME_XML }, + { "zip", MIME_ZIP }, + { "7z", MIME_7Z }, + { NULL, MIME__MAX } +}; + +char* mime_desc_list[MIME__MAX] = { + "audio/aac", /* MIME_AAC */ + "video/x-msvideo", /* MIME_AVI */ + "application/octet-stream", /* MIME_BIN */ + "application/x-bzip2", /* MIME_BZ2 */ + "text/csv", /* MIME_CSV */ + "application/msword", /* MIME_DOC */ + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", /* MIME_DOCX */ + "application/epub+zip", /* MIME_EPUB */ + "application/gzip", /* MIME_GZ */ + "image/gif", /* MIME_GIF */ + "text/html", /* MIME_HTML */ + "application/java-archive", /* MIME_JAR */ + "image/jpeg", /* MIME_JPG */ + "video/x-matroska", /* MIME_MKV */ + "audio/mpeg", /* MIME_MP3 */ + "video/mp4", /* MIME_MP4 */ + "application/vnd.oasis.opendocument.presentation", /* MIME_ODP */ + "application/vnd.oasis.opendocument.spreadsheet", /* MIME_ODS */ + "application/vnd.oasis.opendocument.text", /* MIME_ODT */ + "image/png", /* MIME_PNG */ + "application/pdf", /* MIME_PDF */ + "application/vnd.ms-powerpoint", /* MIME_PPT */ + "application/vnd.openxmlformats-officedocument.presentationml.presentation", /* MIME_PPTX */ + "application/vnd.rar", /* MIME_RAR */ + "application/x-sh", /* MIME_SH */ + "image/svg+xml", /* MIME_SVG */ + "application/x-tar", /* MIME_TAR */ + "text/plain", /* MIME_TXT */ + "video/webm", /* MIME_WEBM */ + "image/webp", /* MIME_WEBP */ + "application/vnd.ms-excel", /* MIME_XLS */ + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", /* MIME_XLSX */ + "application/xml", /* MIME_XML */ + "application/zip", /* MIME_ZIP */ + "application/x-7z-compressed", /* MIME_7Z */ +}; + +enum mime +mime_from_ext(char * ext) +{ + struct mime_map *mime; + + for(mime = mime_ext_list; mime->str != NULL; mime++) { + if (strcasecmp(ext, mime->str) == 0) { + return mime->mime; + } + } + return MIME_BIN; +} + + +char * +mime_str(enum mime mime) +{ + return mime_desc_list[mime]; +} @@ -0,0 +1,85 @@ +/* + * Copyright 2023, 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 MIME_H +#define MIME_H + +enum mime { + MIME_AAC, + MIME_AVI, + MIME_BIN, + MIME_BZ2, + MIME_CSV, + MIME_DOC, + MIME_DOCX, + MIME_EPUB, + MIME_GZ, + MIME_GIF, + MIME_HTML, + MIME_JAR, + MIME_JPG, + MIME_MKV, + MIME_MP3, + MIME_MP4, + MIME_ODP, + MIME_ODS, + MIME_ODT, + MIME_PNG, + MIME_PDF, + MIME_PPT, + MIME_PPTX, + MIME_RAR, + MIME_SH, + MIME_SVG, + MIME_TAR, + MIME_TXT, + MIME_WEBM, + MIME_WEBP, + MIME_XLS, + MIME_XLSX, + MIME_XML, + MIME_ZIP, + MIME_7Z, + MIME__MAX, +}; + +/* + * Find mime from file extension + */ +enum mime +mime_from_ext(char *); + +/* + * Return the mime type description + */ +char * +mime_str(enum mime); + +#endif /* MIME_H */ @@ -38,9 +38,9 @@ #include "url.h" bool -check_request_path(char *path) +check_request_path(char *path, char *suffix) { - char p [PATH_MAX], resolved[PATH_MAX]; + char p[PATH_MAX], resolved[PATH_MAX]; char *rp; /* build absolute path from DATA_DIR */ @@ -50,6 +50,13 @@ check_request_path(char *path) return false; if (strlcat(p, path, sizeof(p)) >= sizeof(p)) return false; + if (strlen(suffix) > 0) { + /* add suffix */ + if(strlcat(p, ".", sizeof(p)) >= sizeof(p)) + return false; + if(strlcat(p, suffix, sizeof(p)) >= sizeof(p)) + return false; + } /* canonicalize the path */ rp = realpath(p, resolved); @@ -38,9 +38,10 @@ * Checks that the path can be safely processed. Namely, it should not contain * "..", which denotes an attempt to get out of the DATA_DIR root folder. * The path is required. + * The suffix is required but can be an empty string */ bool -check_request_path(char *); +check_request_path(char *, char *); /* * Build an URL from the provided components. @@ -42,21 +42,3 @@ read_file(char *path, char **output, size_t * read_size) return 0; } - -char * -v_strcpy(char *str, size_t len) -{ - char *new_str; - size_t new_len; - - new_str = (char *) malloc(sizeof(char) * (len + 1)); - if (new_str == NULL) - return NULL; - - new_len = strlcpy(new_str, str, len + 1); - if (new_len >= len + 1) { - free(new_str); - return NULL; - } - return new_str; -} @@ -3,18 +3,10 @@ /* * Reads the file denoted by path into a newly allocated output buffer. Returns - * 0 for success, -1 for error. read_size is the size of output buffer (only to - * be used in case of success). + * 0 for success, -1 for error. The size_t is the size of output buffer (only + * to be used in case of success). */ int -read_file(char *path, char **output, size_t * read_size); - -/* - * Copies the provided string to a newly allocated buffer. Length of the - * created string is the same as the provided string. - * Returns the new string or NULL in case of failure. - */ -char * -v_strcpy(char *str, size_t len); +read_file(char *, char **, size_t *); #endif /* UTIL_H */ |