diff options
author | Vincent Douillet <vincent@vdouillet.fr> | 2024-05-13 20:44:07 +0200 |
---|---|---|
committer | Vincent Douillet <vincent@vdouillet.fr> | 2024-05-13 22:01:09 +0200 |
commit | 10eb77f9323110c55f88195c5b8207eb524baa73 (patch) | |
tree | 940c83fc095bad3e63045bc6fbe9ada8e4276135 | |
parent | 0effd40bad7ae753102e7040159afd76be4e8732 (diff) |
add unit tests
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | browse.c | 63 | ||||
-rw-r--r-- | browse.h | 2 | ||||
-rw-r--r-- | download.c | 136 | ||||
-rw-r--r-- | download.h | 2 | ||||
-rw-r--r-- | http.h | 8 | ||||
-rw-r--r-- | main.c | 9 | ||||
-rw-r--r-- | test-data/Folder/b.txt | 0 | ||||
-rw-r--r-- | test-data/a.txt | 0 | ||||
-rw-r--r-- | test.c | 74 |
11 files changed, 238 insertions, 67 deletions
@@ -4,3 +4,4 @@ env kcgienv main +test @@ -33,8 +33,9 @@ LDFLAGS_DEPS != pkg-config --libs kcgi kcgi-html CFLAGS=-DLOG_INFO $(CFLAGS_DEPS) LDFLAGS=-static $(LDFLAGS_DEPS) -.if make(debug) -CFLAGS+=-O0 -g -W -Wall -Wextra -DTEMPLATE_DIR='"template"' +.if make(debug) || make(test) +PWD!=pwd +CFLAGS+=-O0 -g -W -Wall -Wextra -DDATA_DIR='"$(PWD)/test-data"' -DTEMPLATE_DIR='"template"' .else CFLAGS+=-O2 .endif @@ -49,6 +50,9 @@ all: main main: mime.o url.o template.o file.o http.o download.o browse.o main.o $(CC) -o $@ $> $(LDFLAGS) +test: mime.o url.o template.o file.o http.o download.o browse.o test.o + $(CC) -o $@ $> $(LDFLAGS) + env: env.o $(CC) -o $@ $> $(LDFLAGS) @@ -72,7 +76,7 @@ install: gzip -fk /var/www/vault-static/fontawesome-6.5.1/webfonts/fa-solid-900.woff2 clean: - rm -f *.o *.BAK main + rm -f *.o *.BAK main test links: links http://localhost/cgi-bin/vault @@ -136,7 +136,7 @@ build_file_list(struct kreq * r, const char *request_dir) return 0; } -const char *const template_keys[] = {"icon", "name", "url", "size"}; +static const char *const template_keys[] = {"icon", "name", "url", "size"}; /* * data required in the template function @@ -203,36 +203,66 @@ template_callback(size_t index, void *arg) return 1; } -void +struct http_ret browse(struct kreq * r) { size_t url_len; char current_dir[PATH_MAX]; struct file *file; struct page_template *tmpl; + struct http_ret ret; struct khtmlreq html; struct ktemplate template; struct template_data data; - /* check that the requested URL can be safely processed */ - if (!check_request_path(r->path, r->suffix)) - http_exit(r, KHTTP_400, "browse: Invalid request path"); + /* initialize vars */ + tmpl = NULL; + + /* initialize return structure for success */ + ret = (struct http_ret) { + KHTTP_200, "" + }; + /* check that the requested URL can be safely processed */ + if (!check_request_path(r->path, r->suffix)) { + ret = (struct http_ret) { + KHTTP_400, + "browse: Invalid request path" + }; + goto end; + } /* list requested directory content */ url_len = url_build(current_dir, PATH_MAX, DATA_DIR, r->path, NULL); - if (url_len == 0) - http_exit(r, KHTTP_404, "browse: Unable to build data path"); - if (url_len >= PATH_MAX) - http_exit(r, KHTTP_414, NULL); - if (build_file_list(r, current_dir) < 0) - http_exit(r, KHTTP_500, "browse: Unable to build file list"); - + if (url_len == 0) { + ret = (struct http_ret) { + KHTTP_404, + "browse: Unable to build data path" + }; + goto end; + } + if (url_len >= PATH_MAX) { + ret = (struct http_ret) { + KHTTP_414, "" + }; + goto end; + } + if (build_file_list(r, current_dir) < 0) { + ret = (struct http_ret) { + KHTTP_500, + "browse: Unable to build file list" + }; + goto end; + } /* read template */ tmpl = page_template_new(BROWSE_URL); - if (tmpl == NULL) - http_exit(r, KHTTP_500, "browse: unable to read template"); - + if (tmpl == NULL) { + ret = (struct http_ret) { + 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, r->mime); @@ -261,6 +291,7 @@ browse(struct kreq * r) K_OK(khtml_close(&html), r); /* free page template and file list */ +end: page_template_free(tmpl); file = NULL; while (!SLIST_EMPTY(&list)) { @@ -269,4 +300,6 @@ browse(struct kreq * r) file_free(file); file = NULL; } + + return ret; } @@ -39,6 +39,6 @@ * Print a browse page that allows to browse through the folder hierarchy. * The KCGI request is required. */ -void browse(struct kreq *); +struct http_ret browse(struct kreq *); #endif /* BROWSE_H */ @@ -76,94 +76,140 @@ build_download_url(struct kreq * r, struct file * file) } static char * -strsplit(char * str, char c) { - char *right, *result; - size_t right_len; +strsplit(char *str, char c) +{ + char *right, *result; + size_t right_len; right = strrchr(str, c); - if(!right || right == str) + if (!right || right == str) return NULL; - // move right part to new buffer + /* move right part to new buffer */ right_len = strlen(right); result = malloc(sizeof(char) * right_len); - if(result == NULL) + if (result == NULL) return NULL; - if(strlcpy(result, right + 1, right_len) >= right_len) { + if (strlcpy(result, right + 1, right_len) >= right_len) { free(result); return NULL; } - - // remove right part from src buffer + /* remove right part from src buffer */ *right = '\0'; return result; } -void +struct http_ret download(struct kreq * r) { char *file_name; void *buffer; - struct file* f; - int fd; + struct file *f; + int fd; char file_path[PATH_MAX], request_path[PATH_MAX]; size_t path_size; + struct http_ret ret; + + /* initialize empty file and return struct to success */ + f = NULL; + ret = (struct http_ret) { + KHTTP_200, "" + }; /* check that the requested URL can be safely processed */ - 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 */ - 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); + if (strlen(r->path) == 0 || !check_request_path(r->path, r->suffix)) { + ret = (struct http_ret) { + KHTTP_400, + "download: invalid request path" + }; + goto end; } - else { - if(snprintf(request_path, sizeof(request_path), "%s", r->path) >= (int) sizeof(request_path)) - http_exit(r, KHTTP_414, NULL); + /* build requested file path, with suffix or without */ + if (strlen(r->suffix) > 0) { + if (snprintf(request_path, sizeof(request_path), "%s.%s", r->path, r->suffix) + >= (int) sizeof(request_path)) { + ret = (struct http_ret) { + KHTTP_414, + "download: request too long" + }; + goto end; + } + } else { + if (snprintf(request_path, sizeof(request_path), "%s", r->path) + >= (int) sizeof(request_path)) { + ret = (struct http_ret) { + KHTTP_414, + "download: request too long" + }; + goto end; + } } 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"); - if (path_size >= PATH_MAX) - http_exit(r, KHTTP_414, NULL); - + if (path_size == 0) { + ret = (struct http_ret) { + KHTTP_404, + "download: unable to build file path" + }; + goto end; + } + if (path_size >= PATH_MAX) { + ret = (struct http_ret) { + KHTTP_414, + "download: request too long" + }; + goto end; + } /* build file metadata */ file_name = strsplit(request_path, '/'); - if(file_name == NULL) - f = file_new("/", 1, request_path, strlen(request_path)); /* ROOT file */ + 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 */ + strlen(file_name)); + free(file_name);/* copied in file_new */ + } + if (f == NULL) { + ret = (struct http_ret) { + KHTTP_404, + "download: file metadata failure" + }; + goto end; } - 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"); + if (fd < 0) { + ret = (struct http_ret) { + KHTTP_404, + "download: unable to open file" + }; + goto end; + } /* mmap does not work with empty file: st_size = 0 */ - if(f->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"); + if (buffer == MAP_FAILED) { + ret = (struct http_ret) { + KHTTP_500, + "download: mmap failed" + }; + goto end; + } } - /* write the file */ http_open_file(r, KHTTP_200, f); - if(f->size > 0) + if (f->size > 0) K_OK(khttp_write(r, (const char *) buffer, f->size), r); /* cleanup */ - if(f->size > 0) +end: + if (f != NULL && f->size > 0) munmap(buffer, f->size); - free(f); + if (f != NULL) + free(f); + + return ret; } @@ -49,6 +49,6 @@ build_download_url(struct kreq *, struct file *); * Return a requested file. * The KCGI request is required. */ -void download(struct kreq *); +struct http_ret download(struct kreq *); #endif /* DOWNLOAD_H */ @@ -36,6 +36,14 @@ #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. */ @@ -55,6 +55,7 @@ main(void) { enum kcgi_err parse_err; struct kreq r; + struct http_ret ret; if (kutil_openlog(LOG_FILE) == 0) http_exit(NULL, KHTTP_500, "Unable to open %s", LOG_FILE); @@ -85,15 +86,19 @@ main(void) case PAGE_BROWSE: if (r.mime != KMIME_TEXT_HTML) http_exit(&r, KHTTP_406, NULL); /* Not Acceptable */ - browse(&r); + ret = browse(&r); break; case PAGE_DOWNLOAD: - download(&r); + ret = download(&r); break; default: http_exit(&r, KHTTP_404, NULL); } khttp_free(&r); + + if (ret.code >= KHTTP_400) + http_exit(&r, ret.code, ret.message); + return EXIT_SUCCESS; } diff --git a/test-data/Folder/b.txt b/test-data/Folder/b.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test-data/Folder/b.txt diff --git a/test-data/a.txt b/test-data/a.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test-data/a.txt @@ -0,0 +1,74 @@ +#include <stdio.h> + +#include "browse.h" +#include "http.h" + +/* minunit, see https://jera.com/techinfo/jtns/jtn002 */ +#define mu_assert(message, test) do { if (!(test)) return message; } while (0) +#define mu_run_test(test) do { char* message = test(); tests_run++; \ + if(message) return message; } while (0) + +int tests_run; + +static char * +test_browse_invalid_traversal() +{ + struct kreq r; + struct http_ret ret; + + /* attempt to traverse out of DATA_DIR... */ + r = (struct kreq) { + .path = "..", + .suffix = "", + .mime = KMIME_TEXT_HTML + }; + ret = browse(&r); + + /* ...should return an error */ + mu_assert("error, browse allowed invalid traversal!", + ret.code >= KHTTP_400); + + return 0; +} + +static char * +test_browse_path_too_long() +{ + struct kreq r; + struct http_ret ret; + + /* a lengthy path should cause URL overflow... */ + 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", + .suffix = "", + .mime = KMIME_TEXT_HTML + }; + ret = browse(&r); + + mu_assert("error, browse allowed invalid traversal!", + ret.code >= KHTTP_400); + + return 0; +} + +static char * +all_tests() +{ + mu_run_test(test_browse_invalid_traversal); + mu_run_test(test_browse_path_too_long); + return 0; +} + +int +main(void) +{ + char *result = all_tests(); + if (result != 0) { + printf("%s\n", result); + } else { + printf("ALL TESTS PASSED\n"); + } + printf("Tests run: %d\n", tests_run); + + return result != 0; +} |