From b4a56a79182a820565bee0fea269519d679936c1 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Fri, 30 Aug 2024 18:11:03 +0900 Subject: [PATCH] WIP Impl. config file parsing for http templates TODO: Still need to test "HTML_FILE". Response to HTTP request. --- .gitignore | 1 + Makefile | 6 +- src/constants.h | 1 + src/http.c | 122 +++++++++++++++++++++++++++++++----- src/http.h | 23 ++++--- src/test.c | 162 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 290 insertions(+), 25 deletions(-) create mode 100644 src/test.c diff --git a/.gitignore b/.gitignore index 06d0d80..5b854f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /c_simple_http /objs/ +/unit_test diff --git a/Makefile b/Makefile index f56f982..76fb0e1 100644 --- a/Makefile +++ b/Makefile @@ -28,15 +28,19 @@ SOURCES = \ OBJECT_DIR = objs OBJECTS = $(addprefix ${OBJECT_DIR}/,$(patsubst %.c,%.c.o,${SOURCES})) -all: c_simple_http +all: c_simple_http unit_test c_simple_http: ${OBJECTS} gcc -o c_simple_http ${CFLAGS} $^ +unit_test: $(filter-out ${OBJECT_DIR}/src/main.c.o,${OBJECTS}) ${OBJECT_DIR}/src/test.c.o + gcc -o unit_test ${CFLAGS} $^ + .PHONY: clean clean: rm -f c_simple_http + rm -f unit_test rm -rf ${OBJECT_DIR} ${OBJECT_DIR}/%.c.o: %.c diff --git a/src/constants.h b/src/constants.h index b2c64a8..12d0189 100644 --- a/src/constants.h +++ b/src/constants.h @@ -20,5 +20,6 @@ #define C_SIMPLE_HTTP_SLEEP_NANOS 1000000 #define C_SIMPLE_HTTP_RECV_BUF_SIZE 1024 #define C_SIMPLE_HTTP_CONFIG_BUF_SIZE 256 +#define C_SIMPLE_HTTP_QUOTE_COUNT_MAX 3 #endif diff --git a/src/http.c b/src/http.c index 7b2e9f9..c47d96d 100644 --- a/src/http.c +++ b/src/http.c @@ -24,18 +24,64 @@ #include #include "constants.h" -typedef struct HashMapWrapper { - SDArchiverHashMap *hash_map; -} HashMapWrapper; +#define SINGLE_QUOTE_DECREMENT() \ + for(; single_quote_count > 0; --single_quote_count) { \ + if ((state & 1) == 0) { \ + key_buf[key_idx++] = '\''; \ + if (key_idx >= C_SIMPLE_HTTP_CONFIG_BUF_SIZE) { \ + fprintf(stderr, \ + "ERROR: config file \"key\" is larger than %u bytes!\n", \ + C_SIMPLE_HTTP_CONFIG_BUF_SIZE - 1); \ + c_simple_http_clean_up_templates(&templates); \ + templates.paths = NULL; \ + return templates; \ + } \ + } else { \ + value_buf[value_idx++] = '\''; \ + if (value_idx >= C_SIMPLE_HTTP_CONFIG_BUF_SIZE) { \ + fprintf(stderr, \ + "ERROR: config file \"value\" is larger than %u bytes!\n", \ + C_SIMPLE_HTTP_CONFIG_BUF_SIZE - 1); \ + c_simple_http_clean_up_templates(&templates); \ + templates.paths = NULL; \ + return templates; \ + } \ + } \ + } + +#define DOUBLE_QUOTE_DECREMENT() \ + for(; double_quote_count > 0; --double_quote_count) { \ + if ((state & 1) == 0) { \ + key_buf[key_idx++] = '"'; \ + if (key_idx >= C_SIMPLE_HTTP_CONFIG_BUF_SIZE) { \ + fprintf(stderr, \ + "ERROR: config file \"key\" is larger than %u bytes!\n", \ + C_SIMPLE_HTTP_CONFIG_BUF_SIZE - 1); \ + c_simple_http_clean_up_templates(&templates); \ + templates.paths = NULL; \ + return templates; \ + } \ + } else { \ + value_buf[value_idx++] = '"'; \ + if (value_idx >= C_SIMPLE_HTTP_CONFIG_BUF_SIZE) { \ + fprintf(stderr, \ + "ERROR: config file \"value\" is larger than %u bytes!\n", \ + C_SIMPLE_HTTP_CONFIG_BUF_SIZE - 1); \ + c_simple_http_clean_up_templates(&templates); \ + templates.paths = NULL; \ + return templates; \ + } \ + } \ + } void c_simple_http_hash_map_wrapper_cleanup_hashmap_fn(void *data) { HashMapWrapper *wrapper = data; - simple_archiver_hash_map_free(&wrapper->hash_map); + simple_archiver_hash_map_free(&wrapper->paths); free(wrapper); } void c_simple_http_hash_map_wrapper_cleanup(HashMapWrapper *wrapper) { - simple_archiver_hash_map_free(&wrapper->hash_map); + simple_archiver_hash_map_free(&wrapper->paths); free(wrapper); } @@ -60,19 +106,67 @@ HTTPTemplates c_simple_http_set_up_templates(const char *config_filename) { // xxx1 - reading value // xx0x - does not have "HTML" key // xx1x - does have "HTML" key + // 00xx - reading value is not quoted + // 01xx - reading value is single quoted + // 10xx - reading value is double quoted unsigned int state = 0; + unsigned char single_quote_count = 0; + unsigned char double_quote_count = 0; int c; while (feof(f) == 0) { c = fgetc(f); if (c == EOF) { break; - } else if (c == ' ' || c == '\t') { - // Ignore whitespace. + } else if ((state & 0xC) == 0 && (c == ' ' || c == '\t')) { + // Ignore whitespace when not quoted. + SINGLE_QUOTE_DECREMENT(); + DOUBLE_QUOTE_DECREMENT(); continue; - } else if ((state & 1) == 0 && (c == '\r' || c == '\n')) { - // Ignore newlines when parsing for key. + } else if ((state & 1) == 0 + && (state & 0xC) == 0 + && (c == '\r' || c == '\n')) { + // Ignore newlines when parsing for key and when not quoted. + SINGLE_QUOTE_DECREMENT(); + DOUBLE_QUOTE_DECREMENT(); continue; + } else if ((state & 1) == 1) { + if (c == '\'') { + ++single_quote_count; + DOUBLE_QUOTE_DECREMENT(); + + if (((state & 0xC) == 0x4 || (state & 0xC) == 0) + && single_quote_count >= C_SIMPLE_HTTP_QUOTE_COUNT_MAX) { + single_quote_count = 0; + + // (state & 0xC) is known to be either 0x4 or 0. + if ((state & 0xC) == 0) { + state |= 0x4; + } else { + state &= 0xFFFFFFF3; + } + } + continue; + } else if (c == '"') { + ++double_quote_count; + SINGLE_QUOTE_DECREMENT(); + + if (((state & 0xC) == 0x8 || (state & 0xC) == 0) + && double_quote_count >= C_SIMPLE_HTTP_QUOTE_COUNT_MAX) { + double_quote_count = 0; + + // (state & 0xC) is known to be either 0x8 or 0. + if ((state & 0xC) == 0) { + state |= 0x8; + } else { + state &= 0xFFFFFFF3; + } + } + continue; + } else { + SINGLE_QUOTE_DECREMENT(); + DOUBLE_QUOTE_DECREMENT(); + } } if ((state & 1) == 0) { if (c != '=') { @@ -99,7 +193,7 @@ HTTPTemplates c_simple_http_set_up_templates(const char *config_filename) { state |= 1; } } else if ((state & 1) == 1) { - if (c != '\n' && c != '\r') { + if ((c != '\n' && c != '\r') || (state & 0xC) != 0) { value_buf[value_idx++] = c; if (value_idx >= C_SIMPLE_HTTP_CONFIG_BUF_SIZE) { fprintf(stderr, @@ -165,15 +259,15 @@ HTTPTemplates c_simple_http_set_up_templates(const char *config_filename) { } HashMapWrapper *wrapper = malloc(sizeof(HashMapWrapper)); - wrapper->hash_map = hash_map; + wrapper->paths = hash_map; if (simple_archiver_hash_map_insert( &templates.paths, wrapper, value, value_idx, - simple_archiver_helper_datastructure_cleanup_nop, - c_simple_http_hash_map_wrapper_cleanup_hashmap_fn) != 0) { + c_simple_http_hash_map_wrapper_cleanup_hashmap_fn, + simple_archiver_helper_datastructure_cleanup_nop) != 0) { fprintf(stderr, "ERROR: Failed to insert new hash map for new PATH block!\n"); c_simple_http_clean_up_templates(&templates); @@ -253,7 +347,7 @@ HTTPTemplates c_simple_http_set_up_templates(const char *config_filename) { memcpy(value, value_buf, value_idx); } - if (simple_archiver_hash_map_insert(&hash_map_wrapper->hash_map, + if (simple_archiver_hash_map_insert(&hash_map_wrapper->paths, value, key, key_idx, diff --git a/src/http.h b/src/http.h index a16e1af..6a8e691 100644 --- a/src/http.h +++ b/src/http.h @@ -26,17 +26,20 @@ typedef struct HTTPTemplates { SDArchiverHashMap *paths; } HTTPTemplates; +typedef HTTPTemplates HashMapWrapper; + /// Each line in the config should be a key-value pair separated by an equals -/// sign. All whitespace is ignored unless if the value is quoted. If there -/// exists a line with the key "PATH", then the value must be a path like -/// "/cache" and all the following key-value pairs are associated with that PATH -/// until the next "PATH" key or EOF. Each "PATH" "block" should have a "HTML" -/// key-value pair where the value is a HTML template. Inside such HTML -/// templates should be strings like "{{{{example_key}}}}" which will be -/// replaced by the string value of the key name deliminated by the four curly -/// braces. "HTML_FILE" specifies a filename to read instead of using a literal -/// string in the config file. It will store the contents of the specified file -/// with the "HTML" key internally. +/// sign. All whitespace is ignored unless if the value is quoted. A part of a +/// string can be "quoted" if it is surrounded by three single-quotes or three +/// double-quotes. If there exists a line with the key "PATH", then the value +/// must be a path like "/cache" and all the following key-value pairs are +/// associated with that PATH until the next "PATH" key or EOF. Each "PATH" +/// "block" should have a "HTML" key-value pair where the value is a HTML +/// template. Inside such HTML templates should be strings like +/// "{{{{example_key}}}}" which will be replaced by the string value of the key +/// name deliminated by the four curly braces. "HTML_FILE" specifies a filename +/// to read instead of using a literal string in the config file. It will store +/// the contents of the specified file with the "HTML" key internally. HTTPTemplates c_simple_http_set_up_templates(const char *config_filename); void c_simple_http_clean_up_templates(HTTPTemplates *templates); diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..f8795f8 --- /dev/null +++ b/src/test.c @@ -0,0 +1,162 @@ +// Standard library includes. +#include +#include + +// Local includes. +#include "http.h" + +// Third party includes. +#include +#include + +static int checks_checked = 0; +static int checks_passed = 0; + +#define RETURN() \ + do { \ + fprintf(stderr, "checked %d\npassed %d\n", checks_checked, checks_passed);\ + return checks_checked == checks_passed ? 0 : 1; \ + } while (0); + +#define CHECK_TRUE(x) \ + do { \ + ++checks_checked; \ + if (!(x)) { \ + printf("CHECK_TRUE at line %u failed: %s\n", __LINE__, #x); \ + } else { \ + ++checks_passed; \ + } \ + } while (0); +#define CHECK_FALSE(x) \ + do { \ + ++checks_checked; \ + if (x) { \ + printf("CHECK_FALSE at line %u failed: %s\n", __LINE__, #x); \ + } else { \ + ++checks_passed; \ + } \ + } while (0); +#define CHECK_STREQ(a, b) \ + do { \ + ++checks_checked; \ + if (strcmp((a), (b)) == 0) { \ + ++checks_passed; \ + } else { \ + printf("CHECK_STREQ at line %u failed: %s != %s\n", __LINE__, #a, #b); \ + } \ + } while (0); + +#define ASSERT_TRUE(x) \ + do { \ + ++checks_checked; \ + if (!(x)) { \ + printf("ASSERT_TRUE at line %u failed: %s\n", __LINE__, #x); \ + RETURN() \ + } else { \ + ++checks_passed; \ + } \ + } while (0); +#define ASSERT_FALSE(x) \ + do { \ + ++checks_checked; \ + if (x) { \ + printf("ASSERT_FALSE at line %u failed: %s\n", __LINE__, #x); \ + RETURN() \ + } else { \ + ++checks_passed; \ + } \ + } while (0); +#define ASSERT_STREQ(a, b) \ + do { \ + ++checks_checked; \ + if (strcmp((a), (b)) == 0) { \ + ++checks_passed; \ + } else { \ + printf("ASSERT_STREQ at line %u failed: %s != %s\n", __LINE__, #a, #b); \ + RETURN() \ + } \ + } while (0); + +int main(void) { + // Test set up templates. + { + const char *test_config_filename = "/tmp/c_simple_http_test.config"; + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *test_file = fopen(test_config_filename, "w"); + ASSERT_TRUE(test_file); + + ASSERT_TRUE( + fwrite("PATH=/\nHTML=''' one two three '''\n", 1, 34, test_file) + == 34); + ASSERT_TRUE( + fwrite("TEST=''' \"one two \"three '''\n", 1, 29, test_file) + == 29); + ASSERT_TRUE( + fwrite("TEST2=' \"one two \"three ''\n", 1, 27, test_file) + == 27); + ASSERT_TRUE( + fwrite("TEST3=\"\"\" \"one two \"three ''\"\"\"\n", 1, 32, test_file) + == 32); + ASSERT_TRUE( + fwrite("TEST4=''' \"\"\"one two \"\"\"three '''\n", 1, 34, test_file) + == 34); + ASSERT_TRUE( + fwrite("TEST5=\"\"\" '''one two '''three \"\"\"\n", 1, 34, test_file) + == 34); + simple_archiver_helper_cleanup_FILE(&test_file); + + __attribute__((cleanup(c_simple_http_clean_up_templates))) + HTTPTemplates templates = + c_simple_http_set_up_templates(test_config_filename); + ASSERT_TRUE(templates.paths); + + HashMapWrapper *first_path_map_wrapper = + simple_archiver_hash_map_get(templates.paths, "/", 2); + ASSERT_TRUE(first_path_map_wrapper); + + const char *value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "PATH", 5); + ASSERT_TRUE(value); + ASSERT_STREQ(value, "/"); + + value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "HTML", 5); + ASSERT_TRUE(value); + // printf("%s\n", value); + ASSERT_STREQ(value, " one two three "); + + value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "TEST", 5); + ASSERT_TRUE(value); + // printf("%s\n", value); + ASSERT_STREQ(value, " \"one two \"three "); + + value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "TEST2", 6); + ASSERT_TRUE(value); + // printf("%s\n", value); + ASSERT_STREQ(value, "'\"onetwo\"three''"); + + value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "TEST3", 6); + ASSERT_TRUE(value); + // printf("%s\n", value); + ASSERT_STREQ(value, " \"one two \"three ''"); + + value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "TEST4", 6); + ASSERT_TRUE(value); + // printf("%s\n", value); + ASSERT_STREQ(value, " \"\"\"one two \"\"\"three "); + + value = + simple_archiver_hash_map_get(first_path_map_wrapper->paths, "TEST5", 6); + ASSERT_TRUE(value); + // printf("%s\n", value); + ASSERT_STREQ(value, " '''one two '''three "); + } + + RETURN() +} + +// vim: ts=2 sts=2 sw=2