Merge branch 'dev'
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 20s

Resolves #3
This commit is contained in:
Stephen Seo 2024-09-26 12:59:27 +09:00
commit 8a5195d5c4
17 changed files with 1329 additions and 152 deletions

2
.gitignore vendored
View file

@ -2,3 +2,5 @@
/objs/ /objs/
/unit_test /unit_test
/build*/ /build*/
/.cache/
/compile_commands.json

View file

@ -11,6 +11,7 @@ set(c_simple_http_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/src/config.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/config.c"
"${CMAKE_CURRENT_SOURCE_DIR}/src/http_template.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/http_template.c"
"${CMAKE_CURRENT_SOURCE_DIR}/src/helpers.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/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/linked_list.c"
"${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/data_structures/hash_map.c" "${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleArchiver/src/data_structures/hash_map.c"

View file

@ -42,7 +42,8 @@ HEADERS = \
src/http.h \ src/http.h \
src/config.h \ src/config.h \
src/http_template.h \ src/http_template.h \
src/helpers.h src/helpers.h \
src/html_cache.h
SOURCES = \ SOURCES = \
src/main.c \ src/main.c \
@ -55,6 +56,7 @@ SOURCES = \
src/config.c \ src/config.c \
src/http_template.c \ src/http_template.c \
src/helpers.c \ src/helpers.c \
src/html_cache.c \
third_party/SimpleArchiver/src/helpers.c \ third_party/SimpleArchiver/src/helpers.c \
third_party/SimpleArchiver/src/data_structures/linked_list.c \ third_party/SimpleArchiver/src/data_structures/linked_list.c \
third_party/SimpleArchiver/src/data_structures/hash_map.c \ third_party/SimpleArchiver/src/data_structures/hash_map.c \

View file

@ -12,6 +12,8 @@ A simple HTTP/1.1 server written in C.
For example: --req-header-to-print=User-Agent For example: --req-header-to-print=User-Agent
Note that this option is case-insensitive Note that this option is case-insensitive
--enable-reload-config-on-change --enable-reload-config-on-change
--enable-cache-dir=<DIR>
--cache-entry-lifetime-seconds=<SECONDS>
## Before Compiling ## Before Compiling

View file

@ -21,6 +21,17 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
// libc includes.
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
// Posix includes.
#include <sys/stat.h>
// Local includes.
#include "constants.h"
void print_usage(void) { void print_usage(void) {
puts("Usage:"); puts("Usage:");
puts(" -p <port> | --port <port>"); puts(" -p <port> | --port <port>");
@ -30,6 +41,8 @@ void print_usage(void) {
puts(" For example: --req-header-to-print=User-Agent"); puts(" For example: --req-header-to-print=User-Agent");
puts(" Note that this option is case-insensitive"); puts(" Note that this option is case-insensitive");
puts(" --enable-reload-config-on-change"); puts(" --enable-reload-config-on-change");
puts(" --enable-cache-dir=<DIR>");
puts(" --cache-entry-lifetime-seconds=<SECONDS>");
} }
Args parse_args(int32_t argc, char **argv) { Args parse_args(int32_t argc, char **argv) {
@ -40,6 +53,7 @@ Args parse_args(int32_t argc, char **argv) {
Args args; Args args;
memset(&args, 0, sizeof(Args)); memset(&args, 0, sizeof(Args));
args.list_of_headers_to_log = simple_archiver_list_init(); args.list_of_headers_to_log = simple_archiver_list_init();
args.cache_lifespan_seconds = C_SIMPLE_HTTP_DEFAULT_CACHE_LIFESPAN_SECONDS;
while (argc > 0) { while (argc > 0) {
if ((strcmp(argv[0], "-p") == 0 || strcmp(argv[0], "--port") == 0) if ((strcmp(argv[0], "-p") == 0 || strcmp(argv[0], "--port") == 0)
@ -69,8 +83,53 @@ Args parse_args(int32_t argc, char **argv) {
} }
} else if (strcmp(argv[0], "--enable-reload-config-on-change") == 0) { } else if (strcmp(argv[0], "--enable-reload-config-on-change") == 0) {
args.flags |= 2; 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);
}
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 { } else {
puts("ERROR: Invalid args!\n"); fprintf(stderr, "ERROR: Invalid args!\n");
print_usage(); print_usage();
exit(1); exit(1);
} }

View file

@ -34,6 +34,10 @@ typedef struct Args {
const char *config_file; const char *config_file;
// Needs to be free'd. // Needs to be free'd.
SDArchiverLinkedList *list_of_headers_to_log; 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;
size_t cache_lifespan_seconds;
} Args; } Args;
void print_usage(void); void print_usage(void);

View file

@ -25,5 +25,6 @@
#define C_SIMPLE_HTTP_CONFIG_BUF_SIZE 1024 #define C_SIMPLE_HTTP_CONFIG_BUF_SIZE 1024
#define C_SIMPLE_HTTP_QUOTE_COUNT_MAX 3 #define C_SIMPLE_HTTP_QUOTE_COUNT_MAX 3
#define C_SIMPLE_HTTP_TRY_CONFIG_RELOAD_MAX_ATTEMPTS 20 #define C_SIMPLE_HTTP_TRY_CONFIG_RELOAD_MAX_ATTEMPTS 20
#define C_SIMPLE_HTTP_DEFAULT_CACHE_LIFESPAN_SECONDS 604800
#endif #endif

View file

@ -21,6 +21,13 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
// libc includes.
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <libgen.h>
int c_simple_http_internal_get_string_part_full_size(void *data, void *ud) { int c_simple_http_internal_get_string_part_full_size(void *data, void *ud) {
C_SIMPLE_HTTP_String_Part *part = data; C_SIMPLE_HTTP_String_Part *part = data;
size_t *count = ud; 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); 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 // vim: et ts=2 sts=2 sw=2

View file

@ -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. /// non-NULL, it must be free'd.
char *c_simple_http_helper_unescape_uri(const char *uri); 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 #endif
// vim: et ts=2 sts=2 sw=2 // vim: et ts=2 sts=2 sw=2

590
src/html_cache.c Normal file
View file

@ -0,0 +1,590 @@
// 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.
// Standard library includes.
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// POSIX includes.
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
// libc includes.
#include <time.h>
// Third-party includes.
#include <SimpleArchiver/src/data_structures/linked_list.h>
#include <SimpleArchiver/src/data_structures/hash_map.h>
#include <SimpleArchiver/src/helpers.h>
// 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)))
char *stripped_path = c_simple_http_strip_path(path, strlen(path));
if (!stripped_path) {
return NULL;
}
if (strcmp(stripped_path, "/") == 0) {
char *buf = malloc(5);
memcpy(buf, "ROOT", 5);
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;
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);
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;
}
}
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) {
// 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);
}
}
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,
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");
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;
memset(&cache_file_stat, 0, sizeof(struct 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;
memset(&config_file_stat, 0, sizeof(struct 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.
struct timespec current_time;
if (clock_gettime(CLOCK_REALTIME, &current_time) != 0) {
memset(&current_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)
|| (current_time.tv_sec - cache_file_stat.st_mtim.tv_sec
> (ssize_t)cache_entry_lifespan))
{
// 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) {
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;
}
// vim: et ts=2 sts=2 sw=2

45
src/html_cache.h Normal file
View file

@ -0,0 +1,45 @@
// 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_
// 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 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,
C_SIMPLE_HTTP_HTTPTemplates *templates,
size_t cache_entry_lifespan,
char **buf_out);
#endif
// vim: et ts=2 sts=2 sw=2

View file

@ -23,10 +23,12 @@
// Third party includes. // Third party includes.
#include <SimpleArchiver/src/helpers.h> #include <SimpleArchiver/src/helpers.h>
#include <SimpleArchiver/src/data_structures/linked_list.h>
// Local includes // Local includes
#include "http_template.h" #include "http_template.h"
#include "helpers.h" #include "helpers.h"
#include "html_cache.h"
#define REQUEST_TYPE_BUFFER_SIZE 16 #define REQUEST_TYPE_BUFFER_SIZE 16
#define REQUEST_PATH_BUFFER_SIZE 256 #define REQUEST_PATH_BUFFER_SIZE 256
@ -60,9 +62,10 @@ const char *c_simple_http_response_code_error_to_response(
char *c_simple_http_request_response( char *c_simple_http_request_response(
const char *request, const char *request,
uint32_t size, uint32_t size,
const C_SIMPLE_HTTP_HTTPTemplates *templates, C_SIMPLE_HTTP_HTTPTemplates *templates,
size_t *out_size, size_t *out_size,
enum C_SIMPLE_HTTP_ResponseCode *out_response_code) { enum C_SIMPLE_HTTP_ResponseCode *out_response_code,
const Args *args) {
if (out_size) { if (out_size) {
*out_size = 0; *out_size = 0;
} }
@ -170,10 +173,43 @@ char *c_simple_http_request_response(
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *stripped_path = c_simple_http_strip_path( char *stripped_path = c_simple_http_strip_path(
request_path_unescaped, strlen(request_path_unescaped)); request_path_unescaped, strlen(request_path_unescaped));
char *generated_buf = c_simple_http_path_to_generated(
stripped_path ? stripped_path : request_path_unescaped, char *generated_buf = NULL;
templates,
&generated_size); if (args->cache_dir) {
int ret = c_simple_http_cache_path(
stripped_path ? stripped_path : request_path_unescaped,
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");
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) { if (!generated_buf || generated_size == 0) {
fprintf(stderr, "ERROR Unable to generate response html for path \"%s\"!\n", fprintf(stderr, "ERROR Unable to generate response html for path \"%s\"!\n",

View file

@ -25,6 +25,7 @@
#include <SimpleArchiver/src/data_structures/hash_map.h> #include <SimpleArchiver/src/data_structures/hash_map.h>
// Local includes. // Local includes.
#include "arg_parse.h"
#include "config.h" #include "config.h"
typedef C_SIMPLE_HTTP_ParsedConfig C_SIMPLE_HTTP_HTTPTemplates; typedef C_SIMPLE_HTTP_ParsedConfig C_SIMPLE_HTTP_HTTPTemplates;
@ -46,9 +47,10 @@ const char *c_simple_http_response_code_error_to_response(
char *c_simple_http_request_response( char *c_simple_http_request_response(
const char *request, const char *request,
uint32_t size, uint32_t size,
const C_SIMPLE_HTTP_HTTPTemplates *templates, C_SIMPLE_HTTP_HTTPTemplates *templates,
size_t *out_size, size_t *out_size,
enum C_SIMPLE_HTTP_ResponseCode *out_response_code enum C_SIMPLE_HTTP_ResponseCode *out_response_code,
const Args *args
); );
/// Takes a PATH string and returns a "bare" path. /// Takes a PATH string and returns a "bare" path.

View file

@ -25,53 +25,8 @@
#include <SimpleArchiver/src/data_structures/linked_list.h> #include <SimpleArchiver/src/data_structures/linked_list.h>
#include <SimpleArchiver/src/helpers.h> #include <SimpleArchiver/src/helpers.h>
typedef struct C_SIMPLE_HTTP_INTERNAL_Template_Node { // Local includes.
char *html; #include "helpers.h"
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;
}
/// Returns 0 if "c_string" ends with "_FILE". /// Returns 0 if "c_string" ends with "_FILE".
int c_simple_http_internal_ends_with_FILE(const char *c_string) { int c_simple_http_internal_ends_with_FILE(const char *c_string) {
@ -96,10 +51,14 @@ int c_simple_http_internal_ends_with_FILE(const char *c_string) {
char *c_simple_http_path_to_generated( char *c_simple_http_path_to_generated(
const char *path, const char *path,
const C_SIMPLE_HTTP_HTTPTemplates *templates, const C_SIMPLE_HTTP_HTTPTemplates *templates,
size_t *output_buf_size) { size_t *output_buf_size,
SDArchiverLinkedList **files_list_out) {
if (output_buf_size) { if (output_buf_size) {
*output_buf_size = 0; *output_buf_size = 0;
} }
if (files_list_out) {
*files_list_out = simple_archiver_list_init();
}
size_t path_len_size_t = strlen(path) + 1; size_t path_len_size_t = strlen(path) + 1;
if (path_len_size_t > 0xFFFFFFFF) { if (path_len_size_t > 0xFFFFFFFF) {
fprintf(stderr, "ERROR: Path string is too large!\n"); fprintf(stderr, "ERROR: Path string is too large!\n");
@ -139,6 +98,11 @@ char *c_simple_http_path_to_generated(
} }
html_buf[html_file_size] = 0; html_buf[html_file_size] = 0;
html_buf_size = (size_t)html_file_size; 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 { } else {
char *stored_html = char *stored_html =
simple_archiver_hash_map_get(wrapped_hash_map->hash_map, "HTML", 5); simple_archiver_hash_map_get(wrapped_hash_map->hash_map, "HTML", 5);
@ -153,13 +117,12 @@ char *c_simple_http_path_to_generated(
// At this point, html_buf contains the raw HTML as a C-string. // At this point, html_buf contains the raw HTML as a C-string.
__attribute__((cleanup(simple_archiver_list_free))) __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 idx = 0;
size_t last_template_idx = 0; size_t last_template_idx = 0;
__attribute__((cleanup(c_simple_http_internal_cleanup_template_node))) C_SIMPLE_HTTP_String_Part string_part;
C_SIMPLE_HTTP_INTERNAL_Template_Node *template_node = NULL;
size_t delimeter_count = 0; size_t delimeter_count = 0;
@ -175,22 +138,23 @@ char *c_simple_http_path_to_generated(
if (delimeter_count >= 3) { if (delimeter_count >= 3) {
delimeter_count = 0; delimeter_count = 0;
state |= 1; state |= 1;
if (template_html_list->count != 0) { if (string_part_list->count != 0) {
C_SIMPLE_HTTP_INTERNAL_Template_Node *last_node = C_SIMPLE_HTTP_String_Part *last_part =
template_html_list->tail->prev->data; string_part_list->tail->prev->data;
last_template_idx = last_node->orig_end_idx; last_template_idx = last_part->extra;
} }
template_node = malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); string_part.size = idx - last_template_idx - 1;
template_node->html_size = idx - last_template_idx - 2; string_part.buf = malloc(string_part.size);
template_node->html = malloc(template_node->html_size);
memcpy( memcpy(
template_node->html, string_part.buf,
html_buf + last_template_idx, html_buf + last_template_idx,
template_node->html_size); string_part.size);
template_node->orig_end_idx = idx + 1; string_part.buf[string_part.size - 1] = 0;
simple_archiver_list_add(template_html_list, template_node, string_part.extra = idx + 1;
c_simple_http_internal_free_template_node); c_simple_http_add_string_part(string_part_list,
template_node = NULL; string_part.buf,
string_part.extra);
free(string_part.buf);
} }
} else { } else {
delimeter_count = 0; delimeter_count = 0;
@ -203,14 +167,14 @@ char *c_simple_http_path_to_generated(
if (delimeter_count >= 3) { if (delimeter_count >= 3) {
delimeter_count = 0; delimeter_count = 0;
state &= 0xFFFFFFFE; state &= 0xFFFFFFFE;
C_SIMPLE_HTTP_INTERNAL_Template_Node *last_node = C_SIMPLE_HTTP_String_Part *last_part =
template_html_list->tail->prev->data; string_part_list->tail->prev->data;
size_t var_size = idx - 2 - last_node->orig_end_idx; size_t var_size = idx - 2 - last_part->extra;
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *var = malloc(var_size + 1); char *var = malloc(var_size + 1);
memcpy( memcpy(
var, var,
html_buf + last_node->orig_end_idx, html_buf + last_part->extra,
var_size); var_size);
var[var_size] = 0; var[var_size] = 0;
const char *value_c_str = const char *value_c_str =
@ -243,14 +207,12 @@ char *c_simple_http_path_to_generated(
value_c_str); value_c_str);
return NULL; return NULL;
} }
template_node = string_part.size = (size_t)file_size + 1;
malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); string_part.buf = malloc(string_part.size);
template_node->html_size = (size_t)file_size; string_part.extra = idx + 1;
template_node->html = malloc(template_node->html_size);
template_node->orig_end_idx = idx + 1;
if (fread(template_node->html, if (fread(string_part.buf,
template_node->html_size, string_part.size - 1,
1, 1,
f) f)
!= 1) { != 1) {
@ -258,26 +220,30 @@ char *c_simple_http_path_to_generated(
value_c_str); value_c_str);
return NULL; 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);
simple_archiver_list_add(
*files_list_out, variable_filename, NULL);
}
} else { } else {
// Variable data is "value_c_str". // Variable data is "value_c_str".
template_node = string_part.size = strlen(value_c_str) + 1;
malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); string_part.buf = malloc(string_part.size);
size_t size = strlen(value_c_str); memcpy(string_part.buf, value_c_str, string_part.size);
template_node->html = malloc(size); string_part.buf[string_part.size - 1] = 0;
memcpy(template_node->html, value_c_str, size); string_part.extra = idx + 1;
template_node->html_size = size;
template_node->orig_end_idx = idx + 1;
} }
} else { } else {
template_node = string_part.buf = NULL;
malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); string_part.size = 0;
template_node->html = NULL; string_part.extra = idx + 1;
template_node->html_size = 0;
template_node->orig_end_idx = idx + 1;
} }
simple_archiver_list_add(template_html_list, template_node, c_simple_http_add_string_part(string_part_list,
c_simple_http_internal_free_template_node); string_part.buf,
template_node = NULL; string_part.extra);
free(string_part.buf);
} }
} else { } else {
delimeter_count = 0; delimeter_count = 0;
@ -285,48 +251,28 @@ char *c_simple_http_path_to_generated(
} }
} }
if (template_html_list->count != 0) { if (string_part_list->count != 0) {
C_SIMPLE_HTTP_INTERNAL_Template_Node *last_node = C_SIMPLE_HTTP_String_Part *last_part =
template_html_list->tail->prev->data; string_part_list->tail->prev->data;
if (idx > last_node->orig_end_idx) { if (idx > last_part->extra) {
template_node = malloc(sizeof(C_SIMPLE_HTTP_INTERNAL_Template_Node)); string_part.size = idx - last_part->extra + 1;
size_t size = idx - last_node->orig_end_idx; string_part.buf = malloc(string_part.size);
template_node->html = malloc(size); memcpy(string_part.buf, html_buf + last_part->extra, string_part.size);
memcpy(template_node->html, html_buf + last_node->orig_end_idx, size); string_part.buf[string_part.size - 1] = 0;
template_node->html_size = size; string_part.extra = idx + 1;
template_node->orig_end_idx = idx + 1; c_simple_http_add_string_part(string_part_list,
simple_archiver_list_add(template_html_list, template_node, string_part.buf,
c_simple_http_internal_free_template_node); string_part.extra);
template_node = NULL; 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( char *combined_buf = c_simple_http_combine_string_parts(string_part_list);
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;
if (output_buf_size) { if (output_buf_size) {
*output_buf_size = final_size; *output_buf_size = strlen(combined_buf);
} }
return to_fill.html; return combined_buf;
} else { } else {
// Prevent cleanup fn from "free"ing html_buf and return it verbatim. // Prevent cleanup fn from "free"ing html_buf and return it verbatim.
char *buf = html_buf; char *buf = html_buf;

View file

@ -22,14 +22,20 @@
// Standard library includes. // Standard library includes.
#include <stddef.h> #include <stddef.h>
// Returns non-NULL on success, which must be free'd after use. // Third-party includes.
// Takes a path string and templates and returns the generated HTML. #include <SimpleArchiver/src/data_structures/linked_list.h>
// If "output_buf_size" is non-NULL, it will be set to the size of the returned
// buffer. // 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( char *c_simple_http_path_to_generated(
const char *path, const char *path,
const C_SIMPLE_HTTP_HTTPTemplates *templates, const C_SIMPLE_HTTP_HTTPTemplates *templates,
size_t *output_buf_size); size_t *output_buf_size,
SDArchiverLinkedList **files_list_out);
#endif #endif

View file

@ -359,7 +359,8 @@ int main(int argc, char **argv) {
(uint32_t)read_ret, (uint32_t)read_ret,
&parsed_config, &parsed_config,
&response_size, &response_size,
&response_code); &response_code,
&args);
if (response && response_code == C_SIMPLE_HTTP_Response_200_OK) { 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, "HTTP/1.1 200 OK\n", 16));
CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11)); CHECK_ERROR_WRITE(write(connection_fd, "Allow: GET\n", 11));

View file

@ -4,15 +4,23 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
// POSIX includes.
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
// Local includes. // Local includes.
#include "config.h" #include "config.h"
#include "helpers.h"
#include "http_template.h" #include "http_template.h"
#include "http.h" #include "http.h"
#include "helpers.h" #include "html_cache.h"
#include "constants.h"
// Third party includes. // Third party includes.
#include <SimpleArchiver/src/helpers.h> #include <SimpleArchiver/src/helpers.h>
#include <SimpleArchiver/src/data_structures/hash_map.h> #include <SimpleArchiver/src/data_structures/hash_map.h>
#include <SimpleArchiver/src/data_structures/linked_list.h>
static int32_t checks_checked = 0; static int32_t checks_checked = 0;
static int32_t checks_passed = 0; static int32_t checks_passed = 0;
@ -90,6 +98,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) { int main(void) {
// Test config. // Test config.
{ {
@ -253,12 +270,18 @@ int main(void) {
size_t output_buf_size; size_t output_buf_size;
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *filenames_list = NULL;
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) __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(buf != NULL);
ASSERT_TRUE(strcmp(buf, "<h1>Test</h1>") == 0); ASSERT_TRUE(strcmp(buf, "<h1>Test</h1>") == 0);
CHECK_TRUE(output_buf_size == 13); CHECK_TRUE(output_buf_size == 13);
CHECK_TRUE(filenames_list->count == 0);
simple_archiver_helper_cleanup_c_string(&buf); simple_archiver_helper_cleanup_c_string(&buf);
simple_archiver_list_free(&filenames_list);
__attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) __attribute__((cleanup(test_internal_cleanup_delete_temporary_file)))
const char *test_http_template_filename2 = const char *test_http_template_filename2 =
@ -289,16 +312,19 @@ int main(void) {
); );
ASSERT_TRUE(config.paths != NULL); 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); ASSERT_TRUE(buf != NULL);
printf("%s\n", buf); //printf("%s\n", buf);
ASSERT_TRUE( ASSERT_TRUE(
strcmp( strcmp(
buf, buf,
"<h1> Some text. </h1><br><h2> More text. </h2>") "<h1> Some text. </h1><br><h2> More text. </h2>")
== 0); == 0);
CHECK_TRUE(output_buf_size == 46); CHECK_TRUE(output_buf_size == 46);
CHECK_TRUE(filenames_list->count == 0);
simple_archiver_helper_cleanup_c_string(&buf); simple_archiver_helper_cleanup_c_string(&buf);
simple_archiver_list_free(&filenames_list);
__attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) __attribute__((cleanup(test_internal_cleanup_delete_temporary_file)))
const char *test_http_template_filename3 = const char *test_http_template_filename3 =
@ -352,16 +378,24 @@ int main(void) {
); );
ASSERT_TRUE(config.paths != NULL); 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); ASSERT_TRUE(buf != NULL);
printf("%s\n", buf); //printf("%s\n", buf);
ASSERT_TRUE( ASSERT_TRUE(
strcmp( strcmp(
buf, buf,
"<h1> testVar text. </h1><br><h2> testVar2 text. </h2>") "<h1> testVar text. </h1><br><h2> testVar2 text. </h2>")
== 0); == 0);
CHECK_TRUE(output_buf_size == 53); 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_helper_cleanup_c_string(&buf);
simple_archiver_list_free(&filenames_list);
__attribute__((cleanup(test_internal_cleanup_delete_temporary_file))) __attribute__((cleanup(test_internal_cleanup_delete_temporary_file)))
const char *test_http_template_filename4 = const char *test_http_template_filename4 =
@ -435,15 +469,27 @@ int main(void) {
); );
ASSERT_TRUE(config.paths != NULL); 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); ASSERT_TRUE(buf != NULL);
printf("%s\n", buf); //printf("%s\n", buf);
ASSERT_TRUE( ASSERT_TRUE(
strcmp( strcmp(
buf, buf,
"<h1> some test text in test var file. </h1>") "<h1> some test text in test var file. </h1>")
== 0); == 0);
CHECK_TRUE(output_buf_size == 43); 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); simple_archiver_helper_cleanup_c_string(&buf);
} }
@ -480,28 +526,39 @@ int main(void) {
free(stripped_path_buf); free(stripped_path_buf);
stripped_path_buf = c_simple_http_strip_path("/someurl/", 9); stripped_path_buf = c_simple_http_strip_path("/someurl/", 9);
//printf("stripped path: %s\n", stripped_path_buf);
CHECK_STREQ(stripped_path_buf, "/someurl"); CHECK_STREQ(stripped_path_buf, "/someurl");
free(stripped_path_buf); free(stripped_path_buf);
stripped_path_buf = c_simple_http_strip_path("/someurl/////", 13); stripped_path_buf = c_simple_http_strip_path("/someurl/////", 13);
//printf("stripped path: %s\n", stripped_path_buf);
CHECK_STREQ(stripped_path_buf, "/someurl"); CHECK_STREQ(stripped_path_buf, "/someurl");
free(stripped_path_buf); free(stripped_path_buf);
stripped_path_buf = c_simple_http_strip_path("/someurl?key=value", 18); 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"); CHECK_STREQ(stripped_path_buf, "/someurl");
free(stripped_path_buf); free(stripped_path_buf);
stripped_path_buf = c_simple_http_strip_path("/someurl#client_data", 20); 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"); CHECK_STREQ(stripped_path_buf, "/someurl");
free(stripped_path_buf); free(stripped_path_buf);
stripped_path_buf = c_simple_http_strip_path("/someurl////?key=value", 22); 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"); CHECK_STREQ(stripped_path_buf, "/someurl");
free(stripped_path_buf); free(stripped_path_buf);
stripped_path_buf = c_simple_http_strip_path("/someurl///#client_data", 23); 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"); CHECK_STREQ(stripped_path_buf, "/someurl");
free(stripped_path_buf); 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. // Test helpers.
@ -542,6 +599,379 @@ int main(void) {
CHECK_TRUE(strcmp(buf, "ABC%ZZ") == 0); CHECK_TRUE(strcmp(buf, "ABC%ZZ") == 0);
free(buf); free(buf);
buf = NULL; 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.
{
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);
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);
// 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(
"<body>{{{VAR_FILE}}}</body>\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.<br>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,
0xFFFFFFFF,
&buf);
CHECK_TRUE(int_ret > 0);
ASSERT_TRUE(buf);
CHECK_TRUE(strcmp(buf, "<body>Some test text.<br>Yep.</body>\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,
0xFFFFFFFF,
&buf);
CHECK_TRUE(int_ret == 0);
ASSERT_TRUE(buf);
CHECK_TRUE(strcmp(buf, "<body>Some test text.<br>Yep.</body>\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=/ .
// 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);
ASSERT_TRUE(
fwrite(
"Alternate test text.<br>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,
0xFFFFFFFF,
&buf);
CHECK_TRUE(int_ret > 0);
ASSERT_TRUE(buf);
CHECK_TRUE(strcmp(buf, "<body>Alternate test text.<br>Yep.</body>\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,
0xFFFFFFFF,
&buf);
CHECK_TRUE(int_ret == 0);
ASSERT_TRUE(buf);
CHECK_TRUE(strcmp(buf, "<body>Alternate test text.<br>Yep.</body>\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);
// 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);
ASSERT_TRUE(
fwrite(
"PATH=/\nHTML='''<h1>{{{VAR_FILE}}}</h1>'''\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,
0xFFFFFFFF,
&buf);
CHECK_TRUE(int_ret > 0);
ASSERT_TRUE(buf);
CHECK_TRUE(strcmp(buf, "<h1>Alternate test text.<br>Yep.</h1>") == 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);
CHECK_TRUE(strcmp(buf, "<h1>Alternate test text.<br>Yep.</h1>") == 0);
free(buf);
buf = NULL;
// Cleanup.
remove("/tmp/c_simple_http_cache_dir/ROOT");
rmdir("/tmp/c_simple_http_cache_dir");
} }
RETURN() RETURN()