From 1f8df737da1fb631f84c7bd9dbe2e01111f1ec80 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Thu, 6 Apr 2023 01:48:32 +0000 Subject: [PATCH] Add HttpRouter API; still have to convert the code to use it. --- TODO.txt | 8 +- src/HashMap.c | 27 +++- src/HttpRouter.c | 296 +++++++++++++++++++++++++++++++++++++++ src/Str.c | 44 ++++++ src/include/HashMap.h | 5 + src/include/HttpRouter.h | 45 ++++++ src/include/Str.h | 3 + 7 files changed, 419 insertions(+), 9 deletions(-) create mode 100644 src/HttpRouter.c create mode 100644 src/include/HttpRouter.h diff --git a/TODO.txt b/TODO.txt index 9f1ec91..889aec8 100644 --- a/TODO.txt +++ b/TODO.txt @@ -44,8 +44,9 @@ Milestone: v0.3.0 [x] Pass TLS certs and keys into HttpServer [x] Debug OpenSSL [x] Replace all usages of curl with http -[ ] Proper HTTP request router - - Support regex matching +[~] Proper HTTP request router + [x] Support regex matching + [ ] Replace current routing system [ ] Token permissions [ ] Move configuration to database @@ -69,6 +70,9 @@ Milestone: v0.3.0 [ ] send-patch [ ] Log [ ] TelodendriaConfig -> Config + [ ] HashMap + [ ] HttpRouter + [ ] Str [~] Client-Server API [x] 4: Token-based user registration diff --git a/src/HashMap.c b/src/HashMap.c index 9c32411..fba25a1 100644 --- a/src/HashMap.c +++ b/src/HashMap.c @@ -247,26 +247,26 @@ HashMapGet(HashMap * map, const char *key) } int -HashMapIterate(HashMap * map, char **key, void **value) +HashMapIterateReentrant(HashMap * map, char **key, void **value, size_t * i) { if (!map) { return 0; } - if (map->iterator >= map->capacity) + if (*i >= map->capacity) { - map->iterator = 0; + *i = 0; *key = NULL; *value = NULL; return 0; } - while (map->iterator < map->capacity) + while (*i < map->capacity) { - HashMapBucket *bucket = map->entries[map->iterator]; + HashMapBucket *bucket = map->entries[*i]; - map->iterator++; + *i = *i + 1; if (bucket && bucket->hash) { @@ -276,10 +276,23 @@ HashMapIterate(HashMap * map, char **key, void **value) } } - map->iterator = 0; + *i = 0; return 0; } +int +HashMapIterate(HashMap * map, char **key, void **value) +{ + if (!map) + { + return 0; + } + else + { + return HashMapIterateReentrant(map, key, value, &map->iterator); + } +} + void HashMapMaxLoadSet(HashMap * map, float load) { diff --git a/src/HttpRouter.c b/src/HttpRouter.c new file mode 100644 index 0000000..ac3d401 --- /dev/null +++ b/src/HttpRouter.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + +#include +#include +#include + +#include +#include + +#define REG_FLAGS (REG_EXTENDED) +#define REG_MAX_SUB 8 + +typedef struct RouteNode +{ + HttpRouteFunc *exec; + HashMap *children; + + regex_t regex; +} RouteNode; + +struct HttpRouter +{ + RouteNode *root; +}; + +static RouteNode * +RouteNodeCreate(char *regex, HttpRouteFunc * exec) +{ + RouteNode *node; + + if (!regex) + { + return NULL; + } + + node = Malloc(sizeof(RouteNode)); + + if (!node) + { + return NULL; + } + + node->children = HashMapCreate(); + if (!node->children) + { + Free(node); + return NULL; + } + + /* Force the regex to match the entire path part exactly. */ + regex = StrConcat(3, "^", regex, "$"); + if (!regex) + { + Free(node); + return NULL; + } + + if (regcomp(&node->regex, regex, REG_FLAGS) != 0) + { + HashMapFree(node->children); + Free(node); + Free(regex); + return NULL; + } + + node->exec = exec; + + Free(regex); + return node; +} + +static void +RouteNodeFree(RouteNode * node) +{ + char *key; + RouteNode *val; + + if (!node) + { + return; + } + + while (HashMapIterate(node->children, &key, (void **) &val)) + { + RouteNodeFree(val); + } + + HashMapFree(node->children); + + regfree(&node->regex); + + Free(node); +} + +HttpRouter * +HttpRouterCreate(void) +{ + HttpRouter *router = Malloc(sizeof(HttpRouter)); + + if (!router) + { + return NULL; + } + + router->root = RouteNodeCreate("/", NULL); + + return router; +} + +void +HttpRouterFree(HttpRouter * router) +{ + if (!router) + { + return; + } + + RouteNodeFree(router->root); + Free(router); +} + +int +HttpRouterAdd(HttpRouter * router, char *regPath, HttpRouteFunc * exec) +{ + RouteNode *node; + char *pathPart; + char *tmp; + + if (!router || !regPath || !exec) + { + return 0; + } + + if (strcmp(regPath, "/") == 0) + { + router->root->exec = exec; + return 1; + } + + regPath = StrDuplicate(regPath); + if (!regPath) + { + return 0; + } + + tmp = regPath; + node = router->root; + + while ((pathPart = strtok_r(tmp, "/", &tmp))) + { + RouteNode *tNode = HashMapGet(node->children, pathPart); + + if (!tNode) + { + tNode = RouteNodeCreate(pathPart, NULL); + RouteNodeFree(HashMapSet(node->children, pathPart, tNode)); + } + + node = tNode; + } + + node->exec = exec; + + Free(regPath); + + return 1; +} + +int +HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret) +{ + RouteNode *node; + char *pathPart; + char *tmp; + HttpRouteFunc *exec; + Array *matches; + size_t i; + int retval; + + if (!router || !path) + { + return 0; + } + + matches = ArrayCreate(); + if (!matches) + { + return 0; + } + + node = router->root; + + if (strcmp(path, "/") == 0) + { + exec = node->exec; + } + else + { + path = StrDuplicate(path); + tmp = path; + while ((pathPart = strtok_r(tmp, "/", &tmp))) + { + char *key; + RouteNode *val = NULL; + + regmatch_t pmatch[REG_MAX_SUB]; + + i = 0; + + while (HashMapIterateReentrant(node->children, &key, (void **) &val, &i)) + { + if (regexec(&val->regex, pathPart, REG_MAX_SUB, pmatch, 0) == 0) + { + break; + } + + val = NULL; + } + + if (!val) + { + exec = NULL; + break; + } + + node = val; + exec = node->exec; + + /* If we want to pass an arg, the match must be in parens */ + if (val->regex.re_nsub) + { + /* pmatch[0] is the whole string, not the first + * subexpression */ + for (i = 1; i < REG_MAX_SUB; i++) + { + if (pmatch[i].rm_so == -1) + { + break; + } + + ArrayAdd(matches, StrSubstr(pathPart, pmatch[i].rm_so, pmatch[i].rm_eo)); + } + } + } + Free(path); + } + + if (!exec) + { + retval = 0; + goto finish; + } + + if (ret) + { + *ret = exec(matches, args); + } + else + { + exec(matches, args); + } + + retval = 1; + +finish: + for (i = 0; i < ArraySize(matches); i++) + { + Free(ArrayGet(matches, i)); + } + ArrayFree(matches); + + return retval; +} diff --git a/src/Str.c b/src/Str.c index 61472ca..5b699f9 100644 --- a/src/Str.c +++ b/src/Str.c @@ -100,6 +100,50 @@ StrDuplicate(const char *inStr) return outStr; } +char * +StrSubstr(const char *inStr, size_t start, size_t end) +{ + size_t len; + size_t i; + size_t j; + + char *outStr; + + if (!inStr) + { + return NULL; + } + + if (start >= end) + { + return NULL; + } + + len = end - start; + + outStr = Malloc(len + 1); + if (!outStr) + { + return NULL; + } + + j = 0; + for (i = start; i < end; i++) + { + if (inStr[i] == '\0') + { + break; + } + + outStr[j] = inStr[i]; + j++; + } + + outStr[j] = '\0'; + + return outStr; +} + char * StrConcat(size_t nStr,...) { diff --git a/src/include/HashMap.h b/src/include/HashMap.h index dbf2420..c99936e 100644 --- a/src/include/HashMap.h +++ b/src/include/HashMap.h @@ -25,6 +25,8 @@ #ifndef TELODENDRIA_HASHMAP_H #define TELODENDRIA_HASHMAP_H +#include + typedef struct HashMap HashMap; extern HashMap * @@ -48,6 +50,9 @@ extern void * extern int HashMapIterate(HashMap *, char **, void **); +extern int + HashMapIterateReentrant(HashMap *, char **, void **, size_t *); + extern void HashMapFree(HashMap *); diff --git a/src/include/HttpRouter.h b/src/include/HttpRouter.h new file mode 100644 index 0000000..64bb1ca --- /dev/null +++ b/src/include/HttpRouter.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TELODENDRIA_HTTPROUTER_H +#define TELODENDRIA_HTTPROUTER_H + +#include + +typedef struct HttpRouter HttpRouter; + +typedef void *(HttpRouteFunc) (Array *, void *); + +extern HttpRouter * + HttpRouterCreate(void); + +extern void + HttpRouterFree(HttpRouter *); + +extern int + HttpRouterAdd(HttpRouter *, char *, HttpRouteFunc *); + +extern int + HttpRouterRoute(HttpRouter *, char *, void *, void **); + +#endif /* TELODENDRIA_HTTPROUTER_H */ diff --git a/src/include/Str.h b/src/include/Str.h index 9150f5b..ec0bbee 100644 --- a/src/include/Str.h +++ b/src/include/Str.h @@ -32,6 +32,9 @@ extern char * extern char * StrDuplicate(const char *); +extern char * + StrSubstr(const char *, size_t, size_t); + extern char * StrConcat(size_t,...);