]> git.seodisparate.com - c_simple_http/commitdiff
Move config-related code from http.c to config.c
authorStephen Seo <seo.disparate@gmail.com>
Sat, 31 Aug 2024 07:48:24 +0000 (16:48 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sat, 31 Aug 2024 07:48:24 +0000 (16:48 +0900)
Also make config-related code more generic and improve unit tests of
config parsing.

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

index 665fca3e783d12d7be6abca0295bdb95a377556e..14bfcd67a55adb52a569daf47f4e9b4e6d577052 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ SOURCES = \
                src/signal_handling.c \
                src/globals.c \
                src/http.c \
+               src/config.c \
                third_party/SimpleArchiver/src/helpers.c \
                third_party/SimpleArchiver/src/data_structures/linked_list.c \
                third_party/SimpleArchiver/src/data_structures/hash_map.c \
diff --git a/src/config.c b/src/config.c
new file mode 100644 (file)
index 0000000..1e999c9
--- /dev/null
@@ -0,0 +1,493 @@
+// 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.
+
+#include "config.h"
+
+// Standard library includes.
+#include <stdlib.h>
+#include <string.h>
+
+// Third party includes.
+#include <SimpleArchiver/src/helpers.h>
+
+// Local includes
+#include "constants.h"
+
+#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_parsed_config(&config); \
+        config.hash_map = NULL; \
+        return config; \
+      } \
+    } 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_parsed_config(&config); \
+        config.hash_map = NULL; \
+        return config; \
+      } \
+    } \
+  }
+
+#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_parsed_config(&config); \
+        config.hash_map = NULL; \
+        return config; \
+      } \
+    } 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_parsed_config(&config); \
+        config.hash_map = NULL; \
+        return config; \
+      } \
+    } \
+  }
+
+void c_simple_http_hash_map_wrapper_cleanup_hashmap_fn(void *data) {
+  C_SIMPLE_HTTP_HashMapWrapper *wrapper = data;
+  simple_archiver_hash_map_free(&wrapper->paths);
+  free(wrapper);
+}
+
+void c_simple_http_hash_map_wrapper_cleanup(C_SIMPLE_HTTP_HashMapWrapper *wrapper) {
+  simple_archiver_hash_map_free(&wrapper->paths);
+  free(wrapper);
+}
+
+void c_simple_http_hash_map_cleanup_helper(SDArchiverHashMap *hash_map) {
+  simple_archiver_hash_map_free(&hash_map);
+}
+
+typedef struct C_SIMPLE_HTTP_INTERNAL_RequiredIter {
+  SDArchiverHashMap *hash_map;
+  const char *path;
+} C_SIMPLE_HTTP_INTERNAL_RequiredIter;
+
+int c_simple_http_required_iter_fn(void *data, void *ud) {
+  C_SIMPLE_HTTP_INTERNAL_RequiredIter *req_iter_struct = ud;
+  if (simple_archiver_hash_map_get(
+      req_iter_struct->hash_map,
+      data,
+      strlen(data) + 1) == NULL) {
+    if (req_iter_struct->path) {
+      fprintf(
+        stderr,
+        "WARNING: Map of PATH \"%s\" does not have required key \"%s\"!\n",
+        req_iter_struct->path,
+        (char*)data);
+      return 1;
+    } else {
+      fprintf(
+        stderr,
+        "WARNING: Map does not have required key \"%s\"!\n",
+        (char*)data);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+typedef struct C_SIMPLE_HTTP_INTERNAL_RequiredCheck {
+  SDArchiverHashMap *map_of_paths_and_their_vars;
+  SDArchiverLinkedList *required;
+} C_SIMPLE_HTTP_INTERNAL_RequiredCheck;
+
+int c_simple_http_check_required_iter_fn(void *path_void_str, void *ud) {
+  C_SIMPLE_HTTP_INTERNAL_RequiredCheck *req = ud;
+  C_SIMPLE_HTTP_HashMapWrapper *wrapper =
+    simple_archiver_hash_map_get(
+      req->map_of_paths_and_their_vars,
+      path_void_str,
+      strlen(path_void_str) + 1);
+  if (!wrapper) {
+    fprintf(stderr,
+            "WARNING: Map of paths does not have path \"%s\"!\n",
+            (char*)path_void_str);
+    return 1;
+  }
+  C_SIMPLE_HTTP_INTERNAL_RequiredIter req_iter_struct;
+  req_iter_struct.hash_map = wrapper->hash_map;
+  req_iter_struct.path = path_void_str;
+  return simple_archiver_list_get(
+    req->required,
+    c_simple_http_required_iter_fn,
+    &req_iter_struct) != NULL ? 1 : 0;
+}
+
+C_SIMPLE_HTTP_ParsedConfig c_simple_http_parse_config(
+  const char *config_filename,
+  const char *separating_key,
+  SDArchiverLinkedList *required_names
+) {
+  C_SIMPLE_HTTP_ParsedConfig config;
+  config.hash_map = NULL;
+
+  if (!config_filename) {
+    fprintf(stderr, "ERROR: config_filename argument is NULL!\n");
+    return config;
+  } else if (!separating_key) {
+    fprintf(stderr, "ERROR: separating_key argument is NULL!\n");
+    return config;
+  }
+  const unsigned int separating_key_size = strlen(separating_key) + 1;
+
+  config.hash_map = simple_archiver_hash_map_init();
+
+  __attribute__((cleanup(simple_archiver_list_free)))
+  SDArchiverLinkedList *paths = simple_archiver_list_init();
+
+  __attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
+  FILE *f = fopen(config_filename, "r");
+  unsigned char key_buf[C_SIMPLE_HTTP_CONFIG_BUF_SIZE];
+  unsigned char value_buf[C_SIMPLE_HTTP_CONFIG_BUF_SIZE];
+  unsigned int key_idx = 0;
+  unsigned int value_idx = 0;
+  __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+  char *current_separating_key_value = NULL;
+  unsigned int current_separating_key_value_size = 0;
+
+  // xxx0 - reading key
+  // xxx1 - reading value
+  // 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 ((state & 0xC) == 0 && (c == ' ' || c == '\t')) {
+      // Ignore whitespace when not quoted.
+      SINGLE_QUOTE_DECREMENT();
+      DOUBLE_QUOTE_DECREMENT();
+      continue;
+    } 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 != '=') {
+        key_buf[key_idx++] = c;
+        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_parsed_config(&config);
+          config.hash_map = NULL;
+          return config;
+        }
+      } else {
+        if (key_idx < C_SIMPLE_HTTP_CONFIG_BUF_SIZE) {
+          key_buf[key_idx++] = 0;
+        } else {
+          fprintf(stderr,
+                  "ERROR: config file \"key\" is larger than %u bytes!\n",
+                  C_SIMPLE_HTTP_CONFIG_BUF_SIZE - 1);
+          c_simple_http_clean_up_parsed_config(&config);
+          config.hash_map = NULL;
+          return config;
+        }
+        state |= 1;
+      }
+    } else if ((state & 1) == 1) {
+      if ((c != '\n' && c != '\r') || (state & 0xC) != 0) {
+        value_buf[value_idx++] = c;
+        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_parsed_config(&config);
+          config.hash_map = NULL;
+          return config;
+        }
+      } else {
+        if (value_idx < C_SIMPLE_HTTP_CONFIG_BUF_SIZE) {
+          value_buf[value_idx++] = 0;
+        } else {
+          fprintf(stderr,
+                  "ERROR: config file \"value\" is larger than %u bytes!\n",
+                  C_SIMPLE_HTTP_CONFIG_BUF_SIZE - 1);
+          c_simple_http_clean_up_parsed_config(&config);
+          config.hash_map = NULL;
+          return config;
+        }
+        state &= 0xFFFFFFFE;
+
+        // Check if key is separating_key.
+        if (strcmp((char*)key_buf, separating_key) == 0) {
+          if (current_separating_key_value) {
+            if (required_names) {
+              C_SIMPLE_HTTP_HashMapWrapper *hash_map_wrapper =
+                simple_archiver_hash_map_get(
+                  config.hash_map,
+                  current_separating_key_value,
+                  current_separating_key_value_size);
+              C_SIMPLE_HTTP_INTERNAL_RequiredIter req_iter_struct;
+              req_iter_struct.hash_map = hash_map_wrapper->hash_map;
+              if (paths->count != 0) {
+                req_iter_struct.path = paths->tail->prev->data;
+              } else {
+                req_iter_struct.path = NULL;
+              }
+              const char *missing_key = simple_archiver_list_get(
+                required_names,
+                c_simple_http_required_iter_fn,
+                &req_iter_struct);
+              if (missing_key) {
+                fprintf(stderr,
+                        "WARNING: config file did not have required key \"%s\"!"
+                        " Returning NULL map!\n",
+                        missing_key);
+                c_simple_http_clean_up_parsed_config(&config);
+                config.hash_map = NULL;
+                return config;
+              }
+            }
+
+            state &= 0xFFFFFFFD;
+            free(current_separating_key_value);
+          }
+          current_separating_key_value = malloc(value_idx);
+          memcpy(current_separating_key_value, value_buf, value_idx);
+          current_separating_key_value_size = value_idx;
+          // At this point, key is separating_key.
+          SDArchiverHashMap *hash_map = simple_archiver_hash_map_init();
+          unsigned char *key = malloc(separating_key_size);
+          strncpy((char*)key, separating_key, separating_key_size);
+          unsigned char *value = malloc(value_idx);
+          memcpy(value, value_buf, value_idx);
+          if (simple_archiver_hash_map_insert(&hash_map,
+                                              value,
+                                              key,
+                                              separating_key_size,
+                                              NULL,
+                                              NULL) != 0) {
+            fprintf(stderr,
+              "ERROR: Failed to create hash map for new separating_key "
+              "block!\n");
+            c_simple_http_clean_up_parsed_config(&config);
+            config.hash_map = NULL;
+            free(key);
+            free(value);
+            return config;
+          }
+
+          C_SIMPLE_HTTP_HashMapWrapper *wrapper = malloc(sizeof(C_SIMPLE_HTTP_HashMapWrapper));
+          wrapper->paths = hash_map;
+
+          if (simple_archiver_hash_map_insert(
+              &config.hash_map,
+              wrapper,
+              value,
+              value_idx,
+              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_parsed_config(&config);
+            config.hash_map = NULL;
+            c_simple_http_hash_map_wrapper_cleanup(wrapper);
+            return config;
+          }
+          simple_archiver_list_add(paths, value,
+              simple_archiver_helper_datastructure_cleanup_nop);
+        } else if (!current_separating_key_value) {
+          fprintf(
+              stderr,
+              "ERROR: config file has invalid key: No preceding \"%s\" "
+              "key!\n", separating_key);
+          c_simple_http_clean_up_parsed_config(&config);
+          config.hash_map = NULL;
+          return config;
+        } else {
+          // Non-separating_key key.
+          C_SIMPLE_HTTP_HashMapWrapper *hash_map_wrapper =
+            simple_archiver_hash_map_get(
+              config.hash_map,
+              current_separating_key_value,
+              current_separating_key_value_size);
+          if (!hash_map_wrapper) {
+            fprintf(stderr,
+              "ERROR: Internal error failed to get existing hash map with path "
+              "\"%s\"!", current_separating_key_value);
+            c_simple_http_clean_up_parsed_config(&config);
+            config.hash_map = NULL;
+            return config;
+          }
+
+          unsigned char *key = malloc(key_idx);
+          memcpy(key, key_buf, key_idx);
+          unsigned char *value;
+          if (strcmp((char*)key_buf, "HTML_FILE") == 0) {
+            __attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
+            FILE *html_file = fopen((char*)value_buf, "r");
+            if (!html_file) {
+              fprintf(stderr,
+                "ERROR: Internal error failed to open HTML_FILE \"%s\"!",
+                value_buf);
+              c_simple_http_clean_up_parsed_config(&config);
+              config.hash_map = NULL;
+              return config;
+            }
+
+            fseek(html_file, 0, SEEK_END);
+            long file_size = ftell(html_file);
+            if (file_size <= 0) {
+              fprintf(stderr,
+                "ERROR: Internal error HTML_FILE \"%s\" is invalid size!",
+                value_buf);
+              c_simple_http_clean_up_parsed_config(&config);
+              config.hash_map = NULL;
+              return config;
+            }
+            fseek(html_file, 0, SEEK_SET);
+            unsigned long file_size_u = file_size;
+
+            unsigned char *read_buf = malloc(file_size_u);
+            size_t read_amount = 0;
+            read_amount = fread(read_buf, 1, file_size_u, html_file);
+            if (read_amount != file_size_u) {
+              fprintf(stderr, "ERROR: Failed to read HTML_FILE \"%s\"!\n",
+                value_buf);
+              free(read_buf);
+              c_simple_http_clean_up_parsed_config(&config);
+              config.hash_map = NULL;
+              return config;
+            }
+            value = read_buf;
+          } else {
+            value = malloc(value_idx);
+            memcpy(value, value_buf, value_idx);
+          }
+
+          if (simple_archiver_hash_map_insert(&hash_map_wrapper->paths,
+                                              value,
+                                              key,
+                                              key_idx,
+                                              NULL,
+                                              NULL) != 0) {
+            fprintf(stderr,
+              "ERROR: Internal error failed to insert into hash map with path "
+              "\"%s\"!", current_separating_key_value);
+            c_simple_http_clean_up_parsed_config(&config);
+            config.hash_map = NULL;
+            free(key);
+            free(value);
+            return config;
+          }
+        }
+        key_idx = 0;
+        value_idx = 0;
+      }
+    }
+  }
+  simple_archiver_helper_cleanup_FILE(&f);
+
+  if (!current_separating_key_value) {
+    fprintf(stderr, "ERROR: Never got \"PATH\" key in config!\n");
+    c_simple_http_clean_up_parsed_config(&config);
+    config.hash_map = NULL;
+  } else if (required_names) {
+    C_SIMPLE_HTTP_INTERNAL_RequiredCheck required_check_struct;
+    required_check_struct.map_of_paths_and_their_vars = config.hash_map;
+    required_check_struct.required = required_names;
+    const char *path_with_missing_key = simple_archiver_list_get(
+      paths,
+      c_simple_http_check_required_iter_fn,
+      &required_check_struct);
+    if (path_with_missing_key) {
+      fprintf(stderr,
+              "WARNING (parse end): config file with path \"%s\" did not have "
+              "required key(s)! Returning NULL map!\n",
+              path_with_missing_key);
+      c_simple_http_clean_up_parsed_config(&config);
+      config.hash_map = NULL;
+    }
+  }
+
+  return config;
+}
+
+void c_simple_http_clean_up_parsed_config(C_SIMPLE_HTTP_ParsedConfig *config) {
+  simple_archiver_hash_map_free(&config->paths);
+}
+
+// vim: ts=2 sts=2 sw=2
diff --git a/src/config.h b/src/config.h
new file mode 100644 (file)
index 0000000..3df8676
--- /dev/null
@@ -0,0 +1,58 @@
+// 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_CONFIG_H_
+#define SEODISPARATE_COM_C_SIMPLE_HTTP_CONFIG_H_
+
+// Third party includes.
+#include <SimpleArchiver/src/data_structures/linked_list.h>
+#include <SimpleArchiver/src/data_structures/hash_map.h>
+
+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".
+  union {
+    SDArchiverHashMap *paths;
+    SDArchiverHashMap *hash_map;
+  };
+} C_SIMPLE_HTTP_ParsedConfig;
+
+typedef C_SIMPLE_HTTP_ParsedConfig C_SIMPLE_HTTP_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. 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.
+C_SIMPLE_HTTP_ParsedConfig c_simple_http_parse_config(
+  const char *config_filename,
+  const char *separating_key,
+  SDArchiverLinkedList *required_names
+);
+
+void c_simple_http_clean_up_parsed_config(C_SIMPLE_HTTP_ParsedConfig *config);
+
+#endif
+
+// vim: ts=2 sts=2 sw=2
index 026542a4bfb4d5a95a767acf9d6f5f3502ef7257..7e99e121643fd493cbcd488c7f23fc89eb64e45f 100644 (file)
 #include <string.h>
 
 // Local includes
-#include <SimpleArchiver/src/helpers.h>
 #include "constants.h"
 
-#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->paths);
-  free(wrapper);
-}
-
-void c_simple_http_hash_map_wrapper_cleanup(HashMapWrapper *wrapper) {
-  simple_archiver_hash_map_free(&wrapper->paths);
-  free(wrapper);
-}
-
-void c_simple_http_hash_map_cleanup_helper(SDArchiverHashMap *hash_map) {
-  simple_archiver_hash_map_free(&hash_map);
-}
-
-HTTPTemplates c_simple_http_set_up_templates(const char *config_filename) {
-  HTTPTemplates templates;
-  templates.paths = simple_archiver_hash_map_init();
-
-  FILE *f = fopen(config_filename, "r");
-  unsigned char key_buf[C_SIMPLE_HTTP_CONFIG_BUF_SIZE];
-  unsigned char value_buf[C_SIMPLE_HTTP_CONFIG_BUF_SIZE];
-  unsigned int key_idx = 0;
-  unsigned int value_idx = 0;
-  __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
-  char *current_path = NULL;
-  unsigned int current_path_size = 0;
-
-  // xxx0 - reading key
-  // 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 ((state & 0xC) == 0 && (c == ' ' || c == '\t')) {
-      // Ignore whitespace when not quoted.
-      SINGLE_QUOTE_DECREMENT();
-      DOUBLE_QUOTE_DECREMENT();
-      continue;
-    } 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 != '=') {
-        key_buf[key_idx++] = c;
-        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 {
-        if (key_idx < C_SIMPLE_HTTP_CONFIG_BUF_SIZE) {
-          key_buf[key_idx++] = 0;
-        } else {
-          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;
-        }
-        state |= 1;
-      }
-    } else if ((state & 1) == 1) {
-      if ((c != '\n' && c != '\r') || (state & 0xC) != 0) {
-        value_buf[value_idx++] = c;
-        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;
-        }
-      } else {
-        if (value_idx < C_SIMPLE_HTTP_CONFIG_BUF_SIZE) {
-          value_buf[value_idx++] = 0;
-        } else {
-          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;
-        }
-        state &= 0xFFFFFFFE;
-
-        // Check if key is "PATH".
-        if (strcmp((char*)key_buf, "PATH") == 0) {
-          if (current_path) {
-            if ((state & 0x2) == 0) {
-              fprintf(stderr,
-                      "ERROR: config file did not have \"HTML\" key for PATH!"
-                      "\n");
-              c_simple_http_clean_up_templates(&templates);
-              templates.paths = NULL;
-              return templates;
-            }
-            state &= 0xFFFFFFFD;
-            free(current_path);
-          }
-          current_path = malloc(value_idx);
-          memcpy(current_path, value_buf, value_idx);
-          current_path_size = value_idx;
-          // At this point, key is "PATH".
-          SDArchiverHashMap *hash_map = simple_archiver_hash_map_init();
-          unsigned char *key = malloc(5);
-          key[0] = 'P';
-          key[1] = 'A';
-          key[2] = 'T';
-          key[3] = 'H';
-          key[4] = 0;
-          unsigned char *value = malloc(value_idx);
-          memcpy(value, value_buf, value_idx);
-          if (simple_archiver_hash_map_insert(&hash_map,
-                                              value,
-                                              key,
-                                              5,
-                                              NULL,
-                                              NULL) != 0) {
-            fprintf(stderr,
-                "ERROR: Failed to create hash map for new PATH block!\n");
-            c_simple_http_clean_up_templates(&templates);
-            templates.paths = NULL;
-            free(key);
-            free(value);
-            return templates;
-          }
-
-          HashMapWrapper *wrapper = malloc(sizeof(HashMapWrapper));
-          wrapper->paths = hash_map;
-
-          if (simple_archiver_hash_map_insert(
-              &templates.paths,
-              wrapper,
-              value,
-              value_idx,
-              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);
-            templates.paths = NULL;
-            c_simple_http_hash_map_wrapper_cleanup(wrapper);
-            return templates;
-          }
-        } else if (!current_path) {
-          fprintf(
-              stderr,
-              "ERROR: config file has invalid key: No preceding \"PATH\" "
-              "key!\n");
-          c_simple_http_clean_up_templates(&templates);
-          templates.paths = NULL;
-          return templates;
-        } else {
-          // Non-"PATH" key.
-          if (strcmp((char*)key_buf, "HTML") == 0
-              || strcmp((char*)key_buf, "HTML_FILE") == 0) {
-            state |= 0x2;
-          }
-          HashMapWrapper *hash_map_wrapper = simple_archiver_hash_map_get(
-            templates.paths,
-            current_path,
-            current_path_size);
-          if (!hash_map_wrapper) {
-            fprintf(stderr,
-              "ERROR: Internal error failed to get existing hash map with path "
-              "\"%s\"!", current_path);
-            c_simple_http_clean_up_templates(&templates);
-            templates.paths = NULL;
-            return templates;
-          }
-
-          unsigned char *key = malloc(key_idx);
-          memcpy(key, key_buf, key_idx);
-          unsigned char *value;
-          if (strcmp((char*)key_buf, "HTML_FILE") == 0) {
-            __attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
-            FILE *html_file = fopen((char*)value_buf, "r");
-            if (!html_file) {
-              fprintf(stderr,
-                "ERROR: Internal error failed to open HTML_FILE \"%s\"!",
-                value_buf);
-              c_simple_http_clean_up_templates(&templates);
-              templates.paths = NULL;
-              return templates;
-            }
-
-            fseek(html_file, 0, SEEK_END);
-            long file_size = ftell(html_file);
-            if (file_size <= 0) {
-              fprintf(stderr,
-                "ERROR: Internal error HTML_FILE \"%s\" is invalid size!",
-                value_buf);
-              c_simple_http_clean_up_templates(&templates);
-              templates.paths = NULL;
-              return templates;
-            }
-            fseek(html_file, 0, SEEK_SET);
-            unsigned long file_size_u = file_size;
-
-            unsigned char *read_buf = malloc(file_size_u);
-            size_t read_amount = 0;
-            read_amount = fread(read_buf, 1, file_size_u, html_file);
-            if (read_amount != file_size_u) {
-              fprintf(stderr, "ERROR: Failed to read HTML_FILE \"%s\"!\n",
-                value_buf);
-              free(read_buf);
-              c_simple_http_clean_up_templates(&templates);
-              templates.paths = NULL;
-              return templates;
-            }
-            value = read_buf;
-          } else {
-            value = malloc(value_idx);
-            memcpy(value, value_buf, value_idx);
-          }
-
-          if (simple_archiver_hash_map_insert(&hash_map_wrapper->paths,
-                                              value,
-                                              key,
-                                              key_idx,
-                                              NULL,
-                                              NULL) != 0) {
-            fprintf(stderr,
-              "ERROR: Internal error failed to insert into hash map with path "
-              "\"%s\"!", current_path);
-            c_simple_http_clean_up_templates(&templates);
-            templates.paths = NULL;
-            free(key);
-            free(value);
-            return templates;
-          }
-        }
-        key_idx = 0;
-        value_idx = 0;
-      }
-    }
-  }
-  fclose(f);
-
-  if (!current_path) {
-    fprintf(stderr, "ERROR: Never got \"PATH\" key in config!\n");
-    c_simple_http_clean_up_templates(&templates);
-    templates.paths = NULL;
-  } else if ((state & 0x2) == 0) {
-    fprintf(stderr,
-      "ERROR: Current \"PATH\" did not get \"HTML\" key in config!\n");
-    c_simple_http_clean_up_templates(&templates);
-    templates.paths = NULL;
-  }
-
-  return templates;
-}
-
-void c_simple_http_clean_up_templates(HTTPTemplates *templates) {
-  simple_archiver_hash_map_free(&templates->paths);
-}
-
-char *c_simple_http_request_response(const char *request, unsigned int size,
-                                     const HTTPTemplates *templates) {
+char *c_simple_http_request_response(
+    const char *request,
+    unsigned int size,
+    const C_SIMPLE_HTTP_HTTPTemplates *templates) {
   // TODO
   return NULL;
 }
index 37309a1d7c36af5995efebaa70aee79468622227..e4264cd2c1a32e6d6d39f35ac81ee43e1701a499 100644 (file)
 #ifndef SEODISPARATE_COM_C_SIMPLE_HTTP_HTTP_H_
 #define SEODISPARATE_COM_C_SIMPLE_HTTP_HTTP_H_
 
+// Third party includes.
 #include <SimpleArchiver/src/data_structures/hash_map.h>
 
-typedef struct HTTPTemplates {
-  /// 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".
-  SDArchiverHashMap *paths;
-} HTTPTemplates;
+// Local includes.
+#include "config.h"
 
-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. 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);
+typedef C_SIMPLE_HTTP_ParsedConfig C_SIMPLE_HTTP_HTTPTemplates;
 
 /// Returned buffer must be "free"d after use.
 /// If the request is not valid, or 404, then the buffer will be NULL.
-char *c_simple_http_request_response(const char *request, unsigned int size,
-                                     const HTTPTemplates *templates);
+char *c_simple_http_request_response(
+  const char *request,
+  unsigned int size,
+  const C_SIMPLE_HTTP_HTTPTemplates *templates
+);
 
 #endif
 
index ed7f8c5b39365f11f87ac065c9faa0216aa841e0..c7a105bb28bb607800f1d25323731a2e296e0698 100644 (file)
@@ -3,7 +3,7 @@
 #include <string.h>
 
 // Local includes.
-#include "http.h"
+#include "config.h"
 
 // Third party includes.
 #include <SimpleArchiver/src/helpers.h>
@@ -103,14 +103,39 @@ int main(void) {
     ASSERT_TRUE(
         fwrite("TEST5=\"\"\" '''one two '''three \"\"\"\n", 1, 34, test_file)
         == 34);
+    ASSERT_TRUE(
+        fwrite("PATH=/derp\nHTML=''' one two three '''\n", 1, 38, test_file)
+        == 38);
+    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("PATH=/doop\n", 1, 11, test_file)
+        == 11);
+    ASSERT_TRUE(
+        fwrite("TEST3=something\n", 1, 16, test_file)
+        == 16);
+    ASSERT_TRUE(
+        fwrite("TEST2=' \"one two \"three ''\n", 1, 27, test_file)
+        == 27);
     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);
+    __attribute__((cleanup(simple_archiver_list_free)))
+    SDArchiverLinkedList *required_names = simple_archiver_list_init();
+    simple_archiver_list_add(
+      required_names,
+      "TEST2",
+      simple_archiver_helper_datastructure_cleanup_nop);
+
+    __attribute__((cleanup(c_simple_http_clean_up_parsed_config)))
+    C_SIMPLE_HTTP_ParsedConfig templates =
+      c_simple_http_parse_config(test_config_filename, "PATH", required_names);
     ASSERT_TRUE(templates.paths);
 
-    HashMapWrapper *first_path_map_wrapper =
+    C_SIMPLE_HTTP_ParsedConfig *first_path_map_wrapper =
       simple_archiver_hash_map_get(templates.paths, "/", 2);
     ASSERT_TRUE(first_path_map_wrapper);
 
@@ -154,6 +179,30 @@ int main(void) {
     ASSERT_TRUE(value);
     // printf("%s\n", value);
     ASSERT_STREQ(value, " '''one two '''three ");
+
+    simple_archiver_list_free(&required_names);
+    required_names = simple_archiver_list_init();
+    simple_archiver_list_add(
+      required_names,
+      "TEST3",
+      simple_archiver_helper_datastructure_cleanup_nop);
+
+    c_simple_http_clean_up_parsed_config(&templates);
+    templates =
+      c_simple_http_parse_config(test_config_filename, "PATH", required_names);
+    ASSERT_FALSE(templates.paths);
+
+    simple_archiver_list_free(&required_names);
+    required_names = simple_archiver_list_init();
+    simple_archiver_list_add(
+      required_names,
+      "TEST",
+      simple_archiver_helper_datastructure_cleanup_nop);
+
+    c_simple_http_clean_up_parsed_config(&templates);
+    templates =
+      c_simple_http_parse_config(test_config_filename, "PATH", required_names);
+    ASSERT_FALSE(templates.paths);
   }
 
   RETURN()