diff --git a/src/arg_parse.c b/src/arg_parse.c index 5fea90a..87a539e 100644 --- a/src/arg_parse.c +++ b/src/arg_parse.c @@ -44,6 +44,8 @@ void print_usage(void) { puts(" --enable-cache-dir="); puts(" --cache-entry-lifetime-seconds="); puts(" --enable-static-dir="); + puts(" --generate-dir="); + puts(" --generate-enable-overwrite"); } Args parse_args(int32_t argc, char **argv) { @@ -152,6 +154,39 @@ Args parse_args(int32_t argc, char **argv) { printf("Directory \"%s\" exists.\n", args.static_dir); } closedir(d); + } else if (strncmp(argv[0], "--generate-dir=", 15) == 0) { + args.generate_dir = argv[0] + 15; + // Check if it actually is an existing directory. + DIR *d = opendir(args.generate_dir); + if (d == NULL) { + if (errno == ENOENT) { + printf( + "Directory \"%s\" doesn't exist, creating it...\n", + args.generate_dir); + int ret = mkdir( + args.generate_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.generate_dir, + errno); + exit(1); + } + } else { + printf("Directory \"%s\" exists.\n", args.generate_dir); + } + closedir(d); + } else if (strcmp(argv[0], "--generate-enable-overwrite") == 0) { + args.flags |= 4; } else { fprintf(stderr, "ERROR: Invalid args!\n"); print_usage(); diff --git a/src/arg_parse.h b/src/arg_parse.h index 7595f72..1a4a510 100644 --- a/src/arg_parse.h +++ b/src/arg_parse.h @@ -28,6 +28,7 @@ typedef struct Args { // xxxx xxx1 - disable peer addr print. // xxxx xx0x - disable listen on config file for reloading. // xxxx xx1x - enable listen on config file for reloading. + // xxxx x1xx - enable overwrite on generate. uint16_t flags; uint16_t port; // Does not need to be free'd, this should point to a string in argv. @@ -41,6 +42,9 @@ typedef struct Args { // Non-NULL if static-dir is specified and files in the dir are to be served. // Does not need to be free'd since it points to a string in argv. const char *static_dir; + // Non-NULL if generate-dir is specified. + // Does not need to be free'd since it points to a string in argv. + const char *generate_dir; } Args; void print_usage(void); diff --git a/src/config.h b/src/config.h index a194d28..9d7c9e6 100644 --- a/src/config.h +++ b/src/config.h @@ -25,6 +25,11 @@ typedef struct C_SIMPLE_HTTP_ParsedConfig { /// Each entry in this data structure is a hash map where its value for the /// key "PATH" is the path it represents. The "key" value should match the /// mentioned value for "PATH". + /// + /// An example mapping for this structure (based on current example config): + /// KEY: "/", VALUE: HashMapWrapper struct + /// KEY: "/inner", VALUE: HashMapWrapper struct + /// KEY: "/inner/further", VALUE: HashMapWrapper struct union { SDArchiverHashMap *paths; SDArchiverHashMap *hash_map; diff --git a/src/main.c b/src/main.c index 325663f..cf1ce8f 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,10 @@ #include // Linux/Unix includes. +#include #include +#include +#include #include #include #include @@ -33,11 +36,14 @@ // Third party includes. #include +#include +#include // Local includes. #include "arg_parse.h" #include "big_endian.h" #include "config.h" +#include "http_template.h" #include "tcp_socket.h" #include "signal_handling.h" #include "globals.h" @@ -326,6 +332,139 @@ int c_simple_http_manage_connections(void *data, void *ud) { return 1; } +int generate_paths_fn(const void *key, + size_t key_size, + const void *value, + void *ud) { + const char *path = key; + const ConnectionContext *ctx = ud; + const char *generate_dir = ctx->args->generate_dir; + + const unsigned long path_len = strlen(path); + const unsigned long generate_dir_len = strlen(generate_dir); + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *string_parts = simple_archiver_list_init(); + + // Add generate_dir as first path of paths to join. + c_simple_http_add_string_part(string_parts, generate_dir, 0); + + // Ensure next character after generate_dir contains a '/' if generate_dir + // didn't contain one at the end. + if (generate_dir_len > 0 && generate_dir[generate_dir_len - 1] != '/') { + c_simple_http_add_string_part(string_parts, "/", 0); + } + + // Append the path. + if (strcmp("path", "/") != 0) { + // Is not root. + uint32_t idx = 0; + while (idx <= path_len && path[idx] == '/') { + ++idx; + } + c_simple_http_add_string_part(string_parts, path + idx, 0); + } + + // Add the final '/'. + if (path_len > 0 && path[path_len - 1] != '/') { + c_simple_http_add_string_part(string_parts, "/", 0); + } + + // Add the ending "index.html". + c_simple_http_add_string_part(string_parts, "index.html", 0); + + // Get the combined string. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *generated_path = c_simple_http_combine_string_parts(string_parts); + if (!generated_path) { + fprintf(stderr, "ERROR Failed to get generated path (path: %s)!\n", path); + return 1; + } + + if ((ctx->args->flags & 4) == 0) { + // Overwrite not enabled, check if file already exists. + FILE *fd = fopen(generated_path, "rb"); + if (fd) { + fclose(fd); + fprintf( + stderr, + "WARNING Path \"%s\" exists and \"--generate-enable-overwrite\" not " + "specified, skipping!\n", + generated_path); + return 0; + } + } + + // Ensure the required dirs exist. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *generated_path_dup = strdup(generated_path); + + uint_fast8_t did_make_generated_path_dir = 0; + char *generated_path_dir = dirname(generated_path_dup); + if (generated_path_dir) { + DIR *fd = opendir(generated_path_dir); + if (!fd) { + if (errno == ENOENT) { + c_simple_http_helper_mkdir_tree(generated_path_dir); + did_make_generated_path_dir = 1; + } else { + fprintf(stderr, + "ERROR opendir on path dirname failed unexpectedly (path: %s)!" + "\n", + path); + return 1; + } + } else { + // Directory already exists. + closedir(fd); + } + } else { + fprintf(stderr, + "ERROR Failed to get dirname of generated path dir (path: %s)" + "!\n", + path); + return 1; + } + + // Generate the html. + size_t html_buf_size = 0; + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *html_buf = c_simple_http_path_to_generated(path, + ctx->parsed, + &html_buf_size, + NULL); + if (!html_buf || html_buf_size == 0) { + fprintf(stderr, + "WARNING Failed to generate html for generate (path: %s), " + "skipping!\n", + path); + if (did_make_generated_path_dir) { + if (rmdir(generated_path_dir) == -1) { + fprintf(stderr, + "WARNING rmdir on generated_path_dir failed, errno: %d\n", + errno); + } + } + return 0; + } + + // Save the html. + FILE *fd = fopen(generated_path, "wb"); + unsigned long fwrite_ret = fwrite(html_buf, 1, html_buf_size, fd); + if (fwrite_ret < html_buf_size) { + fclose(fd); + unlink(generated_path); + fprintf(stderr, + "ERROR Unable to write entirely to \"%s\"!\n", + generated_path); + return 1; + } else { + fclose(fd); + } + + return 0; +} + int main(int argc, char **argv) { __attribute__((cleanup(c_simple_http_free_args))) Args args = parse_args(argc, argv); @@ -348,6 +487,22 @@ int main(int argc, char **argv) { return 5; } + // If generate-dir is specified, the program must stop after generating or + // failure. + if (args.generate_dir) { + ConnectionContext ctx; + ctx.args = &args; + ctx.parsed = &parsed_config; + if (simple_archiver_hash_map_iter(parsed_config.paths, + generate_paths_fn, + &ctx)) { + fprintf(stderr, "ERROR during generating!\n"); + return 1; + } + puts("Finished generating."); + return 0; + } + __attribute__((cleanup(cleanup_tcp_socket))) int tcp_socket = create_tcp_socket(args.port); if (tcp_socket == -1) {