diff --git a/Cytoplasm/src/HttpRouter.c b/Cytoplasm/src/HttpRouter.c new file mode 100644 index 0000000..bd06c64 --- /dev/null +++ b/Cytoplasm/src/HttpRouter.c @@ -0,0 +1,300 @@ +/* + * 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 (StrEquals(regPath, "/")) + { + 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 = NULL; + Array *matches = NULL; + size_t i; + int retval; + + if (!router || !path) + { + return 0; + } + + matches = ArrayCreate(); + if (!matches) + { + return 0; + } + + node = router->root; + + if (StrEquals(path, "/")) + { + 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 */ + char * substr; + regmatch_t cpmatch; + for (i = 1; i < REG_MAX_SUB; i++) + { + cpmatch = pmatch[i]; + substr = StrSubstr(pathPart, cpmatch.rm_so, cpmatch.rm_eo); + if (pmatch[i].rm_so == -1) + { + break; + } + + ArrayAdd(matches, substr); + } + } + } + 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/Cytoplasm/src/Str.c b/Cytoplasm/src/Str.c new file mode 100644 index 0000000..2f4c665 --- /dev/null +++ b/Cytoplasm/src/Str.c @@ -0,0 +1,343 @@ +/* + * 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 +#include +#include +#include +#include +#include + +UInt32 +StrUtf16Decode(UInt16 high, UInt16 low) +{ + if (high <= 0xD7FF) + { + return high; + } + else if (high <= 0xDBFF) + { + unsigned short hS = (high - 0xD800) * 0x400; + unsigned short lS = low - 0xDC00; + + return (lS | hS) + 0x10000; + } + else + { + return 0; + } +} + +char * +StrUtf8Encode(UInt32 codepoint) +{ + char *str; + + str = Malloc(5 * sizeof(char)); + if (!str) + { + return NULL; + } + + if (codepoint <= 0x7F && codepoint != 0) /* Plain ASCII */ + { + str[0] = (char) codepoint; + str[1] = '\0'; + } + else if (codepoint <= 0x07FF) /* 2-byte */ + { + str[0] = (char) (((codepoint >> 6) & 0x1F) | 0xC0); + str[1] = (char) (((codepoint >> 0) & 0x3F) | 0x80); + str[2] = '\0'; + } + else if (codepoint <= 0xFFFF) /* 3-byte */ + { + str[0] = (char) (((codepoint >> 12) & 0x0F) | 0xE0); + str[1] = (char) (((codepoint >> 6) & 0x3F) | 0x80); + str[2] = (char) (((codepoint >> 0) & 0x3F) | 0x80); + str[3] = '\0'; + } + else if (codepoint <= 0x10FFFF)/* 4-byte */ + { + str[0] = (char) (((codepoint >> 18) & 0x07) | 0xF0); + str[1] = (char) (((codepoint >> 12) & 0x3F) | 0x80); + str[2] = (char) (((codepoint >> 6) & 0x3F) | 0x80); + str[3] = (char) (((codepoint >> 0) & 0x3F) | 0x80); + str[4] = '\0'; + } + else + { + /* Send replacement character */ + str[0] = (char) 0xEF; + str[1] = (char) 0xBF; + str[2] = (char) 0xBD; + str[3] = '\0'; + } + + return str; +} + +char * +StrDuplicate(const char *inStr) +{ + size_t len; + char *outStr; + + if (!inStr) + { + return NULL; + } + + len = strlen(inStr); + outStr = Malloc(len + 1); /* For the null terminator */ + if (!outStr) + { + return NULL; + } + + strncpy(outStr, inStr, len + 1); + + 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,...) +{ + va_list argp; + char *str; + char *strp; + size_t strLen = 0; + size_t i; + + va_start(argp, nStr); + for (i = 0; i < nStr; i++) + { + char *argStr = va_arg(argp, char *); + + if (argStr) + { + strLen += strlen(argStr); + } + } + va_end(argp); + + str = Malloc(strLen + 1); + strp = str; + + va_start(argp, nStr); + + for (i = 0; i < nStr; i++) + { + /* Manually copy chars instead of using strcopy() so we don't + * have to call strlen() on the strings again, and we aren't + * writing useless null chars. */ + + char *argStr = va_arg(argp, char *); + + if (argStr) + { + while (*argStr) + { + *strp = *argStr; + strp++; + argStr++; + } + } + } + + va_end(argp); + str[strLen] = '\0'; + return str; +} + +int +StrBlank(const char *str) +{ + int blank = 1; + size_t i = 0; + + while (str[i]) + { + blank &= isspace((unsigned char) str[i]); + /* No need to continue if we don't have a blank */ + if (!blank) + { + break; + } + i++; + } + return blank; +} + +char * +StrRandom(size_t len) +{ + static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + char *str; + int *nums; + size_t i; + + if (!len) + { + return NULL; + } + + str = Malloc(len + 1); + + if (!str) + { + return NULL; + } + + nums = Malloc(len * sizeof(int)); + if (!nums) + { + Free(str); + return NULL; + } + + /* TODO: This seems slow. */ + RandIntN(nums, len, sizeof(charset) - 1); + for (i = 0; i < len; i++) + { + str[i] = charset[nums[i]]; + } + + Free(nums); + str[len] = '\0'; + return str; +} + +char * +StrInt(long i) +{ + char *str; + int len; + + len = snprintf(NULL, 0, "%ld", i); + str = Malloc(len + 1); + if (!str) + { + return NULL; + } + + snprintf(str, len + 1, "%ld", i); + + return str; +} + +char * +StrLower(char *str) +{ + char *ret; + + size_t len; + size_t i; + + if (!str) + { + return NULL; + } + + len = strlen(str); + ret = Malloc(len + 1); + + for (i = 0; i < len; i++) + { + ret[i] = tolower(str[i]); + } + + ret[len] = '\0'; + + return ret; +} + +int +StrEquals(const char *str1, const char *str2) +{ + /* Both strings are NULL, they're equal */ + if (!str1 && !str2) + { + return 1; + } + + /* One or the other is NULL, they're not equal */ + if (!str1 || !str2) + { + return 0; + } + + /* Neither are NULL, do a regular string comparison */ + return strcmp(str1, str2) == 0; +}