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) {