summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Douillet <vincent@vdouillet.fr>2024-05-13 20:44:07 +0200
committerVincent Douillet <vincent@vdouillet.fr>2024-05-13 22:01:09 +0200
commit10eb77f9323110c55f88195c5b8207eb524baa73 (patch)
tree940c83fc095bad3e63045bc6fbe9ada8e4276135
parent0effd40bad7ae753102e7040159afd76be4e8732 (diff)
add unit tests
-rw-r--r--.gitignore1
-rw-r--r--Makefile10
-rw-r--r--browse.c63
-rw-r--r--browse.h2
-rw-r--r--download.c136
-rw-r--r--download.h2
-rw-r--r--http.h8
-rw-r--r--main.c9
-rw-r--r--test-data/Folder/b.txt0
-rw-r--r--test-data/a.txt0
-rw-r--r--test.c74
11 files changed, 238 insertions, 67 deletions
diff --git a/.gitignore b/.gitignore
index 39313a7..6418add 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
env
kcgienv
main
+test
diff --git a/Makefile b/Makefile
index 11e3aef..f24297e 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/browse.c b/browse.c
index 2bb08b6..51f41dd 100644
--- a/browse.c
+++ b/browse.c
@@ -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;
}
diff --git a/browse.h b/browse.h
index 399bdd9..68d8c45 100644
--- a/browse.h
+++ b/browse.h
@@ -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 */
diff --git a/download.c b/download.c
index af490e1..f4a1881 100644
--- a/download.c
+++ b/download.c
@@ -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;
}
diff --git a/download.h b/download.h
index 3d9943a..94daff4 100644
--- a/download.h
+++ b/download.h
@@ -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 */
diff --git a/http.h b/http.h
index 1112f74..5f09c88 100644
--- a/http.h
+++ b/http.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.
*/
diff --git a/main.c b/main.c
index 7a203c8..0b8e867 100644
--- a/main.c
+++ b/main.c
@@ -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
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..390cbc3
--- /dev/null
+++ b/test.c
@@ -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;
+}