vessel/web/path.c
Arija A. 4f0bf80e5f
refact: Remove mem.h
Signed-off-by: Arija A. <ari@ari.lt>
2025-06-21 23:43:31 +03:00

1358 lines
39 KiB
C

#include "include/conf.h"
#include "include/path.h"
#include "include/http.h"
#include "include/path-builtin.h"
#include <vessel/thread.h>
#include <vessel/hmap.h>
#include <vessel/def.h>
#include <vessel/log.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
static vs_HMap vw_types = { 0 };
static vs_AtomBool vw_types_inited = VS_ATOM_INIT_VAR(false);
static inline bool vw_register_type_raw(const char *name,
const uint16_t flags,
vw_PathTypeCompileFunc compile,
vw_PathTypeConsumeFunc consume,
vw_PathTypeConvertFunc convert,
vw_PathTypeCleanupFunc cleanup,
vw_PathTypePrintFunc print) {
if (!consume) {
return false;
}
if ((flags & VW_PATH_TYPE_FLAG_VALIDATE_ONLY) && convert) {
return false;
}
if (!(flags & VW_PATH_TYPE_FLAG_NOCONV) && !convert) {
return false;
}
/* NOTE: Thou shalt not profane the established types, lest thou
* suffer the slings of confusion and the arrows of error. */
if (vs_HMap_find(&vw_types, name)) {
return false;
}
vw_PathType *t = VS_MALLOC(sizeof(*t));
if (!t) {
return false;
}
t->flags = flags;
t->compile = compile;
t->consume = consume;
t->convert = convert;
t->cleanup = cleanup;
t->print = print;
return vs_HMapID_is_valid(vs_HMap_insert(&vw_types, name, t));
}
static void _destroy_types(void) { vs_HMap_destroy_free(&vw_types); }
static bool vw_init_types(void) {
const vs_Logger lg = VS_LOG_ERROR;
if (VS_ATOM_LOAD(&vw_types_inited)) {
return true;
}
if (!vs_HMap_init(&vw_types)) {
vs_log_error(&lg, "Failed to initialize built-in path type hashmap.");
return false;
}
VS_ATOM_STORE(&vw_types_inited, true);
/* compile, consume, convert, cleanup, print */
#define vw__PATH_TYPE_DEFINE_BUILTIN(name, ...) \
if (!vw_register_type_raw(#name, vw__VW_PATH_TYPE_FLAG_BUILTIN_CAUTION, __VA_ARGS__)) { \
vs_flog_error(&lg, "Failed to define built-in path type %s (this is really bad)", #name); \
_destroy_types(); \
VS_ATOM_STORE(&types_inited, false); \
return false; \
}
#define vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(name, flags, ...) \
if (!vw_register_type_raw(#name, vw__VW_PATH_TYPE_FLAG_BUILTIN_CAUTION | (flags), __VA_ARGS__)) { \
vs_flog_error(&lg, "Failed to define built-in path type %s (this is really bad)", #name); \
_destroy_types(); \
VS_ATOM_STORE(&vw_types_inited, false); \
return false; \
}
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(int,
VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG |
VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE,
VW_PATH_TYPE_BUILTIN_ARGS(int));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(str,
VW_PATH_TYPE_FLAG_NOCONV |
VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG,
VW_PATH_TYPE_BUILTIN_NOCONV_ARGS(str));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(path,
VW_PATH_TYPE_FLAG_LAST | VW_PATH_TYPE_FLAG_NOCONV,
VW_PATH_TYPE_BUILTIN_STRLIKE_ARGS(path));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(float,
VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG |
VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE,
VW_PATH_TYPE_BUILTIN_INTLIKE_ARGS(float));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(double,
VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG |
VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE,
VW_PATH_TYPE_BUILTIN_INTLIKE_ARGS(double));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(
bool,
VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG | VW_PATH_TYPE_FLAG_LOWERCASE_ARG |
VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE | VW_PATH_TYPE_FLAG_LOWERCASE_VALUE,
VW_PATH_TYPE_BUILTIN_ARGS(bool));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(
date, VW_PATH_TYPE_FLAG_ALCOMP, VW_PATH_TYPE_BUILTIN_ARGS(date));
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(
uuid,
VW_PATH_TYPE_FLAG_NOCONV | VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG |
VW_PATH_TYPE_FLAG_LOWERCASE_ARG | VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE,
vw_PathType_builtin_uuid_compile,
vw_PathType_builtin_uuid_consume,
NULL,
NULL,
vw_PathType_builtin_uuid_print);
vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS(
hex,
VW_PATH_TYPE_FLAG_NOCONV | VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG |
VW_PATH_TYPE_FLAG_LOWERCASE_ARG | VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE |
VW_PATH_TYPE_FLAG_LOWERCASE_VALUE,
VW_PATH_TYPE_BUILTIN_STRLIKE_ARGS(hex));
#undef vw__PATH_TYPE_DEFINE_BUILTIN
#undef vw__PATH_TYPE_DEFINE_BUILTIN_FLAGS
atexit(&_destroy_types);
return true;
}
bool vw_Path_init(vw_Path *path) {
if (!path) {
return false;
}
path->path = NULL;
return vs_HMap_init(&path->args);
}
bool vw_Path_parse(vw_Path *path, const char *src) {
if (!path) {
return false;
}
const char *query_start = strchr(src, '?');
const size_t path_length = query_start ? (size_t)(query_start - src) : strlen(src);
char *normalized_path =
VS_MALLOC(path_length + 2); /* +1 for ^/ (starting /), +1 for NULL. Minimum base
case of `` (empty string) is 0 + 2 = /\0. */
if (!normalized_path) {
return false;
}
char *src_ptr = vs_dupnstr(src, path_length);
if (!src_ptr) {
VS_FREE(normalized_path);
return false;
}
ssize_t decoded_len = 0;
if ((decoded_len = vw_HTTP_url_decode(src_ptr)) < 1) {
VS_FREE(normalized_path);
return false;
}
vs_lowstr(src_ptr);
char *dst_ptr = normalized_path;
*dst_ptr++ = '/';
bool prev_was_slash = true;
for (ssize_t idx = 0; idx < decoded_len; ++idx) {
if (src_ptr[idx] == '/') {
if (prev_was_slash) {
continue; /* Skip redundant slashes */
}
prev_was_slash = true;
*dst_ptr++ = '/';
continue;
}
if (prev_was_slash && src_ptr[idx] == '.') {
if (src_ptr[idx + 1] == '/' || src_ptr[idx + 1] == '\0') {
/* Parent direcotory */
++idx;
continue;
}
if (src_ptr[idx + 1] == '.' && (src_ptr[idx + 2] == '/' || src_ptr[idx + 2] == '\0')) {
idx += 2; /* Skip ".." and the following slash */
if (dst_ptr - 1 != normalized_path) {
if (dst_ptr != normalized_path && *(dst_ptr - 1) == '/') {
--dst_ptr;
}
while (dst_ptr > normalized_path && *(dst_ptr - 1) != '/') {
--dst_ptr;
}
}
continue;
}
}
/* Normal character */
*dst_ptr++ = src_ptr[idx];
prev_was_slash = false;
}
VS_FREE(src_ptr);
if (dst_ptr - normalized_path != 1 && *(dst_ptr - 1) == '/') {
--dst_ptr;
}
*dst_ptr = '\0';
if (path->path) {
VS_FREE(path->path);
}
path->path = normalized_path;
path->len = strlen(normalized_path);
/* query_start = "?"<query>, therefore query_start+1 = <query> */
if (query_start && !vw_HTTP_parse_query(query_start + 1, &path->args)) {
VS_FREE(normalized_path);
return false;
}
return true;
}
bool vw_Path_destroy(vw_Path *path) {
if (!path) {
return false;
}
VS_FREE(path->path);
return vs_HMap_destroy_free(&path->args);
}
vw_PathPattern *vw_PathPattern_new(void) {
if (!vw_init_types()) {
return NULL;
}
return VS_CALLOC(1, sizeof(vw_PathPattern));
}
vw_PathSegment *vw_PathSegment_new(void) {
if (!vw_init_types()) {
return NULL;
}
return VS_CALLOC(1, sizeof(vw_PathSegment));
}
bool vw_PathType_new(const char *name,
const uint16_t flags,
vw_PathTypeCompileFunc compile,
vw_PathTypeConsumeFunc consume,
vw_PathTypeConvertFunc convert,
vw_PathTypeCleanupFunc cleanup,
vw_PathTypePrintFunc print) {
if (!vw_init_types() || !name) {
return false;
}
if (!vs_strissafe(name, "@-_", 3, false)) {
return false;
}
return vw_register_type_raw(name, flags, compile, consume, convert, cleanup, print);
}
static inline bool vw_PathPattern_compile_dynamic_type(const vs_Logger *lg,
const char **ppat,
char **type_out,
bool *is_custom) {
const char *pat = *ppat;
*is_custom = *pat == '$';
if (*is_custom) {
++pat;
}
const char *type_start = pat;
while (*pat && *pat != '(' && *pat != '!' && *pat != ':' && *pat != '?' && *pat != '>') {
++pat;
}
size_t type_len = 0;
const char *type_end = pat;
if (!vs_strip_spaces(&type_start, &type_end)) {
vs_flog_error(lg, "Type missing in pattern [%s] (whitespace stripping failed)", *ppat);
return false;
}
type_len = (size_t)(type_end - type_start);
if (type_len == 0) {
vs_flog_error(lg, "Type missing in pattern [%s].", *ppat);
return false;
}
char *type = VS_MALLOC(type_len + 1);
if (!type) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for type in pattern [%s].",
type_len + 1,
*ppat);
return false;
}
vs_escstrncpy(type, type_start, type_len);
type[type_len] = '\0';
vs_lowstr(type);
*type_out = type;
*ppat = pat;
return true;
}
static inline bool vw_PathPattern_compile_dynamic_noconv(const char **ppat,
const vw_PathType *pt,
vw_PathSegment *seg) {
const char *pat = *ppat;
if (!*pat) {
return false;
}
if (*pat == '!') {
seg->flags |= VW_PATH_SEGMENT_FLAG_NOCONV;
++pat;
} else if (pt->flags & VW_PATH_TYPE_FLAG_NOCONV) {
seg->flags |= VW_PATH_SEGMENT_FLAG_NOCONV;
}
while (*pat == ' ') {
++pat;
}
*ppat = pat;
return true;
}
static inline bool vw_PathPattern_compile_dynamic_args(const vs_Logger *lg,
const char **ppat,
const char *type,
const vw_PathType *pt,
size_t *arg_len_out,
char **arg_out) {
const char *pat = *ppat;
size_t arg_len = 0;
char *arg = NULL;
if (*pat != '(') {
return true;
}
if (pt->flags & VW_PATH_TYPE_FLAG_ALARG) {
vs_flog_error(lg, "Type %s requires an argument in pattern [%s].", type, *ppat);
return false;
}
if ((pt->flags & VW_PATH_TYPE_FLAG_NOARG)) {
vs_flog_error(lg, "Type %s cannot have an argument in pattern [%s].", type, *ppat);
return false;
}
++pat;
const char *arg_start = pat;
const char *arg_end = pat;
size_t depth = 1;
while (*arg_end && depth > 0) {
switch (*arg_end) {
case '\\':
/* Skip the next character if it's escaped */
++arg_end;
break;
case '(':
++depth;
++arg_end;
break;
case ')':
--depth;
break;
default:
++arg_end;
break;
}
}
if (*arg_end != ')' || depth > 0) {
vs_flog_error(lg,
"Invalid argument syntax in pattern (missing closing "
"parenthesis) [%s].",
*ppat);
return false;
}
const char *closing_paren = arg_end;
if (pt->flags & VW_PATH_TYPE_FLAG_STRIP_SPACES_ARG) {
if (!vs_strip_spaces(&arg_start, &arg_end)) {
vs_flog_error(lg,
"Type %s arguments missing in pattern [%s] (whitespace "
"stripping failed)",
type,
*ppat);
return false;
}
}
arg_len = (size_t)(arg_end - arg_start);
if (arg_len == 0) {
vs_flog_error(lg, "Type %s arguments missing in pattern [%s]", type, *ppat);
return false;
}
if (pt->compile) {
arg = VS_MALLOC(arg_len + 1);
if (!arg) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for argument in "
"pattern [%s].",
arg_len + 1,
*ppat);
return false;
}
if (pt->flags & VW_PATH_TYPE_FLAG_ALARG) {
vs_flog_error(
lg, "Type %s must have a non-empty argument in pattern [%s].", type, *ppat);
VS_FREE(arg);
return false;
}
/* Handle backslashes */
vs_escstrncpy(arg, arg_start, arg_len);
arg[arg_len] = '\0';
if (pt->flags & VW_PATH_TYPE_FLAG_LOWERCASE_ARG) {
vs_lowstr(arg);
}
}
pat = closing_paren + 1;
while (*pat == ' ') {
++pat;
}
*arg_len_out = arg_len;
*arg_out = arg;
*ppat = pat;
return true;
}
static inline bool vw_PathPattern_compile_dynamic_key(const vs_Logger *lg,
const char **ppat,
const char *type,
const vw_PathType *pt,
vw_PathSegment *seg) {
const char *pat = *ppat;
if (*pat != ':') {
return true;
}
if (pt->flags & VW_PATH_TYPE_FLAG_VALIDATE_ONLY) {
vs_flog_error(lg,
"Type %s cannot have a key in pattern [%s] because it "
"is validate-only.",
type,
*ppat);
return false;
}
const char *key_start = pat + 1;
const char *pat_end = key_start;
while (*pat_end && *pat_end != '?' && *pat_end != '>') {
++pat_end;
}
const char *key_end = pat_end;
if (!vs_strip_spaces(&key_start, &key_end)) {
vs_flog_error(lg, "Key missing in pattern [%s] (whitespace stripping failed)", *ppat);
return false;
}
const size_t key_len = (size_t)(key_end - key_start);
if (key_len == 0) {
vs_flog_error(lg, "Key missing in pattern [%s].", *ppat);
return false;
}
if (!vs_strnissafe(key_start, key_len, "@_-", 3)) {
vs_flog_error(lg, "Key includes unsafe/ambiguous characters in pattern [%s].", *ppat);
return false;
}
char *key = VS_MALLOC(key_len + 1);
if (!key) {
vs_flog_error(
lg, "Failed to allocate %zu bytes of memory for key in pattern.", key_len + 1);
return false;
}
vs_escstrncpy(key, key_start, key_len);
key[key_len] = '\0';
vs_lowstr(key);
seg->key = key;
seg->key_len = key_len;
*ppat = pat_end;
return true;
}
static inline bool vw_PathPattern_compile_dynamic_default_optional(const vs_Logger *lg,
const char **ppat,
const char *type,
const vw_PathType *pt,
vw_PathSegment *seg) {
const char *pat = *ppat;
if (*pat != '?') {
return true;
}
if (pt->flags & VW_PATH_TYPE_FLAG_REQUIRED) {
vs_flog_error(lg,
"Type %s cannot be optional in pattern [%s] because it "
"is always required.",
type,
*ppat);
return false;
}
seg->flags |= VW_PATH_SEGMENT_FLAG_OPTIONAL;
++pat;
while (*pat == ' ') {
++pat;
}
if (*pat == '=') {
if (pt->flags & VW_PATH_TYPE_FLAG_NODEFAULT) {
vs_flog_error(lg, "Type %s cannot have default values in pattern [%s].", type, *ppat);
return false;
}
if (!seg->key_len || !seg->key) {
vs_flog_error(lg,
"Type %s cannot have default values in pattern [%s] "
"because it does not have an assigned key.",
type,
*ppat);
return false;
}
seg->always_insert = true;
const char *def_start = pat + 1;
size_t def_len = 0;
if (pt->flags & VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE) {
while (*pat && *pat != '>') {
++pat;
}
const char *tmp_start = def_start;
const char *tmp_end = pat;
if (!vs_strip_spaces(&tmp_start, &tmp_end)) {
vs_flog_error(lg,
"Failed to strip spaces from default value in "
"pattern [%s]",
*ppat);
return false;
}
def_start = tmp_start;
def_len = (size_t)(tmp_end - tmp_start);
} else {
while (*pat && *pat != '>') {
++pat;
}
def_len = (size_t)(pat - def_start);
}
if (def_len == 0) {
/* defaults can be empty */
seg->def = NULL;
seg->def_len = 0;
} else {
char *def = VS_MALLOC(def_len + 1);
if (!def) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for default "
"value in pattern [%s].",
def_len + 1,
*ppat);
if (seg->key) {
VS_FREE(seg->key);
}
return false;
}
vs_escstrncpy(def, def_start, def_len);
def[def_len] = '\0';
if (pt->flags & VW_PATH_TYPE_FLAG_LOWERCASE_VALUE) {
vs_lowstr(def);
}
seg->def = def;
seg->def_len = def_len;
}
}
while (*pat == ' ') {
++pat;
}
*ppat = pat;
return true;
}
static inline bool vw_PathPattern_compile_dynamic_segment(const vs_Logger *lg,
const char **ppat,
const bool prev_dynamic,
vw_PathSegment *seg) {
const char *pat = *ppat;
/* Skip < */
if (*pat != '<') {
return false;
}
++pat;
/* Extract type */
char *type = NULL;
bool is_custom = false;
if (!vw_PathPattern_compile_dynamic_type(lg, &pat, &type, &is_custom)) {
return false;
}
const vs_HMapEntry *entry = vs_HMap_find_unsafe(&vw_types, type);
seg->type = vs_HMapID_get(entry);
/* If no such type was found */
if (!vs_HMapID_is_valid(seg->type)) {
vs_flog_error(lg, "Type %s was not found in pattern [%s].", type, *ppat);
VS_FREE(type);
return false;
}
const vs_HMapEntry *type_entry = vs_HMap_find_id_unsafe(&vw_types, seg->type);
if (!type_entry || !type_entry->value) {
vs_flog_error(lg, "Unable to find (valid) type %s for pattern [%s].", type, *ppat);
VS_FREE(type);
return false;
}
const vw_PathType *pt = (vw_PathType *)type_entry->value;
if (is_custom && (pt->flags & vw__VW_PATH_TYPE_FLAG_BUILTIN_CAUTION)) {
vs_flog_error(lg,
"Type %s is built-in but you are using a custom type. "
"Please remove the '$' character in pattern [%s].",
type,
*ppat);
VS_FREE(type);
return false;
}
if ((pt->flags & VW_PATH_TYPE_FLAG_BOUND) && prev_dynamic) {
vs_flog_error(lg,
"Type %s cannot be used adjacent to other dynamic segments in "
"pattern [%s] and must be strictly surrounded by static segments.",
type,
*ppat);
VS_FREE(type);
return false;
}
/* Check for '!' flag */
if (!vw_PathPattern_compile_dynamic_noconv(&pat, pt, seg)) {
vs_flog_error(lg,
"Type %s was not flagged with '!' (noconvert) and parsing "
"failed in pattern [%s].",
type,
*ppat);
VS_FREE(type);
return false;
}
/* Extract arguments */
char *arg = NULL;
size_t arg_len = 0;
if (!vw_PathPattern_compile_dynamic_args(lg, &pat, type, pt, &arg_len, &arg)) {
vs_flog_error(lg, "Type %s arguments parsing failed in pattern [%s].", type, *ppat);
VS_FREE(type);
return false;
}
/* Extract key */
if (!vw_PathPattern_compile_dynamic_key(lg, &pat, type, pt, seg)) {
vs_flog_error(lg, "Key parsing failed in pattern [%s].", *ppat);
VS_FREE(type);
if (arg) {
VS_FREE(arg);
}
return false;
}
/* Check for '?' flag and default value if present */
if (!vw_PathPattern_compile_dynamic_default_optional(lg, &pat, type, pt, seg)) {
vs_flog_error(lg,
"Type %s was not flagged with '?' (optional) and parsing "
"failed in pattern [%s].",
type,
*ppat);
VS_FREE(type);
if (arg) {
VS_FREE(arg);
}
return false;
}
/* Skip > */
if (*pat != '>') {
vs_flog_error(lg,
"Invalid dynamic segment syntax in pattern (missing closing "
">) [%s].",
*ppat);
goto error;
}
if ((pt->flags & VW_PATH_TYPE_FLAG_LAST) && *(pat + 1)) {
vs_flog_error(lg, "Type %s must be last in pattern [%s].", type, *ppat);
goto error;
}
if ((pt->flags & VW_PATH_TYPE_FLAG_ALCOMP) || (arg && arg_len)) {
const vw_ArgSource ag = { .src = arg, .len = arg_len };
if (pt->compile && !pt->compile(&ag, &seg->compiled)) {
vs_flog_error(lg, "Type %s failed to compile in pattern [%s].", type, *ppat);
goto error;
}
}
if (seg->def && seg->def_len) {
const vw_PathSource sr = { .src = seg->def, .len = seg->def_len, .seg = seg, .full = true };
size_t size = 0;
size_t reqb = 0;
if (!pt->consume(&sr, &size, &reqb) || size != seg->def_len) {
vs_flog_error(lg,
"Default value '%s' does not match type %s (or with the "
"given arguments) in pattern [%s]",
seg->def,
type,
*ppat);
pt->cleanup(seg->compiled);
goto error;
}
if (!(pt->flags & VW_PATH_TYPE_FLAG_NOCONV) &&
!(seg->flags & VW_PATH_SEGMENT_FLAG_NOCONV)) {
seg->def_reqb = reqb;
seg->def_conv = VS_CALLOC(1, reqb);
if (!seg->def_conv) {
vs_flog_error(lg,
"Failed to allocatre %zu bytes for default value '%s' in "
"pattern [%s]",
reqb,
seg->def,
*ppat);
pt->cleanup(seg->compiled);
goto error;
}
if (!pt->convert(&sr, reqb, seg->def_conv)) {
vs_flog_error(lg,
"Failed to convert for default value '%s' for type %s in "
"pattern [%s]",
seg->def,
type,
*ppat);
pt->cleanup(seg->compiled);
VS_FREE(seg->def_conv);
seg->def_conv = NULL;
seg->def_reqb = 0;
goto error;
}
} else {
seg->def_conv = NULL;
seg->def_reqb = 0;
}
}
*ppat = pat;
VS_FREE(type);
if (arg) {
VS_FREE(arg);
}
return true;
error:
VS_FREE(type);
if (arg) {
VS_FREE(arg);
}
if (seg->key) {
VS_FREE(seg->key);
}
if (seg->def) {
VS_FREE(seg->def);
}
if (seg->def_conv) {
VS_FREE(seg->def_conv);
}
return false;
}
static inline bool vw_PathPattern_parse_static_segment(const vs_Logger *lg,
vw_PathPattern *comp,
const char *token,
const char *pat,
bool *prev_dynamic) {
bool optional = false;
if (*token == '?') {
optional = true;
++token;
}
if (token >= pat) {
return true;
}
vw_PathSegment *seg = vw_PathSegment_new();
if (!seg) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for "
"static part segment for pattern [%s].",
sizeof(*seg),
pat);
return false;
}
seg->flags = VW_PATH_SEGMENT_FLAG_STATIC;
if (optional) {
seg->flags |= VW_PATH_SEGMENT_FLAG_OPTIONAL;
}
const size_t bytes = (size_t)(pat - token);
char *static_part = VS_MALLOC(bytes + 1);
if (!static_part) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for "
"static part for pattern [%s].",
bytes + 1,
pat);
VS_FREE(seg);
return false;
}
vs_escstrncpy(static_part, token, bytes);
static_part[bytes] = '\0';
seg->def = static_part;
seg->def_len = bytes;
VS_G_ARRAY_APPEND(comp, seg);
*prev_dynamic = false;
return true;
}
static inline bool vw_PathPattern_parse_dynamic_segment(const vs_Logger *lg,
vw_PathPattern *comp,
const char **pat_ptr,
bool *prev_dynamic) {
vw_PathSegment *seg = vw_PathSegment_new();
if (!seg) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for "
"dynamic segment for pattern [%s].",
sizeof(*seg),
*pat_ptr);
return false;
}
if (!vw_PathPattern_compile_dynamic_segment(lg, pat_ptr, *prev_dynamic, seg)) {
vs_flog_error(lg, "Failed to compile dynamic part for pattern [%s].", *pat_ptr);
VS_FREE(seg);
return false;
}
VS_G_ARRAY_APPEND(comp, seg);
*prev_dynamic = true;
return true;
}
vw_PathPattern *vw_PathPattern_compile(const vs_Logger *lg, const char *pat) {
if (!pat || !vw_init_types()) {
return NULL;
}
vw_PathPattern *comp = vw_PathPattern_new();
if (!comp) {
vs_flog_error(lg,
"Failed to allocate %zu bytes of memory for vw_PathPattern for "
"pattern [%s].",
sizeof(*comp),
pat);
return NULL;
}
const char *token = pat;
bool prev_dynamic = false;
while (*pat) {
switch (*pat) {
case '<':
/* Start of a dynamic segment: Save static part. */
if (!vw_PathPattern_parse_static_segment(lg, comp, token, pat, &prev_dynamic)) {
vw_PathPattern_destroy(comp);
return NULL;
}
if (!vw_PathPattern_parse_dynamic_segment(lg, comp, &pat, &prev_dynamic)) {
vw_PathPattern_destroy(comp);
return NULL;
}
token = pat + 1;
break;
case '\\':
++pat;
break;
}
++pat;
}
/* Last segment (if exists) will always be a static segment */
if (!vw_PathPattern_parse_static_segment(lg, comp, token, pat, &prev_dynamic)) {
vw_PathPattern_destroy(comp);
return NULL;
}
if (comp->size == 0) {
vw_PathPattern_destroy(comp);
return NULL;
}
return comp;
}
static inline bool
vw_PathSegment_match_static(const vw_PathSegment *seg, const char **srcp, const char *end) {
const char *src = *srcp;
const char *pat = seg->def;
const char *pat_end = pat + seg->def_len;
while (src < end && pat < pat_end) {
if (pat + 1 < pat_end && *(pat + 1) == '?') {
if (*src == *pat) {
++src;
}
pat += 2;
} else {
if (*src != *pat) {
return false;
}
++src;
++pat;
}
}
*srcp = src;
return true;
}
#define VW_VW_PATH_SEGMENT_MATCH_DYNAMIC_SKIP_SPACES() \
do { \
if (is_strip_spaces) { \
while (*src == ' ') \
++src; \
} \
} while (0)
static inline bool vw_PathSegment_match_dynamic(const vw_PathSegment *seg,
const char **srcp,
const char *end,
vs_HMap *out) {
const char *src = *srcp;
/* Stuff we need */
char *value = NULL;
void *conv = NULL;
size_t size = 0;
size_t reqb = 0;
/* Get type */
const vs_HMapEntry *type_entry = vs_HMap_find_id_unsafe(&vw_types, seg->type);
if (!type_entry) {
return false;
}
const vw_PathType *pt = type_entry->value;
/* Read flags */
const bool is_noconv = (seg->flags & VW_PATH_SEGMENT_FLAG_NOCONV) != 0;
const bool is_last = (pt->flags & VW_PATH_TYPE_FLAG_LAST) != 0;
const bool is_validate_only = (pt->flags & VW_PATH_TYPE_FLAG_VALIDATE_ONLY) != 0;
const bool is_strip_spaces = (pt->flags & VW_PATH_TYPE_FLAG_STRIP_SPACES_VALUE) != 0;
const bool is_lowercase = (pt->flags & VW_PATH_TYPE_FLAG_LOWERCASE_VALUE) != 0;
const bool has_key = seg->key && seg->key_len;
const bool has_default = seg->def && seg->def_len;
VW_VW_PATH_SEGMENT_MATCH_DYNAMIC_SKIP_SPACES();
/* Step one: Consume the value from source */
const vw_PathSource consume_source = {
.src = src, .len = (size_t)(end - src), seg, .full = false
};
const bool consumed = pt->consume(&consume_source, &size, &reqb);
if (is_validate_only || !has_key) {
VW_VW_PATH_SEGMENT_MATCH_DYNAMIC_SKIP_SPACES();
*srcp = src + size;
return consumed && size > 0;
}
if (consumed && size > 0) {
value = vs_dupnstr(src, size);
if (!value) {
return false;
}
if (is_lowercase) {
vs_lownstr(value, size);
}
src += size;
} else {
if (has_default) {
value = vs_dupnstr(seg->def, seg->def_len);
if (!value) {
return false;
}
size = reqb;
reqb = seg->def_reqb;
} else if (seg->always_insert) {
value = NULL;
reqb = 0;
} else {
return false;
}
}
/* End step one */
VW_VW_PATH_SEGMENT_MATCH_DYNAMIC_SKIP_SPACES();
/* Step two: Convert to value (if applicable) */
if (!is_noconv && reqb > 0 && value) {
conv = VS_MALLOC(reqb);
if (!conv) {
VS_FREE(value);
return false;
}
const vw_PathSource convert_source = { .src = value, .len = size, seg, .full = true };
if (!pt->convert(&convert_source, reqb, conv)) {
VS_FREE(value);
VS_FREE(conv);
return false;
}
}
/* End step two */
vw_PathMatch *m = VS_MALLOC(sizeof(*m));
if (!m) {
VS_FREE(value);
VS_FREE(conv);
return false;
}
m->str = value;
m->size = size;
m->conv = conv;
m->reqb = reqb;
if (!vs_HMapID_is_valid(vs_HMap_insertn(out, seg->key, seg->key_len, m))) {
VS_FREE(value);
VS_FREE(conv);
return false;
}
*srcp = src;
return is_last ? (end == src) : true;
}
size_t vw_PathPattern_matchn(const vw_PathPattern *pat, vs_HMap *out, const char *src, size_t len) {
if (!pat || !pat->items || !pat->size || !src || !len) {
return 0;
}
const char *end = src + len;
size_t idx = 0;
for (idx = 0; idx < pat->size; ++idx) {
const vw_PathSegment *seg = pat->items[idx];
if (!seg) {
vs_HMap_clear_free(out);
return 0;
}
const bool is_optional = (seg->flags & VW_PATH_SEGMENT_FLAG_OPTIONAL) != 0;
const bool is_static = (seg->flags & VW_PATH_SEGMENT_FLAG_STATIC) != 0;
if (is_static) {
const bool static_match = vw_PathSegment_match_static(seg, &src, end);
if (!is_optional && !static_match) {
vw_PathPattern_matchn_clear(out);
return 0;
}
} else {
const bool dynamic_match = vw_PathSegment_match_dynamic(seg, &src, end, out);
if (!is_optional && !dynamic_match) {
vw_PathPattern_matchn_clear(out);
return 0;
}
}
}
return len - (size_t)(end - src);
}
bool vw_PathPattern_print(const vw_PathPattern *pat, const bool show_static_bounds) {
if (!pat) {
return false;
}
for (size_t idx = 0; idx < pat->size; ++idx) {
vw_PathSegment *seg = pat->items[idx];
if (seg->flags & VW_PATH_SEGMENT_FLAG_STATIC) {
if (seg->flags & VW_PATH_SEGMENT_FLAG_OPTIONAL) {
putchar('?');
}
if (show_static_bounds) {
printf("[%s]", seg->def);
} else {
fputs(seg->def, stdout);
}
} else {
const vs_HMapEntry *type_entry = vs_HMap_find_id_unsafe(&vw_types, seg->type);
const vw_PathType *pt = NULL;
putchar('<');
if (type_entry) {
printf("%s", (const char *)type_entry->key);
pt = (const vw_PathType *)type_entry->value;
} else {
printf("type_" VS_HMAP_ID_FMTS, VS_HMAP_ID_FMT(seg->type));
pt = NULL;
}
if (seg->flags & VW_PATH_SEGMENT_FLAG_NOCONV) {
putchar('!');
}
if (seg->compiled) {
if (pt && pt->print) {
putchar('(');
if (!pt->print(seg->compiled)) {
printf("<erroneous intermediate %p>", seg->compiled);
}
putchar(')');
} else {
printf("(<arguments at %p>)", seg->compiled);
}
}
if (seg->key) {
printf(":%s", seg->key);
}
if (seg->flags & VW_PATH_SEGMENT_FLAG_OPTIONAL) {
putchar('?');
}
if (seg->always_insert) {
putchar('=');
}
if (seg->def && seg->def_len) {
printf("%s", seg->def);
}
putchar('>');
}
}
putchar('\n');
return true;
}
bool vw_PathPattern_destroy(vw_PathPattern *pat) {
if (!pat) {
return false;
}
for (size_t idx = 0; idx < pat->size; ++idx) {
vw_PathSegment *seg = pat->items[idx];
if (!(seg->flags & VW_PATH_SEGMENT_FLAG_STATIC)) {
const vs_HMapEntry *type_entry = vs_HMap_find_id(&vw_types, seg->type);
if (type_entry && type_entry->value) {
const vw_PathType *pt = (const vw_PathType *)type_entry->value;
if (pt->cleanup) {
pt->cleanup(seg->compiled);
}
}
if (seg->def_conv && seg->def_reqb) {
VS_FREE(seg->def_conv);
}
}
if (seg->key) {
VS_FREE(seg->key);
}
VS_FREE(seg->def);
VS_FREE(seg);
}
VS_FREE(pat->items);
VS_FREE(pat);
return true;
}
bool vw_PathPattern_matchn_destroy(vs_HMap *out) {
if (!out) {
return false;
}
for (size_t idx = 0; idx < out->size; ++idx) {
vw_PathMatch *m = out->occupied_ents[idx]->value;
VS_FREE(m->str);
VS_FREE(m->conv);
VS_FREE(m);
}
return vs_HMap_destroy(out);
}
bool vw_PathPattern_matchn_clear(vs_HMap *out) {
if (!out) {
return false;
}
for (size_t idx = 0; idx < out->size; ++idx) {
vw_PathMatch *m = out->occupied_ents[idx]->value;
VS_FREE(m->str);
VS_FREE(m->conv);
VS_FREE(m);
}
return vs_HMap_clear(out);
}
void vw_PathPattern_matchn_print(const vs_HMap *out) {
puts("{");
for (size_t idx = 0; idx < out->size; ++idx) {
const vs_HMapEntry *b = out->occupied_ents[idx];
const vw_PathMatch *m = b->value;
printf(" \"%s\" (%zu): %s,\n", (const char *)b->key, b->id, m->str);
}
puts("}");
}