Add CSRF tokens
This commit is contained in:
+45
-45
@@ -1,45 +1,45 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <crypt_blowfish.h>
|
||||
#include "bcrypt.h"
|
||||
#include "random.h"
|
||||
|
||||
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash)
|
||||
{
|
||||
char passzt[128];
|
||||
if (passlen >= (int) sizeof(passzt))
|
||||
return -1;
|
||||
memcpy(passzt, pass, passlen);
|
||||
passzt[passlen] = '\0';
|
||||
|
||||
char random[16];
|
||||
int ret = generate_random_bytes(random, (int) sizeof(random));
|
||||
if (ret) return -1;
|
||||
|
||||
char salt[30];
|
||||
if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL)
|
||||
return -1;
|
||||
|
||||
if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_password(char *pass, int passlen, PasswordHash hash)
|
||||
{
|
||||
char passzt[128];
|
||||
if (passlen >= (int) sizeof(passzt))
|
||||
return -1;
|
||||
memcpy(passzt, pass, passlen);
|
||||
passzt[passlen] = '\0';
|
||||
|
||||
PasswordHash new_hash;
|
||||
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL)
|
||||
return -1;
|
||||
|
||||
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <crypt_blowfish.h>
|
||||
#include "bcrypt.h"
|
||||
#include "random.h"
|
||||
|
||||
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash)
|
||||
{
|
||||
char passzt[128];
|
||||
if (passlen >= (int) sizeof(passzt))
|
||||
return -1;
|
||||
memcpy(passzt, pass, passlen);
|
||||
passzt[passlen] = '\0';
|
||||
|
||||
char random[16];
|
||||
int ret = generate_random_bytes(random, (int) sizeof(random));
|
||||
if (ret) return -1;
|
||||
|
||||
char salt[30];
|
||||
if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL)
|
||||
return -1;
|
||||
|
||||
if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_password(char *pass, int passlen, PasswordHash hash)
|
||||
{
|
||||
char passzt[128];
|
||||
if (passlen >= (int) sizeof(passzt))
|
||||
return -1;
|
||||
memcpy(passzt, pass, passlen);
|
||||
passzt[passlen] = '\0';
|
||||
|
||||
PasswordHash new_hash;
|
||||
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL)
|
||||
return -1;
|
||||
|
||||
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,11 +1,11 @@
|
||||
#ifndef BCRYPT_INCLUDED
|
||||
#define BCRYPT_INCLUDED
|
||||
|
||||
typedef struct {
|
||||
char data[61];
|
||||
} PasswordHash;
|
||||
|
||||
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash);
|
||||
int check_password(char *pass, int passlen, PasswordHash hash);
|
||||
|
||||
#endif // BCRYPT_INCLUDED
|
||||
#ifndef BCRYPT_INCLUDED
|
||||
#define BCRYPT_INCLUDED
|
||||
|
||||
typedef struct {
|
||||
char data[61];
|
||||
} PasswordHash;
|
||||
|
||||
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash);
|
||||
int check_password(char *pass, int passlen, PasswordHash hash);
|
||||
|
||||
#endif // BCRYPT_INCLUDED
|
||||
|
||||
+987
-1095
File diff suppressed because it is too large
Load Diff
+32
-32
@@ -1,32 +1,32 @@
|
||||
#ifdef __linux__
|
||||
#include <errno.h>
|
||||
#include <sys/random.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "random.h"
|
||||
|
||||
int generate_random_bytes(char *dst, int cap)
|
||||
{
|
||||
#ifdef __linux__
|
||||
int copied = 0;
|
||||
while (copied < cap) {
|
||||
int ret = getrandom(dst, (size_t) cap, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
copied += ret;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return BCRYPT_SUCCESS(status) ? 0 : -1;
|
||||
#endif
|
||||
}
|
||||
#ifdef __linux__
|
||||
#include <errno.h>
|
||||
#include <sys/random.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "random.h"
|
||||
|
||||
int generate_random_bytes(char *dst, int cap)
|
||||
{
|
||||
#ifdef __linux__
|
||||
int copied = 0;
|
||||
while (copied < cap) {
|
||||
int ret = getrandom(dst, (size_t) cap, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
copied += ret;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return BCRYPT_SUCCESS(status) ? 0 : -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
#ifndef RANDOM_INCLUDED
|
||||
#define RANDOM_INCLUDED
|
||||
|
||||
int generate_random_bytes(char *dst, int cap);
|
||||
|
||||
#ifndef RANDOM_INCLUDED
|
||||
#define RANDOM_INCLUDED
|
||||
|
||||
int generate_random_bytes(char *dst, int cap);
|
||||
|
||||
#endif // RANDOM_INCLUDED
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "random.h"
|
||||
#include "session.h"
|
||||
|
||||
#define CSRF_RAW_TOKEN_SIZE 32
|
||||
#define SESS_RAW_TOKEN_SIZE 32
|
||||
|
||||
#define CSRF_TOKEN_SIZE (2 * CSRF_RAW_TOKEN_SIZE)
|
||||
#define SESS_TOKEN_SIZE (2 * SESS_RAW_TOKEN_SIZE)
|
||||
|
||||
typedef struct {
|
||||
int user;
|
||||
char csrf[CSRF_TOKEN_SIZE];
|
||||
char sess[SESS_TOKEN_SIZE];
|
||||
} Session;
|
||||
|
||||
struct SessionStorage {
|
||||
int count;
|
||||
int capacity;
|
||||
Session items[];
|
||||
};
|
||||
|
||||
static bool is_hex_digit(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
|
||||
static int hex_digit_to_int(char c)
|
||||
{
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
static void unpack_token(char *src, int srclen, char *dst, int dstlen)
|
||||
{
|
||||
assert(2 * srclen == dstlen);
|
||||
|
||||
for (int i = 0; i < srclen; i++) {
|
||||
static const char table[] = "0123456789abcdef";
|
||||
int low = (src[i] & 0x0F) >> 0;
|
||||
int high = (src[i] & 0xF0) >> 4;
|
||||
dst[(i << 1) | 0] = table[high];
|
||||
dst[(i << 1) | 1] = table[low];
|
||||
}
|
||||
}
|
||||
|
||||
static int pack_token(char *src, int srclen, char *dst, int dstlen)
|
||||
{
|
||||
if (srclen & 1)
|
||||
return -1;
|
||||
|
||||
assert(srclen == 2 * dstlen);
|
||||
|
||||
for (int i = 0; i < srclen; i += 2) {
|
||||
int high = src[i+0];
|
||||
int low = src[i+1];
|
||||
if (!is_hex_digit(high) || !is_hex_digit(low))
|
||||
return -1;
|
||||
dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SessionStorage *session_storage_init(int max_sessions)
|
||||
{
|
||||
int capacity = 2 * max_sessions;
|
||||
SessionStorage *storage = malloc(sizeof(SessionStorage) + capacity * sizeof(Session));
|
||||
if (storage == NULL)
|
||||
return NULL;
|
||||
storage->count = 0;
|
||||
storage->capacity = capacity;
|
||||
for (int i = 0; i < capacity; i++)
|
||||
storage->items[i].user = -1;
|
||||
return storage;
|
||||
}
|
||||
|
||||
void session_storage_free(SessionStorage *storage)
|
||||
{
|
||||
free(storage);
|
||||
}
|
||||
|
||||
#include <stdio.h> // TODO
|
||||
|
||||
static Session *lookup_session_slot(SessionStorage *storage, HTTP_String sess, bool find_unused)
|
||||
{
|
||||
if (find_unused && 2 * storage->count + 2 > storage->capacity)
|
||||
return NULL;
|
||||
|
||||
if (sess.len != SESS_TOKEN_SIZE)
|
||||
return NULL;
|
||||
|
||||
uint64_t key;
|
||||
if (sess.len < (int) (2 * sizeof(key)))
|
||||
return NULL;
|
||||
for (int i = 0; i < (int) sizeof(key); i++) {
|
||||
|
||||
int high = sess.ptr[(i << 1) | 0];
|
||||
int low = sess.ptr[(i << 1) | 1];
|
||||
|
||||
if (!is_hex_digit(sess.ptr[i+0]) ||
|
||||
!is_hex_digit(sess.ptr[i+1]))
|
||||
return NULL;
|
||||
|
||||
key <<= 4;
|
||||
key |= hex_digit_to_int(high);
|
||||
|
||||
key <<= 4;
|
||||
key |= hex_digit_to_int(low);
|
||||
}
|
||||
int i = key % storage->capacity;
|
||||
|
||||
for (int j = 0; j < storage->capacity; j++) {
|
||||
|
||||
if (find_unused) {
|
||||
|
||||
if (storage->items[i].user < 0)
|
||||
return &storage->items[i]; // Unused slot
|
||||
|
||||
} else {
|
||||
|
||||
if (storage->items[i].user == -1)
|
||||
return NULL;
|
||||
|
||||
if (storage->items[i].user != -2)
|
||||
if (!memcmp(storage->items[i].sess, sess.ptr, SESS_TOKEN_SIZE))
|
||||
return &storage->items[i];
|
||||
}
|
||||
|
||||
i++;
|
||||
if (i == storage->capacity)
|
||||
i = 0;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf)
|
||||
{
|
||||
int ret;
|
||||
char raw_sess[SESS_RAW_TOKEN_SIZE];
|
||||
char raw_csrf[CSRF_RAW_TOKEN_SIZE];
|
||||
|
||||
ret = generate_random_bytes(raw_sess, SESS_RAW_TOKEN_SIZE);
|
||||
if (ret) return -1;
|
||||
|
||||
ret = generate_random_bytes(raw_csrf, CSRF_RAW_TOKEN_SIZE);
|
||||
if (ret) return -1;
|
||||
|
||||
char sess[SESS_TOKEN_SIZE];
|
||||
char csrf[CSRF_TOKEN_SIZE];
|
||||
unpack_token(raw_sess, SESS_RAW_TOKEN_SIZE, sess, SESS_TOKEN_SIZE);
|
||||
unpack_token(raw_csrf, CSRF_RAW_TOKEN_SIZE, csrf, CSRF_TOKEN_SIZE);
|
||||
|
||||
Session *found = lookup_session_slot(storage, (HTTP_String) { sess, SESS_TOKEN_SIZE }, true);
|
||||
if (found == NULL) return -1;
|
||||
|
||||
found->user = user;
|
||||
memcpy(found->sess, sess, SESS_TOKEN_SIZE);
|
||||
memcpy(found->csrf, csrf, CSRF_TOKEN_SIZE);
|
||||
|
||||
*psess = (HTTP_String) { found->sess, SESS_TOKEN_SIZE };
|
||||
*pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE };
|
||||
|
||||
storage->count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int delete_session(SessionStorage *storage, HTTP_String sess)
|
||||
{
|
||||
char raw_sess[SESS_RAW_TOKEN_SIZE];
|
||||
if (sess.len != SESS_TOKEN_SIZE || pack_token(sess.ptr, sess.len, raw_sess, (int) sizeof(raw_sess)) < 0)
|
||||
return -1;
|
||||
Session *found = lookup_session_slot(storage, sess, false);
|
||||
if (found == NULL)
|
||||
return false;
|
||||
assert(found->user >= 0);
|
||||
found->user = -2;
|
||||
storage->count--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser)
|
||||
{
|
||||
Session *found = lookup_session_slot(storage, sess, false);
|
||||
if (found == NULL)
|
||||
return -1;
|
||||
assert(found->user >= 0);
|
||||
*pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE };
|
||||
*puser = found->user;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef SESSION_INCLUDED
|
||||
#define SESSION_INCLUDED
|
||||
|
||||
#include <chttp.h> // Only for HTTP_String
|
||||
|
||||
typedef struct SessionStorage SessionStorage;
|
||||
|
||||
SessionStorage *session_storage_init(int max_sessions);
|
||||
void session_storage_free(SessionStorage *storage);
|
||||
|
||||
int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf);
|
||||
int delete_session(SessionStorage *storage, HTTP_String sess);
|
||||
int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser);
|
||||
|
||||
#endif // SESSION_INCLUDED
|
||||
+158
-158
@@ -1,159 +1,159 @@
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "sqlite3utils.h"
|
||||
|
||||
typedef struct {
|
||||
char *str;
|
||||
int len;
|
||||
sqlite3_stmt *stmt;
|
||||
} Prepped;
|
||||
|
||||
struct SQLiteCache {
|
||||
sqlite3 *db;
|
||||
int count;
|
||||
int capacity_log2;
|
||||
Prepped items[];
|
||||
};
|
||||
|
||||
SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2)
|
||||
{
|
||||
SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped));
|
||||
if (cache == NULL)
|
||||
return NULL;
|
||||
|
||||
cache->db = db;
|
||||
cache->count = 0;
|
||||
cache->capacity_log2 = capacity_log2;
|
||||
|
||||
for (int i = 0; i < (1 << capacity_log2); i++)
|
||||
cache->items[i].stmt = NULL;
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
void sqlite_cache_free(SQLiteCache *cache)
|
||||
{
|
||||
for (int i = 0; i < (1 << cache->capacity_log2); i++) {
|
||||
sqlite3_stmt *stmt = cache->items[i].stmt;
|
||||
if (stmt) {
|
||||
free(cache->items[i].str);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
free(cache);
|
||||
}
|
||||
|
||||
sqlite3 *sqlite_cache_getdb(SQLiteCache *cache)
|
||||
{
|
||||
return cache->db;
|
||||
}
|
||||
|
||||
static unsigned long djb2(char *src, int len)
|
||||
{
|
||||
char *ptr = src;
|
||||
char *end = src + len;
|
||||
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
while (ptr < end && (c = *ptr++))
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int lookup(SQLiteCache *cache, char *fmt, int fmtlen)
|
||||
{
|
||||
int mask = (1 << cache->capacity_log2) - 1;
|
||||
int hash = djb2(fmt, fmtlen);
|
||||
int i = hash & mask;
|
||||
int perturb = hash;
|
||||
for (;;) {
|
||||
|
||||
if (cache->items[i].stmt == NULL)
|
||||
return i;
|
||||
|
||||
if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen))
|
||||
return i;
|
||||
|
||||
perturb >>= 5;
|
||||
i = (i * 5 + 1 + perturb) & mask;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen)
|
||||
{
|
||||
if (fmtlen < 0)
|
||||
fmtlen = strlen(fmt);
|
||||
|
||||
int i = lookup(cache, fmt, fmtlen);
|
||||
if (cache->items[i].stmt == NULL) {
|
||||
|
||||
printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL);
|
||||
if (ret != SQLITE_OK) {
|
||||
//fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *cpy = malloc(fmtlen);
|
||||
if (cpy == NULL) {
|
||||
sqlite3_finalize(stmt);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memcpy(cpy, fmt, fmtlen);
|
||||
|
||||
cache->items[i].str = cpy;
|
||||
cache->items[i].len = fmtlen;
|
||||
cache->items[i].stmt = stmt;
|
||||
}
|
||||
sqlite3_stmt *stmt = cache->items[i].stmt;
|
||||
|
||||
*pstmt = stmt;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
|
||||
sqlite3_stmt **pstmt, char *fmt, VArgs args)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt));
|
||||
if (ret != SQLITE_OK)
|
||||
return ret;
|
||||
|
||||
for (int i = 0; i < args.len; i++) {
|
||||
VArg arg = args.ptr[i];
|
||||
switch (arg.type) {
|
||||
case VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break;
|
||||
case VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break;
|
||||
case VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break;
|
||||
case VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break;
|
||||
case VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break;
|
||||
case VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break;
|
||||
case VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break;
|
||||
case VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break;
|
||||
case VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break;
|
||||
case VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break;
|
||||
case VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break;
|
||||
case VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break;
|
||||
case VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break;
|
||||
case VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break;
|
||||
case VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break;
|
||||
case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break;
|
||||
case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break;
|
||||
case VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break;
|
||||
case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break;
|
||||
}
|
||||
if (ret != SQLITE_OK) {
|
||||
sqlite3_reset(stmt);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
*pstmt = stmt;
|
||||
return SQLITE_OK;
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "sqlite3utils.h"
|
||||
|
||||
typedef struct {
|
||||
char *str;
|
||||
int len;
|
||||
sqlite3_stmt *stmt;
|
||||
} Prepped;
|
||||
|
||||
struct SQLiteCache {
|
||||
sqlite3 *db;
|
||||
int count;
|
||||
int capacity_log2;
|
||||
Prepped items[];
|
||||
};
|
||||
|
||||
SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2)
|
||||
{
|
||||
SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped));
|
||||
if (cache == NULL)
|
||||
return NULL;
|
||||
|
||||
cache->db = db;
|
||||
cache->count = 0;
|
||||
cache->capacity_log2 = capacity_log2;
|
||||
|
||||
for (int i = 0; i < (1 << capacity_log2); i++)
|
||||
cache->items[i].stmt = NULL;
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
void sqlite_cache_free(SQLiteCache *cache)
|
||||
{
|
||||
for (int i = 0; i < (1 << cache->capacity_log2); i++) {
|
||||
sqlite3_stmt *stmt = cache->items[i].stmt;
|
||||
if (stmt) {
|
||||
free(cache->items[i].str);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
free(cache);
|
||||
}
|
||||
|
||||
sqlite3 *sqlite_cache_getdb(SQLiteCache *cache)
|
||||
{
|
||||
return cache->db;
|
||||
}
|
||||
|
||||
static unsigned long djb2(char *src, int len)
|
||||
{
|
||||
char *ptr = src;
|
||||
char *end = src + len;
|
||||
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
while (ptr < end && (c = *ptr++))
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int lookup(SQLiteCache *cache, char *fmt, int fmtlen)
|
||||
{
|
||||
int mask = (1 << cache->capacity_log2) - 1;
|
||||
int hash = djb2(fmt, fmtlen);
|
||||
int i = hash & mask;
|
||||
int perturb = hash;
|
||||
for (;;) {
|
||||
|
||||
if (cache->items[i].stmt == NULL)
|
||||
return i;
|
||||
|
||||
if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen))
|
||||
return i;
|
||||
|
||||
perturb >>= 5;
|
||||
i = (i * 5 + 1 + perturb) & mask;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen)
|
||||
{
|
||||
if (fmtlen < 0)
|
||||
fmtlen = strlen(fmt);
|
||||
|
||||
int i = lookup(cache, fmt, fmtlen);
|
||||
if (cache->items[i].stmt == NULL) {
|
||||
|
||||
printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL);
|
||||
if (ret != SQLITE_OK) {
|
||||
//fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *cpy = malloc(fmtlen);
|
||||
if (cpy == NULL) {
|
||||
sqlite3_finalize(stmt);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
memcpy(cpy, fmt, fmtlen);
|
||||
|
||||
cache->items[i].str = cpy;
|
||||
cache->items[i].len = fmtlen;
|
||||
cache->items[i].stmt = stmt;
|
||||
}
|
||||
sqlite3_stmt *stmt = cache->items[i].stmt;
|
||||
|
||||
*pstmt = stmt;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
|
||||
sqlite3_stmt **pstmt, char *fmt, VArgs args)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt));
|
||||
if (ret != SQLITE_OK)
|
||||
return ret;
|
||||
|
||||
for (int i = 0; i < args.len; i++) {
|
||||
VArg arg = args.ptr[i];
|
||||
switch (arg.type) {
|
||||
case VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break;
|
||||
case VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break;
|
||||
case VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break;
|
||||
case VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break;
|
||||
case VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break;
|
||||
case VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break;
|
||||
case VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break;
|
||||
case VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break;
|
||||
case VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break;
|
||||
case VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break;
|
||||
case VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break;
|
||||
case VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break;
|
||||
case VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break;
|
||||
case VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break;
|
||||
case VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break;
|
||||
case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break;
|
||||
case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break;
|
||||
case VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break;
|
||||
case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break;
|
||||
}
|
||||
if (ret != SQLITE_OK) {
|
||||
sqlite3_reset(stmt);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
*pstmt = stmt;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
+20
-20
@@ -1,21 +1,21 @@
|
||||
#ifndef SQLITE3UTILS_INCLUDED
|
||||
#define SQLITE3UTILS_INCLUDED
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "variadic.h"
|
||||
|
||||
#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__))
|
||||
|
||||
typedef struct SQLiteCache SQLiteCache;
|
||||
|
||||
SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2);
|
||||
void sqlite_cache_free(SQLiteCache *cache);
|
||||
sqlite3* sqlite_cache_getdb(SQLiteCache *cache);
|
||||
|
||||
int sqlite3utils_prepare(SQLiteCache *cache,
|
||||
sqlite3_stmt **pstmt, char *fmt, int fmtlen);
|
||||
|
||||
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
|
||||
sqlite3_stmt **pstmt, char *fmt, VArgs args);
|
||||
|
||||
#ifndef SQLITE3UTILS_INCLUDED
|
||||
#define SQLITE3UTILS_INCLUDED
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "variadic.h"
|
||||
|
||||
#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__))
|
||||
|
||||
typedef struct SQLiteCache SQLiteCache;
|
||||
|
||||
SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2);
|
||||
void sqlite_cache_free(SQLiteCache *cache);
|
||||
sqlite3* sqlite_cache_getdb(SQLiteCache *cache);
|
||||
|
||||
int sqlite3utils_prepare(SQLiteCache *cache,
|
||||
sqlite3_stmt **pstmt, char *fmt, int fmtlen);
|
||||
|
||||
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
|
||||
sqlite3_stmt **pstmt, char *fmt, VArgs args);
|
||||
|
||||
#endif // SQLITE3UTILS_INCLUDED
|
||||
+419
-412
@@ -1,412 +1,419 @@
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "template.h"
|
||||
#include "sqlite3utils.h"
|
||||
|
||||
#define TRACE(...) {}
|
||||
//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__);
|
||||
|
||||
typedef struct CachedProgram CachedProgram;
|
||||
struct CachedProgram {
|
||||
char path[1<<8];
|
||||
int pathlen;
|
||||
WL_Program program;
|
||||
};
|
||||
|
||||
struct TemplateCache {
|
||||
int count;
|
||||
int capacity_log2;
|
||||
CachedProgram pool[];
|
||||
};
|
||||
|
||||
TemplateCache *template_cache_init(int capacity_log2)
|
||||
{
|
||||
TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram));
|
||||
if (cache == NULL)
|
||||
return NULL;
|
||||
|
||||
cache->count = 0;
|
||||
cache->capacity_log2 = capacity_log2;
|
||||
|
||||
for (int i = 0; i < (1 << capacity_log2); i++)
|
||||
cache->pool[i].pathlen = -1;
|
||||
return cache;
|
||||
}
|
||||
|
||||
void template_cache_free(TemplateCache *cache)
|
||||
{
|
||||
free(cache);
|
||||
}
|
||||
|
||||
static unsigned long djb2(WL_String str)
|
||||
{
|
||||
char *ptr = str.ptr;
|
||||
char *end = str.ptr + str.len;
|
||||
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
while (ptr < end && (c = *ptr++))
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int lookup(TemplateCache *cache, WL_String path)
|
||||
{
|
||||
int mask = (1 << cache->capacity_log2) - 1;
|
||||
int hash = djb2(path);
|
||||
int i = hash & mask;
|
||||
int perturb = hash;
|
||||
for (;;) {
|
||||
|
||||
if (cache->pool[i].pathlen == -1)
|
||||
return i;
|
||||
|
||||
if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen))
|
||||
return i;
|
||||
|
||||
perturb >>= 5;
|
||||
i = (i * 5 + 1 + perturb) & mask;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
typedef struct LoadedFile LoadedFile;
|
||||
struct LoadedFile {
|
||||
LoadedFile* next;
|
||||
int len;
|
||||
char data[];
|
||||
};
|
||||
|
||||
static LoadedFile *load_file(WL_String path)
|
||||
{
|
||||
char buf[1<<10];
|
||||
if (path.len >= (int) sizeof(buf))
|
||||
return NULL;
|
||||
memcpy(buf, path.ptr, path.len);
|
||||
buf[path.len] = '\0';
|
||||
|
||||
FILE *stream = fopen(buf, "rb");
|
||||
if (stream == NULL)
|
||||
return NULL;
|
||||
|
||||
int ret = fseek(stream, 0, SEEK_END);
|
||||
if (ret) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long tmp = ftell(stream);
|
||||
if (tmp < 0 || tmp > INT_MAX) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
int len = (int) tmp;
|
||||
|
||||
ret = fseek(stream, 0, SEEK_SET);
|
||||
if (ret) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1);
|
||||
if (result == NULL) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
result->next = NULL;
|
||||
result->len = len;
|
||||
|
||||
int read_len = fread(result->data, 1, len+1, stream);
|
||||
if (read_len != len || ferror(stream) || !feof(stream)) {
|
||||
fclose(stream);
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void free_loaded_files(LoadedFile *loaded_file)
|
||||
{
|
||||
while (loaded_file) {
|
||||
LoadedFile *next = loaded_file->next;
|
||||
free(loaded_file);
|
||||
loaded_file = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int compile(WL_String path, WL_Program *program, WL_Arena *arena)
|
||||
{
|
||||
WL_Compiler *compiler = wl_compiler_init(arena);
|
||||
if (compiler == NULL) {
|
||||
TRACE("Couldn't initialize WL compiler object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LoadedFile *loaded_file_head = NULL;
|
||||
LoadedFile **loaded_file_tail = &loaded_file_head;
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
|
||||
LoadedFile *loaded_file = load_file(path);
|
||||
if (loaded_file == NULL) {
|
||||
TRACE("Couldn't load file '%.*s'", path.len, path.ptr);
|
||||
free_loaded_files(loaded_file_head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*loaded_file_tail = loaded_file;
|
||||
loaded_file_tail = &loaded_file->next;
|
||||
|
||||
WL_String content = { loaded_file->data, loaded_file->len };
|
||||
WL_AddResult result = wl_compiler_add(compiler, content);
|
||||
|
||||
if (result.type == WL_ADD_ERROR) {
|
||||
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
|
||||
free_loaded_files(loaded_file_head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (result.type == WL_ADD_LINK) break;
|
||||
|
||||
assert(result.type == WL_ADD_AGAIN);
|
||||
path = result.path;
|
||||
}
|
||||
|
||||
int ret = wl_compiler_link(compiler, program);
|
||||
if (ret < 0) {
|
||||
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free_loaded_files(loaded_file_head);
|
||||
|
||||
TRACE("Compilation succeded");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache)
|
||||
{
|
||||
int num_args = wl_arg_count(rt);
|
||||
if (num_args == 0)
|
||||
return 0;
|
||||
|
||||
WL_String format;
|
||||
if (!wl_arg_str(rt, 0, &format))
|
||||
return -1;
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len);
|
||||
if (ret != SQLITE_OK)
|
||||
return -1;
|
||||
|
||||
for (int i = 1; i < num_args; i++) {
|
||||
|
||||
int64_t ival;
|
||||
double fval;
|
||||
WL_String str;
|
||||
|
||||
if (wl_arg_none(rt, i))
|
||||
ret = sqlite3_bind_null (stmt, i);
|
||||
else if (wl_arg_s64(rt, i, &ival))
|
||||
ret = sqlite3_bind_int64 (stmt, i, ival);
|
||||
else if (wl_arg_f64(rt, i, &fval))
|
||||
ret = sqlite3_bind_double(stmt, i, fval);
|
||||
else if (wl_arg_str(rt, i, &str))
|
||||
ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL);
|
||||
else assert(0);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
sqlite3_reset(stmt);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
wl_push_array(rt, 0);
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
|
||||
int num_cols = sqlite3_column_count(stmt);
|
||||
if (num_cols < 0) {
|
||||
sqlite3_reset(stmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wl_push_map(rt, num_cols);
|
||||
|
||||
for (int i = 0; i < num_cols; i++) {
|
||||
ret = sqlite3_column_type(stmt, i);
|
||||
switch (ret) {
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
{
|
||||
int64_t x = sqlite3_column_int64(stmt, i);
|
||||
wl_push_s64(rt, x);
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_FLOAT:
|
||||
{
|
||||
double x = sqlite3_column_double(stmt, i);
|
||||
wl_push_f64(rt, x);
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_TEXT:
|
||||
{
|
||||
const void *x = sqlite3_column_text(stmt, i);
|
||||
int n = sqlite3_column_bytes(stmt, i);
|
||||
wl_push_str(rt, (WL_String) { (char*) x, n });
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_BLOB:
|
||||
{
|
||||
const void *x = sqlite3_column_blob(stmt, i);
|
||||
int n = sqlite3_column_bytes(stmt, i);
|
||||
wl_push_str(rt, (WL_String) { (char*) x, n });
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_NULL:
|
||||
{
|
||||
wl_push_none(rt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const char *name = sqlite3_column_name(stmt, i);
|
||||
|
||||
wl_push_str(rt, (WL_String) { (char*) name, strlen(name) });
|
||||
wl_insert(rt);
|
||||
}
|
||||
|
||||
wl_append(rt);
|
||||
}
|
||||
|
||||
sqlite3_reset(stmt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, int user_id, int post_id)
|
||||
{
|
||||
(void) dbcache;
|
||||
|
||||
if (wl_streq(name, "login_user_id", -1)) {
|
||||
|
||||
if (user_id < 0)
|
||||
wl_push_none(rt);
|
||||
else
|
||||
wl_push_s64(rt, user_id);
|
||||
|
||||
} else if (wl_streq(name, "post_id", -1)) {
|
||||
|
||||
if (post_id < 0)
|
||||
wl_push_none(rt);
|
||||
else
|
||||
wl_push_s64(rt, post_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache)
|
||||
{
|
||||
if (wl_streq(name, "query", -1)) {
|
||||
query_routine(rt, dbcache);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program)
|
||||
{
|
||||
if (cache == NULL)
|
||||
return -1;
|
||||
|
||||
int i = lookup(cache, path);
|
||||
if (cache->pool[i].pathlen == -1) {
|
||||
|
||||
WL_Program program;
|
||||
int ret = compile(path, &program, arena);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
void *p = malloc(program.len);
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
memcpy(p, program.ptr, program.len);
|
||||
program.ptr = p;
|
||||
|
||||
if ((int) sizeof(cache->pool->path) <= path.len)
|
||||
return -1;
|
||||
memcpy(cache->pool[i].path, path.ptr, path.len);
|
||||
cache->pool[i].path[path.len] = '\0';
|
||||
cache->pool[i].pathlen = path.len;
|
||||
cache->pool[i].program = program;
|
||||
}
|
||||
|
||||
*program = cache->pool[i].program;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void template_eval(HTTP_ResponseBuilder builder, int status,
|
||||
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
||||
SQLiteCache *dbcache, int user_id, int post_id)
|
||||
{
|
||||
http_response_builder_status(builder, status);
|
||||
|
||||
WL_Program program;
|
||||
int ret = get_or_create_program(cache, path, arena, &program);
|
||||
if (ret < 0) {
|
||||
http_response_builder_undo(builder);
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_done(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
//wl_dump_program(program);
|
||||
|
||||
WL_Runtime *rt = wl_runtime_init(arena, program);
|
||||
if (rt == NULL) {
|
||||
http_response_builder_undo(builder);
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_done(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
for (bool done = false; !done; ) {
|
||||
|
||||
WL_EvalResult result = wl_runtime_eval(rt);
|
||||
switch (result.type) {
|
||||
|
||||
case WL_EVAL_DONE:
|
||||
http_response_builder_done(builder);
|
||||
done = true;
|
||||
break;
|
||||
|
||||
case WL_EVAL_ERROR:
|
||||
// wl_runtime_error(rt)
|
||||
http_response_builder_undo(builder);
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_done(builder);
|
||||
return;
|
||||
|
||||
case WL_EVAL_SYSVAR:
|
||||
push_sysvar(rt, result.str, dbcache, user_id, post_id);
|
||||
break;
|
||||
|
||||
case WL_EVAL_SYSCALL:
|
||||
push_syscall(rt, result.str, dbcache);
|
||||
break;
|
||||
|
||||
case WL_EVAL_OUTPUT:
|
||||
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "template.h"
|
||||
#include "sqlite3utils.h"
|
||||
|
||||
#define TRACE(...) {}
|
||||
//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__);
|
||||
|
||||
typedef struct CachedProgram CachedProgram;
|
||||
struct CachedProgram {
|
||||
char path[1<<8];
|
||||
int pathlen;
|
||||
WL_Program program;
|
||||
};
|
||||
|
||||
struct TemplateCache {
|
||||
int count;
|
||||
int capacity_log2;
|
||||
CachedProgram pool[];
|
||||
};
|
||||
|
||||
TemplateCache *template_cache_init(int capacity_log2)
|
||||
{
|
||||
TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram));
|
||||
if (cache == NULL)
|
||||
return NULL;
|
||||
|
||||
cache->count = 0;
|
||||
cache->capacity_log2 = capacity_log2;
|
||||
|
||||
for (int i = 0; i < (1 << capacity_log2); i++)
|
||||
cache->pool[i].pathlen = -1;
|
||||
return cache;
|
||||
}
|
||||
|
||||
void template_cache_free(TemplateCache *cache)
|
||||
{
|
||||
free(cache);
|
||||
}
|
||||
|
||||
static unsigned long djb2(WL_String str)
|
||||
{
|
||||
char *ptr = str.ptr;
|
||||
char *end = str.ptr + str.len;
|
||||
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
while (ptr < end && (c = *ptr++))
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int lookup(TemplateCache *cache, WL_String path)
|
||||
{
|
||||
int mask = (1 << cache->capacity_log2) - 1;
|
||||
int hash = djb2(path);
|
||||
int i = hash & mask;
|
||||
int perturb = hash;
|
||||
for (;;) {
|
||||
|
||||
if (cache->pool[i].pathlen == -1)
|
||||
return i;
|
||||
|
||||
if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen))
|
||||
return i;
|
||||
|
||||
perturb >>= 5;
|
||||
i = (i * 5 + 1 + perturb) & mask;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
typedef struct LoadedFile LoadedFile;
|
||||
struct LoadedFile {
|
||||
LoadedFile* next;
|
||||
int len;
|
||||
char data[];
|
||||
};
|
||||
|
||||
static LoadedFile *load_file(WL_String path)
|
||||
{
|
||||
char buf[1<<10];
|
||||
if (path.len >= (int) sizeof(buf))
|
||||
return NULL;
|
||||
memcpy(buf, path.ptr, path.len);
|
||||
buf[path.len] = '\0';
|
||||
|
||||
FILE *stream = fopen(buf, "rb");
|
||||
if (stream == NULL)
|
||||
return NULL;
|
||||
|
||||
int ret = fseek(stream, 0, SEEK_END);
|
||||
if (ret) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long tmp = ftell(stream);
|
||||
if (tmp < 0 || tmp > INT_MAX) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
int len = (int) tmp;
|
||||
|
||||
ret = fseek(stream, 0, SEEK_SET);
|
||||
if (ret) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1);
|
||||
if (result == NULL) {
|
||||
fclose(stream);
|
||||
return NULL;
|
||||
}
|
||||
result->next = NULL;
|
||||
result->len = len;
|
||||
|
||||
int read_len = fread(result->data, 1, len+1, stream);
|
||||
if (read_len != len || ferror(stream) || !feof(stream)) {
|
||||
fclose(stream);
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void free_loaded_files(LoadedFile *loaded_file)
|
||||
{
|
||||
while (loaded_file) {
|
||||
LoadedFile *next = loaded_file->next;
|
||||
free(loaded_file);
|
||||
loaded_file = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int compile(WL_String path, WL_Program *program, WL_Arena *arena)
|
||||
{
|
||||
WL_Compiler *compiler = wl_compiler_init(arena);
|
||||
if (compiler == NULL) {
|
||||
TRACE("Couldn't initialize WL compiler object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LoadedFile *loaded_file_head = NULL;
|
||||
LoadedFile **loaded_file_tail = &loaded_file_head;
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
|
||||
LoadedFile *loaded_file = load_file(path);
|
||||
if (loaded_file == NULL) {
|
||||
TRACE("Couldn't load file '%.*s'", path.len, path.ptr);
|
||||
free_loaded_files(loaded_file_head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*loaded_file_tail = loaded_file;
|
||||
loaded_file_tail = &loaded_file->next;
|
||||
|
||||
WL_String content = { loaded_file->data, loaded_file->len };
|
||||
WL_AddResult result = wl_compiler_add(compiler, content);
|
||||
|
||||
if (result.type == WL_ADD_ERROR) {
|
||||
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
|
||||
free_loaded_files(loaded_file_head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (result.type == WL_ADD_LINK) break;
|
||||
|
||||
assert(result.type == WL_ADD_AGAIN);
|
||||
path = result.path;
|
||||
}
|
||||
|
||||
int ret = wl_compiler_link(compiler, program);
|
||||
if (ret < 0) {
|
||||
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free_loaded_files(loaded_file_head);
|
||||
|
||||
TRACE("Compilation succeded");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache)
|
||||
{
|
||||
int num_args = wl_arg_count(rt);
|
||||
if (num_args == 0)
|
||||
return 0;
|
||||
|
||||
WL_String format;
|
||||
if (!wl_arg_str(rt, 0, &format))
|
||||
return -1;
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len);
|
||||
if (ret != SQLITE_OK)
|
||||
return -1;
|
||||
|
||||
for (int i = 1; i < num_args; i++) {
|
||||
|
||||
int64_t ival;
|
||||
double fval;
|
||||
WL_String str;
|
||||
|
||||
if (wl_arg_none(rt, i))
|
||||
ret = sqlite3_bind_null (stmt, i);
|
||||
else if (wl_arg_s64(rt, i, &ival))
|
||||
ret = sqlite3_bind_int64 (stmt, i, ival);
|
||||
else if (wl_arg_f64(rt, i, &fval))
|
||||
ret = sqlite3_bind_double(stmt, i, fval);
|
||||
else if (wl_arg_str(rt, i, &str))
|
||||
ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL);
|
||||
else assert(0);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
sqlite3_reset(stmt);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
wl_push_array(rt, 0);
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
|
||||
int num_cols = sqlite3_column_count(stmt);
|
||||
if (num_cols < 0) {
|
||||
sqlite3_reset(stmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wl_push_map(rt, num_cols);
|
||||
|
||||
for (int i = 0; i < num_cols; i++) {
|
||||
ret = sqlite3_column_type(stmt, i);
|
||||
switch (ret) {
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
{
|
||||
int64_t x = sqlite3_column_int64(stmt, i);
|
||||
wl_push_s64(rt, x);
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_FLOAT:
|
||||
{
|
||||
double x = sqlite3_column_double(stmt, i);
|
||||
wl_push_f64(rt, x);
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_TEXT:
|
||||
{
|
||||
const void *x = sqlite3_column_text(stmt, i);
|
||||
int n = sqlite3_column_bytes(stmt, i);
|
||||
wl_push_str(rt, (WL_String) { (char*) x, n });
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_BLOB:
|
||||
{
|
||||
const void *x = sqlite3_column_blob(stmt, i);
|
||||
int n = sqlite3_column_bytes(stmt, i);
|
||||
wl_push_str(rt, (WL_String) { (char*) x, n });
|
||||
}
|
||||
break;
|
||||
|
||||
case SQLITE_NULL:
|
||||
{
|
||||
wl_push_none(rt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const char *name = sqlite3_column_name(stmt, i);
|
||||
|
||||
wl_push_str(rt, (WL_String) { (char*) name, strlen(name) });
|
||||
wl_insert(rt);
|
||||
}
|
||||
|
||||
wl_append(rt);
|
||||
}
|
||||
|
||||
sqlite3_reset(stmt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
|
||||
{
|
||||
(void) dbcache;
|
||||
|
||||
if (wl_streq(name, "login_user_id", -1)) {
|
||||
|
||||
if (user_id < 0)
|
||||
wl_push_none(rt);
|
||||
else
|
||||
wl_push_s64(rt, user_id);
|
||||
|
||||
} else if (wl_streq(name, "post_id", -1)) {
|
||||
|
||||
if (post_id < 0)
|
||||
wl_push_none(rt);
|
||||
else
|
||||
wl_push_s64(rt, post_id);
|
||||
|
||||
} else if (wl_streq(name, "csrf", -1)) {
|
||||
|
||||
if (csrf.len == 0)
|
||||
wl_push_none(rt);
|
||||
else
|
||||
wl_push_str(rt, (WL_String) { csrf.ptr, csrf.len });
|
||||
}
|
||||
}
|
||||
|
||||
static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache)
|
||||
{
|
||||
if (wl_streq(name, "query", -1)) {
|
||||
query_routine(rt, dbcache);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program)
|
||||
{
|
||||
if (cache == NULL)
|
||||
return -1;
|
||||
|
||||
int i = lookup(cache, path);
|
||||
if (cache->pool[i].pathlen == -1) {
|
||||
|
||||
WL_Program program;
|
||||
int ret = compile(path, &program, arena);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
void *p = malloc(program.len);
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
memcpy(p, program.ptr, program.len);
|
||||
program.ptr = p;
|
||||
|
||||
if ((int) sizeof(cache->pool->path) <= path.len)
|
||||
return -1;
|
||||
memcpy(cache->pool[i].path, path.ptr, path.len);
|
||||
cache->pool[i].path[path.len] = '\0';
|
||||
cache->pool[i].pathlen = path.len;
|
||||
cache->pool[i].program = program;
|
||||
}
|
||||
|
||||
*program = cache->pool[i].program;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void template_eval(HTTP_ResponseBuilder builder, int status,
|
||||
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
||||
SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
|
||||
{
|
||||
http_response_builder_status(builder, status);
|
||||
|
||||
WL_Program program;
|
||||
int ret = get_or_create_program(cache, path, arena, &program);
|
||||
if (ret < 0) {
|
||||
http_response_builder_undo(builder);
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_done(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
//wl_dump_program(program);
|
||||
|
||||
WL_Runtime *rt = wl_runtime_init(arena, program);
|
||||
if (rt == NULL) {
|
||||
http_response_builder_undo(builder);
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_done(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
for (bool done = false; !done; ) {
|
||||
|
||||
WL_EvalResult result = wl_runtime_eval(rt);
|
||||
switch (result.type) {
|
||||
|
||||
case WL_EVAL_DONE:
|
||||
http_response_builder_done(builder);
|
||||
done = true;
|
||||
break;
|
||||
|
||||
case WL_EVAL_ERROR:
|
||||
// wl_runtime_error(rt)
|
||||
http_response_builder_undo(builder);
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_done(builder);
|
||||
return;
|
||||
|
||||
case WL_EVAL_SYSVAR:
|
||||
push_sysvar(rt, result.str, dbcache, csrf, user_id, post_id);
|
||||
break;
|
||||
|
||||
case WL_EVAL_SYSCALL:
|
||||
push_syscall(rt, result.str, dbcache);
|
||||
break;
|
||||
|
||||
case WL_EVAL_OUTPUT:
|
||||
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,16 +1,16 @@
|
||||
#ifndef TEMPLATE_INCLUDED
|
||||
#define TEMPLATE_INCLUDED
|
||||
|
||||
#include "wl.h"
|
||||
#include "chttp.h"
|
||||
#include "sqlite3utils.h"
|
||||
|
||||
typedef struct TemplateCache TemplateCache;
|
||||
TemplateCache *template_cache_init(int capacity_log2);
|
||||
void template_cache_free(TemplateCache *cache);
|
||||
|
||||
void template_eval(HTTP_ResponseBuilder builder, int status,
|
||||
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
||||
SQLiteCache *dbcache, int user_id, int post_id);
|
||||
|
||||
#ifndef TEMPLATE_INCLUDED
|
||||
#define TEMPLATE_INCLUDED
|
||||
|
||||
#include "wl.h"
|
||||
#include "chttp.h"
|
||||
#include "sqlite3utils.h"
|
||||
|
||||
typedef struct TemplateCache TemplateCache;
|
||||
TemplateCache *template_cache_init(int capacity_log2);
|
||||
void template_cache_free(TemplateCache *cache);
|
||||
|
||||
void template_eval(HTTP_ResponseBuilder builder, int status,
|
||||
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
||||
SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id);
|
||||
|
||||
#endif // TEMPLATE_INCLUDED
|
||||
+21
-21
@@ -1,21 +1,21 @@
|
||||
#include "variadic.h"
|
||||
|
||||
VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; }
|
||||
VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; }
|
||||
VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; }
|
||||
VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; }
|
||||
VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; }
|
||||
VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; }
|
||||
VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; }
|
||||
VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; }
|
||||
VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; }
|
||||
VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; }
|
||||
VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; }
|
||||
VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; }
|
||||
VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; }
|
||||
VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; }
|
||||
VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; }
|
||||
VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; }
|
||||
VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; }
|
||||
VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; }
|
||||
VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; }
|
||||
#include "variadic.h"
|
||||
|
||||
VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; }
|
||||
VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; }
|
||||
VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; }
|
||||
VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; }
|
||||
VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; }
|
||||
VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; }
|
||||
VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; }
|
||||
VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; }
|
||||
VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; }
|
||||
VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; }
|
||||
VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; }
|
||||
VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; }
|
||||
VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; }
|
||||
VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; }
|
||||
VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; }
|
||||
VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; }
|
||||
VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; }
|
||||
VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; }
|
||||
VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; }
|
||||
|
||||
+109
-109
@@ -1,110 +1,110 @@
|
||||
#ifndef VARIADIC_INCLUDED
|
||||
#define VARIADIC_INCLUDED
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "chttp.h"
|
||||
|
||||
typedef enum {
|
||||
VARG_TYPE_C,
|
||||
VARG_TYPE_S,
|
||||
VARG_TYPE_I,
|
||||
VARG_TYPE_L,
|
||||
VARG_TYPE_LL,
|
||||
VARG_TYPE_SC,
|
||||
VARG_TYPE_SS,
|
||||
VARG_TYPE_SI,
|
||||
VARG_TYPE_SL,
|
||||
VARG_TYPE_SLL,
|
||||
VARG_TYPE_UC,
|
||||
VARG_TYPE_US,
|
||||
VARG_TYPE_UI,
|
||||
VARG_TYPE_UL,
|
||||
VARG_TYPE_ULL,
|
||||
VARG_TYPE_F,
|
||||
VARG_TYPE_D,
|
||||
VARG_TYPE_B,
|
||||
VARG_TYPE_STR,
|
||||
} VArgType;
|
||||
|
||||
typedef struct {
|
||||
VArgType type;
|
||||
union {
|
||||
char c;
|
||||
short s;
|
||||
int i;
|
||||
long l;
|
||||
long long ll;
|
||||
signed char sc;
|
||||
signed short ss;
|
||||
signed int si;
|
||||
signed long sl;
|
||||
signed long long sll;
|
||||
unsigned char uc;
|
||||
unsigned short us;
|
||||
unsigned int ui;
|
||||
unsigned long ul;
|
||||
unsigned long long ull;
|
||||
float f;
|
||||
double d;
|
||||
bool b;
|
||||
HTTP_String str;
|
||||
};
|
||||
} VArg;
|
||||
|
||||
VArg varg_from_c (char c);
|
||||
VArg varg_from_s (short s);
|
||||
VArg varg_from_i (int i);
|
||||
VArg varg_from_l (long l);
|
||||
VArg varg_from_ll (long long ll);
|
||||
VArg varg_from_sc (char sc);
|
||||
VArg varg_from_ss (short ss);
|
||||
VArg varg_from_si (int si);
|
||||
VArg varg_from_sl (long sl);
|
||||
VArg varg_from_sll (long long sll);
|
||||
VArg varg_from_uc (char uc);
|
||||
VArg varg_from_us (short us);
|
||||
VArg varg_from_ui (int ui);
|
||||
VArg varg_from_ul (long ul);
|
||||
VArg varg_from_ull (long long ull);
|
||||
VArg varg_from_f (float f);
|
||||
VArg varg_from_d (double d);
|
||||
VArg varg_from_b (bool b);
|
||||
VArg varg_from_str (HTTP_String str);
|
||||
|
||||
#define VARG(X) (_Generic((X), \
|
||||
char : varg_from_c, \
|
||||
short : varg_from_s, \
|
||||
int : varg_from_i, \
|
||||
long : varg_from_l, \
|
||||
long long : varg_from_ll, \
|
||||
signed char : varg_from_sc, \
|
||||
/*signed short : varg_from_ss,*/ \
|
||||
/*signed int : varg_from_si,*/ \
|
||||
/*signed long : varg_from_sl,*/ \
|
||||
/*signed long long : varg_from_sll,*/ \
|
||||
unsigned char : varg_from_uc, \
|
||||
unsigned short : varg_from_us, \
|
||||
unsigned int : varg_from_ui, \
|
||||
unsigned long : varg_from_ul, \
|
||||
unsigned long long: varg_from_ull, \
|
||||
float : varg_from_f, \
|
||||
double : varg_from_d, \
|
||||
bool : varg_from_b, \
|
||||
HTTP_String : varg_from_str \
|
||||
))(X)
|
||||
|
||||
typedef struct {
|
||||
int len;
|
||||
VArg *ptr;
|
||||
} VArgs;
|
||||
|
||||
#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } }
|
||||
#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } }
|
||||
#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } }
|
||||
#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } }
|
||||
#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } }
|
||||
|
||||
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME
|
||||
#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__)
|
||||
|
||||
#ifndef VARIADIC_INCLUDED
|
||||
#define VARIADIC_INCLUDED
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "chttp.h"
|
||||
|
||||
typedef enum {
|
||||
VARG_TYPE_C,
|
||||
VARG_TYPE_S,
|
||||
VARG_TYPE_I,
|
||||
VARG_TYPE_L,
|
||||
VARG_TYPE_LL,
|
||||
VARG_TYPE_SC,
|
||||
VARG_TYPE_SS,
|
||||
VARG_TYPE_SI,
|
||||
VARG_TYPE_SL,
|
||||
VARG_TYPE_SLL,
|
||||
VARG_TYPE_UC,
|
||||
VARG_TYPE_US,
|
||||
VARG_TYPE_UI,
|
||||
VARG_TYPE_UL,
|
||||
VARG_TYPE_ULL,
|
||||
VARG_TYPE_F,
|
||||
VARG_TYPE_D,
|
||||
VARG_TYPE_B,
|
||||
VARG_TYPE_STR,
|
||||
} VArgType;
|
||||
|
||||
typedef struct {
|
||||
VArgType type;
|
||||
union {
|
||||
char c;
|
||||
short s;
|
||||
int i;
|
||||
long l;
|
||||
long long ll;
|
||||
signed char sc;
|
||||
signed short ss;
|
||||
signed int si;
|
||||
signed long sl;
|
||||
signed long long sll;
|
||||
unsigned char uc;
|
||||
unsigned short us;
|
||||
unsigned int ui;
|
||||
unsigned long ul;
|
||||
unsigned long long ull;
|
||||
float f;
|
||||
double d;
|
||||
bool b;
|
||||
HTTP_String str;
|
||||
};
|
||||
} VArg;
|
||||
|
||||
VArg varg_from_c (char c);
|
||||
VArg varg_from_s (short s);
|
||||
VArg varg_from_i (int i);
|
||||
VArg varg_from_l (long l);
|
||||
VArg varg_from_ll (long long ll);
|
||||
VArg varg_from_sc (char sc);
|
||||
VArg varg_from_ss (short ss);
|
||||
VArg varg_from_si (int si);
|
||||
VArg varg_from_sl (long sl);
|
||||
VArg varg_from_sll (long long sll);
|
||||
VArg varg_from_uc (char uc);
|
||||
VArg varg_from_us (short us);
|
||||
VArg varg_from_ui (int ui);
|
||||
VArg varg_from_ul (long ul);
|
||||
VArg varg_from_ull (long long ull);
|
||||
VArg varg_from_f (float f);
|
||||
VArg varg_from_d (double d);
|
||||
VArg varg_from_b (bool b);
|
||||
VArg varg_from_str (HTTP_String str);
|
||||
|
||||
#define VARG(X) (_Generic((X), \
|
||||
char : varg_from_c, \
|
||||
short : varg_from_s, \
|
||||
int : varg_from_i, \
|
||||
long : varg_from_l, \
|
||||
long long : varg_from_ll, \
|
||||
signed char : varg_from_sc, \
|
||||
/*signed short : varg_from_ss,*/ \
|
||||
/*signed int : varg_from_si,*/ \
|
||||
/*signed long : varg_from_sl,*/ \
|
||||
/*signed long long : varg_from_sll,*/ \
|
||||
unsigned char : varg_from_uc, \
|
||||
unsigned short : varg_from_us, \
|
||||
unsigned int : varg_from_ui, \
|
||||
unsigned long : varg_from_ul, \
|
||||
unsigned long long: varg_from_ull, \
|
||||
float : varg_from_f, \
|
||||
double : varg_from_d, \
|
||||
bool : varg_from_b, \
|
||||
HTTP_String : varg_from_str \
|
||||
))(X)
|
||||
|
||||
typedef struct {
|
||||
int len;
|
||||
VArg *ptr;
|
||||
} VArgs;
|
||||
|
||||
#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } }
|
||||
#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } }
|
||||
#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } }
|
||||
#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } }
|
||||
#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } }
|
||||
|
||||
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME
|
||||
#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__)
|
||||
|
||||
#endif // VARIADIC_INCLUDED
|
||||
Reference in New Issue
Block a user