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 */ | 
