From b79b62ade82776f359bf22f092a4456f9f1adc42 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Sun, 19 Oct 2025 18:12:08 +0200 Subject: [PATCH] Add JSON support --- 3p/json.c | 1005 +++++++++++++++++++++++++++++++++++++ 3p/json.h | 253 ++++++++++ TODO.md | 2 + amalg.py | 13 + cweb.h | 1362 ++++++++++++++++++++++++++++++++++++++++++++++++++- demo/main.c | 29 +- src/main.c | 72 ++- src/main.h | 13 +- 8 files changed, 2742 insertions(+), 7 deletions(-) create mode 100644 3p/json.c create mode 100644 3p/json.h create mode 100644 TODO.md diff --git a/3p/json.c b/3p/json.c new file mode 100644 index 0000000..b937e51 --- /dev/null +++ b/3p/json.c @@ -0,0 +1,1005 @@ +#include +#include +#include +#include +#include "json.h" + +//////////////////////////////////////////////////////////////////// +// PARSER +//////////////////////////////////////////////////////////////////// + +typedef struct { + char* src; + size_t len; + size_t cur; + JSON_Arena *arena; + JSON_Error *error; +} Context; + +static bool is_wspace(char c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +static bool is_control(char c) +{ + return c >= 0 && c < ' '; // TODO: is this correct? +} + +static void consume_wspace(Context *ctx) +{ + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; +} + +static void *alloc(JSON_Arena *arena, size_t len, size_t align) +{ + size_t pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1); + + if (arena->len - arena->cur < len + pad) + return NULL; + + arena->cur += pad; + void *ptr = arena->ptr + arena->cur; + + arena->cur += len; + arena->last = ptr; + return ptr; +} + +static bool grow_alloc(JSON_Arena *arena, void *ptr, size_t new_len) +{ + if (ptr == NULL || arena->last != ptr) + return false; + + size_t old_len = (arena->ptr + arena->cur) - arena->last; + if (new_len < old_len) + return false; + + size_t increase_len = new_len - old_len; + if (arena->len - arena->cur < increase_len) + return false; + + arena->cur += increase_len; + return true; +} + +typedef struct { + JSON_Arena *arena; + char* ptr; + size_t len; + bool err; +} GrowingBuffer; + +static GrowingBuffer gbinit(JSON_Arena *arena) +{ + return (GrowingBuffer) { arena, NULL, 0, false }; +} + +static void gbappends(GrowingBuffer *gb, char *str, size_t len) +{ + if (gb->err) + return; + + if (len == 0) + return; + + if (gb->ptr == NULL) { + gb->ptr = alloc(gb->arena, len, 1); + if (gb->ptr == NULL) { + gb->err = true; + return; + } + } else { + if (!grow_alloc(gb->arena, gb->ptr, gb->len + len)) { + gb->err = true; + return; + } + } + + memcpy(gb->ptr + gb->len, str, len); + gb->len += len; +} + +static void gbappendc(GrowingBuffer *gb, char c) +{ + gbappends(gb, &c, 1); +} + +static JSON *parse_any(Context *ctx); + +static void +consume_unescaped_string_contents(Context *ctx) +{ + while (ctx->cur < ctx->len + && ctx->src[ctx->cur] != '"' + && ctx->src[ctx->cur] != '\\' + && !is_control(ctx->src[ctx->cur])) + ctx->cur++; +} + +static char *parse_string_raw(Context *ctx, size_t *plen) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '"'); + ctx->cur++; + + GrowingBuffer gb = gbinit(ctx->arena); + + for (;;) { + + size_t off = ctx->cur; + consume_unescaped_string_contents(ctx); + + gbappends(&gb, + ctx->src + off, + ctx->cur - off + ); + + if (ctx->cur == ctx->len) + return NULL; + + char c = ctx->src[ctx->cur++]; + + if (is_control(c)) + return NULL; + + if (c == '"') + break; + + assert(c == '\\'); + + if (ctx->cur == ctx->len) + return NULL; + c = ctx->src[ctx->cur++]; + + switch (c) { + case '"': gbappendc(&gb, '"'); break; + case '\\': gbappendc(&gb, '\\'); break; + case '/': gbappendc(&gb, '/'); break; + case 'b': gbappendc(&gb, '\b'); break; + case 'f': gbappendc(&gb, '\f'); break; + case 'n': gbappendc(&gb, '\n'); break; + case 'r': gbappendc(&gb, '\r'); break; + case 't': gbappendc(&gb, '\t'); break; + + case 'u': + // TODO: not implemented yet + return NULL; + + default: + return NULL; + } + } + + if (gb.err) + return NULL; + + *plen = gb.len; + return gb.ptr; +} + +static JSON *parse_string(Context *ctx) +{ + size_t len; + char *ptr = parse_string_raw(ctx, &len); + if (ptr == NULL) + return NULL; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_STRING; + val->len = len; + val->sval = ptr; + + return val; +} + +static bool node_with_key_exists(JSON *head, char *key, size_t key_len) +{ + if (head == NULL) + return false; + + JSON *cursor = head; + while (cursor) { + + if (cursor->key_len == key_len && memcmp(cursor->key, key, key_len) == 0) + return true; + + cursor = cursor->next; + } + + return false; +} + +static JSON *parse_object(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '{'); + ctx->cur++; + + consume_wspace(ctx); + + int count = 0; + JSON *head = NULL; + JSON **tail = &head; + + if (ctx->src[ctx->cur] != '}') { + + for (;;) { + + if (ctx->cur == ctx->len || ctx->src[ctx->cur] != '"') + return NULL; + + size_t key_len; + char *key = parse_string_raw(ctx, &key_len); + if (key == NULL) + return NULL; + + if (node_with_key_exists(head, key, key_len)) + return NULL; + + consume_wspace(ctx); + + if (ctx->cur == ctx->len || ctx->src[ctx->cur] != ':') + return NULL; + ctx->cur++; + + consume_wspace(ctx); + + JSON *val = parse_any(ctx); + if (val == NULL) + return NULL; + + val->next = NULL; + val->key = key; + val->key_len = key_len; + + *tail = val; + tail = &val->next; + count++; + + consume_wspace(ctx); + + if (ctx->cur == ctx->len) + return NULL; + + if (ctx->src[ctx->cur] == '}') + break; + + if (ctx->src[ctx->cur] != ',') + return NULL; + ctx->cur++; + + consume_wspace(ctx); + } + } + + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '}'); + ctx->cur++; + + JSON *obj = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (obj == NULL) + return NULL; + obj->type = JSON_TYPE_OBJECT; + obj->len = (size_t) count; + obj->head = head; + + return obj; +} + +static JSON *parse_array(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '['); + ctx->cur++; + + consume_wspace(ctx); + + int count = 0; + JSON *head = NULL; + JSON **tail = &head; + + if (ctx->src[ctx->cur] != ']') { + + for (;;) { + + JSON *val = parse_any(ctx); + if (val == NULL) + return NULL; + + val->next = NULL; + val->key = NULL; + val->key_len = 0; + + *tail = val; + tail = &val->next; + count++; + + consume_wspace(ctx); + + if (ctx->cur == ctx->len) + return NULL; + + if (ctx->src[ctx->cur] == ']') + break; + + if (ctx->src[ctx->cur] != ',') + return NULL; + ctx->cur++; + + consume_wspace(ctx); + } + } + + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == ']'); + ctx->cur++; + + JSON *arr = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (arr == NULL) + return NULL; + arr->type = JSON_TYPE_ARRAY; + arr->len = (size_t) count; + arr->head = head; + + return arr; +} + +static JSON *parse_float(Context *ctx) +{ + bool neg = false; + if (ctx->src[ctx->cur] == '-') { + ctx->cur++; + if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) + return NULL; + neg = true; + } + + double x = 0; + do { + assert(ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); + int d = ctx->src[ctx->cur++] - '0'; + if (neg) d = -d; + x *= 10; + x += d; + } while (ctx->src[ctx->cur] != '.'); + + // Consume dot + ctx->cur++; + + if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) + return NULL; + + double s = 0.1; + do { + int d = ctx->src[ctx->cur++] - '0'; + if (neg) d = -d; + x += s * d; + s /= 10; + } while (ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_FLOAT; + val->fval = x; + + return val; +} + +static JSON *parse_int(Context *ctx) +{ + assert(ctx->cur < ctx->len && (is_digit(ctx->src[ctx->cur]) || ctx->src[ctx->cur] == '-')); + + bool neg = false; + if (ctx->src[ctx->cur] == '-') { + ctx->cur++; + if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) + return NULL; + neg = true; + } + + int64_t x = 0; + do { + int d = ctx->src[ctx->cur++] - '0'; + if (neg) { + if (x < (INT64_MIN + d) / 10) + return NULL; + d = -d; + } else { + if (x > (INT64_MAX - d) / 10) + return NULL; + } + x *= 10; + x += d; + } while (ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_INT; + val->ival = x; + + return val; +} + +static bool follows_float(Context *ctx) +{ + assert(ctx->cur < ctx->len && (is_digit(ctx->src[ctx->cur]) || ctx->src[ctx->cur] == '-')); + size_t peek = ctx->cur; + + if (peek < ctx->len && ctx->src[peek] == '-') { + peek++; + if (peek == ctx->len || !is_digit(ctx->src[peek])) + return false; + } + + do + peek++; + while (peek < ctx->len && is_digit(ctx->src[peek])); + + return peek < ctx->len && ctx->src[peek] == '.'; +} + +static JSON *parse_number(Context *ctx) +{ + if (follows_float(ctx)) + return parse_float(ctx); + return parse_int(ctx); +} + +static JSON *parse_true(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 't'); + ctx->cur++; + + if (ctx->len - ctx->cur <= 2 + || ctx->src[ctx->cur+0] != 'r' + || ctx->src[ctx->cur+1] != 'u' + || ctx->src[ctx->cur+2] != 'e') + return NULL; + ctx->cur += 3; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_BOOL; + val->bval = true; + + return val; +} + +static JSON *parse_false(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 'f'); + ctx->cur++; + + if (ctx->len - ctx->cur <= 3 + || ctx->src[ctx->cur+0] != 'a' + || ctx->src[ctx->cur+1] != 'l' + || ctx->src[ctx->cur+2] != 's' + || ctx->src[ctx->cur+3] != 'e') + return NULL; + ctx->cur += 4; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_BOOL; + val->bval = false; + + return val; +} + +static JSON *parse_null(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 'n'); + ctx->cur++; + + if (ctx->len - ctx->cur <= 2 + || ctx->src[ctx->cur+0] != 'u' + || ctx->src[ctx->cur+1] != 'l' + || ctx->src[ctx->cur+2] != 'l') + return NULL; + ctx->cur += 3; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_NULL; + + return val; +} + +static JSON *parse_any(Context *ctx) +{ + if (ctx->cur == ctx->len) + return NULL; + char c = ctx->src[ctx->cur]; + + if (c == '"') + return parse_string(ctx); + + if (c == '{') + return parse_object(ctx); + + if (c == '[') + return parse_array(ctx); + + if (is_digit(c) || c == '-') + return parse_number(ctx); + + if (c == 't') + return parse_true(ctx); + + if (c == 'f') + return parse_false(ctx); + + if (c == 'n') + return parse_null(ctx); + + return NULL; +} + +static JSON *decode(Context *ctx) +{ + consume_wspace(ctx); + + JSON *x = parse_any(ctx); + if (x == NULL) + return NULL; + x->key = NULL; + x->key_len = 0; + x->next = NULL; + + consume_wspace(ctx); + + if (ctx->cur < ctx->len) + return NULL; + + return x; +} + +JSON_Arena json_arena_init(char *ptr, size_t len) +{ + return (JSON_Arena) { NULL, ptr, len, 0 }; +} + +JSON *json_decode(char *src, int len, JSON_Arena *arena, JSON_Error *error) +{ + if (src == NULL) src = ""; + if (len < 0) len = strlen(src); + + Context ctx = { src, len, 0, arena, error }; + return decode(&ctx); +} + +//////////////////////////////////////////////////////////////////// +// UTILITIES +//////////////////////////////////////////////////////////////////// + +const char *json_type_name(JSON_Type type) +{ + switch (type) { + case JSON_TYPE_BOOL : return "bool"; + case JSON_TYPE_NULL : return "null"; + case JSON_TYPE_OBJECT: return "object"; + case JSON_TYPE_ARRAY : return "array"; + case JSON_TYPE_FLOAT : return "float"; + case JSON_TYPE_INT : return "int"; + case JSON_TYPE_STRING: return "string"; + } + return "???"; +} + +JSON_Type json_get_type(JSON *json) +{ + assert(json); + return json->type; +} + +JSON_String json_get_key(JSON *json) +{ + if (json == NULL || json->key == NULL) + return (JSON_String) { NULL, 0 }; + return (JSON_String) { json->key, json->key_len }; +} + +bool json_get_bool(JSON *json, bool fallback) +{ + if (json == NULL || json->type != JSON_TYPE_BOOL) + return fallback; + return json->bval; +} + +int64_t json_get_int(JSON *json, int64_t fallback) +{ + if (json == NULL || json->type != JSON_TYPE_INT) + return fallback; + return json->ival; +} + +double json_get_float(JSON *json, double fallback) +{ + if (json == NULL || json->type != JSON_TYPE_FLOAT) + return fallback; + return json->fval; +} + +JSON_String json_get_string(JSON *json) +{ + if (json == NULL || json->type != JSON_TYPE_STRING) + return (JSON_String) { NULL, 0 }; + return (JSON_String) { json->sval, json->len }; +} + +JSON *json_get_field(JSON *obj, JSON_String key) +{ + if (obj == NULL) + return NULL; + + if (obj->type != JSON_TYPE_OBJECT) + return NULL; + + JSON *child = obj->head; + while (child) { + if (child->key_len == (size_t) key.len && + !memcmp(child->key, key.ptr, key.len)) + return child; + child = child->next; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// PATTERN MATCHING +//////////////////////////////////////////////////////////////////// + +typedef struct { + char *src; + int len; + int cur; + int cur_arg; + JSON_MatchArgs args; + JSON_Error *err; +} MatchContext; + +static int match_any(MatchContext *ctx, JSON *json); + +static void report_match_error(MatchContext *ctx, char *fmt, ...) +{ + char *dst = ctx->err->msg; + int cap = (int) sizeof(ctx->err->msg); + + va_list args; + va_start(args, fmt); + int ret = vsnprintf(dst, cap, fmt, args); + va_end(args); + + if (ret < 0) + ret = 0; + + if (ret >= cap) + ret = cap-1; + + dst[ret] = '\0'; +} + +static int match_obj(MatchContext *ctx, JSON *json) +{ + assert(json->type == JSON_TYPE_OBJECT); + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside an object"); + return -1; + } + + // Empty object? + if (ctx->cur < ctx->len && ctx->src[ctx->cur] == '}') { + ctx->cur++; + return 0; + } + + for (;;) { + + assert(ctx->cur < ctx->len && !is_wspace(ctx->src[ctx->cur])); + + if (ctx->src[ctx->cur] != '\'') { + report_match_error(ctx, "Invalid character inside object, where key was expected"); + return -1; + } + ctx->cur++; + + char key[128]; + int key_len = 0; + + for (;;) { + + int off = ctx->cur; + + while (ctx->cur < ctx->len + && ctx->src[ctx->cur] != '\'' + && ctx->src[ctx->cur] != '\\') + ctx->cur++; + + char *substr_ptr = ctx->src + off; + int substr_len = ctx->cur - off; + + if (substr_len > 0) { + if (sizeof(key) - key_len <= (size_t) substr_len) { + report_match_error(ctx, "Key buffer limit reached (you can't have keys longer than %d bytes)", (int) sizeof(key)-1); + return -1; + } + memcpy(key + key_len, substr_ptr, substr_len); + key_len += substr_len; + } + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside a string"); + return -1; + } + + if (ctx->src[ctx->cur] == '\'') { + ctx->cur++; + break; + } + + assert(ctx->src[ctx->cur] == '\\'); + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside a string"); + return -1; + } + + if (sizeof(key) - key_len <= 1) { + report_match_error(ctx, "Key buffer limit reached (you can't have keys longer than %d bytes)", (int) sizeof(key)-1); + return -1; + } + + switch (ctx->src[ctx->cur]) { + + case '\'': key[key_len++] = '\''; break; + case '\\': key[key_len++] = '\\'; break; + + default: + report_match_error(ctx, "Invalid escape character"); + return -1; + } + } + + // Consume spaces to the next separator + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len || ctx->src[ctx->cur] != ':') { + report_match_error(ctx, "Missing ':' after key"); + return -1; + } + ctx->cur++; + + // Look for a field with the given key in the object + JSON *child = json->head; + while (child) { + if (child->key_len == (size_t) key_len && + !memcmp(child->key, key, key_len)) + break; + child = child->next; + } + + if (child == NULL) { + report_match_error(ctx, "Field '%.*s' is missing", key_len, key); + return 1; + } + + int ret = match_any(ctx, child); + if (ret < 0) + return ret; + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside an object"); + return -1; + } + + if (ctx->src[ctx->cur] == '}') { + ctx->cur++; + break; + } + + if (ctx->src[ctx->cur] != ',') { + report_match_error(ctx, "Invalid character where ',' or '}' were expected"); + return -1; + } + ctx->cur++; + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside an object"); + return -1; + } + } + + return 0; +} + +static int match_any(MatchContext *ctx, JSON *json) +{ + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended where a value was expected"); + return -1; + } + + // The * character matches anything but doesn't extract any value + if (ctx->src[ctx->cur] == '*') { + ctx->cur++; + return 0; + } + + // The ? character matches anything and writes it to output + if (ctx->src[ctx->cur] == '?') { + ctx->cur++; + + if (ctx->cur_arg == ctx->args.len) { + report_match_error(ctx, "Missing output arguments"); + return -1; + } + + JSON_MatchArg arg = ctx->args.ptr[ctx->cur_arg++]; + switch (arg.type) { + + case JSON_MATCH_ARG_TYPE_BOOL: + if (json->type != JSON_TYPE_BOOL) { + report_match_error(ctx, "Expected boolean, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.bptr = json->bval; + break; + + case JSON_MATCH_ARG_TYPE_INT: + if (json->type != JSON_TYPE_INT) { + report_match_error(ctx, "Expected integer, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.iptr = json->ival; + break; + + case JSON_MATCH_ARG_TYPE_FLOAT: + if (json->type != JSON_TYPE_FLOAT) { + report_match_error(ctx, "Expected float, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.fptr = json->fval; + break; + + case JSON_MATCH_ARG_TYPE_STRING: + if (json->type != JSON_TYPE_STRING) { + report_match_error(ctx, "Expected string, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.sptr = (JSON_String) { json->sval, json->len }; + break; + + case JSON_MATCH_ARG_TYPE_ANY: + *arg.anyptr = json; + break; + } + + return 0; + } + + if (ctx->src[ctx->cur] == '{') { + ctx->cur++; + + if (json->type != JSON_TYPE_OBJECT) { + report_match_error(ctx, "Expected object, got %s instead", json_type_name(json->type)); + return 1; + } + + return match_obj(ctx, json); + } + + char c = ctx->src[ctx->cur]; + if (c >= ' ' && c <= '~') report_match_error(ctx, "Unexpected character '%c' in pattern", c); + else report_match_error(ctx, "Unexpected byte %x in pattern", c); + + return -1; +} + +int json_match_impl(JSON *json, JSON_Error *err, char *pattern, JSON_MatchArgs args) +{ + MatchContext ctx = { + .src = pattern, + .len = strlen(pattern), + .cur = 0, + .cur_arg = 0, + .args = args, + .err = err, + }; + return match_any(&ctx, json); +} + +JSON_MatchArg json_match_arg_bool (bool *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_BOOL, .bptr=x }; } +JSON_MatchArg json_match_arg_int (int64_t *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_INT, .iptr=x }; } +JSON_MatchArg json_match_arg_float (double *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_FLOAT, .fptr=x }; } +JSON_MatchArg json_match_arg_string(JSON_String *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_STRING, .sptr=x }; } +JSON_MatchArg json_match_arg_any (JSON **x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_ANY, .anyptr=x }; } + +//////////////////////////////////////////////////////////////////// +// STRING ESCAPING +//////////////////////////////////////////////////////////////////// + +JSON_String json_escape(JSON_Arena *arena, JSON_String str) +{ + // TODO: This is just a best-effort implementation. Should handle UTF-8 precisely. + + char *src = str.ptr; + int len = str.len; + int cur = 0; + + char *dst = arena->ptr + arena->cur; + int cap = arena->len - arena->cur; + int num = 0; + + for (;;) { + + int off = cur; + + while (cur < len && (src[cur] >= ' ' && src[cur] <= '~' && src[cur] != '"' && src[cur] != '\\')) + cur++; + + char *substr_ptr = src + off; + int substr_len = cur - off; + + if (substr_len > 0) { + if (substr_len > cap - num) + return (JSON_String) { NULL, 0 }; + memcpy(dst + num, substr_ptr, substr_len); + num += substr_len; + } + + if (cur == len) + break; + + if (2 > cap - num) + return (JSON_String) { NULL, 0 }; + + cur++; + if (src[cur-1] == '"') { + dst[num++] = '\\'; + dst[num++] = '"'; + } else if (src[cur-1] == '\\') { + dst[num++] = '\\'; + dst[num++] = '\\'; + } else if (src[cur-1] == '\n') { + dst[num++] = '\\'; + dst[num++] = 'n'; + } else if (src[cur-1] == '\r') { + dst[num++] = '\\'; + dst[num++] = 'r'; + } else if (src[cur-1] == '\t') { + dst[num++] = '\\'; + dst[num++] = 't'; + } else { + return (JSON_String) { NULL, 0 }; + } + } + + arena->cur += num; + return (JSON_String) { dst, num }; +} diff --git a/3p/json.h b/3p/json.h new file mode 100644 index 0000000..62dcdeb --- /dev/null +++ b/3p/json.h @@ -0,0 +1,253 @@ +#ifndef JSON_INCLUDED +#define JSON_INCLUDED + +#include +#include +#include + +///////////////////////////////////////////////////////// +// BASICS +///////////////////////////////////////////////////////// + +// Utility string type +typedef struct { + char *ptr; + int len; +} JSON_String; + +// Translate string literal to JSON_String +#define JSON_STR(X) ((JSON_String) { (X), (int) sizeof(X)-1 }) + +typedef struct { + char msg[100]; +} JSON_Error; + +typedef struct { + char *last; + char *ptr; + size_t len; + size_t cur; +} JSON_Arena; + +JSON_Arena json_arena_init(char *ptr, size_t len); + +///////////////////////////////////////////////////////// +// DECODE +///////////////////////////////////////////////////////// + +// Type tag for the JSON struct +typedef enum { + JSON_TYPE_BOOL, + JSON_TYPE_NULL, + JSON_TYPE_OBJECT, + JSON_TYPE_ARRAY, + JSON_TYPE_FLOAT, + JSON_TYPE_INT, + JSON_TYPE_STRING, +} JSON_Type; + +// This represents a "JSON value" +// +// Although there are helper functions to access +// data from JSON values, accessing these fields +// directly is allowed. +typedef struct JSON JSON; +struct JSON { + + // If this value is contained by an object + // or array, this points to the next item + // that object or array. If NULL, this is + // the last element. + JSON *next; + + // Type encoded by this struct + JSON_Type type; + + // If this value is contained by an object, + // this pointer and length encode the key + // associated to this value in that object. + // If the value is not contained by an object, + // the key is NULL and the length is 0. + char *key; + size_t key_len; + + // Number of elements contained by this + // array. The meaning on this field depends + // on the type encoded by this struct: + // string -> number of characters + // object -> number of key-value pairs + // array -> number of items + // For any other type, this is set to 0. + size_t len; + + union { + + // Pointer to the first child value when the + // type of this value is array or object + JSON *head; + + // Floating-point value + double fval; + + // Integer value + int64_t ival; + + // String value + // + // This is not null-terminated. The length is + // stored in the "len" field. + char *sval; + + // Boolean value + bool bval; + }; +}; + +JSON *json_decode(char *src, int len, JSON_Arena *arena, JSON_Error *error); + +///////////////////////////////////////////////////////// +// UTILITIES +///////////////////////////////////////////////////////// + +// Returns a human-readable version of the type +const char *json_type_name(JSON_Type type); + +// Return the type of a JSON value. +// +// The argument can't be NULL. +JSON_Type json_get_type(JSON *json); + +// Return the key associated to a JSON value. +// +// This operation is only valid if the value is +// contained by an object JSON value. +JSON_String json_get_key(JSON *json); + +// Return the boolean value associated to this +// JSON value. +// +// If the value is NULL or not a boolean, the +// fallback value is returned. +bool json_get_bool(JSON *json, bool fallback); + +// Return the integer value associated to this +// JSON value. +// +// If the value is NULL or not an integer, the +// fallback value is returned. +int64_t json_get_int(JSON *json, int64_t fallback); + +// Return the floating point value associated to +// this JSON value. +// +// If the value is NULL or not an double, the +// fallback value is returned. +double json_get_float(JSON *json, double fallback); + +// Return the string value associated to this +// JSON value. +// +// If the value is NULL or not a string, the +// empty string is returned. +JSON_String json_get_string(JSON *json); + +// Return the children of an object with the +// specified key. +// +// If the value is NULL, not an object, or does +// not contain the key, NULL is returned. +JSON *json_get_field(JSON *obj, JSON_String key); + +// Note that there is no helper method for accessing +// array values. To iterate over an array JSON value, +// you need to follow the next pointers: +// +// JSON *array = ...; +// for (JSON *child = array->head; child; child = child->next) { +// ... +// } + +///////////////////////////////////////////////////////// +// INTERNAL +///////////////////////////////////////////////////////// + +#define __JSON_HELPER_ARG(X) \ + _Generic((X), \ + bool* : json_match_arg_bool, \ + int64_t* : json_match_arg_int, \ + double* : json_match_arg_float, \ + JSON_String*: json_match_arg_string, \ + JSON** : json_match_arg_any \ + )(X) + +// Helper macros +#define __JSON_HELPER_DISPATCH_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N +#define __JSON_HELPER_CONCAT_0(A, B) A ## B +#define __JSON_HELPER_CONCAT_1(A, B) __JSON_HELPER_CONCAT_0(A, B) +#define __JSON_HELPER_ARGS_0() (JSON_MatchArgs) { 0, (JSON_MatchArg[]) {}} +#define __JSON_HELPER_ARGS_1(a) (JSON_MatchArgs) { 1, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a) }} +#define __JSON_HELPER_ARGS_2(a, b) (JSON_MatchArgs) { 2, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b) }} +#define __JSON_HELPER_ARGS_3(a, b, c) (JSON_MatchArgs) { 3, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c) }} +#define __JSON_HELPER_ARGS_4(a, b, c, d) (JSON_MatchArgs) { 4, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d) }} +#define __JSON_HELPER_ARGS_5(a, b, c, d, e) (JSON_MatchArgs) { 5, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e) }} +#define __JSON_HELPER_ARGS_6(a, b, c, d, e, f) (JSON_MatchArgs) { 6, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e), __JSON_HELPER_ARG(f) }} +#define __JSON_HELPER_ARGS_7(a, b, c, d, e, f, g) (JSON_MatchArgs) { 7, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e), __JSON_HELPER_ARG(f), __JSON_HELPER_ARG(g) }} +#define __JSON_HELPER_ARGS_8(a, b, c, d, e, f, g, h) (JSON_MatchArgs) { 8, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e), __JSON_HELPER_ARG(f), __JSON_HELPER_ARG(g), __JSON_HELPER_ARG(h) }} +#define __JSON_COUNT_ARGS(...) __JSON_HELPER_DISPATCH_N(DUMMY, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +#define JSON_MATCH_ARGS(...) __JSON_HELPER_CONCAT_1(__JSON_HELPER_ARGS_, __JSON_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__) + +///////////////////////////////////////////////////////// +// PATTERN MATCHING +///////////////////////////////////////////////////////// + +typedef enum { + JSON_MATCH_ARG_TYPE_BOOL, + JSON_MATCH_ARG_TYPE_INT, + JSON_MATCH_ARG_TYPE_FLOAT, + JSON_MATCH_ARG_TYPE_STRING, + JSON_MATCH_ARG_TYPE_ANY, +} JSON_MatchArgType; + +typedef struct { + JSON_MatchArgType type; + union { + bool *bptr; + int64_t *iptr; + double *fptr; + JSON_String *sptr; + JSON **anyptr; + }; +} JSON_MatchArg; + +typedef struct { + int len; + JSON_MatchArg *ptr; +} JSON_MatchArgs; + +// Returns: +// 0 Pattern matched +// 1 Pattern didn't match +// -1 Invalid pattern +int json_match_impl(JSON *json, JSON_Error *err, char *pattern, JSON_MatchArgs args); + +#define json_match(json, err, pattern, ...) \ + json_match_impl((json), (err), (pattern), JSON_MATCH_ARGS(__VA_ARGS__)) + +// Don't use these directly +JSON_MatchArg json_match_arg_bool (bool *x); +JSON_MatchArg json_match_arg_int (int64_t *x); +JSON_MatchArg json_match_arg_float (double *x); +JSON_MatchArg json_match_arg_string(JSON_String *x); +JSON_MatchArg json_match_arg_any (JSON **x); + +///////////////////////////////////////////////////////// +// STRING ESCAPING +///////////////////////////////////////////////////////// + +JSON_String json_escape(JSON_Arena *arena, JSON_String str); + +///////////////////////////////////////////////////////// +// END +///////////////////////////////////////////////////////// +#endif // JSON_INCLUDED \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..637ff6d --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +session expiration +consider SameSite=Stricts \ No newline at end of file diff --git a/amalg.py b/amalg.py index 03a8c37..009f3cf 100644 --- a/amalg.py +++ b/amalg.py @@ -105,6 +105,19 @@ header.append_text("#undef MAX\n") header.append_text("#undef ASSERT\n") header.append_text("#undef SIZEOF\n") +header.append_text("#define is_digit is_digit__json\n") +header.append_text("#define alloc alloc__json\n") +header.append_text("#define grow_alloc grow_alloc__json\n") +header.append_text("#define parse_array parse_array__json\n") + +header.append_file("3p/json.h") +header.append_file("3p/json.c") + +header.append_text("#undef parse_array") +header.append_text("#undef grow_alloc\n") +header.append_text("#undef alloc\n") +header.append_text("#undef is_digit\n") + header.append_file("3p/crypt_blowfish.h") header.append_file("3p/crypt_blowfish.c") header.append_file("src/main.c") diff --git a/cweb.h b/cweb.h index 309e29a..121c2e4 100644 --- a/cweb.h +++ b/cweb.h @@ -343,13 +343,15 @@ typedef struct { void *handle; } CWEB_QueryResult; CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args); // Helper -#define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) +#define cweb_database_select(cweb, fmt, ...) \ + cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) // Returns the next row from the query result iterator. int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args); // Helper -#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) +#define cweb_next_query_row(res, ...) \ + cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) // Frees the result of a database query void cweb_free_query_result(CWEB_QueryResult *res); @@ -359,6 +361,13 @@ int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash); // Checks whether the password matches the given hash int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash); + +int cweb_json_match_impl(CWEB_Request *req, char *pattern, CWEB_VArgs args); + +#define cweb_json_match(req, pattern, ...) \ + cweb_json_match_impl((req), (pattern), CWEB_VARGS(__VA_ARGS__)) + +CWEB_String cweb_json_escape(CWEB_Request *req, CWEB_String str); #endif // CWEB_INCLUDED #ifdef CWEB_IMPLEMENTATION #define CWEB_AMALGAMATION @@ -12137,6 +12146,1283 @@ void wl_runtime_dump(WL_Runtime *rt) #undef MAX #undef ASSERT #undef SIZEOF +#define is_digit is_digit__json +#define alloc alloc__json +#define grow_alloc grow_alloc__json +#define parse_array parse_array__json + +//////////////////////////////////////////////////////////////////////////////////////// +// 3p/json.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "3p/json.h" +#ifndef JSON_INCLUDED +#define JSON_INCLUDED + +#include +#include +#include + +///////////////////////////////////////////////////////// +// BASICS +///////////////////////////////////////////////////////// + +// Utility string type +typedef struct { + char *ptr; + int len; +} JSON_String; + +// Translate string literal to JSON_String +#define JSON_STR(X) ((JSON_String) { (X), (int) sizeof(X)-1 }) + +typedef struct { + char msg[100]; +} JSON_Error; + +typedef struct { + char *last; + char *ptr; + size_t len; + size_t cur; +} JSON_Arena; + +JSON_Arena json_arena_init(char *ptr, size_t len); + +///////////////////////////////////////////////////////// +// DECODE +///////////////////////////////////////////////////////// + +// Type tag for the JSON struct +typedef enum { + JSON_TYPE_BOOL, + JSON_TYPE_NULL, + JSON_TYPE_OBJECT, + JSON_TYPE_ARRAY, + JSON_TYPE_FLOAT, + JSON_TYPE_INT, + JSON_TYPE_STRING, +} JSON_Type; + +// This represents a "JSON value" +// +// Although there are helper functions to access +// data from JSON values, accessing these fields +// directly is allowed. +typedef struct JSON JSON; +struct JSON { + + // If this value is contained by an object + // or array, this points to the next item + // that object or array. If NULL, this is + // the last element. + JSON *next; + + // Type encoded by this struct + JSON_Type type; + + // If this value is contained by an object, + // this pointer and length encode the key + // associated to this value in that object. + // If the value is not contained by an object, + // the key is NULL and the length is 0. + char *key; + size_t key_len; + + // Number of elements contained by this + // array. The meaning on this field depends + // on the type encoded by this struct: + // string -> number of characters + // object -> number of key-value pairs + // array -> number of items + // For any other type, this is set to 0. + size_t len; + + union { + + // Pointer to the first child value when the + // type of this value is array or object + JSON *head; + + // Floating-point value + double fval; + + // Integer value + int64_t ival; + + // String value + // + // This is not null-terminated. The length is + // stored in the "len" field. + char *sval; + + // Boolean value + bool bval; + }; +}; + +JSON *json_decode(char *src, int len, JSON_Arena *arena, JSON_Error *error); + +///////////////////////////////////////////////////////// +// UTILITIES +///////////////////////////////////////////////////////// + +// Returns a human-readable version of the type +const char *json_type_name(JSON_Type type); + +// Return the type of a JSON value. +// +// The argument can't be NULL. +JSON_Type json_get_type(JSON *json); + +// Return the key associated to a JSON value. +// +// This operation is only valid if the value is +// contained by an object JSON value. +JSON_String json_get_key(JSON *json); + +// Return the boolean value associated to this +// JSON value. +// +// If the value is NULL or not a boolean, the +// fallback value is returned. +bool json_get_bool(JSON *json, bool fallback); + +// Return the integer value associated to this +// JSON value. +// +// If the value is NULL or not an integer, the +// fallback value is returned. +int64_t json_get_int(JSON *json, int64_t fallback); + +// Return the floating point value associated to +// this JSON value. +// +// If the value is NULL or not an double, the +// fallback value is returned. +double json_get_float(JSON *json, double fallback); + +// Return the string value associated to this +// JSON value. +// +// If the value is NULL or not a string, the +// empty string is returned. +JSON_String json_get_string(JSON *json); + +// Return the children of an object with the +// specified key. +// +// If the value is NULL, not an object, or does +// not contain the key, NULL is returned. +JSON *json_get_field(JSON *obj, JSON_String key); + +// Note that there is no helper method for accessing +// array values. To iterate over an array JSON value, +// you need to follow the next pointers: +// +// JSON *array = ...; +// for (JSON *child = array->head; child; child = child->next) { +// ... +// } + +///////////////////////////////////////////////////////// +// INTERNAL +///////////////////////////////////////////////////////// + +#define __JSON_HELPER_ARG(X) \ + _Generic((X), \ + bool* : json_match_arg_bool, \ + int64_t* : json_match_arg_int, \ + double* : json_match_arg_float, \ + JSON_String*: json_match_arg_string, \ + JSON** : json_match_arg_any \ + )(X) + +// Helper macros +#define __JSON_HELPER_DISPATCH_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N +#define __JSON_HELPER_CONCAT_0(A, B) A ## B +#define __JSON_HELPER_CONCAT_1(A, B) __JSON_HELPER_CONCAT_0(A, B) +#define __JSON_HELPER_ARGS_0() (JSON_MatchArgs) { 0, (JSON_MatchArg[]) {}} +#define __JSON_HELPER_ARGS_1(a) (JSON_MatchArgs) { 1, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a) }} +#define __JSON_HELPER_ARGS_2(a, b) (JSON_MatchArgs) { 2, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b) }} +#define __JSON_HELPER_ARGS_3(a, b, c) (JSON_MatchArgs) { 3, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c) }} +#define __JSON_HELPER_ARGS_4(a, b, c, d) (JSON_MatchArgs) { 4, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d) }} +#define __JSON_HELPER_ARGS_5(a, b, c, d, e) (JSON_MatchArgs) { 5, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e) }} +#define __JSON_HELPER_ARGS_6(a, b, c, d, e, f) (JSON_MatchArgs) { 6, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e), __JSON_HELPER_ARG(f) }} +#define __JSON_HELPER_ARGS_7(a, b, c, d, e, f, g) (JSON_MatchArgs) { 7, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e), __JSON_HELPER_ARG(f), __JSON_HELPER_ARG(g) }} +#define __JSON_HELPER_ARGS_8(a, b, c, d, e, f, g, h) (JSON_MatchArgs) { 8, (JSON_MatchArg[]) { __JSON_HELPER_ARG(a), __JSON_HELPER_ARG(b), __JSON_HELPER_ARG(c), __JSON_HELPER_ARG(d), __JSON_HELPER_ARG(e), __JSON_HELPER_ARG(f), __JSON_HELPER_ARG(g), __JSON_HELPER_ARG(h) }} +#define __JSON_COUNT_ARGS(...) __JSON_HELPER_DISPATCH_N(DUMMY, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +#define JSON_MATCH_ARGS(...) __JSON_HELPER_CONCAT_1(__JSON_HELPER_ARGS_, __JSON_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__) + +///////////////////////////////////////////////////////// +// PATTERN MATCHING +///////////////////////////////////////////////////////// + +typedef enum { + JSON_MATCH_ARG_TYPE_BOOL, + JSON_MATCH_ARG_TYPE_INT, + JSON_MATCH_ARG_TYPE_FLOAT, + JSON_MATCH_ARG_TYPE_STRING, + JSON_MATCH_ARG_TYPE_ANY, +} JSON_MatchArgType; + +typedef struct { + JSON_MatchArgType type; + union { + bool *bptr; + int64_t *iptr; + double *fptr; + JSON_String *sptr; + JSON **anyptr; + }; +} JSON_MatchArg; + +typedef struct { + int len; + JSON_MatchArg *ptr; +} JSON_MatchArgs; + +// Returns: +// 0 Pattern matched +// 1 Pattern didn't match +// -1 Invalid pattern +int json_match_impl(JSON *json, JSON_Error *err, char *pattern, JSON_MatchArgs args); + +#define json_match(json, err, pattern, ...) \ + json_match_impl((json), (err), (pattern), JSON_MATCH_ARGS(__VA_ARGS__)) + +// Don't use these directly +JSON_MatchArg json_match_arg_bool (bool *x); +JSON_MatchArg json_match_arg_int (int64_t *x); +JSON_MatchArg json_match_arg_float (double *x); +JSON_MatchArg json_match_arg_string(JSON_String *x); +JSON_MatchArg json_match_arg_any (JSON **x); + +///////////////////////////////////////////////////////// +// STRING ESCAPING +///////////////////////////////////////////////////////// + +JSON_String json_escape(JSON_Arena *arena, JSON_String str); + +///////////////////////////////////////////////////////// +// END +///////////////////////////////////////////////////////// +#endif // JSON_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// 3p/json.c +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "3p/json.c" +#include +#include +#include +#include +#include "json.h" + +//////////////////////////////////////////////////////////////////// +// PARSER +//////////////////////////////////////////////////////////////////// + +typedef struct { + char* src; + size_t len; + size_t cur; + JSON_Arena *arena; + JSON_Error *error; +} Context; + +static bool is_wspace(char c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +static bool is_control(char c) +{ + return c >= 0 && c < ' '; // TODO: is this correct? +} + +static void consume_wspace(Context *ctx) +{ + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; +} + +static void *alloc(JSON_Arena *arena, size_t len, size_t align) +{ + size_t pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1); + + if (arena->len - arena->cur < len + pad) + return NULL; + + arena->cur += pad; + void *ptr = arena->ptr + arena->cur; + + arena->cur += len; + arena->last = ptr; + return ptr; +} + +static bool grow_alloc(JSON_Arena *arena, void *ptr, size_t new_len) +{ + if (ptr == NULL || arena->last != ptr) + return false; + + size_t old_len = (arena->ptr + arena->cur) - arena->last; + if (new_len < old_len) + return false; + + size_t increase_len = new_len - old_len; + if (arena->len - arena->cur < increase_len) + return false; + + arena->cur += increase_len; + return true; +} + +typedef struct { + JSON_Arena *arena; + char* ptr; + size_t len; + bool err; +} GrowingBuffer; + +static GrowingBuffer gbinit(JSON_Arena *arena) +{ + return (GrowingBuffer) { arena, NULL, 0, false }; +} + +static void gbappends(GrowingBuffer *gb, char *str, size_t len) +{ + if (gb->err) + return; + + if (len == 0) + return; + + if (gb->ptr == NULL) { + gb->ptr = alloc(gb->arena, len, 1); + if (gb->ptr == NULL) { + gb->err = true; + return; + } + } else { + if (!grow_alloc(gb->arena, gb->ptr, gb->len + len)) { + gb->err = true; + return; + } + } + + memcpy(gb->ptr + gb->len, str, len); + gb->len += len; +} + +static void gbappendc(GrowingBuffer *gb, char c) +{ + gbappends(gb, &c, 1); +} + +static JSON *parse_any(Context *ctx); + +static void +consume_unescaped_string_contents(Context *ctx) +{ + while (ctx->cur < ctx->len + && ctx->src[ctx->cur] != '"' + && ctx->src[ctx->cur] != '\\' + && !is_control(ctx->src[ctx->cur])) + ctx->cur++; +} + +static char *parse_string_raw(Context *ctx, size_t *plen) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '"'); + ctx->cur++; + + GrowingBuffer gb = gbinit(ctx->arena); + + for (;;) { + + size_t off = ctx->cur; + consume_unescaped_string_contents(ctx); + + gbappends(&gb, + ctx->src + off, + ctx->cur - off + ); + + if (ctx->cur == ctx->len) + return NULL; + + char c = ctx->src[ctx->cur++]; + + if (is_control(c)) + return NULL; + + if (c == '"') + break; + + assert(c == '\\'); + + if (ctx->cur == ctx->len) + return NULL; + c = ctx->src[ctx->cur++]; + + switch (c) { + case '"': gbappendc(&gb, '"'); break; + case '\\': gbappendc(&gb, '\\'); break; + case '/': gbappendc(&gb, '/'); break; + case 'b': gbappendc(&gb, '\b'); break; + case 'f': gbappendc(&gb, '\f'); break; + case 'n': gbappendc(&gb, '\n'); break; + case 'r': gbappendc(&gb, '\r'); break; + case 't': gbappendc(&gb, '\t'); break; + + case 'u': + // TODO: not implemented yet + return NULL; + + default: + return NULL; + } + } + + if (gb.err) + return NULL; + + *plen = gb.len; + return gb.ptr; +} + +static JSON *parse_string(Context *ctx) +{ + size_t len; + char *ptr = parse_string_raw(ctx, &len); + if (ptr == NULL) + return NULL; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_STRING; + val->len = len; + val->sval = ptr; + + return val; +} + +static bool node_with_key_exists(JSON *head, char *key, size_t key_len) +{ + if (head == NULL) + return false; + + JSON *cursor = head; + while (cursor) { + + if (cursor->key_len == key_len && memcmp(cursor->key, key, key_len) == 0) + return true; + + cursor = cursor->next; + } + + return false; +} + +static JSON *parse_object(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '{'); + ctx->cur++; + + consume_wspace(ctx); + + int count = 0; + JSON *head = NULL; + JSON **tail = &head; + + if (ctx->src[ctx->cur] != '}') { + + for (;;) { + + if (ctx->cur == ctx->len || ctx->src[ctx->cur] != '"') + return NULL; + + size_t key_len; + char *key = parse_string_raw(ctx, &key_len); + if (key == NULL) + return NULL; + + if (node_with_key_exists(head, key, key_len)) + return NULL; + + consume_wspace(ctx); + + if (ctx->cur == ctx->len || ctx->src[ctx->cur] != ':') + return NULL; + ctx->cur++; + + consume_wspace(ctx); + + JSON *val = parse_any(ctx); + if (val == NULL) + return NULL; + + val->next = NULL; + val->key = key; + val->key_len = key_len; + + *tail = val; + tail = &val->next; + count++; + + consume_wspace(ctx); + + if (ctx->cur == ctx->len) + return NULL; + + if (ctx->src[ctx->cur] == '}') + break; + + if (ctx->src[ctx->cur] != ',') + return NULL; + ctx->cur++; + + consume_wspace(ctx); + } + } + + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '}'); + ctx->cur++; + + JSON *obj = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (obj == NULL) + return NULL; + obj->type = JSON_TYPE_OBJECT; + obj->len = (size_t) count; + obj->head = head; + + return obj; +} + +static JSON *parse_array(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '['); + ctx->cur++; + + consume_wspace(ctx); + + int count = 0; + JSON *head = NULL; + JSON **tail = &head; + + if (ctx->src[ctx->cur] != ']') { + + for (;;) { + + JSON *val = parse_any(ctx); + if (val == NULL) + return NULL; + + val->next = NULL; + val->key = NULL; + val->key_len = 0; + + *tail = val; + tail = &val->next; + count++; + + consume_wspace(ctx); + + if (ctx->cur == ctx->len) + return NULL; + + if (ctx->src[ctx->cur] == ']') + break; + + if (ctx->src[ctx->cur] != ',') + return NULL; + ctx->cur++; + + consume_wspace(ctx); + } + } + + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == ']'); + ctx->cur++; + + JSON *arr = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (arr == NULL) + return NULL; + arr->type = JSON_TYPE_ARRAY; + arr->len = (size_t) count; + arr->head = head; + + return arr; +} + +static JSON *parse_float(Context *ctx) +{ + bool neg = false; + if (ctx->src[ctx->cur] == '-') { + ctx->cur++; + if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) + return NULL; + neg = true; + } + + double x = 0; + do { + assert(ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); + int d = ctx->src[ctx->cur++] - '0'; + if (neg) d = -d; + x *= 10; + x += d; + } while (ctx->src[ctx->cur] != '.'); + + // Consume dot + ctx->cur++; + + if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) + return NULL; + + double s = 0.1; + do { + int d = ctx->src[ctx->cur++] - '0'; + if (neg) d = -d; + x += s * d; + s /= 10; + } while (ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_FLOAT; + val->fval = x; + + return val; +} + +static JSON *parse_int(Context *ctx) +{ + assert(ctx->cur < ctx->len && (is_digit(ctx->src[ctx->cur]) || ctx->src[ctx->cur] == '-')); + + bool neg = false; + if (ctx->src[ctx->cur] == '-') { + ctx->cur++; + if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) + return NULL; + neg = true; + } + + int64_t x = 0; + do { + int d = ctx->src[ctx->cur++] - '0'; + if (neg) { + if (x < (INT64_MIN + d) / 10) + return NULL; + d = -d; + } else { + if (x > (INT64_MAX - d) / 10) + return NULL; + } + x *= 10; + x += d; + } while (ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_INT; + val->ival = x; + + return val; +} + +static bool follows_float(Context *ctx) +{ + assert(ctx->cur < ctx->len && (is_digit(ctx->src[ctx->cur]) || ctx->src[ctx->cur] == '-')); + size_t peek = ctx->cur; + + if (peek < ctx->len && ctx->src[peek] == '-') { + peek++; + if (peek == ctx->len || !is_digit(ctx->src[peek])) + return false; + } + + do + peek++; + while (peek < ctx->len && is_digit(ctx->src[peek])); + + return peek < ctx->len && ctx->src[peek] == '.'; +} + +static JSON *parse_number(Context *ctx) +{ + if (follows_float(ctx)) + return parse_float(ctx); + return parse_int(ctx); +} + +static JSON *parse_true(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 't'); + ctx->cur++; + + if (ctx->len - ctx->cur <= 2 + || ctx->src[ctx->cur+0] != 'r' + || ctx->src[ctx->cur+1] != 'u' + || ctx->src[ctx->cur+2] != 'e') + return NULL; + ctx->cur += 3; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_BOOL; + val->bval = true; + + return val; +} + +static JSON *parse_false(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 'f'); + ctx->cur++; + + if (ctx->len - ctx->cur <= 3 + || ctx->src[ctx->cur+0] != 'a' + || ctx->src[ctx->cur+1] != 'l' + || ctx->src[ctx->cur+2] != 's' + || ctx->src[ctx->cur+3] != 'e') + return NULL; + ctx->cur += 4; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_BOOL; + val->bval = false; + + return val; +} + +static JSON *parse_null(Context *ctx) +{ + assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 'n'); + ctx->cur++; + + if (ctx->len - ctx->cur <= 2 + || ctx->src[ctx->cur+0] != 'u' + || ctx->src[ctx->cur+1] != 'l' + || ctx->src[ctx->cur+2] != 'l') + return NULL; + ctx->cur += 3; + + JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); + if (val == NULL) + return NULL; + val->type = JSON_TYPE_NULL; + + return val; +} + +static JSON *parse_any(Context *ctx) +{ + if (ctx->cur == ctx->len) + return NULL; + char c = ctx->src[ctx->cur]; + + if (c == '"') + return parse_string(ctx); + + if (c == '{') + return parse_object(ctx); + + if (c == '[') + return parse_array(ctx); + + if (is_digit(c) || c == '-') + return parse_number(ctx); + + if (c == 't') + return parse_true(ctx); + + if (c == 'f') + return parse_false(ctx); + + if (c == 'n') + return parse_null(ctx); + + return NULL; +} + +static JSON *decode(Context *ctx) +{ + consume_wspace(ctx); + + JSON *x = parse_any(ctx); + if (x == NULL) + return NULL; + x->key = NULL; + x->key_len = 0; + x->next = NULL; + + consume_wspace(ctx); + + if (ctx->cur < ctx->len) + return NULL; + + return x; +} + +JSON_Arena json_arena_init(char *ptr, size_t len) +{ + return (JSON_Arena) { NULL, ptr, len, 0 }; +} + +JSON *json_decode(char *src, int len, JSON_Arena *arena, JSON_Error *error) +{ + if (src == NULL) src = ""; + if (len < 0) len = strlen(src); + + Context ctx = { src, len, 0, arena, error }; + return decode(&ctx); +} + +//////////////////////////////////////////////////////////////////// +// UTILITIES +//////////////////////////////////////////////////////////////////// + +const char *json_type_name(JSON_Type type) +{ + switch (type) { + case JSON_TYPE_BOOL : return "bool"; + case JSON_TYPE_NULL : return "null"; + case JSON_TYPE_OBJECT: return "object"; + case JSON_TYPE_ARRAY : return "array"; + case JSON_TYPE_FLOAT : return "float"; + case JSON_TYPE_INT : return "int"; + case JSON_TYPE_STRING: return "string"; + } + return "???"; +} + +JSON_Type json_get_type(JSON *json) +{ + assert(json); + return json->type; +} + +JSON_String json_get_key(JSON *json) +{ + if (json == NULL || json->key == NULL) + return (JSON_String) { NULL, 0 }; + return (JSON_String) { json->key, json->key_len }; +} + +bool json_get_bool(JSON *json, bool fallback) +{ + if (json == NULL || json->type != JSON_TYPE_BOOL) + return fallback; + return json->bval; +} + +int64_t json_get_int(JSON *json, int64_t fallback) +{ + if (json == NULL || json->type != JSON_TYPE_INT) + return fallback; + return json->ival; +} + +double json_get_float(JSON *json, double fallback) +{ + if (json == NULL || json->type != JSON_TYPE_FLOAT) + return fallback; + return json->fval; +} + +JSON_String json_get_string(JSON *json) +{ + if (json == NULL || json->type != JSON_TYPE_STRING) + return (JSON_String) { NULL, 0 }; + return (JSON_String) { json->sval, json->len }; +} + +JSON *json_get_field(JSON *obj, JSON_String key) +{ + if (obj == NULL) + return NULL; + + if (obj->type != JSON_TYPE_OBJECT) + return NULL; + + JSON *child = obj->head; + while (child) { + if (child->key_len == (size_t) key.len && + !memcmp(child->key, key.ptr, key.len)) + return child; + child = child->next; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// PATTERN MATCHING +//////////////////////////////////////////////////////////////////// + +typedef struct { + char *src; + int len; + int cur; + int cur_arg; + JSON_MatchArgs args; + JSON_Error *err; +} MatchContext; + +static int match_any(MatchContext *ctx, JSON *json); + +static void report_match_error(MatchContext *ctx, char *fmt, ...) +{ + char *dst = ctx->err->msg; + int cap = (int) sizeof(ctx->err->msg); + + va_list args; + va_start(args, fmt); + int ret = vsnprintf(dst, cap, fmt, args); + va_end(args); + + if (ret < 0) + ret = 0; + + if (ret >= cap) + ret = cap-1; + + dst[ret] = '\0'; +} + +static int match_obj(MatchContext *ctx, JSON *json) +{ + assert(json->type == JSON_TYPE_OBJECT); + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside an object"); + return -1; + } + + // Empty object? + if (ctx->cur < ctx->len && ctx->src[ctx->cur] == '}') { + ctx->cur++; + return 0; + } + + for (;;) { + + assert(ctx->cur < ctx->len && !is_wspace(ctx->src[ctx->cur])); + + if (ctx->src[ctx->cur] != '\'') { + report_match_error(ctx, "Invalid character inside object, where key was expected"); + return -1; + } + ctx->cur++; + + char key[128]; + int key_len = 0; + + for (;;) { + + int off = ctx->cur; + + while (ctx->cur < ctx->len + && ctx->src[ctx->cur] != '\'' + && ctx->src[ctx->cur] != '\\') + ctx->cur++; + + char *substr_ptr = ctx->src + off; + int substr_len = ctx->cur - off; + + if (substr_len > 0) { + if (sizeof(key) - key_len <= (size_t) substr_len) { + report_match_error(ctx, "Key buffer limit reached (you can't have keys longer than %d bytes)", (int) sizeof(key)-1); + return -1; + } + memcpy(key + key_len, substr_ptr, substr_len); + key_len += substr_len; + } + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside a string"); + return -1; + } + + if (ctx->src[ctx->cur] == '\'') { + ctx->cur++; + break; + } + + assert(ctx->src[ctx->cur] == '\\'); + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside a string"); + return -1; + } + + if (sizeof(key) - key_len <= 1) { + report_match_error(ctx, "Key buffer limit reached (you can't have keys longer than %d bytes)", (int) sizeof(key)-1); + return -1; + } + + switch (ctx->src[ctx->cur]) { + + case '\'': key[key_len++] = '\''; break; + case '\\': key[key_len++] = '\\'; break; + + default: + report_match_error(ctx, "Invalid escape character"); + return -1; + } + } + + // Consume spaces to the next separator + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len || ctx->src[ctx->cur] != ':') { + report_match_error(ctx, "Missing ':' after key"); + return -1; + } + ctx->cur++; + + // Look for a field with the given key in the object + JSON *child = json->head; + while (child) { + if (child->key_len == (size_t) key_len && + !memcmp(child->key, key, key_len)) + break; + child = child->next; + } + + if (child == NULL) { + report_match_error(ctx, "Field '%.*s' is missing", key_len, key); + return 1; + } + + int ret = match_any(ctx, child); + if (ret < 0) + return ret; + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside an object"); + return -1; + } + + if (ctx->src[ctx->cur] == '}') { + ctx->cur++; + break; + } + + if (ctx->src[ctx->cur] != ',') { + report_match_error(ctx, "Invalid character where ',' or '}' were expected"); + return -1; + } + ctx->cur++; + + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended inside an object"); + return -1; + } + } + + return 0; +} + +static int match_any(MatchContext *ctx, JSON *json) +{ + while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) + ctx->cur++; + + if (ctx->cur == ctx->len) { + report_match_error(ctx, "Pattern string ended where a value was expected"); + return -1; + } + + // The * character matches anything but doesn't extract any value + if (ctx->src[ctx->cur] == '*') { + ctx->cur++; + return 0; + } + + // The ? character matches anything and writes it to output + if (ctx->src[ctx->cur] == '?') { + ctx->cur++; + + if (ctx->cur_arg == ctx->args.len) { + report_match_error(ctx, "Missing output arguments"); + return -1; + } + + JSON_MatchArg arg = ctx->args.ptr[ctx->cur_arg++]; + switch (arg.type) { + + case JSON_MATCH_ARG_TYPE_BOOL: + if (json->type != JSON_TYPE_BOOL) { + report_match_error(ctx, "Expected boolean, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.bptr = json->bval; + break; + + case JSON_MATCH_ARG_TYPE_INT: + if (json->type != JSON_TYPE_INT) { + report_match_error(ctx, "Expected integer, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.iptr = json->ival; + break; + + case JSON_MATCH_ARG_TYPE_FLOAT: + if (json->type != JSON_TYPE_FLOAT) { + report_match_error(ctx, "Expected float, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.fptr = json->fval; + break; + + case JSON_MATCH_ARG_TYPE_STRING: + if (json->type != JSON_TYPE_STRING) { + report_match_error(ctx, "Expected string, got %s instead", json_type_name(json->type)); + return 1; + } + *arg.sptr = (JSON_String) { json->sval, json->len }; + break; + + case JSON_MATCH_ARG_TYPE_ANY: + *arg.anyptr = json; + break; + } + + return 0; + } + + if (ctx->src[ctx->cur] == '{') { + ctx->cur++; + + if (json->type != JSON_TYPE_OBJECT) { + report_match_error(ctx, "Expected object, got %s instead", json_type_name(json->type)); + return 1; + } + + return match_obj(ctx, json); + } + + char c = ctx->src[ctx->cur]; + if (c >= ' ' && c <= '~') report_match_error(ctx, "Unexpected character '%c' in pattern", c); + else report_match_error(ctx, "Unexpected byte %x in pattern", c); + + return -1; +} + +int json_match_impl(JSON *json, JSON_Error *err, char *pattern, JSON_MatchArgs args) +{ + MatchContext ctx = { + .src = pattern, + .len = strlen(pattern), + .cur = 0, + .cur_arg = 0, + .args = args, + .err = err, + }; + return match_any(&ctx, json); +} + +JSON_MatchArg json_match_arg_bool (bool *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_BOOL, .bptr=x }; } +JSON_MatchArg json_match_arg_int (int64_t *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_INT, .iptr=x }; } +JSON_MatchArg json_match_arg_float (double *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_FLOAT, .fptr=x }; } +JSON_MatchArg json_match_arg_string(JSON_String *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_STRING, .sptr=x }; } +JSON_MatchArg json_match_arg_any (JSON **x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_ANY, .anyptr=x }; } + +//////////////////////////////////////////////////////////////////// +// STRING ESCAPING +//////////////////////////////////////////////////////////////////// + +JSON_String json_escape(JSON_Arena *arena, JSON_String str) +{ + // TODO: This is just a best-effort implementation. Should handle UTF-8 precisely. + + char *src = str.ptr; + int len = str.len; + int cur = 0; + + char *dst = arena->ptr + arena->cur; + int cap = arena->len - arena->cur; + int num = 0; + + for (;;) { + + int off = cur; + + while (cur < len && (src[cur] >= ' ' && src[cur] <= '~' && src[cur] != '"' && src[cur] != '\\')) + cur++; + + char *substr_ptr = src + off; + int substr_len = cur - off; + + if (substr_len > 0) { + if (substr_len > cap - num) + return (JSON_String) { NULL, 0 }; + memcpy(dst + num, substr_ptr, substr_len); + num += substr_len; + } + + if (cur == len) + break; + + if (2 > cap - num) + return (JSON_String) { NULL, 0 }; + + cur++; + if (src[cur-1] == '"') { + dst[num++] = '\\'; + dst[num++] = '"'; + } else if (src[cur-1] == '\\') { + dst[num++] = '\\'; + dst[num++] = '\\'; + } else if (src[cur-1] == '\n') { + dst[num++] = '\\'; + dst[num++] = 'n'; + } else if (src[cur-1] == '\r') { + dst[num++] = '\\'; + dst[num++] = 'r'; + } else if (src[cur-1] == '\t') { + dst[num++] = '\\'; + dst[num++] = 't'; + } else { + return (JSON_String) { NULL, 0 }; + } + } + + arena->cur += num; + return (JSON_String) { dst, num }; +} +#undef parse_array#undef grow_alloc +#undef alloc +#undef is_digit //////////////////////////////////////////////////////////////////////////////////////// // 3p/crypt_blowfish.h @@ -13115,6 +14401,7 @@ char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, #include "wl.h" #endif #include "chttp.h" +#include "json.h" #include "main.h" #endif @@ -15120,7 +16407,7 @@ static char *read_file(char *path, int *len) static int symbol_table_from_current_process(SymbolTable *st) { uint64_t base_addr = query_base_addr(); - if (base_addr == -1) + if (base_addr == (uint64_t) -1) return -1; st->base_addr = base_addr; @@ -15237,6 +16524,8 @@ static char* crash_logger_signal_stack; static void crash_handler(int sig, siginfo_t *info, void *ucontext) { + (void) info; + if (crash_logger_symbol_init) { // Buffer for evaluating format strings @@ -15375,4 +16664,71 @@ static void crash_logger_free(void) } #endif +//////////////////////////////////////////////////////////////// +// JSON +//////////////////////////////////////////////////////////////// + +#define MATCH_ARG_LIMIT 16 + +int cweb_json_match_impl(CWEB_Request *req, char *pattern, CWEB_VArgs args) +{ + HTTP_String body = req->req->body; + + JSON_Error error; + JSON_Arena arena = json_arena_init( + req->arena.ptr + req->arena.cur, + req->arena.len - req->arena.cur + ); + JSON *json = json_decode(body.ptr, body.len, &arena, &error); + if (json == NULL) { +// printf("Parsing failed: %s\n", error.msg); + return 1; + } + req->arena.cur += arena.cur; + + if (MATCH_ARG_LIMIT < args.len) + return -1; + + JSON_MatchArg match_args[MATCH_ARG_LIMIT]; + JSON_String string_proxies[MATCH_ARG_LIMIT]; + + for (int i = 0; i < args.len; i++) { + CWEB_VArg arg = args.ptr[i]; + switch (arg.type) { + case CWEB_VARG_TYPE_PB : match_args[i] = json_match_arg_bool(arg.pb); break; + case CWEB_VARG_TYPE_PLL : match_args[i] = json_match_arg_int((int64_t*) arg.pll); break; + case CWEB_VARG_TYPE_PD : match_args[i] = json_match_arg_float(arg.pd); break; + case CWEB_VARG_TYPE_PSTR: match_args[i] = json_match_arg_string(&string_proxies[i]); break; + default: return -1; + } + } + + int ret = json_match_impl(json, &error, pattern, (JSON_MatchArgs) { args.len, match_args }); + + if (ret == 0) + for (int i = 0; i < args.len; i++) { + CWEB_VArg arg = args.ptr[i]; + if (arg.type == CWEB_VARG_TYPE_PSTR) + *arg.pstr = (CWEB_String) { + string_proxies[i].ptr, + string_proxies[i].len, + }; + } + + return ret; +} + +CWEB_String cweb_json_escape(CWEB_Request *req, CWEB_String str) +{ + JSON_Arena arena = json_arena_init( + req->arena.ptr + req->arena.cur, + req->arena.len - req->arena.cur + ); + + JSON_String tmp = json_escape(&arena, (JSON_String) { str.ptr, str.len }); + + req->arena.cur += req->arena.cur; + + return (CWEB_String) { tmp.ptr, tmp.len }; +} #endif // CWEB_IMPLEMENTATION diff --git a/demo/main.c b/demo/main.c index d61aa1d..290f2ac 100644 --- a/demo/main.c +++ b/demo/main.c @@ -336,8 +336,15 @@ int main(void) return -1; { + int pid; +#ifdef _WIN32 + pid = GetCurrentProcessId(); +#else + pid = getpid(); +#endif + char buf[128]; - int len = snprintf(buf, sizeof(buf), "crash_%d.txt", getpid()); + int len = snprintf(buf, sizeof(buf), "crash_%d.txt", pid); cweb_enable_crash_logger((CWEB_String) { buf, len }); } @@ -378,6 +385,26 @@ int main(void) CWEB_Request *req = cweb_wait(cweb); if (req == NULL) break; + if (cweb_match_endpoint(req, CWEB_STR("/json_demo"))) { + + CWEB_String username; + CWEB_String password; + if (cweb_json_match(req, "{ 'username': ?, 'password': ? }", &username, &password)) { + cweb_respond_basic(req, 500, CWEB_STR("error")); + continue; + } + + printf("username=[%.*s]\n", username.len, username.ptr); + printf("password=[%.*s]\n", password.len, password.ptr); + + CWEB_String res = cweb_format(req, + "\\{\"message\": \"Your name is {}\"}", + cweb_json_escape(req, username) + ); + cweb_respond_basic(req, 200, res); + continue; + } + if (0) {} else if (cweb_match_endpoint(req, CWEB_STR("/api/login"))) endpoint_api_login(cweb, req); else if (cweb_match_endpoint(req, CWEB_STR("/api/signup"))) endpoint_api_signup(cweb, req); diff --git a/src/main.c b/src/main.c index ee455c0..51dc00d 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,7 @@ #include "wl.h" #endif #include "chttp.h" +#include "json.h" #include "main.h" #endif @@ -2027,7 +2028,7 @@ static char *read_file(char *path, int *len) static int symbol_table_from_current_process(SymbolTable *st) { uint64_t base_addr = query_base_addr(); - if (base_addr == -1) + if (base_addr == (uint64_t) -1) return -1; st->base_addr = base_addr; @@ -2144,6 +2145,8 @@ static char* crash_logger_signal_stack; static void crash_handler(int sig, siginfo_t *info, void *ucontext) { + (void) info; + if (crash_logger_symbol_init) { // Buffer for evaluating format strings @@ -2282,3 +2285,70 @@ static void crash_logger_free(void) } #endif +//////////////////////////////////////////////////////////////// +// JSON +//////////////////////////////////////////////////////////////// + +#define MATCH_ARG_LIMIT 16 + +int cweb_json_match_impl(CWEB_Request *req, char *pattern, CWEB_VArgs args) +{ + HTTP_String body = req->req->body; + + JSON_Error error; + JSON_Arena arena = json_arena_init( + req->arena.ptr + req->arena.cur, + req->arena.len - req->arena.cur + ); + JSON *json = json_decode(body.ptr, body.len, &arena, &error); + if (json == NULL) { +// printf("Parsing failed: %s\n", error.msg); + return 1; + } + req->arena.cur += arena.cur; + + if (MATCH_ARG_LIMIT < args.len) + return -1; + + JSON_MatchArg match_args[MATCH_ARG_LIMIT]; + JSON_String string_proxies[MATCH_ARG_LIMIT]; + + for (int i = 0; i < args.len; i++) { + CWEB_VArg arg = args.ptr[i]; + switch (arg.type) { + case CWEB_VARG_TYPE_PB : match_args[i] = json_match_arg_bool(arg.pb); break; + case CWEB_VARG_TYPE_PLL : match_args[i] = json_match_arg_int((int64_t*) arg.pll); break; + case CWEB_VARG_TYPE_PD : match_args[i] = json_match_arg_float(arg.pd); break; + case CWEB_VARG_TYPE_PSTR: match_args[i] = json_match_arg_string(&string_proxies[i]); break; + default: return -1; + } + } + + int ret = json_match_impl(json, &error, pattern, (JSON_MatchArgs) { args.len, match_args }); + + if (ret == 0) + for (int i = 0; i < args.len; i++) { + CWEB_VArg arg = args.ptr[i]; + if (arg.type == CWEB_VARG_TYPE_PSTR) + *arg.pstr = (CWEB_String) { + string_proxies[i].ptr, + string_proxies[i].len, + }; + } + + return ret; +} + +CWEB_String cweb_json_escape(CWEB_Request *req, CWEB_String str) +{ + JSON_Arena arena = json_arena_init( + req->arena.ptr + req->arena.cur, + req->arena.len - req->arena.cur + ); + + JSON_String tmp = json_escape(&arena, (JSON_String) { str.ptr, str.len }); + + req->arena.cur += req->arena.cur; + + return (CWEB_String) { tmp.ptr, tmp.len }; +} \ No newline at end of file diff --git a/src/main.h b/src/main.h index d2dc944..3fbbe18 100644 --- a/src/main.h +++ b/src/main.h @@ -314,13 +314,15 @@ typedef struct { void *handle; } CWEB_QueryResult; CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args); // Helper -#define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) +#define cweb_database_select(cweb, fmt, ...) \ + cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) // Returns the next row from the query result iterator. int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args); // Helper -#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) +#define cweb_next_query_row(res, ...) \ + cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) // Frees the result of a database query void cweb_free_query_result(CWEB_QueryResult *res); @@ -330,3 +332,10 @@ int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash); // Checks whether the password matches the given hash int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash); + +int cweb_json_match_impl(CWEB_Request *req, char *pattern, CWEB_VArgs args); + +#define cweb_json_match(req, pattern, ...) \ + cweb_json_match_impl((req), (pattern), CWEB_VARGS(__VA_ARGS__)) + +CWEB_String cweb_json_escape(CWEB_Request *req, CWEB_String str);