1358 lines
39 KiB
C
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("}");
|
|
}
|