General fixes for cWEB and updated WL
This commit is contained in:
@@ -761,14 +761,19 @@ static Node *parse_html(Parser *p)
|
||||
|
||||
bool no_body = false;
|
||||
Scanner *s = &p->s;
|
||||
for (;;) {
|
||||
for (int quote = 0;;) {
|
||||
|
||||
int off = s->cur;
|
||||
|
||||
bool quotes = false;
|
||||
while (s->cur < s->len && s->src[s->cur] != '\\' && (quotes || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) {
|
||||
if (s->src[s->cur] == '"')
|
||||
quotes = !quotes;
|
||||
while (s->cur < s->len && s->src[s->cur] != '\\' && (quote || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) {
|
||||
if (quote) {
|
||||
if (quote == s->src[s->cur])
|
||||
quote = 0;
|
||||
} else {
|
||||
char c = s->src[s->cur];
|
||||
if (c == '"' || c == '\'')
|
||||
quote = c;
|
||||
}
|
||||
s->cur++;
|
||||
}
|
||||
|
||||
@@ -1432,7 +1437,7 @@ static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags)
|
||||
Token t1 = next_token_or_newline(p);
|
||||
if (precedence(t1, flags) < min_prec) {
|
||||
p->s = saved;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
Node *right = parse_atom(p);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1 })
|
||||
|
||||
typedef struct WL_Runtime WL_Runtime;
|
||||
typedef struct WL_Compiler WL_Compiler;
|
||||
|
||||
@@ -45,17 +47,90 @@ typedef struct {
|
||||
WL_String str;
|
||||
} WL_EvalResult;
|
||||
|
||||
WL_Compiler* wl_compiler_init (WL_Arena *arena);
|
||||
WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String path, WL_String content);
|
||||
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
|
||||
WL_String wl_compiler_error (WL_Compiler *compiler);
|
||||
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
|
||||
void wl_dump_program (WL_Program program);
|
||||
// Creates a compilation unit for a program
|
||||
// The provided arena (which can't be NULL) is
|
||||
// used for all memory allocations until a
|
||||
// program is produced or an error occurs.
|
||||
// If not enough memory is provided, NULL is
|
||||
// returned.
|
||||
WL_Compiler *wl_compiler_init(WL_Arena *arena);
|
||||
|
||||
WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program);
|
||||
WL_EvalResult wl_runtime_eval (WL_Runtime *rt);
|
||||
WL_String wl_runtime_error (WL_Runtime *rt);
|
||||
void wl_runtime_dump (WL_Runtime *rt);
|
||||
// Adds a file to the current compilation unit
|
||||
// and returns
|
||||
//
|
||||
// WL_ADD_ERROR if an error occurred
|
||||
//
|
||||
// WL_ADD_AGAIN if the file included a different
|
||||
// file that also needs to be added to the unit,
|
||||
// in which case the file name is in the "path"
|
||||
// field of the return value.
|
||||
//
|
||||
// WL_ADD_LINK all sources were processed and
|
||||
// the unit is ready for linking
|
||||
//
|
||||
// Note that the source of all files needs to
|
||||
// stay alive until the program is linked as the
|
||||
// compiler may keep references to it until then.
|
||||
WL_AddResult wl_compiler_add(WL_Compiler *compiler, WL_String path, WL_String content);
|
||||
|
||||
// Links a compilation unit producing an executable.
|
||||
// The output program is just an array of bytes
|
||||
// allocated using the compiler's arena memory and
|
||||
// may be written to disk or any other external system
|
||||
// for caching.
|
||||
//
|
||||
// On error, -1 is returned and the error text can
|
||||
// be retrieved using wl_compiler_error. On success,
|
||||
// 0 is returned.
|
||||
int wl_compiler_link(WL_Compiler *compiler, WL_Program *program);
|
||||
|
||||
// Returns the null-terminated error string for a
|
||||
// compilation unit that failed.
|
||||
WL_String wl_compiler_error(WL_Compiler *compiler);
|
||||
|
||||
// Serializes the AST of the source parsed in this
|
||||
// compilation unit by writing the string to the
|
||||
// rovided buffer and returning the number of bytes
|
||||
// written to it.
|
||||
//
|
||||
// If the provided buffer is too small, the function
|
||||
// fills it up and returns the number of bytes that
|
||||
// would have been written if the buffer was large
|
||||
// enough. On error, -1 is returned.
|
||||
int wl_dump_ast(WL_Compiler *compiler, char *dst, int cap);
|
||||
|
||||
// Writes the bytecode of a program to stdout as a
|
||||
// human-readable string.
|
||||
void wl_dump_program(WL_Program program);
|
||||
|
||||
// Creates an evaluation context for a bytecode program
|
||||
// All memory used while running the program will be
|
||||
// allocated from the provided arena.
|
||||
//
|
||||
// If not enough memory was provided or the program is
|
||||
// invalid, NULL is returned.
|
||||
WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program);
|
||||
|
||||
// Run the program associated to this runtime until an
|
||||
// event happens. The event may be one of:
|
||||
//
|
||||
// WL_EVAL_DONE if execution is complete
|
||||
//
|
||||
// WL_EVAL_ERROR if execution failed
|
||||
//
|
||||
// WL_EVAL_OUTPUT if data is available for output,
|
||||
// in which case the field "str" points to it
|
||||
//
|
||||
// WL_EVAL_SYSVAR if the program requested the value
|
||||
// of an external symbol.
|
||||
//
|
||||
// WL_EVAL_SYSCALL
|
||||
//
|
||||
WL_EvalResult wl_runtime_eval(WL_Runtime *rt);
|
||||
|
||||
WL_String wl_runtime_error(WL_Runtime *rt);
|
||||
|
||||
void wl_runtime_dump(WL_Runtime *rt);
|
||||
|
||||
bool wl_streq (WL_String a, char *b, int blen);
|
||||
int wl_arg_count (WL_Runtime *rt);
|
||||
|
||||
@@ -5,7 +5,7 @@ ifeq ($(OS),Windows_NT)
|
||||
FLAGS += -lws2_32 -lbcrypt
|
||||
else
|
||||
EXT = .out
|
||||
FLAGS += -lssl -lcrypto -DHTTPS_ENABLED
|
||||
FLAGS += -lssl -lcrypto -DHTTPS_ENABLED -fsanitize=address,undefined
|
||||
endif
|
||||
|
||||
all: cweb.h cozisnews$(EXT)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
Copyright © 2025 Francesco Cozzuto
|
||||
Copyright © 2025 Francesco Cozzuto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the “Software”),
|
||||
copy of this software and associated documentation files (the “Software”),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
@@ -11,7 +11,7 @@ Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
@@ -35,6 +35,8 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#define CWEB_STR(X) ((CWEB_String) { (X), (int) sizeof(X)-1 })
|
||||
|
||||
#define CWEB_TRACE(X, ...) fprintf(stderr, ("TRACE: " X "\n"), ##__VA_ARGS__);
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
int len;
|
||||
@@ -6486,6 +6488,8 @@ int http_serve(char *addr, int port, HTTP_Router *router)
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1 })
|
||||
|
||||
typedef struct WL_Runtime WL_Runtime;
|
||||
typedef struct WL_Compiler WL_Compiler;
|
||||
|
||||
@@ -6530,17 +6534,90 @@ typedef struct {
|
||||
WL_String str;
|
||||
} WL_EvalResult;
|
||||
|
||||
WL_Compiler* wl_compiler_init (WL_Arena *arena);
|
||||
WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String path, WL_String content);
|
||||
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
|
||||
WL_String wl_compiler_error (WL_Compiler *compiler);
|
||||
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
|
||||
void wl_dump_program (WL_Program program);
|
||||
// Creates a compilation unit for a program
|
||||
// The provided arena (which can't be NULL) is
|
||||
// used for all memory allocations until a
|
||||
// program is produced or an error occurs.
|
||||
// If not enough memory is provided, NULL is
|
||||
// returned.
|
||||
WL_Compiler *wl_compiler_init(WL_Arena *arena);
|
||||
|
||||
WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program);
|
||||
WL_EvalResult wl_runtime_eval (WL_Runtime *rt);
|
||||
WL_String wl_runtime_error (WL_Runtime *rt);
|
||||
void wl_runtime_dump (WL_Runtime *rt);
|
||||
// Adds a file to the current compilation unit
|
||||
// and returns
|
||||
//
|
||||
// WL_ADD_ERROR if an error occurred
|
||||
//
|
||||
// WL_ADD_AGAIN if the file included a different
|
||||
// file that also needs to be added to the unit,
|
||||
// in which case the file name is in the "path"
|
||||
// field of the return value.
|
||||
//
|
||||
// WL_ADD_LINK all sources were processed and
|
||||
// the unit is ready for linking
|
||||
//
|
||||
// Note that the source of all files needs to
|
||||
// stay alive until the program is linked as the
|
||||
// compiler may keep references to it until then.
|
||||
WL_AddResult wl_compiler_add(WL_Compiler *compiler, WL_String path, WL_String content);
|
||||
|
||||
// Links a compilation unit producing an executable.
|
||||
// The output program is just an array of bytes
|
||||
// allocated using the compiler's arena memory and
|
||||
// may be written to disk or any other external system
|
||||
// for caching.
|
||||
//
|
||||
// On error, -1 is returned and the error text can
|
||||
// be retrieved using wl_compiler_error. On success,
|
||||
// 0 is returned.
|
||||
int wl_compiler_link(WL_Compiler *compiler, WL_Program *program);
|
||||
|
||||
// Returns the null-terminated error string for a
|
||||
// compilation unit that failed.
|
||||
WL_String wl_compiler_error(WL_Compiler *compiler);
|
||||
|
||||
// Serializes the AST of the source parsed in this
|
||||
// compilation unit by writing the string to the
|
||||
// rovided buffer and returning the number of bytes
|
||||
// written to it.
|
||||
//
|
||||
// If the provided buffer is too small, the function
|
||||
// fills it up and returns the number of bytes that
|
||||
// would have been written if the buffer was large
|
||||
// enough. On error, -1 is returned.
|
||||
int wl_dump_ast(WL_Compiler *compiler, char *dst, int cap);
|
||||
|
||||
// Writes the bytecode of a program to stdout as a
|
||||
// human-readable string.
|
||||
void wl_dump_program(WL_Program program);
|
||||
|
||||
// Creates an evaluation context for a bytecode program
|
||||
// All memory used while running the program will be
|
||||
// allocated from the provided arena.
|
||||
//
|
||||
// If not enough memory was provided or the program is
|
||||
// invalid, NULL is returned.
|
||||
WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program);
|
||||
|
||||
// Run the program associated to this runtime until an
|
||||
// event happens. The event may be one of:
|
||||
//
|
||||
// WL_EVAL_DONE if execution is complete
|
||||
//
|
||||
// WL_EVAL_ERROR if execution failed
|
||||
//
|
||||
// WL_EVAL_OUTPUT if data is available for output,
|
||||
// in which case the field "str" points to it
|
||||
//
|
||||
// WL_EVAL_SYSVAR if the program requested the value
|
||||
// of an external symbol.
|
||||
//
|
||||
// WL_EVAL_SYSCALL
|
||||
//
|
||||
WL_EvalResult wl_runtime_eval(WL_Runtime *rt);
|
||||
|
||||
WL_String wl_runtime_error(WL_Runtime *rt);
|
||||
|
||||
void wl_runtime_dump(WL_Runtime *rt);
|
||||
|
||||
bool wl_streq (WL_String a, char *b, int blen);
|
||||
int wl_arg_count (WL_Runtime *rt);
|
||||
@@ -7342,14 +7419,19 @@ static Node *parse_html(Parser *p)
|
||||
|
||||
bool no_body = false;
|
||||
Scanner *s = &p->s;
|
||||
for (;;) {
|
||||
for (int quote = 0;;) {
|
||||
|
||||
int off = s->cur;
|
||||
|
||||
bool quotes = false;
|
||||
while (s->cur < s->len && s->src[s->cur] != '\\' && (quotes || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) {
|
||||
if (s->src[s->cur] == '"')
|
||||
quotes = !quotes;
|
||||
while (s->cur < s->len && s->src[s->cur] != '\\' && (quote || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) {
|
||||
if (quote) {
|
||||
if (quote == s->src[s->cur])
|
||||
quote = 0;
|
||||
} else {
|
||||
char c = s->src[s->cur];
|
||||
if (c == '"' || c == '\'')
|
||||
quote = c;
|
||||
}
|
||||
s->cur++;
|
||||
}
|
||||
|
||||
@@ -8013,7 +8095,7 @@ static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags)
|
||||
Token t1 = next_token_or_newline(p);
|
||||
if (precedence(t1, flags) < min_prec) {
|
||||
p->s = saved;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
Node *right = parse_atom(p);
|
||||
@@ -13534,6 +13616,49 @@ CWEB_String cweb_trim(CWEB_String s)
|
||||
return (CWEB_String) { s2.ptr, s2.len };
|
||||
}
|
||||
|
||||
static const char *type_names__[] = {
|
||||
[CWEB_VARG_TYPE_C] = "char",
|
||||
[CWEB_VARG_TYPE_S] = "short",
|
||||
[CWEB_VARG_TYPE_I] = "int",
|
||||
[CWEB_VARG_TYPE_L] = "long",
|
||||
[CWEB_VARG_TYPE_LL] = "long long",
|
||||
[CWEB_VARG_TYPE_SC] = "signed char",
|
||||
[CWEB_VARG_TYPE_SS] = "signed short",
|
||||
[CWEB_VARG_TYPE_SI] = "signed int",
|
||||
[CWEB_VARG_TYPE_SL] = "signed long",
|
||||
[CWEB_VARG_TYPE_SLL] = "signed long long",
|
||||
[CWEB_VARG_TYPE_UC] = "unsigned char",
|
||||
[CWEB_VARG_TYPE_US] = "unsigned short",
|
||||
[CWEB_VARG_TYPE_UI] = "unsigned int",
|
||||
[CWEB_VARG_TYPE_UL] = "unsigned long",
|
||||
[CWEB_VARG_TYPE_ULL] = "unsigned long long",
|
||||
[CWEB_VARG_TYPE_F] = "float",
|
||||
[CWEB_VARG_TYPE_D] = "double",
|
||||
[CWEB_VARG_TYPE_B] = "bool",
|
||||
[CWEB_VARG_TYPE_STR] = "string",
|
||||
[CWEB_VARG_TYPE_HASH] = "hash",
|
||||
[CWEB_VARG_TYPE_PC] = "char*",
|
||||
[CWEB_VARG_TYPE_PS] = "short*",
|
||||
[CWEB_VARG_TYPE_PI] = "int*",
|
||||
[CWEB_VARG_TYPE_PL] = "long*",
|
||||
[CWEB_VARG_TYPE_PLL] = "long long*",
|
||||
[CWEB_VARG_TYPE_PSC] = "signed char*",
|
||||
[CWEB_VARG_TYPE_PSS] = "signed short*",
|
||||
[CWEB_VARG_TYPE_PSI] = "signed int*",
|
||||
[CWEB_VARG_TYPE_PSL] = "signed long*",
|
||||
[CWEB_VARG_TYPE_PSLL] = "signed long long*",
|
||||
[CWEB_VARG_TYPE_PUC] = "unsigned char*",
|
||||
[CWEB_VARG_TYPE_PUS] = "unsigned short*",
|
||||
[CWEB_VARG_TYPE_PUI] = "unsigned int*",
|
||||
[CWEB_VARG_TYPE_PUL] = "unsigned long*",
|
||||
[CWEB_VARG_TYPE_PULL] = "unsigned long long*",
|
||||
[CWEB_VARG_TYPE_PF] = "float*",
|
||||
[CWEB_VARG_TYPE_PD] = "double*",
|
||||
[CWEB_VARG_TYPE_PB] = "bool*",
|
||||
[CWEB_VARG_TYPE_PSTR] = "string*",
|
||||
[CWEB_VARG_TYPE_PHASH] = "hash*",
|
||||
};
|
||||
|
||||
CWEB_VArg cweb_varg_from_c (char c) { return (CWEB_VArg) { CWEB_VARG_TYPE_C, .c=c }; }
|
||||
CWEB_VArg cweb_varg_from_s (short s) { return (CWEB_VArg) { CWEB_VARG_TYPE_S, .s=s }; }
|
||||
CWEB_VArg cweb_varg_from_i (int i) { return (CWEB_VArg) { CWEB_VARG_TYPE_I, .i=i }; }
|
||||
@@ -13575,7 +13700,6 @@ CWEB_VArg cweb_varg_from_pb (bool *pb) { return (CWEB_VArg) { CWEB_VA
|
||||
CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSTR, .pstr=pstr }; }
|
||||
CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash) { return (CWEB_VArg) { CWEB_VARG_TYPE_PHASH, .phash=phash }; }
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *dst;
|
||||
int cap;
|
||||
@@ -13728,7 +13852,7 @@ static void free_loaded_files(LoadedFile *loaded_file)
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
// RANDOM
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -13783,14 +13907,18 @@ int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash)
|
||||
int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash)
|
||||
{
|
||||
char passzt[128];
|
||||
if (pass.len >= (int) sizeof(passzt))
|
||||
if (pass.len >= (int) sizeof(passzt)) {
|
||||
CWEB_TRACE("%s: static buffer limit reached", __func__);
|
||||
return -1;
|
||||
}
|
||||
memcpy(passzt, pass.ptr, pass.len);
|
||||
passzt[pass.len] = '\0';
|
||||
|
||||
CWEB_PasswordHash new_hash;
|
||||
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL)
|
||||
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL) {
|
||||
CWEB_TRACE("%s: couldn't calculate hash (password=[%.*s], hash=[%.*s])", __func__, pass.len, pass.ptr, (int) strnlen(hash.data, sizeof(hash.data)), hash.data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
|
||||
return 1;
|
||||
@@ -13863,7 +13991,7 @@ static int pack_token(char *src, int srclen, char *dst, int dstlen)
|
||||
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);
|
||||
dst[i >> 1] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -14181,11 +14309,14 @@ int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args)
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args);
|
||||
if (ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "sqlite3 prepare+bind error: %s (%s:%d)\n", sqlite3_errmsg(cweb->db), __FILE__, __LINE__); // TODO
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = sqlite3_step(stmt);
|
||||
if (ret != SQLITE_DONE) {
|
||||
fprintf(stderr, "sqlite3_step error: %s (%s:%d)\n", sqlite3_errmsg(cweb->db), __FILE__, __LINE__); // TODO
|
||||
sqlite3_reset(stmt);
|
||||
return -1;
|
||||
}
|
||||
@@ -14211,8 +14342,10 @@ CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs arg
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args);
|
||||
if (ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "sqlite3 prepare+bind error: %s (%s:%d)\n", sqlite3_errmsg(cweb->db), __FILE__, __LINE__); // TODO
|
||||
return (CWEB_QueryResult) { NULL };
|
||||
}
|
||||
|
||||
return (CWEB_QueryResult) { stmt };
|
||||
#else
|
||||
@@ -14223,21 +14356,27 @@ CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs arg
|
||||
int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
{
|
||||
#ifdef CWEB_ENABLE_DATABASE
|
||||
if (res->handle == NULL)
|
||||
if (res->handle == NULL) {
|
||||
CWEB_TRACE("%s failed because database is initialized", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = sqlite3_step(res->handle);
|
||||
|
||||
if (ret == SQLITE_DONE)
|
||||
if (ret == SQLITE_DONE) {
|
||||
CWEB_TRACE("%s returned no row", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret != SQLITE_ROW) {
|
||||
CWEB_TRACE("%s didn't return ROW or DONE (%d)", __func__, ret);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sqlite3_column_count(res->handle) != args.len) {
|
||||
CWEB_TRACE("%s returned an unexpected column count", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -14250,6 +14389,7 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
{
|
||||
int64_t x = sqlite3_column_int64(res->handle, i);
|
||||
if (x < INT_MIN || x > INT_MAX) {
|
||||
CWEB_TRACE("%s couldn't bind integer out of range", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -14262,6 +14402,7 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
{
|
||||
int64_t x = sqlite3_column_int64(res->handle, i);
|
||||
if (x < LONG_MIN || x > LONG_MAX) {
|
||||
CWEB_TRACE("%s couldn't bind integer out of range", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -14300,7 +14441,27 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
}
|
||||
break;
|
||||
|
||||
case CWEB_VARG_TYPE_PHASH:
|
||||
{
|
||||
char *ptr = sqlite3_column_text(res->handle, i);
|
||||
int len = sqlite3_column_bytes(res->handle, i);
|
||||
|
||||
CWEB_TRACE("%s: hash value (\"%.*s\", %d)", __func__, len, ptr, len);
|
||||
|
||||
CWEB_PasswordHash *hash = args.ptr[i].phash;
|
||||
if ((int) sizeof(hash->data) <= len) {
|
||||
CWEB_TRACE("%s couldn't bind to hash argument", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
}
|
||||
memset(hash->data, 0, sizeof(hash->data));
|
||||
memcpy(hash->data, ptr, len);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CWEB_TRACE("%s couldn't bind to unexpected argument type %s", __func__, type_names__[args.ptr[i].type]);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -14311,6 +14472,7 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
#else
|
||||
(void) res;
|
||||
(void) args;
|
||||
CWEB_TRACE("%s is unsupported", __func__);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
+5
-1
@@ -55,20 +55,24 @@ static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass)
|
||||
CWEB_PasswordHash hash;
|
||||
int ret = cweb_next_query_row(&res, &user_id, &hash);
|
||||
if (ret < 0) {
|
||||
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
|
||||
cweb_free_query_result(&res);
|
||||
return -1;
|
||||
}
|
||||
if (ret == 0) {
|
||||
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
|
||||
cweb_free_query_result(&res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = cweb_check_password(pass, hash);
|
||||
if (ret < 0) {
|
||||
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
|
||||
cweb_free_query_result(&res);
|
||||
return -1;
|
||||
}
|
||||
if (ret > 0) {
|
||||
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
|
||||
cweb_free_query_result(&res);
|
||||
return 0;
|
||||
}
|
||||
@@ -329,7 +333,7 @@ int main(void)
|
||||
{
|
||||
CWEB_String addr = CWEB_STR("127.0.0.1");
|
||||
uint16_t port = 8080;
|
||||
CWEB_String database_file = CWEB_STR("demo.db");
|
||||
CWEB_String database_file = CWEB_STR(":memory:");
|
||||
CWEB_String schema_file = CWEB_STR("demo/schema.sql");
|
||||
|
||||
if (cweb_global_init() < 0)
|
||||
|
||||
+4
-3
@@ -42,8 +42,9 @@ let style =
|
||||
let main =
|
||||
<main>
|
||||
|
||||
\if len(posts) == 0:
|
||||
\if len(posts) == 0: {
|
||||
<div id="no-posts">There are no posts yet!</div>
|
||||
}
|
||||
|
||||
\for post in posts: {
|
||||
|
||||
@@ -55,10 +56,10 @@ let main =
|
||||
|
||||
<div class="item">
|
||||
<div>
|
||||
<a href=\'"'\link\'"'>\escape(post.title)</a>
|
||||
<a href="\{link}">\{escape post.title}</a>
|
||||
</div>
|
||||
<div>
|
||||
<span>\escape(post.date)</span> | <span>\escape(post.num_comments) comments</span>
|
||||
<span>\{escape post.date}</span> | <span>\{escape post.num_comments} comments</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
+70
-69
@@ -1,75 +1,76 @@
|
||||
|
||||
procedure page(title, login_user_id, style, main)
|
||||
procedure page(title, login_user_id, style, main) {
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>CN - \escape(title)</title>
|
||||
<style>
|
||||
body {
|
||||
line-height: 200%;
|
||||
font-family: monospace;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
nav {
|
||||
overflow: auto;
|
||||
background: #5780C9;
|
||||
color: #6E93D4;
|
||||
padding: 5px 10px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
nav div {
|
||||
float: left
|
||||
}
|
||||
nav div:last-child {
|
||||
float: right
|
||||
}
|
||||
nav a {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
}
|
||||
nav a.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
main {
|
||||
padding: 5px 10px;
|
||||
background: #F7E6C0;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
footer {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
</style>
|
||||
\(style)
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div>
|
||||
<a href="/index">index</a>
|
||||
\if login_user_id != none: {
|
||||
"|\n"
|
||||
<a href="/write">write</a>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>CN - \{escape title}</title>
|
||||
<style>
|
||||
body {
|
||||
line-height: 200%;
|
||||
font-family: monospace;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
</div>
|
||||
\if login_user_id == none:
|
||||
nav {
|
||||
overflow: auto;
|
||||
background: #5780C9;
|
||||
color: #6E93D4;
|
||||
padding: 5px 10px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
nav div {
|
||||
float: left
|
||||
}
|
||||
nav div:last-child {
|
||||
float: right
|
||||
}
|
||||
nav a {
|
||||
font-size: 14px;
|
||||
color: #1D2B42;
|
||||
}
|
||||
nav a.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
main {
|
||||
padding: 5px 10px;
|
||||
background: #F7E6C0;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
footer {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
</style>
|
||||
\{style}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div>
|
||||
<a href="/login">log-in</a>
|
||||
|
|
||||
<a href="/signup">sign-up</a>
|
||||
<a href="/index">index</a>
|
||||
\if login_user_id != none: {
|
||||
"|\n"
|
||||
<a href="/write">write</a>
|
||||
}
|
||||
</div>
|
||||
else
|
||||
<div>
|
||||
<a href="">settings</a>
|
||||
|
|
||||
<a href="/api/logout">log-out</a>
|
||||
</div>
|
||||
</nav>
|
||||
\main
|
||||
<footer>
|
||||
Made with love by cozis
|
||||
</footer>
|
||||
</body>
|
||||
\if login_user_id == none:
|
||||
<div>
|
||||
<a href="/login">log-in</a>
|
||||
|
|
||||
<a href="/signup">sign-up</a>
|
||||
</div>
|
||||
else
|
||||
<div>
|
||||
<a href="">settings</a>
|
||||
|
|
||||
<a href="/api/logout">log-out</a>
|
||||
</div>
|
||||
</nav>
|
||||
\{main}
|
||||
<footer>
|
||||
Made with love by cozis
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
+41
-41
@@ -180,13 +180,13 @@ let main =
|
||||
<main>
|
||||
<div class="thread-header">
|
||||
<div class="thread-title">
|
||||
<span>\escape(post.title)</span>
|
||||
<span>\{escape post.title}</span>
|
||||
</div>
|
||||
<div class="thread-meta">
|
||||
submitted 3 hours ago by <a href="">\escape(post.username)</a> | <a href="">\len(comments)</a>
|
||||
submitted 3 hours ago by <a href="">\{escape post.username}</a> | <a href="">\{len comments}</a>
|
||||
</div>
|
||||
<div class="thread-text">
|
||||
<pre>\escape(post.content)</pre>
|
||||
<pre>\{escape post.content}</pre>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
@@ -194,50 +194,14 @@ let main =
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="hidden" name="parent_post" value=\'"'\post_id\'"' />
|
||||
<input type="hidden" name="csrf" value="\{$csrf}" />
|
||||
<input type="hidden" name="parent_post" value="\{post_id}" />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
\procedure render_comment(post_id, comment)
|
||||
<div class="comment">
|
||||
<div class="vote-buttons">
|
||||
<a href="">▲</a>
|
||||
<a href="">▼</a>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-meta">
|
||||
<a href="">\escape(comment.username)</a> 2 hours ago
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
<pre>\escape(comment.content)</pre>
|
||||
</div>
|
||||
\if $login_user_id != none:
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="hidden" name="parent_post" value=\'"'\post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="comment-child">
|
||||
\for child in comment.child:
|
||||
render_comment(post_id, child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
\if len(root_comments) == 0:
|
||||
<div id="no-comments">
|
||||
No comments
|
||||
@@ -246,4 +210,40 @@ let main =
|
||||
render_comment(post_id, comment)
|
||||
</main>
|
||||
|
||||
procedure render_comment(post_id, comment) {
|
||||
<div class="comment">
|
||||
<div class="vote-buttons">
|
||||
<a href="">▲</a>
|
||||
<a href="">▼</a>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-meta">
|
||||
<a href="">\{escape comment.username}</a> 2 hours ago
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
<pre>\{escape comment.content}</pre>
|
||||
</div>
|
||||
\if $login_user_id != none:
|
||||
<details>
|
||||
<summary>
|
||||
reply
|
||||
</summary>
|
||||
<div class="add-comment">
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="csrf" value="\{$csrf}" />
|
||||
<input type="hidden" name="parent_post" value="\{post_id}" />
|
||||
<input type="hidden" name="parent_comment" value="\{comment.id}" />
|
||||
<textarea name="content" placeholder="Add a comment..."></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="comment-child">
|
||||
\for child in comment.child:
|
||||
render_comment(post_id, child)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
page(post.title, $login_user_id, style, main)
|
||||
@@ -1,73 +0,0 @@
|
||||
include "page.wl"
|
||||
|
||||
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
|
||||
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
|
||||
|
||||
let lookup = {}
|
||||
|
||||
for comment in comments: {
|
||||
comment.child = []
|
||||
lookup[comment.id] = comment
|
||||
}
|
||||
|
||||
let roots = []
|
||||
|
||||
for comment in comments: {
|
||||
if comment.parent_comment == none:
|
||||
roots << comment
|
||||
else
|
||||
lookup[comment.parent_comment].child << comment
|
||||
}
|
||||
|
||||
comments = roots
|
||||
|
||||
let post = posts[0]
|
||||
|
||||
let style =
|
||||
<style>
|
||||
.child {
|
||||
border-left: 3px solid #ccc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
form textarea {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
procedure render_comment(parent)
|
||||
<div>
|
||||
<a href="">\parent.username</a>
|
||||
<p>
|
||||
\parent.content
|
||||
</p>
|
||||
<div class="child">
|
||||
\if $login_user_id != none:
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
\for child in parent.child:
|
||||
render_comment(child)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let main =
|
||||
<main>
|
||||
<h3>\post.title</h3>
|
||||
<p>\post.content</p>
|
||||
<div>
|
||||
<form action="/api/comment" method="POST">
|
||||
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
|
||||
<textarea name="content"></textarea>
|
||||
<input type="submit" vaue="Publish" />
|
||||
</form>
|
||||
</div>
|
||||
\if len comments == 0:
|
||||
<span>No comments yet!</span>
|
||||
else for comment in comments:
|
||||
render_comment(comment)
|
||||
</main>
|
||||
|
||||
page(post.title, $login_user_id, style, main)
|
||||
+1
-1
@@ -60,7 +60,7 @@ let main =
|
||||
<main>
|
||||
<form action="/api/post" method="POST">
|
||||
|
||||
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||
<input type="hidden" name="csrf" value="\{$csrf}" />
|
||||
<input type="text" id="title" name="title" placeholder="Title" required />
|
||||
|
||||
<div class="checkbox-row">
|
||||
|
||||
+89
-9
@@ -122,6 +122,49 @@ CWEB_String cweb_trim(CWEB_String s)
|
||||
return (CWEB_String) { s2.ptr, s2.len };
|
||||
}
|
||||
|
||||
static const char *type_names__[] = {
|
||||
[CWEB_VARG_TYPE_C] = "char",
|
||||
[CWEB_VARG_TYPE_S] = "short",
|
||||
[CWEB_VARG_TYPE_I] = "int",
|
||||
[CWEB_VARG_TYPE_L] = "long",
|
||||
[CWEB_VARG_TYPE_LL] = "long long",
|
||||
[CWEB_VARG_TYPE_SC] = "signed char",
|
||||
[CWEB_VARG_TYPE_SS] = "signed short",
|
||||
[CWEB_VARG_TYPE_SI] = "signed int",
|
||||
[CWEB_VARG_TYPE_SL] = "signed long",
|
||||
[CWEB_VARG_TYPE_SLL] = "signed long long",
|
||||
[CWEB_VARG_TYPE_UC] = "unsigned char",
|
||||
[CWEB_VARG_TYPE_US] = "unsigned short",
|
||||
[CWEB_VARG_TYPE_UI] = "unsigned int",
|
||||
[CWEB_VARG_TYPE_UL] = "unsigned long",
|
||||
[CWEB_VARG_TYPE_ULL] = "unsigned long long",
|
||||
[CWEB_VARG_TYPE_F] = "float",
|
||||
[CWEB_VARG_TYPE_D] = "double",
|
||||
[CWEB_VARG_TYPE_B] = "bool",
|
||||
[CWEB_VARG_TYPE_STR] = "string",
|
||||
[CWEB_VARG_TYPE_HASH] = "hash",
|
||||
[CWEB_VARG_TYPE_PC] = "char*",
|
||||
[CWEB_VARG_TYPE_PS] = "short*",
|
||||
[CWEB_VARG_TYPE_PI] = "int*",
|
||||
[CWEB_VARG_TYPE_PL] = "long*",
|
||||
[CWEB_VARG_TYPE_PLL] = "long long*",
|
||||
[CWEB_VARG_TYPE_PSC] = "signed char*",
|
||||
[CWEB_VARG_TYPE_PSS] = "signed short*",
|
||||
[CWEB_VARG_TYPE_PSI] = "signed int*",
|
||||
[CWEB_VARG_TYPE_PSL] = "signed long*",
|
||||
[CWEB_VARG_TYPE_PSLL] = "signed long long*",
|
||||
[CWEB_VARG_TYPE_PUC] = "unsigned char*",
|
||||
[CWEB_VARG_TYPE_PUS] = "unsigned short*",
|
||||
[CWEB_VARG_TYPE_PUI] = "unsigned int*",
|
||||
[CWEB_VARG_TYPE_PUL] = "unsigned long*",
|
||||
[CWEB_VARG_TYPE_PULL] = "unsigned long long*",
|
||||
[CWEB_VARG_TYPE_PF] = "float*",
|
||||
[CWEB_VARG_TYPE_PD] = "double*",
|
||||
[CWEB_VARG_TYPE_PB] = "bool*",
|
||||
[CWEB_VARG_TYPE_PSTR] = "string*",
|
||||
[CWEB_VARG_TYPE_PHASH] = "hash*",
|
||||
};
|
||||
|
||||
CWEB_VArg cweb_varg_from_c (char c) { return (CWEB_VArg) { CWEB_VARG_TYPE_C, .c=c }; }
|
||||
CWEB_VArg cweb_varg_from_s (short s) { return (CWEB_VArg) { CWEB_VARG_TYPE_S, .s=s }; }
|
||||
CWEB_VArg cweb_varg_from_i (int i) { return (CWEB_VArg) { CWEB_VARG_TYPE_I, .i=i }; }
|
||||
@@ -163,7 +206,6 @@ CWEB_VArg cweb_varg_from_pb (bool *pb) { return (CWEB_VArg) { CWEB_VA
|
||||
CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSTR, .pstr=pstr }; }
|
||||
CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash) { return (CWEB_VArg) { CWEB_VARG_TYPE_PHASH, .phash=phash }; }
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *dst;
|
||||
int cap;
|
||||
@@ -316,7 +358,7 @@ static void free_loaded_files(LoadedFile *loaded_file)
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
// RANDOM
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -371,14 +413,18 @@ int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash)
|
||||
int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash)
|
||||
{
|
||||
char passzt[128];
|
||||
if (pass.len >= (int) sizeof(passzt))
|
||||
if (pass.len >= (int) sizeof(passzt)) {
|
||||
CWEB_TRACE("%s: static buffer limit reached", __func__);
|
||||
return -1;
|
||||
}
|
||||
memcpy(passzt, pass.ptr, pass.len);
|
||||
passzt[pass.len] = '\0';
|
||||
|
||||
CWEB_PasswordHash new_hash;
|
||||
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL)
|
||||
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL) {
|
||||
CWEB_TRACE("%s: couldn't calculate hash (password=[%.*s], hash=[%.*s])", __func__, pass.len, pass.ptr, (int) strnlen(hash.data, sizeof(hash.data)), hash.data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
|
||||
return 1;
|
||||
@@ -451,7 +497,7 @@ static int pack_token(char *src, int srclen, char *dst, int dstlen)
|
||||
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);
|
||||
dst[i >> 1] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -769,11 +815,14 @@ int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args)
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args);
|
||||
if (ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "sqlite3 prepare+bind error: %s (%s:%d)\n", sqlite3_errmsg(cweb->db), __FILE__, __LINE__); // TODO
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = sqlite3_step(stmt);
|
||||
if (ret != SQLITE_DONE) {
|
||||
fprintf(stderr, "sqlite3_step error: %s (%s:%d)\n", sqlite3_errmsg(cweb->db), __FILE__, __LINE__); // TODO
|
||||
sqlite3_reset(stmt);
|
||||
return -1;
|
||||
}
|
||||
@@ -799,8 +848,10 @@ CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs arg
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args);
|
||||
if (ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK) {
|
||||
fprintf(stderr, "sqlite3 prepare+bind error: %s (%s:%d)\n", sqlite3_errmsg(cweb->db), __FILE__, __LINE__); // TODO
|
||||
return (CWEB_QueryResult) { NULL };
|
||||
}
|
||||
|
||||
return (CWEB_QueryResult) { stmt };
|
||||
#else
|
||||
@@ -811,21 +862,27 @@ CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs arg
|
||||
int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
{
|
||||
#ifdef CWEB_ENABLE_DATABASE
|
||||
if (res->handle == NULL)
|
||||
if (res->handle == NULL) {
|
||||
CWEB_TRACE("%s failed because database is initialized", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = sqlite3_step(res->handle);
|
||||
|
||||
if (ret == SQLITE_DONE)
|
||||
if (ret == SQLITE_DONE) {
|
||||
CWEB_TRACE("%s returned no row", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret != SQLITE_ROW) {
|
||||
CWEB_TRACE("%s didn't return ROW or DONE (%d)", __func__, ret);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sqlite3_column_count(res->handle) != args.len) {
|
||||
CWEB_TRACE("%s returned an unexpected column count", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -838,6 +895,7 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
{
|
||||
int64_t x = sqlite3_column_int64(res->handle, i);
|
||||
if (x < INT_MIN || x > INT_MAX) {
|
||||
CWEB_TRACE("%s couldn't bind integer out of range", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -850,6 +908,7 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
{
|
||||
int64_t x = sqlite3_column_int64(res->handle, i);
|
||||
if (x < LONG_MIN || x > LONG_MAX) {
|
||||
CWEB_TRACE("%s couldn't bind integer out of range", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -888,7 +947,27 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
}
|
||||
break;
|
||||
|
||||
case CWEB_VARG_TYPE_PHASH:
|
||||
{
|
||||
char *ptr = sqlite3_column_text(res->handle, i);
|
||||
int len = sqlite3_column_bytes(res->handle, i);
|
||||
|
||||
CWEB_TRACE("%s: hash value (\"%.*s\", %d)", __func__, len, ptr, len);
|
||||
|
||||
CWEB_PasswordHash *hash = args.ptr[i].phash;
|
||||
if ((int) sizeof(hash->data) <= len) {
|
||||
CWEB_TRACE("%s couldn't bind to hash argument", __func__);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
}
|
||||
memset(hash->data, 0, sizeof(hash->data));
|
||||
memcpy(hash->data, ptr, len);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CWEB_TRACE("%s couldn't bind to unexpected argument type %s", __func__, type_names__[args.ptr[i].type]);
|
||||
sqlite3_reset(res->handle);
|
||||
res->handle = NULL;
|
||||
return -1;
|
||||
@@ -899,6 +978,7 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args)
|
||||
#else
|
||||
(void) res;
|
||||
(void) args;
|
||||
CWEB_TRACE("%s is unsupported", __func__);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#define CWEB_STR(X) ((CWEB_String) { (X), (int) sizeof(X)-1 })
|
||||
|
||||
#define CWEB_TRACE(X, ...) fprintf(stderr, ("TRACE: " X "\n"), ##__VA_ARGS__);
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
int len;
|
||||
|
||||
Reference in New Issue
Block a user