Add JSON support

This commit is contained in:
2025-10-19 18:12:08 +02:00
parent 06294c78ca
commit b79b62ade8
8 changed files with 2742 additions and 7 deletions
+1005
View File
File diff suppressed because it is too large Load Diff
+253
View File
@@ -0,0 +1,253 @@
#ifndef JSON_INCLUDED
#define JSON_INCLUDED
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
/////////////////////////////////////////////////////////
// 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
+2
View File
@@ -0,0 +1,2 @@
session expiration
consider SameSite=Stricts
+13
View File
@@ -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")
+1359 -3
View File
File diff suppressed because it is too large Load Diff
+28 -1
View File
@@ -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);
+71 -1
View File
@@ -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 };
}
+11 -2
View File
@@ -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);