From 7a8582faac8f5267eac1e834c13776568b5a8fab Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 14:26:07 +0900 Subject: [PATCH 01/22] Change template generation: output used filenames --- src/http.c | 3 ++- src/http_template.c | 17 +++++++++++++++- src/http_template.h | 16 ++++++++++----- src/test.c | 47 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/http.c b/src/http.c index f79797d..08165db 100644 --- a/src/http.c +++ b/src/http.c @@ -162,7 +162,8 @@ char *c_simple_http_request_response( char *generated_buf = c_simple_http_path_to_generated( stripped_path ? stripped_path : request_path, templates, - &generated_size); + &generated_size, + NULL); // TODO Use the output parameter "filenames list" here. if (!generated_buf || generated_size == 0) { fprintf(stderr, "ERROR Unable to generate response html for path \"%s\"!\n", diff --git a/src/http_template.c b/src/http_template.c index 510ee8b..2a47a3d 100644 --- a/src/http_template.c +++ b/src/http_template.c @@ -96,10 +96,14 @@ int c_simple_http_internal_ends_with_FILE(const char *c_string) { char *c_simple_http_path_to_generated( const char *path, const C_SIMPLE_HTTP_HTTPTemplates *templates, - size_t *output_buf_size) { + size_t *output_buf_size, + SDArchiverLinkedList **files_list_out) { if (output_buf_size) { *output_buf_size = 0; } + if (files_list_out) { + *files_list_out = simple_archiver_list_init(); + } size_t path_len_size_t = strlen(path) + 1; if (path_len_size_t > 0xFFFFFFFF) { fprintf(stderr, "ERROR: Path string is too large!\n"); @@ -139,6 +143,11 @@ char *c_simple_http_path_to_generated( } html_buf[html_file_size] = 0; html_buf_size = (size_t)html_file_size; + if (files_list_out) { + char *html_filename_copy = malloc(strlen(html_filename) + 1); + strcpy(html_filename_copy, html_filename); + simple_archiver_list_add(*files_list_out, html_filename_copy, NULL); + } } else { char *stored_html = simple_archiver_hash_map_get(wrapped_hash_map->hash_map, "HTML", 5); @@ -258,6 +267,12 @@ char *c_simple_http_path_to_generated( value_c_str); return NULL; } + if (files_list_out) { + char *variable_filename = malloc(strlen(value_c_str) + 1); + strcpy(variable_filename, value_c_str); + simple_archiver_list_add( + *files_list_out, variable_filename, NULL); + } } else { // Variable data is "value_c_str". template_node = diff --git a/src/http_template.h b/src/http_template.h index ae4586b..8aa12ba 100644 --- a/src/http_template.h +++ b/src/http_template.h @@ -22,14 +22,20 @@ // Standard library includes. #include -// Returns non-NULL on success, which must be free'd after use. -// Takes a path string and templates and returns the generated HTML. -// If "output_buf_size" is non-NULL, it will be set to the size of the returned -// buffer. +// Third-party includes. +#include + +// Returns non-NULL on success, which must be free'd after use. Takes a path +// string and templates and returns the generated HTML. If "output_buf_size" is +// non-NULL, it will be set to the size of the returned buffer. If +// "files_list_out" is non-NULL, then the pointer will be set to a newly +// allocated linked-list containing filenames used in generating the HTML. This +// newly allocated linked-list must be freed after use. char *c_simple_http_path_to_generated( const char *path, const C_SIMPLE_HTTP_HTTPTemplates *templates, - size_t *output_buf_size); + size_t *output_buf_size, + SDArchiverLinkedList **files_list_out); #endif diff --git a/src/test.c b/src/test.c index 66c4d0e..241b62e 100644 --- a/src/test.c +++ b/src/test.c @@ -5,6 +5,7 @@ #include // Local includes. +#include "SimpleArchiver/src/data_structures/linked_list.h" #include "config.h" #include "http_template.h" #include "http.h" @@ -89,6 +90,15 @@ void test_internal_cleanup_delete_temporary_file(const char **filename) { } } +int test_internal_check_matching_string_in_list(void *value, void *ud) { + if (value && ud) { + if (strcmp(value, ud) == 0) { + return 1; + } + } + return 0; +} + int main(void) { // Test config. { @@ -252,12 +262,18 @@ int main(void) { size_t output_buf_size; + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *filenames_list = NULL; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) - char *buf = c_simple_http_path_to_generated("/", &config, &output_buf_size); + char *buf = c_simple_http_path_to_generated( + "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); ASSERT_TRUE(strcmp(buf, "

Test

") == 0); CHECK_TRUE(output_buf_size == 13); + CHECK_TRUE(filenames_list->count == 0); simple_archiver_helper_cleanup_c_string(&buf); + simple_archiver_list_free(&filenames_list); __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) const char *test_http_template_filename2 = @@ -288,7 +304,8 @@ int main(void) { ); ASSERT_TRUE(config.paths != NULL); - buf = c_simple_http_path_to_generated("/", &config, &output_buf_size); + buf = c_simple_http_path_to_generated( + "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); printf("%s\n", buf); ASSERT_TRUE( @@ -297,7 +314,9 @@ int main(void) { "

Some text.


More text.

") == 0); CHECK_TRUE(output_buf_size == 46); + CHECK_TRUE(filenames_list->count == 0); simple_archiver_helper_cleanup_c_string(&buf); + simple_archiver_list_free(&filenames_list); __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) const char *test_http_template_filename3 = @@ -351,7 +370,8 @@ int main(void) { ); ASSERT_TRUE(config.paths != NULL); - buf = c_simple_http_path_to_generated("/", &config, &output_buf_size); + buf = c_simple_http_path_to_generated( + "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); printf("%s\n", buf); ASSERT_TRUE( @@ -360,7 +380,14 @@ int main(void) { "

testVar text.


testVar2 text.

") == 0); CHECK_TRUE(output_buf_size == 53); + CHECK_TRUE(filenames_list->count == 1); + CHECK_TRUE(simple_archiver_list_get( + filenames_list, + test_internal_check_matching_string_in_list, + (void*)test_http_template_html_filename) + != NULL); simple_archiver_helper_cleanup_c_string(&buf); + simple_archiver_list_free(&filenames_list); __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) const char *test_http_template_filename4 = @@ -434,7 +461,8 @@ int main(void) { ); ASSERT_TRUE(config.paths != NULL); - buf = c_simple_http_path_to_generated("/", &config, &output_buf_size); + buf = c_simple_http_path_to_generated( + "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); printf("%s\n", buf); ASSERT_TRUE( @@ -443,6 +471,17 @@ int main(void) { "

some test text in test var file.

") == 0); CHECK_TRUE(output_buf_size == 43); + CHECK_TRUE(filenames_list->count == 2); + CHECK_TRUE(simple_archiver_list_get( + filenames_list, + test_internal_check_matching_string_in_list, + (void*)test_http_template_html_filename2) + != NULL); + CHECK_TRUE(simple_archiver_list_get( + filenames_list, + test_internal_check_matching_string_in_list, + (void*)test_http_template_html_var_filename) + != NULL); simple_archiver_helper_cleanup_c_string(&buf); } From 55d3a61c0cad20c16d1fccd338ec6b41e15834e0 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 14:26:50 +0900 Subject: [PATCH 02/22] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 21a9836..42d57bb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /objs/ /unit_test /build*/ +/.cache/ +/compile_commands.json From ffc8e99f73fc1e2870d133623688d0d0a53995e2 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 14:42:41 +0900 Subject: [PATCH 03/22] Add new arg for program: --enable-cache-dir= --- src/arg_parse.c | 39 +++++++++++++++++++++++++++++++++++++++ src/arg_parse.h | 3 +++ 2 files changed, 42 insertions(+) diff --git a/src/arg_parse.c b/src/arg_parse.c index 7250e36..ce626e0 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -21,6 +21,14 @@ #include #include +// libc includes. +#include +#include +#include + +// Posix includes. +#include + void print_usage(void) { puts("Usage:"); puts(" -p | --port "); @@ -30,6 +38,7 @@ void print_usage(void) { puts(" For example: --req-header-to-print=User-Agent"); puts(" Note that this option is case-insensitive"); puts(" --enable-reload-config-on-change"); + puts(" --enable-cache-dir="); } Args parse_args(int32_t argc, char **argv) { @@ -69,6 +78,36 @@ Args parse_args(int32_t argc, char **argv) { } } else if (strcmp(argv[0], "--enable-reload-config-on-change") == 0) { args.flags |= 2; + } else if (strncmp(argv[0], "--enable-cache-dir=", 19) == 0) { + args.cache_dir = argv[0] + 19; + // Check if it actually is an existing directory. + DIR *d = opendir(args.cache_dir); + if (d == NULL) { + if (errno == ENOENT) { + printf( + "Directory \"%s\" doesn't exist, creating it...\n", + args.cache_dir); + int ret = mkdir( + args.cache_dir, + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret == -1) { + fprintf( + stderr, + "ERROR Failed to create new directory (errno %d)\n", + errno); + exit(1); + } + } else { + fprintf( + stderr, + "ERROR Failed to open directory \"%s\" (errno %d)!\n", + args.cache_dir, + errno); + exit(1); + } + } else { + printf("Directory \"%s\" exists.\n", args.cache_dir); + } } else { puts("ERROR: Invalid args!\n"); print_usage(); diff --git a/src/arg_parse.h b/src/arg_parse.h index 8f56fcd..c14ee90 100644 --- a/src/arg_parse.h +++ b/src/arg_parse.h @@ -34,6 +34,9 @@ typedef struct Args { const char *config_file; // Needs to be free'd. SDArchiverLinkedList *list_of_headers_to_log; + // Non-NULL if cache-dir is specified and cache is to be used. + // Does not need to be free'd since it points to a string in argv. + const char *cache_dir; } Args; void print_usage(void); From 6d5a1d1bdd13b236c86b38edc3433f52e45e36d7 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 14:43:28 +0900 Subject: [PATCH 04/22] Minor fix --- src/arg_parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arg_parse.c b/src/arg_parse.c index ce626e0..3a0c090 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -109,7 +109,7 @@ Args parse_args(int32_t argc, char **argv) { printf("Directory \"%s\" exists.\n", args.cache_dir); } } else { - puts("ERROR: Invalid args!\n"); + fprintf(stderr, "ERROR: Invalid args!\n"); print_usage(); exit(1); } From 6f845a7185c98a069cbfd30759ad928cf4ffe3ea Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 15:39:35 +0900 Subject: [PATCH 05/22] Add helper to create string parts and combine them --- src/helpers.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/helpers.h | 21 +++++++++++++ src/test.c | 18 ++++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/helpers.c b/src/helpers.c index c8e49d2..df77357 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -18,6 +18,93 @@ // Standard library includes. #include +#include +#include + +int c_simple_http_internal_get_string_part_full_size(void *data, void *ud) { + C_SIMPLE_HTTP_String_Part *part = data; + size_t *count = ud; + + *count += part->size - 1; + + return 0; +} + +int c_simple_http_internal_combine_string_parts_from_list(void *data, void *ud) { + C_SIMPLE_HTTP_String_Part *part = data; + void **ptrs = ud; + char *buf = ptrs[0]; + size_t *current_count = ptrs[1]; + const size_t *total_count = ptrs[2]; + + if (*current_count + part->size - 1 > *total_count) { + fprintf(stderr, "ERROR Invalid state combining string parts!\n"); + return 1; + } + + memcpy(buf + *current_count, part->buf, part->size - 1); + + *current_count += part->size - 1; + + return 0; +} + +void c_simple_http_cleanup_string_part(void *data) { + C_SIMPLE_HTTP_String_Part *part = data; + if (part) { + if (part->buf) { + free(part->buf); + part->buf = NULL; + } + free(part); + } +} + +void c_simple_http_add_string_part( + SDArchiverLinkedList *list, const char *c_string, size_t extra) { + C_SIMPLE_HTTP_String_Part *string_part = + malloc(sizeof(C_SIMPLE_HTTP_String_Part)); + + string_part->size = strlen(c_string) + 1; + string_part->buf = malloc(string_part->size); + memcpy(string_part->buf, c_string, string_part->size); + + string_part->extra = extra; + + simple_archiver_list_add( + list, string_part, c_simple_http_cleanup_string_part); +} + +char *c_simple_http_combine_string_parts(const SDArchiverLinkedList *list) { + if (!list || list->count == 0) { + return NULL; + } + + size_t count = 0; + + simple_archiver_list_get( + list, c_simple_http_internal_get_string_part_full_size, &count); + + char *buf = malloc(count + 1); + size_t current_count = 0; + + void **ptrs = malloc(sizeof(void*) * 3); + ptrs[0] = buf; + ptrs[1] = ¤t_count; + ptrs[2] = &count; + + if (simple_archiver_list_get( + list, c_simple_http_internal_combine_string_parts_from_list, ptrs)) { + free(buf); + return NULL; + } + + free(ptrs); + + buf[count] = 0; + + return buf; +} void c_simple_http_helper_to_lowercase_in_place(char *buf, size_t size) { for (size_t idx = 0; idx < size; ++idx) { diff --git a/src/helpers.h b/src/helpers.h index c6743fb..a53a947 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -20,6 +20,27 @@ // Standard library includes. #include +// Third-party includes. +#include + +typedef struct C_SIMPLE_HTTP_String_Part { + char *buf; + size_t size; + size_t extra; +} C_SIMPLE_HTTP_String_Part; + +/// Assumes "data" is a C_SIMPLE_HTTP_String_Part, "data" was malloced, and +/// "data->buf" was malloced. +void c_simple_http_cleanup_string_part(void *data); + +/// Puts a malloced instance of String_Part into the list. +/// The given c_string will be copied into a newly malloced buffer. +void c_simple_http_add_string_part( + SDArchiverLinkedList *list, const char *c_string, size_t extra); + +/// Combines all String_Parts in the list and returns it as a single buffer. +char *c_simple_http_combine_string_parts(const SDArchiverLinkedList *list); + /// Modifies "buf" in-place to change all uppercase to lowercase alpha chars. void c_simple_http_helper_to_lowercase_in_place(char *buf, size_t size); diff --git a/src/test.c b/src/test.c index 241b62e..afcc0c9 100644 --- a/src/test.c +++ b/src/test.c @@ -5,14 +5,15 @@ #include // Local includes. -#include "SimpleArchiver/src/data_structures/linked_list.h" #include "config.h" +#include "helpers.h" #include "http_template.h" #include "http.h" // Third party includes. #include #include +#include static int32_t checks_checked = 0; static int32_t checks_passed = 0; @@ -542,6 +543,21 @@ int main(void) { free(stripped_path_buf); } + // Test helpers. + { + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *list = simple_archiver_list_init(); + + c_simple_http_add_string_part(list, "one\n", 0); + c_simple_http_add_string_part(list, "two\n", 0); + c_simple_http_add_string_part(list, "three\n", 0); + + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = c_simple_http_combine_string_parts(list); + ASSERT_TRUE(buf); + ASSERT_TRUE(strcmp(buf, "one\ntwo\nthree\n") == 0); + } + RETURN() } From 8974c7b31f350aa688a19761fa7f103e0c34df0a Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 15:44:04 +0900 Subject: [PATCH 06/22] Add WIP html_cache --- CMakeLists.txt | 1 + Makefile | 4 +++- src/html_cache.c | 36 ++++++++++++++++++++++++++++++++++++ src/html_cache.h | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/html_cache.c create mode 100644 src/html_cache.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f4b7de8..f20ee8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(c_simple_http_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/config.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/http_template.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/helpers.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/html_cache.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/helpers.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/data_structures/linked_list.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/data_structures/hash_map.c" diff --git a/Makefile b/Makefile index b076b58..a3ee88e 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,8 @@ HEADERS = \ src/http.h \ src/config.h \ src/http_template.h \ - src/helpers.h + src/helpers.h \ + src/html_cache.h SOURCES = \ src/main.c \ @@ -55,6 +56,7 @@ SOURCES = \ src/config.c \ src/http_template.c \ src/helpers.c \ + src/html_cache.c \ third_party/SimpleArchiver/src/helpers.c \ third_party/SimpleArchiver/src/data_structures/linked_list.c \ third_party/SimpleArchiver/src/data_structures/hash_map.c \ diff --git a/src/html_cache.c b/src/html_cache.c new file mode 100644 index 0000000..7d04da4 --- /dev/null +++ b/src/html_cache.c @@ -0,0 +1,36 @@ +// ISC License +// +// Copyright (c) 2024 Stephen Seo +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +// Third-party includes. +//#include + +char *c_simple_http_path_to_cache_filename(const char *path) { +// SDArchiverLinkedList *parts = simple_archiver_list_init(); + // TODO + + return 0; +} + +int c_simple_http_cache_path( + const char *path, + const char *config_filename, + const char *cache_dir, + char **buf_out) { + // TODO + return 0; +} + +// vim: et ts=2 sts=2 sw=2 diff --git a/src/html_cache.h b/src/html_cache.h new file mode 100644 index 0000000..a382c34 --- /dev/null +++ b/src/html_cache.h @@ -0,0 +1,39 @@ +// ISC License +// +// Copyright (c) 2024 Stephen Seo +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef SEODISPARATE_COM_C_SIMPLE_HTTP_HTML_CACHE_H_ +#define SEODISPARATE_COM_C_SIMPLE_HTTP_HTML_CACHE_H_ + +/// Must be free'd if non-NULL. +char *c_simple_http_path_to_cache_filename(const char *path); + +/// Must be free'd if non-NULL. +char *c_simple_http_cache_filename_to_path(const char *cache_filename); + +/// Given a "path", returns non-zero if the cache is invalidated. +/// "config_filename" is required to check its timestamp. "cache_dir" is +/// required to actually get the cache file to check against. "buf_out" will be +/// populated if non-NULL, and will either be fetched from the cache or from the +/// config (using http_template). Note that "buf_out" will point to a c-string. +int c_simple_http_cache_path( + const char *path, + const char *config_filename, + const char *cache_dir, + char **buf_out); + +#endif + +// vim: et ts=2 sts=2 sw=2 From f8b2f6355401b7a57e32d338328eecaa25fe0722 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 22 Sep 2024 19:09:07 +0900 Subject: [PATCH 07/22] Fix potential memory leak Fixes https://git.seodisparate.com/stephenseo/c_simple_http/issues/5 --- src/helpers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers.c b/src/helpers.c index df77357..4744c49 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -96,6 +96,7 @@ char *c_simple_http_combine_string_parts(const SDArchiverLinkedList *list) { if (simple_archiver_list_get( list, c_simple_http_internal_combine_string_parts_from_list, ptrs)) { free(buf); + free(ptrs); return NULL; } From 0956ae165e645fb21b3a46c4ab53aeacd7343fe2 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 23 Sep 2024 14:09:25 +0900 Subject: [PATCH 08/22] Refactor http_template to use String_Part --- src/helpers.c | 19 ++++- src/helpers.h | 2 + src/http_template.c | 185 ++++++++++++++------------------------------ 3 files changed, 77 insertions(+), 129 deletions(-) diff --git a/src/helpers.c b/src/helpers.c index 4744c49..b2f2448 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -30,13 +30,19 @@ int c_simple_http_internal_get_string_part_full_size(void *data, void *ud) { return 0; } -int c_simple_http_internal_combine_string_parts_from_list(void *data, void *ud) { +int c_simple_http_internal_combine_string_parts_from_list(void *data, + void *ud) { C_SIMPLE_HTTP_String_Part *part = data; void **ptrs = ud; char *buf = ptrs[0]; size_t *current_count = ptrs[1]; const size_t *total_count = ptrs[2]; + if (!part->buf || part->size == 0) { + // Empty string part, just continue. + return 0; + } + if (*current_count + part->size - 1 > *total_count) { fprintf(stderr, "ERROR Invalid state combining string parts!\n"); return 1; @@ -49,12 +55,21 @@ int c_simple_http_internal_combine_string_parts_from_list(void *data, void *ud) return 0; } +void c_simple_http_cleanup_attr_string_part(C_SIMPLE_HTTP_String_Part **part) { + if (part && *part) { + if ((*part)->buf) { + free((*part)->buf); + } + free(*part); + } + *part = NULL; +} + void c_simple_http_cleanup_string_part(void *data) { C_SIMPLE_HTTP_String_Part *part = data; if (part) { if (part->buf) { free(part->buf); - part->buf = NULL; } free(part); } diff --git a/src/helpers.h b/src/helpers.h index a53a947..6c4554c 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -29,6 +29,8 @@ typedef struct C_SIMPLE_HTTP_String_Part { size_t extra; } C_SIMPLE_HTTP_String_Part; +void c_simple_http_cleanup_attr_string_part(C_SIMPLE_HTTP_String_Part **); + /// Assumes "data" is a C_SIMPLE_HTTP_String_Part, "data" was malloced, and /// "data->buf" was malloced. void c_simple_http_cleanup_string_part(void *data); diff --git a/src/http_template.c b/src/http_template.c index 2a47a3d..b651018 100644 --- a/src/http_template.c +++ b/src/http_template.c @@ -25,53 +25,8 @@ #include #include -typedef struct C_SIMPLE_HTTP_INTERNAL_Template_Node { - char *html; - size_t html_size; - union { - size_t orig_end_idx; - size_t html_capacity; - }; -} C_SIMPLE_HTTP_INTERNAL_Template_Node; - -void c_simple_http_internal_free_template_node(void *data) { - C_SIMPLE_HTTP_INTERNAL_Template_Node *node = data; - if (node) { - if (node->html) { - free(node->html); - } - free(node); - } -} - -void c_simple_http_internal_cleanup_template_node( - C_SIMPLE_HTTP_INTERNAL_Template_Node **node) { - if (node && *node) { - c_simple_http_internal_free_template_node(*node); - *node = NULL; - } -} - -int c_simple_http_internal_get_final_size_fn(void *data, void *ud) { - size_t *final_size = ud; - C_SIMPLE_HTTP_INTERNAL_Template_Node *node = data; - *final_size += node->html_size; - return 0; -} - -int c_simple_http_internal_fill_buf_fn(void *data, void *ud) { - C_SIMPLE_HTTP_INTERNAL_Template_Node *node = data; - C_SIMPLE_HTTP_INTERNAL_Template_Node *to_fill = ud; - if (to_fill->html_capacity < to_fill->html_size + node->html_size) { - return 1; - } - memcpy( - to_fill->html + to_fill->html_size, - node->html, - node->html_size); - to_fill->html_size += node->html_size; - return 0; -} +// Local includes. +#include "helpers.h" /// Returns 0 if "c_string" ends with "_FILE". int c_simple_http_internal_ends_with_FILE(const char *c_string) { @@ -162,13 +117,12 @@ char *c_simple_http_path_to_generated( // At this point, html_buf contains the raw HTML as a C-string. __attribute__((cleanup(simple_archiver_list_free))) - SDArchiverLinkedList *template_html_list = simple_archiver_list_init(); + SDArchiverLinkedList *string_part_list = simple_archiver_list_init(); size_t idx = 0; size_t last_template_idx = 0; - __attribute__((cleanup(c_simple_http_internal_cleanup_template_node))) - C_SIMPLE_HTTP_INTERNAL_Template_Node *template_node = NULL; + C_SIMPLE_HTTP_String_Part string_part; size_t delimeter_count = 0; @@ -184,22 +138,23 @@ char *c_simple_http_path_to_generated( if (delimeter_count >= 3) { delimeter_count = 0; state |= 1; - if (template_html_list->count != 0) { - C_SIMPLE_HTTP_INTERNAL_Template_Node *last_node = - template_html_list->tail->prev->data; - last_template_idx = last_node->orig_end_idx; + if (string_part_list->count != 0) { + C_SIMPLE_HTTP_String_Part *last_part = + string_part_list->tail->prev->data; + last_template_idx = last_part->extra; } - template_node = malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); - template_node->html_size = idx - last_template_idx - 2; - template_node->html = malloc(template_node->html_size); + string_part.size = idx - last_template_idx - 1; + string_part.buf = malloc(string_part.size); memcpy( - template_node->html, + string_part.buf, html_buf + last_template_idx, - template_node->html_size); - template_node->orig_end_idx = idx + 1; - simple_archiver_list_add(template_html_list, template_node, - c_simple_http_internal_free_template_node); - template_node = NULL; + string_part.size); + string_part.buf[string_part.size - 1] = 0; + string_part.extra = idx + 1; + c_simple_http_add_string_part(string_part_list, + string_part.buf, + string_part.extra); + free(string_part.buf); } } else { delimeter_count = 0; @@ -212,14 +167,14 @@ char *c_simple_http_path_to_generated( if (delimeter_count >= 3) { delimeter_count = 0; state &= 0xFFFFFFFE; - C_SIMPLE_HTTP_INTERNAL_Template_Node *last_node = - template_html_list->tail->prev->data; - size_t var_size = idx - 2 - last_node->orig_end_idx; + C_SIMPLE_HTTP_String_Part *last_part = + string_part_list->tail->prev->data; + size_t var_size = idx - 2 - last_part->extra; __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) char *var = malloc(var_size + 1); memcpy( var, - html_buf + last_node->orig_end_idx, + html_buf + last_part->extra, var_size); var[var_size] = 0; const char *value_c_str = @@ -252,14 +207,12 @@ char *c_simple_http_path_to_generated( value_c_str); return NULL; } - template_node = - malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); - template_node->html_size = (size_t)file_size; - template_node->html = malloc(template_node->html_size); - template_node->orig_end_idx = idx + 1; + string_part.size = (size_t)file_size + 1; + string_part.buf = malloc(string_part.size); + string_part.extra = idx + 1; - if (fread(template_node->html, - template_node->html_size, + if (fread(string_part.buf, + string_part.size - 1, 1, f) != 1) { @@ -267,6 +220,7 @@ char *c_simple_http_path_to_generated( value_c_str); return NULL; } + string_part.buf[string_part.size - 1] = 0; if (files_list_out) { char *variable_filename = malloc(strlen(value_c_str) + 1); strcpy(variable_filename, value_c_str); @@ -275,24 +229,21 @@ char *c_simple_http_path_to_generated( } } else { // Variable data is "value_c_str". - template_node = - malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); - size_t size = strlen(value_c_str); - template_node->html = malloc(size); - memcpy(template_node->html, value_c_str, size); - template_node->html_size = size; - template_node->orig_end_idx = idx + 1; + string_part.size = strlen(value_c_str) + 1; + string_part.buf = malloc(string_part.size); + memcpy(string_part.buf, value_c_str, string_part.size); + string_part.buf[string_part.size - 1] = 0; + string_part.extra = idx + 1; } } else { - template_node = - malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); - template_node->html = NULL; - template_node->html_size = 0; - template_node->orig_end_idx = idx + 1; + string_part.buf = NULL; + string_part.size = 0; + string_part.extra = idx + 1; } - simple_archiver_list_add(template_html_list, template_node, - c_simple_http_internal_free_template_node); - template_node = NULL; + c_simple_http_add_string_part(string_part_list, + string_part.buf, + string_part.extra); + free(string_part.buf); } } else { delimeter_count = 0; @@ -300,48 +251,28 @@ char *c_simple_http_path_to_generated( } } - if (template_html_list->count != 0) { - C_SIMPLE_HTTP_INTERNAL_Template_Node *last_node = - template_html_list->tail->prev->data; - if (idx > last_node->orig_end_idx) { - template_node = malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); - size_t size = idx - last_node->orig_end_idx; - template_node->html = malloc(size); - memcpy(template_node->html, html_buf + last_node->orig_end_idx, size); - template_node->html_size = size; - template_node->orig_end_idx = idx + 1; - simple_archiver_list_add(template_html_list, template_node, - c_simple_http_internal_free_template_node); - template_node = NULL; + if (string_part_list->count != 0) { + C_SIMPLE_HTTP_String_Part *last_part = + string_part_list->tail->prev->data; + if (idx > last_part->extra) { + string_part.size = idx - last_part->extra + 1; + string_part.buf = malloc(string_part.size); + memcpy(string_part.buf, html_buf + last_part->extra, string_part.size); + string_part.buf[string_part.size - 1] = 0; + string_part.extra = idx + 1; + c_simple_http_add_string_part(string_part_list, + string_part.buf, + string_part.extra); + free(string_part.buf); - last_node = template_html_list->tail->prev->data; + last_part = string_part_list->tail->prev->data; } - size_t final_size = 0; - simple_archiver_list_get( - template_html_list, - c_simple_http_internal_get_final_size_fn, - &final_size); - if (final_size == 0) { - fprintf(stderr, "ERROR final_size calculated as ZERO from templates!\n"); - return NULL; - } - C_SIMPLE_HTTP_INTERNAL_Template_Node to_fill; - to_fill.html = malloc(final_size + 1); - to_fill.html_size = 0; - to_fill.html_capacity = final_size; - if (simple_archiver_list_get( - template_html_list, - c_simple_http_internal_fill_buf_fn, - &to_fill) != NULL) { - fprintf(stderr, "ERROR internal issue processing final html buffer!\n"); - free(to_fill.html); - return NULL; - } - to_fill.html[final_size] = 0; + + char *combined_buf = c_simple_http_combine_string_parts(string_part_list); if (output_buf_size) { - *output_buf_size = final_size; + *output_buf_size = strlen(combined_buf); } - return to_fill.html; + return combined_buf; } else { // Prevent cleanup fn from "free"ing html_buf and return it verbatim. char *buf = html_buf; From abc61a5504e3b493c632bb27870eaae73dec13b3 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 23 Sep 2024 14:58:28 +0900 Subject: [PATCH 09/22] Impl. path-to-cache-filename, fixes to strip-path --- src/html_cache.c | 67 +++++++++++++++++++++++++++++++++++++++--- src/http.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ src/test.c | 49 +++++++++++++++++++++++++++++-- 3 files changed, 184 insertions(+), 7 deletions(-) diff --git a/src/html_cache.c b/src/html_cache.c index 7d04da4..cbf5f17 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -14,14 +14,73 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +// Standard library includes. +#include +#include +#include + // Third-party includes. -//#include +#include +#include + +// Local includes. +#include "http.h" +#include "helpers.h" char *c_simple_http_path_to_cache_filename(const char *path) { -// SDArchiverLinkedList *parts = simple_archiver_list_init(); - // TODO + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *stripped_path = c_simple_http_strip_path(path, strlen(path)); - return 0; + if (!stripped_path) { + return NULL; + } + + if (strcmp(stripped_path, "/") == 0) { + char *buf = malloc(5); + memcpy(buf, "ROOT", 5); + return buf; + } + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *parts = simple_archiver_list_init(); + + size_t idx = 0; + size_t prev_idx = 0; + const size_t path_len = strlen(stripped_path); + size_t size; + + for (; idx < path_len && stripped_path[idx] != 0; ++idx) { + if (stripped_path[idx] == '/') { + size = idx - prev_idx + 1; + char *temp_buf = malloc(size); + memcpy(temp_buf, stripped_path + prev_idx, size - 1); + temp_buf[size - 1] = 0; + c_simple_http_add_string_part(parts, temp_buf, 0); + free(temp_buf); + + temp_buf = malloc(5); + memcpy(temp_buf, "0x2F", 5); + c_simple_http_add_string_part(parts, temp_buf, 0); + free(temp_buf); + + prev_idx = idx + 1; + } + } + + if (idx > prev_idx) { + size = idx - prev_idx + 1; + char *temp_buf = malloc(size); + memcpy(temp_buf, stripped_path + prev_idx, size - 1); + temp_buf[size - 1] = 0; + c_simple_http_add_string_part(parts, temp_buf, 0); + free(temp_buf); + } + + if (prev_idx == 0) { + return stripped_path; + } else { + return c_simple_http_combine_string_parts(parts); + } } int c_simple_http_cache_path( diff --git a/src/http.c b/src/http.c index 08165db..ceccbcd 100644 --- a/src/http.c +++ b/src/http.c @@ -23,8 +23,10 @@ // Third party includes. #include +#include // Local includes +#include "SimpleArchiver/src/data_structures/priority_heap.h" #include "constants.h" #include "http_template.h" #include "helpers.h" @@ -193,6 +195,14 @@ char *c_simple_http_request_response( } char *c_simple_http_strip_path(const char *path, size_t path_size) { + if (path_size == 1 && path[0] == '/') { + // Edge case: root path. + char *buf = malloc(2); + buf[0] = '/'; + buf[1] = 0; + return buf; + } + size_t idx = 0; for (; idx < path_size && path[idx] != 0; ++idx) { if (path[idx] == '?' || path[idx] == '#') { @@ -204,7 +214,72 @@ char *c_simple_http_strip_path(const char *path, size_t path_size) { memcpy(stripped_path, path, idx); stripped_path[idx] = 0; + // Strip multiple '/' into one. + __attribute((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *parts = simple_archiver_list_init(); + + idx = 0; + size_t prev_idx = 0; + size_t size; + char *buf; + uint_fast8_t slash_visited = 0; + + for (; stripped_path[idx] != 0; ++idx) { + if (stripped_path[idx] == '/') { + if (slash_visited) { + // Intentionally left blank. + } else { + slash_visited = 1; + + if (idx > prev_idx) { + size = idx - prev_idx + 1; + buf = malloc(size); + memcpy(buf, stripped_path + prev_idx, size - 1); + buf[size - 1] = 0; + + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + } + } else { + if (slash_visited) { + buf = malloc(2); + buf[0] = '/'; + buf[1] = 0; + + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + + prev_idx = idx; + } + + slash_visited = 0; + } + } + + if (!slash_visited && idx > prev_idx) { + size = idx - prev_idx + 1; + buf = malloc(size); + memcpy(buf, stripped_path + prev_idx, size - 1); + buf[size - 1] = 0; + + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } else if (slash_visited && prev_idx == 0) { + buf = malloc(2); + buf[0] = '/'; + buf[1] = 0; + + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + + free(stripped_path); + + stripped_path = c_simple_http_combine_string_parts(parts); + // Strip trailing '/'. + idx = strlen(stripped_path); while (idx-- > 1) { if (stripped_path[idx] == '/' || stripped_path[idx] == 0) { stripped_path[idx] = 0; diff --git a/src/test.c b/src/test.c index afcc0c9..86cc92a 100644 --- a/src/test.c +++ b/src/test.c @@ -9,6 +9,7 @@ #include "helpers.h" #include "http_template.h" #include "http.h" +#include "html_cache.h" // Third party includes. #include @@ -308,7 +309,7 @@ int main(void) { buf = c_simple_http_path_to_generated( "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); - printf("%s\n", buf); + //printf("%s\n", buf); ASSERT_TRUE( strcmp( buf, @@ -374,7 +375,7 @@ int main(void) { buf = c_simple_http_path_to_generated( "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); - printf("%s\n", buf); + //printf("%s\n", buf); ASSERT_TRUE( strcmp( buf, @@ -465,7 +466,7 @@ int main(void) { buf = c_simple_http_path_to_generated( "/", &config, &output_buf_size, &filenames_list); ASSERT_TRUE(buf != NULL); - printf("%s\n", buf); + //printf("%s\n", buf); ASSERT_TRUE( strcmp( buf, @@ -519,28 +520,39 @@ int main(void) { free(stripped_path_buf); stripped_path_buf = c_simple_http_strip_path("/someurl/", 9); + //printf("stripped path: %s\n", stripped_path_buf); CHECK_STREQ(stripped_path_buf, "/someurl"); free(stripped_path_buf); stripped_path_buf = c_simple_http_strip_path("/someurl/////", 13); + //printf("stripped path: %s\n", stripped_path_buf); CHECK_STREQ(stripped_path_buf, "/someurl"); free(stripped_path_buf); stripped_path_buf = c_simple_http_strip_path("/someurl?key=value", 18); + //printf("stripped path: %s\n", stripped_path_buf); CHECK_STREQ(stripped_path_buf, "/someurl"); free(stripped_path_buf); stripped_path_buf = c_simple_http_strip_path("/someurl#client_data", 20); + //printf("stripped path: %s\n", stripped_path_buf); CHECK_STREQ(stripped_path_buf, "/someurl"); free(stripped_path_buf); stripped_path_buf = c_simple_http_strip_path("/someurl////?key=value", 22); + //printf("stripped path: %s\n", stripped_path_buf); CHECK_STREQ(stripped_path_buf, "/someurl"); free(stripped_path_buf); stripped_path_buf = c_simple_http_strip_path("/someurl///#client_data", 23); + //printf("stripped path: %s\n", stripped_path_buf); CHECK_STREQ(stripped_path_buf, "/someurl"); free(stripped_path_buf); + + stripped_path_buf = c_simple_http_strip_path("/someurl/////inner", 18); + //printf("stripped path: %s\n", stripped_path_buf); + CHECK_STREQ(stripped_path_buf, "/someurl/inner"); + free(stripped_path_buf); } // Test helpers. @@ -558,6 +570,37 @@ int main(void) { ASSERT_TRUE(strcmp(buf, "one\ntwo\nthree\n") == 0); } + // Test html_cache. + { + char *ret = c_simple_http_path_to_cache_filename("/"); + CHECK_TRUE(strcmp(ret, "ROOT") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("////"); + CHECK_TRUE(strcmp(ret, "ROOT") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/inner"); + CHECK_TRUE(strcmp(ret, "0x2Finner") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/inner////"); + CHECK_TRUE(strcmp(ret, "0x2Finner") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/outer/inner"); + CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/outer/inner////"); + CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/outer///inner"); + CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); + free(ret); + } + RETURN() } From 7cc0d624bef892000b8c4f81c0a54733f29eda0a Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 23 Sep 2024 15:10:39 +0900 Subject: [PATCH 10/22] Fix use-after-free in path-to-filename function --- src/html_cache.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/html_cache.c b/src/html_cache.c index cbf5f17..f851c09 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -77,7 +77,10 @@ char *c_simple_http_path_to_cache_filename(const char *path) { } if (prev_idx == 0) { - return stripped_path; + // Prevent string from being free'd by moving it to another variable. + char *temp = stripped_path; + stripped_path = NULL; + return temp; } else { return c_simple_http_combine_string_parts(parts); } From 206cad6f57daf5641f4f79555fb87a6c54db7887 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 23 Sep 2024 17:42:02 +0900 Subject: [PATCH 11/22] Impl. alternate delimeter creating cache-filename --- src/html_cache.c | 28 +++++++++++++++++++++++----- src/test.c | 17 +++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/html_cache.c b/src/html_cache.c index f851c09..d5dbc3c 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -41,12 +41,23 @@ char *c_simple_http_path_to_cache_filename(const char *path) { return buf; } + // Check if path has "0x2F" inside of it to determine if delimeters will be + // "0x2F" or "%2F". + uint_fast8_t is_normal_delimeter = 1; + const size_t path_len = strlen(stripped_path); + for (size_t idx = 0; stripped_path[idx] != 0; ++idx) { + if (idx + 4 <= path_len && strncmp(stripped_path + idx, "0x2F", 4) == 0) { + is_normal_delimeter = 0; + break; + } + } + + // Create the cache-filename piece by piece. __attribute__((cleanup(simple_archiver_list_free))) SDArchiverLinkedList *parts = simple_archiver_list_init(); size_t idx = 0; size_t prev_idx = 0; - const size_t path_len = strlen(stripped_path); size_t size; for (; idx < path_len && stripped_path[idx] != 0; ++idx) { @@ -58,10 +69,17 @@ char *c_simple_http_path_to_cache_filename(const char *path) { c_simple_http_add_string_part(parts, temp_buf, 0); free(temp_buf); - temp_buf = malloc(5); - memcpy(temp_buf, "0x2F", 5); - c_simple_http_add_string_part(parts, temp_buf, 0); - free(temp_buf); + if (is_normal_delimeter) { + temp_buf = malloc(5); + memcpy(temp_buf, "0x2F", 5); + c_simple_http_add_string_part(parts, temp_buf, 0); + free(temp_buf); + } else { + temp_buf = malloc(4); + memcpy(temp_buf, "%2F", 4); + c_simple_http_add_string_part(parts, temp_buf, 0); + free(temp_buf); + } prev_idx = idx + 1; } diff --git a/src/test.c b/src/test.c index 86cc92a..8f049f4 100644 --- a/src/test.c +++ b/src/test.c @@ -599,6 +599,23 @@ int main(void) { ret = c_simple_http_path_to_cache_filename("/outer///inner"); CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); free(ret); + + ret = c_simple_http_path_to_cache_filename("/outer/with_hex_0x2F_inner"); + CHECK_TRUE(strcmp(ret, "%2Fouter%2Fwith_hex_0x2F_inner") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/outer/0x2F_hex_inner"); + CHECK_TRUE(strcmp(ret, "%2Fouter%2F0x2F_hex_inner") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename("/outer0x2F/inner_hex_0x2F"); + CHECK_TRUE(strcmp(ret, "%2Fouter0x2F%2Finner_hex_0x2F") == 0); + free(ret); + + ret = c_simple_http_path_to_cache_filename( + "/0x2Fouter0x2F/0x2Finner_0x2F_hex_0x2F"); + CHECK_TRUE(strcmp(ret, "%2F0x2Fouter0x2F%2F0x2Finner_0x2F_hex_0x2F") == 0); + free(ret); } RETURN() From fdaaf046006742da82ec9d195a5dd8bd83649f97 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 23 Sep 2024 19:44:51 +0900 Subject: [PATCH 12/22] Use "Connection: close" in response headers The current implementation always closes the connection after sending the response, so it should notify the client to close the connection. --- src/http.c | 9 +++++++-- src/main.c | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/http.c b/src/http.c index ceccbcd..3d77df8 100644 --- a/src/http.c +++ b/src/http.c @@ -39,16 +39,21 @@ const char *c_simple_http_response_code_error_to_response( enum C_SIMPLE_HTTP_ResponseCode response_code) { switch (response_code) { case C_SIMPLE_HTTP_Response_400_Bad_Request: - return "HTTP/1.1 400 Bad Request\nAllow: GET\nContent-Type: text/html\n" + return "HTTP/1.1 400 Bad Request\nAllow: GET\n" + "Connection: close\n" + "Content-Type: text/html\n" "Content-Length: 25\n\n" "

400 Bad Request

\n"; case C_SIMPLE_HTTP_Response_404_Not_Found: - return "HTTP/1.1 404 Not Found\nAllow: GET\nContent-Type: text/html\n" + return "HTTP/1.1 404 Not Found\nAllow: GET\n" + "Connection: close\n" + "Content-Type: text/html\n" "Content-Length: 23\n\n" "

404 Not Found

\n"; case C_SIMPLE_HTTP_Response_500_Internal_Server_Error: default: return "HTTP/1.1 500 Internal Server Error\nAllow: GET\n" + "Connection: close\n" "Content-Type: text/html\n" "Content-Length: 35\n\n" "

500 Internal Server Error

\n"; diff --git a/src/main.c b/src/main.c index 035c24d..7c65510 100644 --- a/src/main.c +++ b/src/main.c @@ -363,6 +363,7 @@ int main(int argc, char **argv) { if (response && response_code == C_SIMPLE_HTTP_Response_200_OK) { CHECK_ERROR_WRITE(write(connection_fd, "HTTP/1.1 200 OK\n", 16)); CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11)); + CHECK_ERROR_WRITE(write(connection_fd, "Connection: close\n", 18)); CHECK_ERROR_WRITE(write( connection_fd, "Content-Type: text/html\n", 24)); char content_length_buf[128]; @@ -400,6 +401,7 @@ int main(int argc, char **argv) { CHECK_ERROR_WRITE(write( connection_fd, "HTTP/1.1 500 Internal Server Error\n", 35)); CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11)); + CHECK_ERROR_WRITE(write(connection_fd, "Connection: close\n", 18)); CHECK_ERROR_WRITE(write( connection_fd, "Content-Type: text/html\n", 24)); CHECK_ERROR_WRITE(write(connection_fd, "Content-Length: 35\n", 19)); From 856c205f319d2cd7a072292cf9f483684b87d5ab Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Tue, 24 Sep 2024 13:16:34 +0900 Subject: [PATCH 13/22] Unescape percent-encoded uri when handling request Resolves https://git.seodisparate.com/stephenseo/c_simple_http/issues/6 --- src/helpers.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/helpers.h | 7 +++++ src/http.c | 19 +++++++++----- src/test.c | 25 ++++++++++++++++++ 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/src/helpers.c b/src/helpers.c index b2f2448..bf3a6fd 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -144,4 +144,76 @@ char *c_simple_http_helper_to_lowercase(const char *buf, size_t size) { return ret_buf; } +char c_simple_http_helper_hex_to_value(const char upper, const char lower) { + char result = 0; + + if (upper >= '0' && upper <= '9') { + result |= (char)(upper - '0') << 4; + } else if (upper >= 'a' && upper <= 'f') { + result |= (char)(upper - 'a' + 10) << 4; + } else if (upper >= 'A' && upper <= 'F') { + result |= (char)(upper - 'A' + 10) << 4; + } else { + return 0; + } + + if (lower >= '0' && lower <= '9') { + result |= lower - '0'; + } else if (lower >= 'a' && lower <= 'f') { + result |= lower - 'a' + 10; + } else if (lower >= 'A' && lower <= 'F') { + result |= lower - 'A' + 10; + } else { + return 0; + } + + return result; +} + +char *c_simple_http_helper_unescape_uri(const char *uri) { + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *parts = simple_archiver_list_init(); + + const size_t size = strlen(uri); + size_t idx = 0; + size_t prev_idx = 0; + size_t buf_size; + char *buf; + + for (; idx <= size; ++idx) { + if (uri[idx] == '%' && idx + 2 < size) { + if (idx > prev_idx) { + buf_size = idx - prev_idx + 1; + buf = malloc(buf_size); + memcpy(buf, uri + prev_idx, buf_size - 1); + buf[buf_size - 1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + buf = malloc(2); + buf[0] = c_simple_http_helper_hex_to_value(uri[idx + 1], uri[idx + 2]); + buf[1] = 0; + if (buf[0] == 0) { + free(buf); + return NULL; + } + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + prev_idx = idx + 3; + idx += 2; + } + } + + if (idx > prev_idx) { + buf_size = idx - prev_idx + 1; + buf = malloc(buf_size); + memcpy(buf, uri + prev_idx, buf_size - 1); + buf[buf_size - 1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + + return c_simple_http_combine_string_parts(parts); +} + // vim: et ts=2 sts=2 sw=2 diff --git a/src/helpers.h b/src/helpers.h index 6c4554c..4a63834 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -50,6 +50,13 @@ void c_simple_http_helper_to_lowercase_in_place(char *buf, size_t size); /// uppercase to lowercase alpha chars. char *c_simple_http_helper_to_lowercase(const char *buf, size_t size); +/// Converts two hexadecimal digits into its corresponding value. +char c_simple_http_helper_hex_to_value(const char upper, const char lower); + +/// Unescapes percent-encoded parts in the given uri string. If this returns +/// non-NULL, it must be free'd. +char *c_simple_http_helper_unescape_uri(const char *uri); + #endif // vim: et ts=2 sts=2 sw=2 diff --git a/src/http.c b/src/http.c index 3d77df8..0db0099 100644 --- a/src/http.c +++ b/src/http.c @@ -26,8 +26,6 @@ #include // Local includes -#include "SimpleArchiver/src/data_structures/priority_heap.h" -#include "constants.h" #include "http_template.h" #include "helpers.h" @@ -118,6 +116,13 @@ char *c_simple_http_request_response( } #ifndef NDEBUG fprintf(stderr, "Parsing request: got path \"%s\"\n", request_path); +#endif + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *request_path_unescaped = + c_simple_http_helper_unescape_uri(request_path); +#ifndef NDEBUG + fprintf( + stderr, "Parsing request: unescaped path \"%s\"\n", request_path_unescaped); #endif // skip whitespace until next part. for (; idx < size @@ -165,9 +170,9 @@ char *c_simple_http_request_response( size_t generated_size = 0; __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) char *stripped_path = c_simple_http_strip_path( - request_path, request_path_idx); + request_path_unescaped, strlen(request_path_unescaped)); char *generated_buf = c_simple_http_path_to_generated( - stripped_path ? stripped_path : request_path, + stripped_path ? stripped_path : request_path_unescaped, templates, &generated_size, NULL); // TODO Use the output parameter "filenames list" here. @@ -179,8 +184,10 @@ char *c_simple_http_request_response( if ( simple_archiver_hash_map_get( templates->hash_map, - stripped_path ? stripped_path : request_path, - stripped_path ? strlen(stripped_path) + 1 : request_path_idx + 1) + stripped_path ? stripped_path : request_path_unescaped, + stripped_path + ? strlen(stripped_path) + 1 + : strlen(request_path_unescaped) + 1) == NULL) { *out_response_code = C_SIMPLE_HTTP_Response_404_Not_Found; } else { diff --git a/src/test.c b/src/test.c index 8f049f4..7c807a7 100644 --- a/src/test.c +++ b/src/test.c @@ -568,6 +568,31 @@ int main(void) { char *buf = c_simple_http_combine_string_parts(list); ASSERT_TRUE(buf); ASSERT_TRUE(strcmp(buf, "one\ntwo\nthree\n") == 0); + free(buf); + buf = NULL; + + char hex_result = c_simple_http_helper_hex_to_value('2', 'f'); + CHECK_TRUE(hex_result == '/'); + hex_result = c_simple_http_helper_hex_to_value('2', 'F'); + CHECK_TRUE(hex_result == '/'); + + hex_result = c_simple_http_helper_hex_to_value('7', 'a'); + CHECK_TRUE(hex_result == 'z'); + hex_result = c_simple_http_helper_hex_to_value('7', 'A'); + CHECK_TRUE(hex_result == 'z'); + + hex_result = c_simple_http_helper_hex_to_value('4', '1'); + CHECK_TRUE(hex_result == 'A'); + + buf = c_simple_http_helper_unescape_uri("%2fderp%2Fdoop%21"); + CHECK_TRUE(strcmp(buf, "/derp/doop!") == 0); + free(buf); + buf = NULL; + + buf = c_simple_http_helper_unescape_uri("%41%42%43%25%5A%5a"); + CHECK_TRUE(strcmp(buf, "ABC%ZZ") == 0); + free(buf); + buf = NULL; } // Test html_cache. From b9e4e3de5f3dde5dd94dd8d9287b9bf3182c1cd3 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 25 Sep 2024 13:10:44 +0900 Subject: [PATCH 14/22] Impl. c_simple_http_cache_filename_to_path(...) --- src/html_cache.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++ src/test.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/src/html_cache.c b/src/html_cache.c index d5dbc3c..0f114b5 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -104,6 +104,83 @@ char *c_simple_http_path_to_cache_filename(const char *path) { } } +char *c_simple_http_cache_filename_to_path(const char *cache_filename) { + uint_fast8_t is_percent_encoded = 0; + if (!cache_filename) { + return NULL; + } else if (strcmp(cache_filename, "ROOT") == 0) { + char *buf = malloc(2); + buf[0] = '/'; + buf[1] = 0; + return buf; + } else if (cache_filename[0] == '%') { + is_percent_encoded = 1; + } + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *parts = simple_archiver_list_init(); + + size_t idx = 0; + size_t prev_idx = 0; + const size_t size = strlen(cache_filename); + char *buf; + size_t buf_size; + + for(; idx < size && cache_filename[idx] != 0; ++idx) { + if (is_percent_encoded && strncmp(cache_filename + idx, "%2F", 3) == 0) { + if (prev_idx < idx) { + buf_size = idx - prev_idx + 2; + buf = malloc(buf_size); + memcpy(buf, cache_filename + prev_idx, buf_size - 2); + buf[buf_size - 2] = '/'; + buf[buf_size - 1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } else { + buf_size = 2; + buf = malloc(buf_size); + buf[0] = '/'; + buf[1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + idx += 2; + prev_idx = idx + 1; + } else if (!is_percent_encoded + && strncmp(cache_filename + idx, "0x2F", 4) == 0) { + if (prev_idx < idx) { + buf_size = idx - prev_idx + 2; + buf = malloc(buf_size); + memcpy(buf, cache_filename + prev_idx, buf_size - 2); + buf[buf_size - 2] = '/'; + buf[buf_size - 1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } else { + buf_size = 2; + buf = malloc(buf_size); + buf[0] = '/'; + buf[1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + idx += 3; + prev_idx = idx + 1; + } + } + + if (prev_idx < idx) { + buf_size = idx - prev_idx + 1; + buf = malloc(buf_size); + memcpy(buf, cache_filename + prev_idx, buf_size - 1); + buf[buf_size - 1] = 0; + c_simple_http_add_string_part(parts, buf, 0); + free(buf); + } + + return c_simple_http_combine_string_parts(parts); +} + int c_simple_http_cache_path( const char *path, const char *config_filename, diff --git a/src/test.c b/src/test.c index 7c807a7..e9a5da0 100644 --- a/src/test.c +++ b/src/test.c @@ -598,49 +598,129 @@ int main(void) { // Test html_cache. { char *ret = c_simple_http_path_to_cache_filename("/"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "ROOT") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("////"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "ROOT") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/inner"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "0x2Finner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/inner////"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "0x2Finner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/outer/inner"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/outer/inner////"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/outer///inner"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "0x2Fouter0x2Finner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/outer/with_hex_0x2F_inner"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "%2Fouter%2Fwith_hex_0x2F_inner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/outer/0x2F_hex_inner"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "%2Fouter%2F0x2F_hex_inner") == 0); free(ret); ret = c_simple_http_path_to_cache_filename("/outer0x2F/inner_hex_0x2F"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "%2Fouter0x2F%2Finner_hex_0x2F") == 0); free(ret); ret = c_simple_http_path_to_cache_filename( "/0x2Fouter0x2F/0x2Finner_0x2F_hex_0x2F"); + ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "%2F0x2Fouter0x2F%2F0x2Finner_0x2F_hex_0x2F") == 0); free(ret); + + ret = c_simple_http_cache_filename_to_path("0x2Fouter0x2Finner"); + ASSERT_TRUE(ret); + printf("%s\n", ret); + CHECK_TRUE(strcmp(ret, "/outer/inner") == 0); + free(ret); + + ret = c_simple_http_cache_filename_to_path("0x2Fouter0x2Finner0x2F%2F0x2Fmore_inner"); + ASSERT_TRUE(ret); + CHECK_TRUE(strcmp(ret, "/outer/inner/%2F/more_inner") == 0); + free(ret); + + ret = c_simple_http_cache_filename_to_path("%2Fouter%2Finner"); + ASSERT_TRUE(ret); + CHECK_TRUE(strcmp(ret, "/outer/inner") == 0); + free(ret); + + ret = c_simple_http_cache_filename_to_path("%2Fouter%2Finner%2F0x2F%2Fmore_inner"); + ASSERT_TRUE(ret); + CHECK_TRUE(strcmp(ret, "/outer/inner/0x2F/more_inner") == 0); + free(ret); + + const char *uri0 = "/a/simple/url/with/inner/paths"; + ret = + c_simple_http_path_to_cache_filename(uri0); + ASSERT_TRUE(ret); + CHECK_TRUE( + strcmp(ret, "0x2Fa0x2Fsimple0x2Furl0x2Fwith0x2Finner0x2Fpaths") + == 0); + char *ret2 = c_simple_http_cache_filename_to_path(ret); + free(ret); + ASSERT_TRUE(ret2); + CHECK_TRUE(strcmp(ret2, uri0) == 0); + free(ret2); + + const char *uri1 = "/a/url/with/0x2F/in/it"; + ret = + c_simple_http_path_to_cache_filename(uri1); + ASSERT_TRUE(ret); + CHECK_TRUE( + strcmp(ret, "%2Fa%2Furl%2Fwith%2F0x2F%2Fin%2Fit") + == 0); + ret2 = c_simple_http_cache_filename_to_path(ret); + free(ret); + ASSERT_TRUE(ret2); + CHECK_TRUE(strcmp(ret2, uri1) == 0); + free(ret2); + + const char *uri2 = "/"; + ret = + c_simple_http_path_to_cache_filename(uri2); + ASSERT_TRUE(ret); + CHECK_TRUE(strcmp(ret, "ROOT") == 0); + ret2 = c_simple_http_cache_filename_to_path(ret); + free(ret); + ASSERT_TRUE(ret2); + CHECK_TRUE(strcmp(ret2, uri2) == 0); + free(ret2); + + const char *uri3 = "/a"; + ret = + c_simple_http_path_to_cache_filename(uri3); + ASSERT_TRUE(ret); + CHECK_TRUE(strcmp(ret, "0x2Fa") == 0); + ret2 = c_simple_http_cache_filename_to_path(ret); + free(ret); + ASSERT_TRUE(ret2); + CHECK_TRUE(strcmp(ret2, uri3) == 0); + free(ret2); } RETURN() From 4e670d24c88f81329e9312f3b053b7c1d24531fc Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 25 Sep 2024 13:14:01 +0900 Subject: [PATCH 15/22] Change StringPart's extra type to uintptr_t --- src/helpers.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers.h b/src/helpers.h index 4a63834..137bf06 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -19,6 +19,7 @@ // Standard library includes. #include +#include // Third-party includes. #include @@ -26,7 +27,7 @@ typedef struct C_SIMPLE_HTTP_String_Part { char *buf; size_t size; - size_t extra; + uintptr_t extra; } C_SIMPLE_HTTP_String_Part; void c_simple_http_cleanup_attr_string_part(C_SIMPLE_HTTP_String_Part **); From 07153f3588add49531561b6e853c94fe111f374c Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Tue, 24 Sep 2024 14:53:28 +0900 Subject: [PATCH 16/22] Update example config --- example_config/example.config | 36 +++++++++++++++++++++++++++++++++++ example_config/inner.html | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/example_config/example.config b/example_config/example.config index 77b8d17..768af8c 100644 --- a/example_config/example.config +++ b/example_config/example.config @@ -38,3 +38,39 @@ HTML_FILE='''example_config/inner.html''' VAR_FILE='''example_config/var.html''' PATH=/error + +PATH=/inner/further +HTML=''' + + + + + +

Nested inner: further

+ {{{VAR}}} +
+ back + + +''' +VAR='''yep''' diff --git a/example_config/inner.html b/example_config/inner.html index 7de1791..cdf8566 100644 --- a/example_config/inner.html +++ b/example_config/inner.html @@ -28,6 +28,7 @@ {{{VAR_FILE}}}
-Back. +further
+Back. From a017fccc2750c71953a0b72c9a7e0f67a7a67eb4 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 25 Sep 2024 13:28:42 +0900 Subject: [PATCH 17/22] Fix type when setting "extra" var on string part --- src/helpers.c | 2 +- src/helpers.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers.c b/src/helpers.c index bf3a6fd..b391e22 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -76,7 +76,7 @@ void c_simple_http_cleanup_string_part(void *data) { } void c_simple_http_add_string_part( - SDArchiverLinkedList *list, const char *c_string, size_t extra) { + SDArchiverLinkedList *list, const char *c_string, uintptr_t extra) { C_SIMPLE_HTTP_String_Part *string_part = malloc(sizeof(C_SIMPLE_HTTP_String_Part)); diff --git a/src/helpers.h b/src/helpers.h index 137bf06..bd649d9 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -39,7 +39,7 @@ void c_simple_http_cleanup_string_part(void *data); /// Puts a malloced instance of String_Part into the list. /// The given c_string will be copied into a newly malloced buffer. void c_simple_http_add_string_part( - SDArchiverLinkedList *list, const char *c_string, size_t extra); + SDArchiverLinkedList *list, const char *c_string, uintptr_t extra); /// Combines all String_Parts in the list and returns it as a single buffer. char *c_simple_http_combine_string_parts(const SDArchiverLinkedList *list); From 83e4a519853a3b54221ee1098297597dd6c1b766 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 25 Sep 2024 16:12:25 +0900 Subject: [PATCH 18/22] Impl. html cache (mostly done) TODO: Invalidate cache if it is too old. --- src/arg_parse.c | 1 + src/helpers.c | 45 ++++++ src/helpers.h | 5 + src/html_cache.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++- src/html_cache.h | 7 +- src/http.c | 46 +++++- src/http.h | 4 +- src/main.c | 4 +- src/test.c | 191 +++++++++++++++++++++++- 9 files changed, 658 insertions(+), 13 deletions(-) diff --git a/src/arg_parse.c b/src/arg_parse.c index 3a0c090..7cb7611 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -108,6 +108,7 @@ Args parse_args(int32_t argc, char **argv) { } else { printf("Directory \"%s\" exists.\n", args.cache_dir); } + closedir(d); } else { fprintf(stderr, "ERROR: Invalid args!\n"); print_usage(); diff --git a/src/helpers.c b/src/helpers.c index b391e22..c8b8cee 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -21,6 +21,13 @@ #include #include +// libc includes. +#include +#include +#include +#include +#include + int c_simple_http_internal_get_string_part_full_size(void *data, void *ud) { C_SIMPLE_HTTP_String_Part *part = data; size_t *count = ud; @@ -216,4 +223,42 @@ char *c_simple_http_helper_unescape_uri(const char *uri) { return c_simple_http_combine_string_parts(parts); } +int c_simple_http_helper_mkdir_tree(const char *path) { + // Check if dir already exists. + DIR *dir_ptr = opendir(path); + if (dir_ptr) { + // Directory already exists. + closedir(dir_ptr); + return 1; + } else if (errno == ENOENT) { + // Directory doesn't exist, create dir tree. + closedir(dir_ptr); + + size_t buf_size = strlen(path) + 1; + char *buf = malloc(buf_size); + memcpy(buf, path, buf_size - 1); + buf[buf_size - 1] = 0; + + char *dirname_buf = dirname(buf); + // Recursive call to ensure parent directories are created. + int ret = c_simple_http_helper_mkdir_tree(dirname_buf); + free(buf); + if (ret == 1 || ret == 0) { + // Parent directory should be created by now. + ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret != 0) { + return 4; + } + } else { + return 3; + } + + return 0; + } else { + // Other directory error. + closedir(dir_ptr); + return 2; + } +} + // vim: et ts=2 sts=2 sw=2 diff --git a/src/helpers.h b/src/helpers.h index bd649d9..5e1bd9f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -58,6 +58,11 @@ char c_simple_http_helper_hex_to_value(const char upper, const char lower); /// non-NULL, it must be free'd. char *c_simple_http_helper_unescape_uri(const char *uri); +/// Returns zero if successful. "dirpath" will point to a directory on success. +/// Returns 1 if the directory already exists. +/// Other return values are errors. +int c_simple_http_helper_mkdir_tree(const char *dirpath); + #endif // vim: et ts=2 sts=2 sw=2 diff --git a/src/html_cache.c b/src/html_cache.c index 0f114b5..212ccba 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -16,9 +16,15 @@ // Standard library includes. #include +#include #include #include +// POSIX includes. +#include +#include +#include + // Third-party includes. #include #include @@ -26,6 +32,21 @@ // Local includes. #include "http.h" #include "helpers.h" +#include "http_template.h" + +int c_simple_http_internal_write_filenames_to_cache_file(void *data, void *ud) { + char *filename = data; + FILE *cache_fd = ud; + + const size_t filename_size = strlen(filename); + if (fwrite(filename, 1, filename_size, cache_fd) != filename_size) { + return 1; + } else if (fwrite("\n", 1, 1, cache_fd) != 1) { + return 1; + } + + return 0; +} char *c_simple_http_path_to_cache_filename(const char *path) { __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) @@ -185,8 +206,353 @@ int c_simple_http_cache_path( const char *path, const char *config_filename, const char *cache_dir, + const C_SIMPLE_HTTP_HTTPTemplates *templates, char **buf_out) { - // TODO + if (!path) { + fprintf(stderr, "ERROR cache_path function: path is NULL!\n"); + return -9; + } else if (!config_filename) { + fprintf(stderr, "ERROR cache_path function: config_filename is NULL!\n"); + return -10; + } else if (!cache_dir) { + fprintf(stderr, "ERROR cache_path function: cache_dir is NULL!\n"); + return -11; + } else if (!templates) { + fprintf(stderr, "ERROR cache_path function: templates is NULL!\n"); + return -12; + } else if (!buf_out) { + fprintf(stderr, "ERROR cache_path function: buf_out is NULL!\n"); + return -13; + } + + int ret = c_simple_http_helper_mkdir_tree(cache_dir); + if (ret != 0 && ret != 1) { + fprintf( + stderr, "ERROR failed to ensure cache_dir \"%s\" exists!\n", cache_dir); + return -15; + } + + // Get the cache filename from the path. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *cache_filename = c_simple_http_path_to_cache_filename(path); + if (!cache_filename) { + fprintf(stderr, "ERROR Failed to convert path to cache_filename!"); + return -1; + } + + // Combine the cache_dir with cache filename. + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *parts = simple_archiver_list_init(); + + c_simple_http_add_string_part(parts, cache_dir, 0); + c_simple_http_add_string_part(parts, "/", 0); + c_simple_http_add_string_part(parts, cache_filename, 0); + + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *cache_filename_full = c_simple_http_combine_string_parts(parts); + + simple_archiver_list_free(&parts); + parts = simple_archiver_list_init(); + + if (!cache_filename_full) { + fprintf(stderr, "ERROR Failed to create full-path to cache filename!\n"); + return -2; + } + + // Get "stat" info on the cache filename. + uint_fast8_t force_cache_update = 0; + struct stat cache_file_stat; + ret = stat(cache_filename_full, &cache_file_stat); + if (ret == -1) { + if (errno == ENOENT) { + fprintf(stderr, "NOTICE cache file doesn't exist, will create...\n"); + } else { + fprintf( + stderr, + "ERROR getting stat info on file \"%s\" (errno %d)! " + "Assuming out of date!\n", + cache_filename_full, + errno); + } + force_cache_update = 1; + } + + // Get "stat" info on config file. + struct stat config_file_stat; + ret = stat(config_filename, &config_file_stat); + if (ret == -1) { + if (errno == ENOENT) { + fprintf( + stderr, "ERROR config file \"%s\" doesn't exist!\n", config_filename); + } else { + fprintf( + stderr, + "ERROR getting stat info on config file \"%s\" (errno %d)!\n", + config_filename, + errno); + } + return -3; + } + + if (!force_cache_update) { + do { + // Check filenames in cache file. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *cache_fd = fopen(cache_filename_full, "r"); + const size_t buf_size = 1024; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = malloc(buf_size); + + // Check header. + if (fread(buf, 1, 20, cache_fd) != 20) { + fprintf(stderr, "ERROR Failed to read header from cache file!\n"); + return -14; + } else if (strncmp(buf, "--- CACHE ENTRY ---\n", 20) != 0) { + fprintf( + stderr, + "WARNING Cache is invalid (bad header), assuming out of date!\n"); + force_cache_update = 1; + break; + } + + // Check filenames. + size_t buf_idx = 0; + while(1) { + ret = fgetc(cache_fd); + if (ret == EOF) { + fprintf( + stderr, "WARNING Cache is invalid (EOF), assuming out of date!\n"); + force_cache_update = 1; + break; + } else if (ret == '\n') { + // Got filename in "buf" of size "buf_idx". + if (strncmp(buf, "--- BEGIN HTML ---", 18) == 0) { + // Got end header instead of filename. + break; + } else if (buf_idx < buf_size) { + buf[buf_idx++] = 0; + } else { + fprintf( + stderr, + "WARNING Cache is invalid (too large filename), assuming out of " + "date!\n"); + force_cache_update = 1; + break; + } + + struct stat file_stat; + ret = stat(buf, &file_stat); + if (ret == -1) { + if (errno == ENOENT) { + fprintf( + stderr, + "WARNING Invalid filename cache entry \"%s\" (doesn't exist)! " + "Assuming out of date!\n", + buf); + force_cache_update = 1; + break; + } else { + fprintf( + stderr, + "WARNING Invalid filename cache entry \"%s\" (stat errno %d)! " + "Assuming out of date!\n", + buf, + errno); + force_cache_update = 1; + break; + } + } + + if (cache_file_stat.st_mtim.tv_sec < file_stat.st_mtim.tv_sec + || (cache_file_stat.st_mtim.tv_sec == file_stat.st_mtim.tv_sec + && cache_file_stat.st_mtim.tv_nsec + < file_stat.st_mtim.tv_nsec)) { + // File is newer than cache. + force_cache_update = 1; + break; + } + + buf_idx = 0; + } else if (buf_idx < buf_size) { + buf[buf_idx++] = (char)ret; + } else { + fprintf( + stderr, + "WARNING Cache is invalid (too large filename), assuming out of " + "date!\n"); + force_cache_update = 1; + break; + } + } + } while(0); + } + + // Compare modification times. +CACHE_FILE_WRITE_CHECK: + if (force_cache_update + || cache_file_stat.st_mtim.tv_sec < config_file_stat.st_mtim.tv_sec + || (cache_file_stat.st_mtim.tv_sec == config_file_stat.st_mtim.tv_sec + && cache_file_stat.st_mtim.tv_nsec < config_file_stat.st_mtim.tv_nsec)) + { + // Cache file is out of date. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *cache_fd = fopen(cache_filename_full, "w"); + if (fwrite("--- CACHE ENTRY ---\n", 1, 20, cache_fd) != 20) { + fprintf( + stderr, + "ERROR Failed to write to cache file \"%s\"!\n", + cache_filename_full); + return -5; + } + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *used_filenames = NULL; + + size_t generated_html_size = 0; + + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *generated_html = c_simple_http_path_to_generated( + path, templates, &generated_html_size, &used_filenames); + + if (!generated_html) { + fprintf(stderr, "ERROR Failed to generate html for path \"%s\"!\n", path); + simple_archiver_helper_cleanup_FILE(&cache_fd); + remove(cache_filename_full); + return -4; + } + + if (simple_archiver_list_get( + used_filenames, + c_simple_http_internal_write_filenames_to_cache_file, + cache_fd)) { + fprintf(stderr, "ERROR Failed to write filenames to cache file!\n"); + return -6; + } else if (fwrite("--- BEGIN HTML ---\n", 1, 19, cache_fd) != 19) { + fprintf(stderr, "ERROR Failed to write end of cache file header!\n"); + return -7; + } else if ( + fwrite( + generated_html, + 1, + generated_html_size, + cache_fd) + != generated_html_size) { + fprintf(stderr, "ERROR Failed to write html to cache file!\n"); + return -8; + } + + *buf_out = generated_html; + generated_html = NULL; + return 1; + } + + // Cache file is newer. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *cache_fd = fopen(cache_filename_full, "rb"); + + const size_t buf_size = 128; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = malloc(buf_size); + + // Get first header. + if (fread(buf, 1, 20, cache_fd) != 20) { + fprintf( + stderr, + "WARNING Invalid cache file (read header), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } else if (strncmp("--- CACHE ENTRY ---\n", buf, 20) != 0) { + fprintf( + stderr, + "WARNING Invalid cache file (check header), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + // Get filenames end header. + uint_fast8_t reached_end_header = 0; + size_t buf_idx = 0; + while (1) { + ret = fgetc(cache_fd); + if (ret == EOF) { + fprintf( + stderr, "WARNING Invalid cache file (EOF), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } else if (ret == '\n') { + if (strncmp("--- BEGIN HTML ---", buf, 18) == 0) { + reached_end_header = 1; + break; + } + buf_idx = 0; + continue; + } + + if (buf_idx < buf_size) { + buf[buf_idx++] = (char)ret; + } + } + + if (!reached_end_header) { + fprintf( + stderr, + "WARNING Invalid cache file (no end header), assuming out of date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + // Remaining bytes in cache_fd is cached html. Fetch it and return it. + const long html_start_idx = ftell(cache_fd); + if (html_start_idx <= 0) { + fprintf( + stderr, + "WARNING Failed to get position in cache file, assuming " + "invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + ret = fseek(cache_fd, 0, SEEK_END); + if (ret != 0) { + fprintf( + stderr, + "WARNING Failed to seek in cache file, assuming invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + const long html_end_idx = ftell(cache_fd); + if (html_end_idx <= 0) { + fprintf( + stderr, + "WARNING Failed to get end position in cache file, assuming " + "invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + ret = fseek(cache_fd, html_start_idx, SEEK_SET); + if (ret != 0) { + fprintf( + stderr, + "WARNING Failed to seek in cache file, assuming invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + const size_t html_size = (size_t)html_end_idx - (size_t)html_start_idx + 1; + *buf_out = malloc(html_size); + + if (fread(*buf_out, 1, html_size - 1, cache_fd) != html_size - 1) { + fprintf( + stderr, + "WARNING Failed to read html in cache file, assuming " + "invalid/out-of-date!\n"); + force_cache_update = 1; + goto CACHE_FILE_WRITE_CHECK; + } + + (*buf_out)[html_size - 1] = 0; + return 0; } diff --git a/src/html_cache.h b/src/html_cache.h index a382c34..95a36ab 100644 --- a/src/html_cache.h +++ b/src/html_cache.h @@ -17,21 +17,26 @@ #ifndef SEODISPARATE_COM_C_SIMPLE_HTTP_HTML_CACHE_H_ #define SEODISPARATE_COM_C_SIMPLE_HTTP_HTML_CACHE_H_ +// Local includes. +#include "http.h" + /// Must be free'd if non-NULL. char *c_simple_http_path_to_cache_filename(const char *path); /// Must be free'd if non-NULL. char *c_simple_http_cache_filename_to_path(const char *cache_filename); -/// Given a "path", returns non-zero if the cache is invalidated. +/// Given a "path", returns positive-non-zero if the cache is invalidated. /// "config_filename" is required to check its timestamp. "cache_dir" is /// required to actually get the cache file to check against. "buf_out" will be /// populated if non-NULL, and will either be fetched from the cache or from the /// config (using http_template). Note that "buf_out" will point to a c-string. +/// Returns a negative value on error. int c_simple_http_cache_path( const char *path, const char *config_filename, const char *cache_dir, + const C_SIMPLE_HTTP_HTTPTemplates *templates, char **buf_out); #endif diff --git a/src/http.c b/src/http.c index 0db0099..e32e54b 100644 --- a/src/http.c +++ b/src/http.c @@ -28,6 +28,7 @@ // Local includes #include "http_template.h" #include "helpers.h" +#include "html_cache.h" #define REQUEST_TYPE_BUFFER_SIZE 16 #define REQUEST_PATH_BUFFER_SIZE 256 @@ -63,7 +64,9 @@ char *c_simple_http_request_response( uint32_t size, const C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, - enum C_SIMPLE_HTTP_ResponseCode *out_response_code) { + enum C_SIMPLE_HTTP_ResponseCode *out_response_code, + const char *cache_dir, + const char *config_filename) { if (out_size) { *out_size = 0; } @@ -171,11 +174,42 @@ char *c_simple_http_request_response( __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) char *stripped_path = c_simple_http_strip_path( request_path_unescaped, strlen(request_path_unescaped)); - char *generated_buf = c_simple_http_path_to_generated( - stripped_path ? stripped_path : request_path_unescaped, - templates, - &generated_size, - NULL); // TODO Use the output parameter "filenames list" here. + + char *generated_buf = NULL; + + if (cache_dir) { + int ret = c_simple_http_cache_path( + stripped_path ? stripped_path : request_path_unescaped, + config_filename, + cache_dir, + templates, + &generated_buf); + if (ret < 0) { + fprintf(stderr, "ERROR Failed to generate template with cache!\n"); + if (out_response_code) { + if ( + simple_archiver_hash_map_get( + templates->hash_map, + stripped_path ? stripped_path : request_path_unescaped, + stripped_path + ? strlen(stripped_path) + 1 + : strlen(request_path_unescaped) + 1) + == NULL) { + *out_response_code = C_SIMPLE_HTTP_Response_404_Not_Found; + } else { + *out_response_code = C_SIMPLE_HTTP_Response_500_Internal_Server_Error; + } + } + return NULL; + } + generated_size = strlen(generated_buf); + } else { + generated_buf = c_simple_http_path_to_generated( + stripped_path ? stripped_path : request_path_unescaped, + templates, + &generated_size, + NULL); + } if (!generated_buf || generated_size == 0) { fprintf(stderr, "ERROR Unable to generate response html for path \"%s\"!\n", diff --git a/src/http.h b/src/http.h index d050b27..725fbf8 100644 --- a/src/http.h +++ b/src/http.h @@ -48,7 +48,9 @@ char *c_simple_http_request_response( uint32_t size, const C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, - enum C_SIMPLE_HTTP_ResponseCode *out_response_code + enum C_SIMPLE_HTTP_ResponseCode *out_response_code, + const char *cache_dir, + const char *config_filename ); /// Takes a PATH string and returns a "bare" path. diff --git a/src/main.c b/src/main.c index 7c65510..9c9f51c 100644 --- a/src/main.c +++ b/src/main.c @@ -359,7 +359,9 @@ int main(int argc, char **argv) { (uint32_t)read_ret, &parsed_config, &response_size, - &response_code); + &response_code, + args.cache_dir, + args.config_file); if (response && response_code == C_SIMPLE_HTTP_Response_200_OK) { CHECK_ERROR_WRITE(write(connection_fd, "HTTP/1.1 200 OK\n", 16)); CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11)); diff --git a/src/test.c b/src/test.c index e9a5da0..9c17dbe 100644 --- a/src/test.c +++ b/src/test.c @@ -4,6 +4,11 @@ #include #include +// POSIX includes. +#include +#include +#include + // Local includes. #include "config.h" #include "helpers.h" @@ -593,6 +598,19 @@ int main(void) { CHECK_TRUE(strcmp(buf, "ABC%ZZ") == 0); free(buf); buf = NULL; + + DIR *dirp = opendir("/tmp/create_dirs_dir"); + uint_fast8_t dir_exists = dirp ? 1 : 0; + closedir(dirp); + ASSERT_FALSE(dir_exists); + + int ret = c_simple_http_helper_mkdir_tree("/tmp/create_dirs_dir/dir/"); + int ret2 = rmdir("/tmp/create_dirs_dir/dir"); + int ret3 = rmdir("/tmp/create_dirs_dir"); + + CHECK_TRUE(ret == 0); + CHECK_TRUE(ret2 == 0); + CHECK_TRUE(ret3 == 0); } // Test html_cache. @@ -655,11 +673,11 @@ int main(void) { ret = c_simple_http_cache_filename_to_path("0x2Fouter0x2Finner"); ASSERT_TRUE(ret); - printf("%s\n", ret); CHECK_TRUE(strcmp(ret, "/outer/inner") == 0); free(ret); - ret = c_simple_http_cache_filename_to_path("0x2Fouter0x2Finner0x2F%2F0x2Fmore_inner"); + ret = c_simple_http_cache_filename_to_path( + "0x2Fouter0x2Finner0x2F%2F0x2Fmore_inner"); ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "/outer/inner/%2F/more_inner") == 0); free(ret); @@ -669,7 +687,8 @@ int main(void) { CHECK_TRUE(strcmp(ret, "/outer/inner") == 0); free(ret); - ret = c_simple_http_cache_filename_to_path("%2Fouter%2Finner%2F0x2F%2Fmore_inner"); + ret = c_simple_http_cache_filename_to_path( + "%2Fouter%2Finner%2F0x2F%2Fmore_inner"); ASSERT_TRUE(ret); CHECK_TRUE(strcmp(ret, "/outer/inner/0x2F/more_inner") == 0); free(ret); @@ -721,6 +740,172 @@ int main(void) { ASSERT_TRUE(ret2); CHECK_TRUE(strcmp(ret2, uri3) == 0); free(ret2); + + // Set up test config to get template map to test cache. + __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) + const char *test_http_template_filename5 = + "/tmp/c_simple_http_template_test5.config"; + __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) + const char *test_http_template_html_filename3 = + "/tmp/c_simple_http_template_test3.html"; + __attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) + const char *test_http_template_html_var_filename2 = + "/tmp/c_simple_http_template_test_var2.html"; + + FILE *test_file = fopen(test_http_template_filename5, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "PATH=/\nHTML_FILE=/tmp/c_simple_http_template_test3.html\n", + 1, + 56, + test_file) + == 56); + ASSERT_TRUE( + fwrite( + "VAR_FILE=/tmp/c_simple_http_template_test_var2.html\n", + 1, + 52, + test_file) + == 52); + fclose(test_file); + + test_file = fopen(test_http_template_html_filename3, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "{{{VAR_FILE}}}\n", + 1, + 28, + test_file) + == 28); + fclose(test_file); + + test_file = fopen(test_http_template_html_var_filename2, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "Some test text.
Yep.", + 1, + 23, + test_file) + == 23); + fclose(test_file); + + __attribute__((cleanup(c_simple_http_clean_up_parsed_config))) + C_SIMPLE_HTTP_ParsedConfig templates = + c_simple_http_parse_config(test_http_template_filename5, "PATH", NULL); + ASSERT_TRUE(templates.paths); + + // Run cache function. Should return >0 due to new/first cache entry. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *buf = NULL; + int int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + + CHECK_TRUE(int_ret > 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Some test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Check/get size of cache file. + FILE *cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + uint_fast8_t cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_0 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + + // Re-run cache function, checking that it is not invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret == 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Some test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Check/get size of cache file. + cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_1 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + CHECK_TRUE(cache_file_size_0 == cache_file_size_1); + + // Change a file used by the template for PATH=/ . + test_file = fopen(test_http_template_html_var_filename2, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "Alternate test text.
Yep.", + 1, + 28, + test_file) + == 28); + fclose(test_file); + + // Re-run cache function, checking that it is invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret > 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Alternate test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Get/check size of cache file. + cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_2 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + CHECK_TRUE(cache_file_size_0 != cache_file_size_2); + + // Re-run cache function, checking that it is not invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret == 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "Alternate test text.
Yep.\n") == 0); + free(buf); + buf = NULL; + + // Get/check size of cache file. + cache_file = fopen("/tmp/c_simple_http_cache_dir/ROOT", "r"); + cache_file_exists = cache_file ? 1 : 0; + fseek(cache_file, 0, SEEK_END); + const long cache_file_size_3 = ftell(cache_file); + fclose(cache_file); + ASSERT_TRUE(cache_file_exists); + CHECK_TRUE(cache_file_size_2 == cache_file_size_3); + + // Cleanup. + remove("/tmp/c_simple_http_cache_dir/ROOT"); + rmdir("/tmp/c_simple_http_cache_dir"); } RETURN() From 037845e50158d3ef9231cdb499b548c18dfb535e Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 25 Sep 2024 16:23:04 +0900 Subject: [PATCH 19/22] Sleep in test to ensure changed file timestamp --- src/test.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test.c b/src/test.c index 9c17dbe..8430b10 100644 --- a/src/test.c +++ b/src/test.c @@ -847,6 +847,11 @@ int main(void) { CHECK_TRUE(cache_file_size_0 == cache_file_size_1); // Change a file used by the template for PATH=/ . + // Sleep first since granularity is by the second. + puts("Sleeping for two seconds to ensure edited file's timestamp has " + "changed..."); + sleep(2); + puts("Done sleeping."); test_file = fopen(test_http_template_html_var_filename2, "w"); ASSERT_TRUE(test_file); From 9459ec93132e3efd0d154b6dcb5a7b03ef6e7b0f Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Thu, 26 Sep 2024 11:51:42 +0900 Subject: [PATCH 20/22] Reload config file if cache is older than config --- src/html_cache.c | 23 ++++++++++++++++++++++- src/html_cache.h | 2 +- src/http.c | 2 +- src/http.h | 2 +- src/test.c | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/html_cache.c b/src/html_cache.c index 212ccba..4ab6fab 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -27,6 +27,7 @@ // Third-party includes. #include +#include #include // Local includes. @@ -206,7 +207,7 @@ int c_simple_http_cache_path( const char *path, const char *config_filename, const char *cache_dir, - const C_SIMPLE_HTTP_HTTPTemplates *templates, + C_SIMPLE_HTTP_HTTPTemplates *templates, char **buf_out) { if (!path) { fprintf(stderr, "ERROR cache_path function: path is NULL!\n"); @@ -262,6 +263,7 @@ int c_simple_http_cache_path( // Get "stat" info on the cache filename. uint_fast8_t force_cache_update = 0; struct stat cache_file_stat; + memset(&cache_file_stat, 0, sizeof(struct stat)); ret = stat(cache_filename_full, &cache_file_stat); if (ret == -1) { if (errno == ENOENT) { @@ -279,6 +281,7 @@ int c_simple_http_cache_path( // Get "stat" info on config file. struct stat config_file_stat; + memset(&config_file_stat, 0, sizeof(struct stat)); ret = stat(config_filename, &config_file_stat); if (ret == -1) { if (errno == ENOENT) { @@ -395,6 +398,24 @@ CACHE_FILE_WRITE_CHECK: && cache_file_stat.st_mtim.tv_nsec < config_file_stat.st_mtim.tv_nsec)) { // Cache file is out of date. + + if (cache_file_stat.st_mtim.tv_sec < config_file_stat.st_mtim.tv_sec + || (cache_file_stat.st_mtim.tv_sec == config_file_stat.st_mtim.tv_sec + && cache_file_stat.st_mtim.tv_nsec + < config_file_stat.st_mtim.tv_nsec)) + { + // Reload config file. + C_SIMPLE_HTTP_HTTPTemplates new_parsed_config = + c_simple_http_parse_config( + config_filename, + "PATH", + NULL); + if (new_parsed_config.hash_map) { + simple_archiver_hash_map_free(&templates->hash_map); + *templates = new_parsed_config; + } + } + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *cache_fd = fopen(cache_filename_full, "w"); if (fwrite("--- CACHE ENTRY ---\n", 1, 20, cache_fd) != 20) { diff --git a/src/html_cache.h b/src/html_cache.h index 95a36ab..ebaa385 100644 --- a/src/html_cache.h +++ b/src/html_cache.h @@ -36,7 +36,7 @@ int c_simple_http_cache_path( const char *path, const char *config_filename, const char *cache_dir, - const C_SIMPLE_HTTP_HTTPTemplates *templates, + C_SIMPLE_HTTP_HTTPTemplates *templates, char **buf_out); #endif diff --git a/src/http.c b/src/http.c index e32e54b..9ced398 100644 --- a/src/http.c +++ b/src/http.c @@ -62,7 +62,7 @@ const char *c_simple_http_response_code_error_to_response( char *c_simple_http_request_response( const char *request, uint32_t size, - const C_SIMPLE_HTTP_HTTPTemplates *templates, + C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, enum C_SIMPLE_HTTP_ResponseCode *out_response_code, const char *cache_dir, diff --git a/src/http.h b/src/http.h index 725fbf8..87618c4 100644 --- a/src/http.h +++ b/src/http.h @@ -46,7 +46,7 @@ const char *c_simple_http_response_code_error_to_response( char *c_simple_http_request_response( const char *request, uint32_t size, - const C_SIMPLE_HTTP_HTTPTemplates *templates, + C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, enum C_SIMPLE_HTTP_ResponseCode *out_response_code, const char *cache_dir, diff --git a/src/test.c b/src/test.c index 8430b10..b397679 100644 --- a/src/test.c +++ b/src/test.c @@ -908,6 +908,40 @@ int main(void) { ASSERT_TRUE(cache_file_exists); CHECK_TRUE(cache_file_size_2 == cache_file_size_3); + // Edit config file. + test_file = fopen(test_http_template_filename5, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite( + "PATH=/\nHTML='''

{{{VAR_FILE}}}

'''\n", + 1, + 42, + test_file) + == 42); + ASSERT_TRUE( + fwrite( + "VAR_FILE=/tmp/c_simple_http_template_test_var2.html\n", + 1, + 52, + test_file) + == 52); + + fclose(test_file); + + // Re-run cache function, checking that it is invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + &buf); + CHECK_TRUE(int_ret > 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "

Alternate test text.
Yep.

") == 0); + free(buf); + buf = NULL; + // Cleanup. remove("/tmp/c_simple_http_cache_dir/ROOT"); rmdir("/tmp/c_simple_http_cache_dir"); From 700adf2f7b37c4eea98510e21cdc5b162a3ce4ed Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Thu, 26 Sep 2024 12:37:45 +0900 Subject: [PATCH 21/22] Impl. cache file lifetime checking By default cache files are invalidated when the are aged for longer than 1 week. This "timeout-time" can be modified with a paramter/argument. --- src/arg_parse.c | 19 +++++++++++++++++++ src/arg_parse.h | 1 + src/constants.h | 1 + src/html_cache.c | 12 +++++++++++- src/html_cache.h | 1 + src/http.c | 10 +++++----- src/http.h | 4 ++-- src/main.c | 3 +-- src/test.c | 27 +++++++++++++++++++++++++++ 9 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/arg_parse.c b/src/arg_parse.c index 7cb7611..7801ca2 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -29,6 +29,9 @@ // Posix includes. #include +// Local includes. +#include "constants.h" + void print_usage(void) { puts("Usage:"); puts(" -p | --port "); @@ -39,6 +42,7 @@ void print_usage(void) { puts(" Note that this option is case-insensitive"); puts(" --enable-reload-config-on-change"); puts(" --enable-cache-dir="); + puts(" --cache-entry-lifetime-seconds="); } Args parse_args(int32_t argc, char **argv) { @@ -49,6 +53,7 @@ Args parse_args(int32_t argc, char **argv) { Args args; memset(&args, 0, sizeof(Args)); args.list_of_headers_to_log = simple_archiver_list_init(); + args.cache_lifespan_seconds = C_SIMPLE_HTTP_DEFAULT_CACHE_LIFESPAN_SECONDS; while (argc > 0) { if ((strcmp(argv[0], "-p") == 0 || strcmp(argv[0], "--port") == 0) @@ -109,6 +114,20 @@ Args parse_args(int32_t argc, char **argv) { printf("Directory \"%s\" exists.\n", args.cache_dir); } closedir(d); + } else if (strncmp(argv[0], "--cache-entry-lifetime-seconds=", 31) == 0) { + args.cache_lifespan_seconds = strtoul(argv[0] + 31, NULL, 10); + if (args.cache_lifespan_seconds == 0) { + fprintf( + stderr, + "ERROR: Invalid --cache-entry-lifetime-seconds=%s entry!\n", + argv[0] + 31); + print_usage(); + exit(1); + } else { + printf( + "NOTICE set cache-entry-lifetime to %lu\n", + args.cache_lifespan_seconds); + } } else { fprintf(stderr, "ERROR: Invalid args!\n"); print_usage(); diff --git a/src/arg_parse.h b/src/arg_parse.h index c14ee90..24ba5f7 100644 --- a/src/arg_parse.h +++ b/src/arg_parse.h @@ -37,6 +37,7 @@ typedef struct Args { // Non-NULL if cache-dir is specified and cache is to be used. // Does not need to be free'd since it points to a string in argv. const char *cache_dir; + size_t cache_lifespan_seconds; } Args; void print_usage(void); diff --git a/src/constants.h b/src/constants.h index c6ba249..b8d1ebf 100644 --- a/src/constants.h +++ b/src/constants.h @@ -25,5 +25,6 @@ #define C_SIMPLE_HTTP_CONFIG_BUF_SIZE 1024 #define C_SIMPLE_HTTP_QUOTE_COUNT_MAX 3 #define C_SIMPLE_HTTP_TRY_CONFIG_RELOAD_MAX_ATTEMPTS 20 +#define C_SIMPLE_HTTP_DEFAULT_CACHE_LIFESPAN_SECONDS 604800 #endif diff --git a/src/html_cache.c b/src/html_cache.c index 4ab6fab..4b8403f 100644 --- a/src/html_cache.c +++ b/src/html_cache.c @@ -25,6 +25,9 @@ #include #include +// libc includes. +#include + // Third-party includes. #include #include @@ -208,6 +211,7 @@ int c_simple_http_cache_path( const char *config_filename, const char *cache_dir, C_SIMPLE_HTTP_HTTPTemplates *templates, + size_t cache_entry_lifespan, char **buf_out) { if (!path) { fprintf(stderr, "ERROR cache_path function: path is NULL!\n"); @@ -391,11 +395,17 @@ int c_simple_http_cache_path( } // Compare modification times. + struct timespec current_time; + if (clock_gettime(CLOCK_REALTIME, ¤t_time) != 0) { + memset(¤t_time, 0, sizeof(struct timespec)); + } CACHE_FILE_WRITE_CHECK: if (force_cache_update || cache_file_stat.st_mtim.tv_sec < config_file_stat.st_mtim.tv_sec || (cache_file_stat.st_mtim.tv_sec == config_file_stat.st_mtim.tv_sec - && cache_file_stat.st_mtim.tv_nsec < config_file_stat.st_mtim.tv_nsec)) + && cache_file_stat.st_mtim.tv_nsec < config_file_stat.st_mtim.tv_nsec) + || (current_time.tv_sec - cache_file_stat.st_mtim.tv_sec + > (ssize_t)cache_entry_lifespan)) { // Cache file is out of date. diff --git a/src/html_cache.h b/src/html_cache.h index ebaa385..5167425 100644 --- a/src/html_cache.h +++ b/src/html_cache.h @@ -37,6 +37,7 @@ int c_simple_http_cache_path( const char *config_filename, const char *cache_dir, C_SIMPLE_HTTP_HTTPTemplates *templates, + size_t cache_entry_lifespan, char **buf_out); #endif diff --git a/src/http.c b/src/http.c index 9ced398..0e688aa 100644 --- a/src/http.c +++ b/src/http.c @@ -65,8 +65,7 @@ char *c_simple_http_request_response( C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, enum C_SIMPLE_HTTP_ResponseCode *out_response_code, - const char *cache_dir, - const char *config_filename) { + const Args *args) { if (out_size) { *out_size = 0; } @@ -177,12 +176,13 @@ char *c_simple_http_request_response( char *generated_buf = NULL; - if (cache_dir) { + if (args->cache_dir) { int ret = c_simple_http_cache_path( stripped_path ? stripped_path : request_path_unescaped, - config_filename, - cache_dir, + args->config_file, + args->cache_dir, templates, + args->cache_lifespan_seconds, &generated_buf); if (ret < 0) { fprintf(stderr, "ERROR Failed to generate template with cache!\n"); diff --git a/src/http.h b/src/http.h index 87618c4..7b7a52a 100644 --- a/src/http.h +++ b/src/http.h @@ -25,6 +25,7 @@ #include // Local includes. +#include "arg_parse.h" #include "config.h" typedef C_SIMPLE_HTTP_ParsedConfig C_SIMPLE_HTTP_HTTPTemplates; @@ -49,8 +50,7 @@ char *c_simple_http_request_response( C_SIMPLE_HTTP_HTTPTemplates *templates, size_t *out_size, enum C_SIMPLE_HTTP_ResponseCode *out_response_code, - const char *cache_dir, - const char *config_filename + const Args *args ); /// Takes a PATH string and returns a "bare" path. diff --git a/src/main.c b/src/main.c index 9c9f51c..976f245 100644 --- a/src/main.c +++ b/src/main.c @@ -360,8 +360,7 @@ int main(int argc, char **argv) { &parsed_config, &response_size, &response_code, - args.cache_dir, - args.config_file); + &args); if (response && response_code == C_SIMPLE_HTTP_Response_200_OK) { CHECK_ERROR_WRITE(write(connection_fd, "HTTP/1.1 200 OK\n", 16)); CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11)); diff --git a/src/test.c b/src/test.c index b397679..d47079f 100644 --- a/src/test.c +++ b/src/test.c @@ -15,6 +15,7 @@ #include "http_template.h" #include "http.h" #include "html_cache.h" +#include "constants.h" // Third party includes. #include @@ -808,6 +809,7 @@ int main(void) { test_http_template_filename5, "/tmp/c_simple_http_cache_dir", &templates, + 0xFFFFFFFF, &buf); CHECK_TRUE(int_ret > 0); @@ -830,6 +832,7 @@ int main(void) { test_http_template_filename5, "/tmp/c_simple_http_cache_dir", &templates, + 0xFFFFFFFF, &buf); CHECK_TRUE(int_ret == 0); ASSERT_TRUE(buf); @@ -870,6 +873,7 @@ int main(void) { test_http_template_filename5, "/tmp/c_simple_http_cache_dir", &templates, + 0xFFFFFFFF, &buf); CHECK_TRUE(int_ret > 0); ASSERT_TRUE(buf); @@ -892,6 +896,7 @@ int main(void) { test_http_template_filename5, "/tmp/c_simple_http_cache_dir", &templates, + 0xFFFFFFFF, &buf); CHECK_TRUE(int_ret == 0); ASSERT_TRUE(buf); @@ -909,6 +914,10 @@ int main(void) { CHECK_TRUE(cache_file_size_2 == cache_file_size_3); // Edit config file. + puts("Sleeping for two seconds to ensure edited file's timestamp has " + "changed..."); + sleep(2); + puts("Done sleeping."); test_file = fopen(test_http_template_filename5, "w"); ASSERT_TRUE(test_file); @@ -935,6 +944,24 @@ int main(void) { test_http_template_filename5, "/tmp/c_simple_http_cache_dir", &templates, + 0xFFFFFFFF, + &buf); + CHECK_TRUE(int_ret > 0); + ASSERT_TRUE(buf); + CHECK_TRUE(strcmp(buf, "

Alternate test text.
Yep.

") == 0); + free(buf); + buf = NULL; + + puts("Sleeping for two seconds to ensure cache file has aged..."); + sleep(2); + puts("Done sleeping."); + // Re-run cache function, checking that it is invalidated. + int_ret = c_simple_http_cache_path( + "/", + test_http_template_filename5, + "/tmp/c_simple_http_cache_dir", + &templates, + 1, &buf); CHECK_TRUE(int_ret > 0); ASSERT_TRUE(buf); From aef990f3414f42a27b125001a1d034b7a7be3f9d Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Thu, 26 Sep 2024 12:38:51 +0900 Subject: [PATCH 22/22] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1e218c1..862c2f6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ A simple HTTP/1.1 server written in C. For example: --req-header-to-print=User-Agent Note that this option is case-insensitive --enable-reload-config-on-change + --enable-cache-dir= + --cache-entry-lifetime-seconds= ## Before Compiling