diff --git a/.gitignore b/.gitignore index c68d06b..215e916 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ src/*.o *.o .cache/ +*swp* diff --git a/include/Parameters.h b/include/Parameters.h new file mode 100644 index 0000000..c27b9a8 --- /dev/null +++ b/include/Parameters.h @@ -0,0 +1,76 @@ +#ifndef PARAMETERS_H +#define PARAMETERS_H + +#include +#include +#include +#include +#include + +/* + * Implementation notes: (22 Jan 2023) + * + * Parameters will be stored as an array sorted on-demand. + * If calling paramGet and it's unsorted, it will sort automatically. + * If calling paramGet and it's already sorted, it doesn't touch it. + * Adding a new parameter will toggle the is_sorted to false. + * + * I was considering using the tree implementation of Route but I think + * it would make more sense to move the logic into its own data structure + * that can be used across different components (like Route & Parameter) + * because otherwise it would be confusing if we had a Route struct inside + * of a Parameter. For now, I'll use this array structure. + * + */ + +struct Parameter +{ + char* key; + char* value; +}; + +struct ParameterArray +{ + struct Parameter * parameters; + bool is_sorted; + size_t n_members; + size_t capacity; +}; + +// Initialise array of parameters with capacity. +// Returns pointer to array on success, NULL on failure +struct ParameterArray * paramInit(const size_t capacity); + +// Parse parameters from a string (e.g., when receiving ROUTE url). +// Takes an existing Parameter pointer and adds new parameters parsed from string. +// This will MODIFY the url inplace to remove all the parameter text before it's passed +// on to render a static template. +void paramParse( + struct ParameterArray * params, + char* url); + +// Clear all keys & values and set n_members back to zero. +// This allows us to reuse the struct for multiple requests. +void paramClear(struct ParameterArray * params); + +// Add new parameter to parameter array. +// Returns true on success and false on failure (e.g., memory allocation) +bool paramAdd( + struct ParameterArray * params, + const char* key, + const char* value); + +// Sort parameters by keys ascending. This is primarily used internally +// but can be used externally if needed for whatever reason. +void paramSort(struct ParameterArray * params); + +// Return a pointer to a parameter with a given key. +// Any modifications done will be reflected in parameter array. +struct Parameter * paramGet( + struct ParameterArray * params, + const char* key); + +// Free all memory consumed by parameters +void paramFree(struct ParameterArray * params); + +#endif diff --git a/src/Parameters.c b/src/Parameters.c new file mode 100644 index 0000000..df35ff3 --- /dev/null +++ b/src/Parameters.c @@ -0,0 +1,175 @@ +#include "Parameters.h" + +static void _print_critical() +{ + printf("============ CRITICAL ============\n"); + printf("Memory could not be allocated for parameters.\n"); +} + +struct ParameterArray * paramInit(const size_t capacity) +{ + struct ParameterArray * params = malloc(sizeof(struct ParameterArray)); + + if (!params) + { + _print_critical(); + return NULL; + } + + params->parameters = calloc(capacity, sizeof(struct Parameter)); + + if (!params->parameters) + { + _print_critical(); + return NULL; + } + + params->is_sorted = false; + params->n_members = 0; + params->capacity = capacity; + + for (size_t i = 0; i < capacity; ++i) + { + params->parameters[i].key = NULL; + params->parameters[i].value = NULL; + } + + return params; +} + +bool paramAdd( + struct ParameterArray * params, + const char* key, + const char* value) +{ + const size_t i = params->n_members; + + params->parameters[i].key = strdup(key); + params->parameters[i].value = strdup(value); + + params->is_sorted = false; + params->n_members++; + + // follow doubling rule + if (params->n_members == params->capacity) + { + void* alloc = realloc(params->parameters, params->capacity * 2 * sizeof(struct Parameter)); + if (!alloc) + { + _print_critical(); + return false; + } + + params->capacity *= 2; + params->parameters = alloc; + } + + return true; +} + +static int key_cmp(const void* a, const void* b) +{ + const struct Parameter * _a = a; + const struct Parameter * _b = b; + + return strcmp(_a->key, _b->key); +} + +void paramSort(struct ParameterArray * params) +{ + qsort(params->parameters, params->n_members, sizeof(struct Parameter), &key_cmp); + params->is_sorted = true; +} + +struct Parameter * paramGet( + struct ParameterArray * params, + const char* key) +{ + if (!params->is_sorted) + paramSort(params); + + struct Parameter search_key; + search_key.key = (char*)key; // try to silent warning about losing const-ness + + return bsearch(&search_key, params->parameters, params->n_members, sizeof(struct Parameter), &key_cmp); + +} + +void paramParse( + struct ParameterArray * params, + char* url) +{ + char* url_copy = strdup(url); + memset(url, 0, strlen(url)); + + // temporary large buffers to record the key/values before passing + // them to paramAdd + char key[1024] = {0}; + char value[1024] = {0}; + + /* + * Parsing Rule: + * k = keys + * v = values + */ + char parsing_rule = 0; + + for (size_t i = 0; i < strlen(url_copy); ++i) + { + switch (url_copy[i]) + { + case '?': + parsing_rule = 'k'; + continue; + case '=': + parsing_rule = 'v'; + continue; + case '&': + paramAdd(params, key, value); + memset(key, 0, 1024); + memset(value, 0, 1024); + parsing_rule = 'k'; + continue; + default: + if (parsing_rule == 0) + strncat(url, &url_copy[i], 1); + break; + } + + switch (parsing_rule) + { + case 'k': + strncat(key, &url_copy[i], 1); + break; + case 'v': + strncat(value, &url_copy[i], 1); + break; + } + } + + paramAdd(params, key, value); + free(url_copy); +} + +void paramClear(struct ParameterArray * params) +{ + for (size_t i = 0; i < params->n_members; ++i) + { + free(params->parameters[i].key); + params->parameters[i].key = NULL; + + free(params->parameters[i].value); + params->parameters[i].value = NULL; + } + + params->n_members = 0; +} + +void paramFree(struct ParameterArray * params) +{ + paramClear(params); + free(params->parameters); + params->parameters = NULL; + free(params); + params = NULL; +} diff --git a/src/main.c b/src/main.c index 867ae86..e597b55 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include "HTTP_Server.h" #include "Routes.h" #include "Response.h" +#include "Parameters.h" int main() { // initiate HTTP_Server @@ -23,9 +24,12 @@ int main() { struct Route * route = initRoute("/", "index.html"); addRoute(route, "/about", "about.html"); + // create dynamic parameters array + struct ParameterArray * params = paramInit(10); + printf("\n====================================\n"); - printf("=========ALL VAILABLE ROUTES========\n"); + printf("=========ALL AVAILABLE ROUTES========\n"); // display all available routes inorder(route); @@ -64,6 +68,21 @@ int main() { printf("The method is %s\n", method); printf("The route is %s\n", urlRoute); + if (strlen(urlRoute) > 0) + { + paramParse(params, urlRoute); + + printf("*** PARSED PARAMETERS ***\n"); + for (size_t i = 0; i < params->n_members; ++i) + { + printf("KEY: %s\n", params->parameters[i].key); + printf("VALUE: %s\n\n", params->parameters[i].value); + } + // do something with parameters and can clear them to re-use for the next + // request + paramClear(params); + } + char template[100] = ""; diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..c0033d0 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,4 @@ +# this is not a general makefile but something quick and dirty so I can actually run the debugger +# (I suck at raw makefiles, too much time using CMake that I'm out of touch :L) +all: + gcc -g -O0 param_parser.c ../src/Parameters.c -I../include -o param_parser diff --git a/test/param_parser b/test/param_parser new file mode 100755 index 0000000..18f6a2f Binary files /dev/null and b/test/param_parser differ diff --git a/test/param_parser.c b/test/param_parser.c new file mode 100644 index 0000000..baa7843 --- /dev/null +++ b/test/param_parser.c @@ -0,0 +1,41 @@ +#include "Parameters.h" + +/* + * Just testing the functionality locally so I don't have to + * keep running/killing the server over and over. + * + * These could be converted to "proper" tests but for now + * they are purely visual. + */ +int main() +{ + char* url = strdup("/someNested/route?name=Zach&val=100&jimmy=John"); + struct ParameterArray * params = paramInit(10); + paramParse(params, url); + printf("PARSED PARAMS:\n\n"); + for (size_t i = 0; i < params->n_members; ++i) + { + printf("KEY: %s\n", params->parameters[i].key); + printf("VALUE: %s\n\n", params->parameters[i].value); + } + printf("CLEANED URL: %s\n\n", url); + + struct Parameter* search = NULL; + + printf("SEARCH KEY: name\n"); + search = paramGet(params, "name"); + printf("FOUND VALUE: %s\n\n", search->value); + + printf("SEARCH KEY: val\n"); + search = paramGet(params, "val"); + printf("FOUND VALUE: %s\n\n", search->value); + + printf("SEARCH KEY: xyz\n"); + search = paramGet(params, "xyz"); + if (!search) + printf("KEY DOESN'T EXIST\n\n"); + + paramFree(params); + free(url); + return 0; +}