/c_simple_http
/objs/
+/unit_test
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
#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
#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);
}
// 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 != '=') {
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,
}
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);
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,
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);
--- /dev/null
+// 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