]> git.seodisparate.com - c_simple_http/commitdiff
WIP Impl. config file parsing for http templates
authorStephen Seo <seo.disparate@gmail.com>
Fri, 30 Aug 2024 09:11:03 +0000 (18:11 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Fri, 30 Aug 2024 09:11:03 +0000 (18:11 +0900)
TODO:
    Still need to test "HTML_FILE".
    Response to HTTP request.

.gitignore
Makefile
src/constants.h
src/http.c
src/http.h
src/test.c [new file with mode: 0644]

index 06d0d805a759404fd0fe2194d80b55fd4125845e..5b854f62f435c1cb55d556a3369e2d75e75d9b8b 100644 (file)
@@ -1,2 +1,3 @@
 /c_simple_http
 /objs/
+/unit_test
index f56f982543256ca49fdc5da61559a7030986fac7..76fb0e10906c12ece6b8f3f07076be2d67eab893 100644 (file)
--- 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
index b2c64a855a2f614dd6e49f82bd22a1f6eb62269d..12d0189ec28dda928b210178689b2a7dd68c230e 100644 (file)
@@ -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
index 7b2e9f9498ed8955d202abd1a259da045f7a2b2d..c47d96d979dd0be5b2058303137884fdaa85757d 100644 (file)
 #include <helpers.h>
 #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,
index a16e1af29a843573d6e96ed7359e1b301f8c3ac5..6a8e6919e88d3ce904e82b42e6430d8a128e8098 100644 (file)
@@ -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 (file)
index 0000000..f8795f8
--- /dev/null
@@ -0,0 +1,162 @@
+// Standard library includes.
+#include <stdio.h>
+#include <string.h>
+
+// Local includes.
+#include "http.h"
+
+// Third party includes.
+#include <helpers.h>
+#include <hash_map.h>
+
+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