Template and prepared statement caching
This commit is contained in:
@@ -2684,9 +2684,6 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one)
|
|||||||
|
|
||||||
case NODE_OPER_SHOVEL:
|
case NODE_OPER_SHOVEL:
|
||||||
{
|
{
|
||||||
Node *dst = node->left;
|
|
||||||
Node *src = node->right;
|
|
||||||
|
|
||||||
walk_expr_node(cg, node->left, true);
|
walk_expr_node(cg, node->left, true);
|
||||||
|
|
||||||
cg_push_scope(cg, SCOPE_ASSIGNMENT);
|
cg_push_scope(cg, SCOPE_ASSIGNMENT);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
typedef struct WL_Runtime WL_Runtime;
|
typedef struct WL_Runtime WL_Runtime;
|
||||||
typedef struct WL_Compiler WL_Compiler;
|
typedef struct WL_Compiler WL_Compiler;
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ sqlite3.o: 3p/sqlite3.c
|
|||||||
gcc -o $@ -c $<
|
gcc -o $@ -c $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm *.o *.out *.exe
|
rm *.o *.out *.exe
|
||||||
|
|||||||
+10
-3
@@ -1,6 +1,6 @@
|
|||||||
include "pages/page.wl"
|
include "pages/page.wl"
|
||||||
|
|
||||||
let posts = $query("SELECT id, title, is_link, content, 0 as num_comments, CURRENT_TIMESTAMP as date FROM Posts")
|
let posts = $query("SELECT P.id, P.title, P.is_link, P.content, (SELECT COUNT(*) FROM Comments as C WHERE c.parent_post=P.id) as num_comments, CURRENT_TIMESTAMP as date FROM Posts as P")
|
||||||
|
|
||||||
if posts == none:
|
if posts == none:
|
||||||
posts = []
|
posts = []
|
||||||
@@ -30,13 +30,20 @@ let style =
|
|||||||
width: 250px;
|
width: 250px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#no-posts {
|
||||||
|
margin: 60px auto;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #7A5F2A;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
let main =
|
let main =
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
\if len(posts) == 0:
|
\if len(posts) == 0:
|
||||||
<div>There are no posts yet!</div>
|
<div id="no-posts">There are no posts yet!</div>
|
||||||
|
|
||||||
\for post in posts: {
|
\for post in posts: {
|
||||||
|
|
||||||
@@ -51,7 +58,7 @@ let main =
|
|||||||
<a href=\'"'\link\'"'>\post.title</a>
|
<a href=\'"'\link\'"'>\post.title</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>\post.date</span> | <span><a href=\'"'\link\'"'>\post.num_comments comments</a></span>
|
<span>\post.date</span> | <span>\post.num_comments comments</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -4,7 +4,10 @@ include "pages/login_and_signup_style.wl"
|
|||||||
let main =
|
let main =
|
||||||
<main>
|
<main>
|
||||||
<span>Welcome back!</span>
|
<span>Welcome back!</span>
|
||||||
<form action="/api/login" method="POST">
|
|
||||||
|
<div id="response"></div>
|
||||||
|
|
||||||
|
<form hx-post="/api/login" hx-target="#response">
|
||||||
<input type="text" name="username" placeholder="username" />
|
<input type="text" name="username" placeholder="username" />
|
||||||
<input type="password" name="password" placeholder="password" />
|
<input type="password" name="password" placeholder="password" />
|
||||||
<input type="submit" value="Log-In" />
|
<input type="submit" value="Log-In" />
|
||||||
|
|||||||
@@ -59,4 +59,16 @@ let style =
|
|||||||
.form-links a:hover {
|
.form-links a:hover {
|
||||||
color: #1D2B42;
|
color: #1D2B42;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#response {
|
||||||
|
max-width: 300px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
#response .error {
|
||||||
|
margin-top: 30px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #E44C82;
|
||||||
|
background: #F295B5;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
+5
-4
@@ -43,15 +43,16 @@ procedure page(title, login_user_id, style, main)
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
\(style)
|
\(style)
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav>
|
||||||
<div>
|
<div>
|
||||||
<a href="/index">index</a>
|
<a href="/index">index</a>
|
||||||
\if login_user_id != none: {
|
\if login_user_id != none: {
|
||||||
"|\n"
|
"|\n"
|
||||||
<a href="/write">write</a>
|
<a href="/write">write</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
\if login_user_id == none:
|
\if login_user_id == none:
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+20
-16
@@ -118,21 +118,17 @@ let style =
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
.add-comment {
|
.add-comment {
|
||||||
margin: 20px 0;
|
|
||||||
padding: 15px;
|
|
||||||
background: #E8D4A9;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #D4C298;
|
|
||||||
}
|
}
|
||||||
.add-comment form {
|
.add-comment form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.add-comment form textarea {
|
.add-comment form textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background: #F7E6C0;
|
background: white;
|
||||||
border: 1px solid #D4C298;
|
border: 1px solid #D4C298;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@@ -141,7 +137,7 @@ let style =
|
|||||||
}
|
}
|
||||||
.add-comment form input[type=submit] {
|
.add-comment form input[type=submit] {
|
||||||
background: #5780C9;
|
background: #5780C9;
|
||||||
color: #F7E6C0;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
@@ -149,6 +145,7 @@ let style =
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
float: right;
|
||||||
}
|
}
|
||||||
.add-comment input[type=submit]:hover {
|
.add-comment input[type=submit]:hover {
|
||||||
background: #1D2B42;
|
background: #1D2B42;
|
||||||
@@ -162,14 +159,19 @@ let style =
|
|||||||
::-webkit-details-marker {
|
::-webkit-details-marker {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed {
|
#no-comments {
|
||||||
|
margin: 30px 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
color: #7A5F2A;
|
color: #7A5F2A;
|
||||||
font-size: 11px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
.collapsed:hover {
|
pre {
|
||||||
color: #1D2B42;
|
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||||
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||||
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -183,7 +185,7 @@ let main =
|
|||||||
submitted 3 hours ago by <a href="">\post.username</a> | <a href="">\len comments</a>
|
submitted 3 hours ago by <a href="">\post.username</a> | <a href="">\len comments</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="thread-text">
|
<div class="thread-text">
|
||||||
\post.content
|
<pre>\post.content</pre>
|
||||||
</div>
|
</div>
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
@@ -210,7 +212,7 @@ let main =
|
|||||||
<a href="">\comment.username</a> 2 hours ago
|
<a href="">\comment.username</a> 2 hours ago
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-text">
|
<div class="comment-text">
|
||||||
\comment.content
|
<pre>\comment.content</pre>
|
||||||
</div>
|
</div>
|
||||||
\if $login_user_id != none:
|
\if $login_user_id != none:
|
||||||
<details>
|
<details>
|
||||||
@@ -234,7 +236,9 @@ let main =
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
\if len root_comments == 0:
|
\if len root_comments == 0:
|
||||||
<span>(No comments)</span>
|
<div id="no-comments">
|
||||||
|
No comments
|
||||||
|
</div>
|
||||||
else for comment in root_comments:
|
else for comment in root_comments:
|
||||||
render_comment(comment)
|
render_comment(comment)
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+4
-1
@@ -4,7 +4,10 @@ include "pages/login_and_signup_style.wl"
|
|||||||
let main =
|
let main =
|
||||||
<main>
|
<main>
|
||||||
<span>Welcome!</span>
|
<span>Welcome!</span>
|
||||||
<form action="/api/signup" method="POST">
|
|
||||||
|
<div id="response"></div>
|
||||||
|
|
||||||
|
<form hx-post="/api/signup" hx-target="#response">
|
||||||
<input type="text" name="username" placeholder="username" />
|
<input type="text" name="username" placeholder="username" />
|
||||||
<input type="email" name="email" placeholder="email" />
|
<input type="email" name="email" placeholder="email" />
|
||||||
<input type="password" name="password1" placeholder="password" />
|
<input type="password" name="password1" placeholder="password" />
|
||||||
|
|||||||
+321
-376
@@ -6,10 +6,24 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "chttp.h"
|
#include "chttp.h"
|
||||||
#include "wl.h"
|
|
||||||
#include "sqlite3utils.h"
|
#include "sqlite3utils.h"
|
||||||
|
#include "template.h"
|
||||||
|
|
||||||
sqlite3 *db;
|
#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1})
|
||||||
|
|
||||||
|
#define HTML_STR(X) html_str(HTTP_STR(#X))
|
||||||
|
|
||||||
|
static HTTP_String html_str(HTTP_String str)
|
||||||
|
{
|
||||||
|
str = http_trim(str);
|
||||||
|
if (str.len > 0 && str.ptr[0] == '(') {
|
||||||
|
str.ptr++;
|
||||||
|
str.len--;
|
||||||
|
}
|
||||||
|
if (str.len > 0 && str.ptr[str.len-1] == ')')
|
||||||
|
str.len--;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
int load_file(char *file, char **data, long *size)
|
int load_file(char *file, char **data, long *size)
|
||||||
{
|
{
|
||||||
@@ -29,309 +43,64 @@ int load_file(char *file, char **data, long *size)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int query_routine(WL_Runtime *rt)
|
int create_user(SQLiteCache *dbcache, HTTP_String name, HTTP_String email, HTTP_String pass)
|
||||||
{
|
|
||||||
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 = sqlite3_prepare_v2(db, format.ptr, format.len, &stmt, 0);
|
|
||||||
if (ret != SQLITE_OK) {
|
|
||||||
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < num_args; i++) {
|
|
||||||
|
|
||||||
int64_t ival;
|
|
||||||
double fval;
|
|
||||||
WL_String str;
|
|
||||||
|
|
||||||
if (0) {}
|
|
||||||
else 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) {
|
|
||||||
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
|
|
||||||
sqlite3_finalize(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) {
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
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_finalize(stmt);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int evaluate_template(HTTP_ResponseBuilder builder,
|
|
||||||
WL_Program program, WL_Arena arena, int user_id, int post_id)
|
|
||||||
{
|
|
||||||
//wl_dump_program(program);
|
|
||||||
|
|
||||||
WL_Runtime *rt = wl_runtime_init(&arena, program);
|
|
||||||
if (rt == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
WL_EvalResult result = wl_runtime_eval(rt);
|
|
||||||
switch (result.type) {
|
|
||||||
|
|
||||||
case WL_EVAL_DONE:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case WL_EVAL_ERROR:
|
|
||||||
printf("Error: %s\n", wl_runtime_error(rt).ptr); // TODO
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
case WL_EVAL_SYSVAR:
|
|
||||||
if (wl_streq(result.str, "login_user_id", -1)) {
|
|
||||||
|
|
||||||
if (user_id < 0)
|
|
||||||
wl_push_none(rt);
|
|
||||||
else
|
|
||||||
wl_push_s64(rt, user_id);
|
|
||||||
|
|
||||||
} else if (wl_streq(result.str, "post_id", -1)) {
|
|
||||||
|
|
||||||
if (post_id < 0)
|
|
||||||
wl_push_none(rt);
|
|
||||||
else
|
|
||||||
wl_push_s64(rt, post_id);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WL_EVAL_SYSCALL:
|
|
||||||
if (wl_streq(result.str, "query", -1)) {
|
|
||||||
query_routine(rt);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WL_EVAL_OUTPUT:
|
|
||||||
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void evaluate_template_2(HTTP_ResponseBuilder builder, WL_Arena arena, char *file, int user_id, int post_id)
|
|
||||||
{
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/html"));
|
|
||||||
|
|
||||||
WL_Compiler *compiler = wl_compiler_init(&arena);
|
|
||||||
if (compiler == NULL) {
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
char *loaded_files[128];
|
|
||||||
int num_loaded_files = 0;
|
|
||||||
|
|
||||||
WL_AddResult result;
|
|
||||||
WL_String path = { file, strlen(file) };
|
|
||||||
for (int i = 0;; i++) {
|
|
||||||
|
|
||||||
char buf[1<<10];
|
|
||||||
if (path.len >= (int) sizeof(buf)) {
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
memcpy(buf, path.ptr, path.len);
|
|
||||||
buf[path.len] = '\0';
|
|
||||||
|
|
||||||
FILE *f = fopen(buf, "rb");
|
|
||||||
if (f == NULL) {
|
|
||||||
http_response_builder_undo(builder);
|
|
||||||
http_response_builder_status(builder, 500);
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Couldn't find file '"));
|
|
||||||
http_response_builder_body(builder, (HTTP_String) { path.ptr, path.len });
|
|
||||||
http_response_builder_body(builder, HTTP_STR("'"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
long file_size = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
|
|
||||||
char *file_data = malloc(file_size);
|
|
||||||
|
|
||||||
fread(file_data, 1, file_size, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
result = wl_compiler_add(compiler, (WL_String) { file_data, file_size });
|
|
||||||
|
|
||||||
loaded_files[num_loaded_files++] = file_data;
|
|
||||||
|
|
||||||
if (result.type == WL_ADD_ERROR) {
|
|
||||||
printf("Compilation of '%.*s' failed\n", path.len, path.ptr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.type == WL_ADD_LINK)
|
|
||||||
break;
|
|
||||||
|
|
||||||
assert(result.type == WL_ADD_AGAIN);
|
|
||||||
path = result.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
WL_Program program;
|
|
||||||
int ret = wl_compiler_link(compiler, &program);
|
|
||||||
|
|
||||||
for (int i = 0; i < num_loaded_files; i++)
|
|
||||||
free(loaded_files[i]);
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
WL_String err = wl_compiler_error(compiler);
|
|
||||||
http_response_builder_undo(builder);
|
|
||||||
http_response_builder_status(builder, 500);
|
|
||||||
http_response_builder_body(builder, (HTTP_String) { err.ptr, err.len });
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evaluate_template(builder, program, arena, user_id, post_id) < 0) {
|
|
||||||
http_response_builder_undo(builder);
|
|
||||||
http_response_builder_status(builder, 500);
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
int create_user(HTTP_String name, HTTP_String email, HTTP_String pass)
|
|
||||||
{
|
{
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
int ret = sqlite3utils_prepare(db, &stmt,
|
int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt,
|
||||||
"INSERT INTO Users(username, email, password) VALUES (?, ?, ?)", name, email, pass);
|
"INSERT INTO Users(username, email, password) VALUES (?, ?, ?)", name, email, pass);
|
||||||
if (ret != SQLITE_OK)
|
if (ret != SQLITE_OK)
|
||||||
return -500;
|
return -500;
|
||||||
|
|
||||||
ret = sqlite3_step(stmt);
|
ret = sqlite3_step(stmt);
|
||||||
if (ret != SQLITE_DONE) {
|
if (ret != SQLITE_DONE) {
|
||||||
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
|
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache)));
|
||||||
// TODO: What if the user exists?
|
// TODO: What if the user exists?
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_reset(stmt);
|
||||||
return -500;
|
return -500;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t tmp = sqlite3_last_insert_rowid(db);
|
int64_t tmp = sqlite3_last_insert_rowid(sqlite_cache_getdb(dbcache));
|
||||||
if (tmp < 0 || tmp > INT_MAX) {
|
if (tmp < 0 || tmp > INT_MAX) {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_reset(stmt);
|
||||||
return -500;
|
return -500;
|
||||||
}
|
}
|
||||||
int user_id = (int) tmp;
|
int user_id = (int) tmp;
|
||||||
|
|
||||||
ret = sqlite3_finalize(stmt);
|
ret = sqlite3_reset(stmt);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
|
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache)));
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_reset(stmt);
|
||||||
return -500;
|
return -500;
|
||||||
}
|
}
|
||||||
|
|
||||||
return user_id;
|
return user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
int user_exists(HTTP_String name, HTTP_String pass)
|
int user_exists(SQLiteCache *dbcache, HTTP_String name, HTTP_String pass)
|
||||||
{
|
{
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
int ret = sqlite3utils_prepare(db, &stmt,
|
int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt,
|
||||||
"SELECT id FROM Users WHERE username=? AND password=?", name, pass);
|
"SELECT id FROM Users WHERE username=? AND password=?", name, pass);
|
||||||
if (ret != SQLITE_OK)
|
if (ret != SQLITE_OK)
|
||||||
return -500;
|
return -500;
|
||||||
|
|
||||||
ret = sqlite3_step(stmt);
|
ret = sqlite3_step(stmt);
|
||||||
if (ret == SQLITE_DONE) {
|
if (ret == SQLITE_DONE) {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_reset(stmt);
|
||||||
return -404;
|
return -404;
|
||||||
}
|
}
|
||||||
if (ret != SQLITE_ROW) {
|
if (ret != SQLITE_ROW) {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_reset(stmt);
|
||||||
return -500;
|
return -500;
|
||||||
}
|
}
|
||||||
|
|
||||||
int user_id = sqlite3_column_int(stmt, 0);
|
int user_id = sqlite3_column_int(stmt, 0);
|
||||||
if (user_id < 0) {
|
if (user_id < 0) {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_reset(stmt);
|
||||||
return -500;
|
return -500;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sqlite3_finalize(stmt);
|
ret = sqlite3_reset(stmt);
|
||||||
if (ret != SQLITE_OK)
|
if (ret != SQLITE_OK)
|
||||||
return -500;
|
return -500;
|
||||||
|
|
||||||
@@ -386,7 +155,7 @@ HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name)
|
|||||||
return HTTP_STR("");
|
return HTTP_STR("");
|
||||||
}
|
}
|
||||||
|
|
||||||
void *alloc(WL_Arena *arena, int num, int align)
|
static void *alloc(WL_Arena *arena, int num, int align)
|
||||||
{
|
{
|
||||||
int pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1);
|
int pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1);
|
||||||
if (arena->len - arena->cur < num + pad)
|
if (arena->len - arena->cur < num + pad)
|
||||||
@@ -493,6 +262,28 @@ HTTP_String http_getparam(HTTP_String body, HTTP_String str, WL_Arena *arena)
|
|||||||
return HTTP_STR("");
|
return HTTP_STR("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_digit(char c)
|
||||||
|
{
|
||||||
|
return c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_getparami(HTTP_String body, HTTP_String str, WL_Arena *arena)
|
||||||
|
{
|
||||||
|
HTTP_String out = http_getparam(body, str, arena);
|
||||||
|
if (out.len == 0 || !is_digit(out.ptr[0]))
|
||||||
|
return -1;
|
||||||
|
int cur = 0;
|
||||||
|
int buf = 0;
|
||||||
|
do {
|
||||||
|
int d = out.ptr[cur++] - '0';
|
||||||
|
if (buf > (INT_MAX - d) / 10)
|
||||||
|
return -1;
|
||||||
|
buf = buf * 10 + d;
|
||||||
|
} while (cur < out.len && is_digit(out.ptr[cur]));
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
#define SESSION_LIMIT 1024
|
#define SESSION_LIMIT 1024
|
||||||
#define USERNAME_LIMIT 64
|
#define USERNAME_LIMIT 64
|
||||||
|
|
||||||
@@ -593,6 +384,7 @@ int main(void)
|
|||||||
|
|
||||||
printf("%s\n", sqlite3_libversion());
|
printf("%s\n", sqlite3_libversion());
|
||||||
|
|
||||||
|
sqlite3 *db;
|
||||||
int ret = sqlite3_open(":memory:", &db);
|
int ret = sqlite3_open(":memory:", &db);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
|
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
|
||||||
@@ -617,6 +409,10 @@ int main(void)
|
|||||||
|
|
||||||
free(schema_data);
|
free(schema_data);
|
||||||
|
|
||||||
|
TemplateCache *tpcache = template_cache_init(4);
|
||||||
|
SQLiteCache *dbcache = sqlite_cache_init(db, 5);
|
||||||
|
|
||||||
|
|
||||||
HTTP_String addr = HTTP_STR("127.0.0.1");
|
HTTP_String addr = HTTP_STR("127.0.0.1");
|
||||||
uint16_t port = 8080;
|
uint16_t port = 8080;
|
||||||
|
|
||||||
@@ -667,15 +463,25 @@ int main(void)
|
|||||||
if (http_streq(path, HTTP_STR("/api/login"))) {
|
if (http_streq(path, HTTP_STR("/api/login"))) {
|
||||||
|
|
||||||
if (req->method != HTTP_METHOD_POST) {
|
if (req->method != HTTP_METHOD_POST) {
|
||||||
http_response_builder_status(builder, 405);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid request method
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user_id != -1) {
|
if (user_id != -1) {
|
||||||
// Already logged in
|
// Already logged in
|
||||||
http_response_builder_status(builder, 303);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index"));
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
You are already logged in
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -687,14 +493,24 @@ int main(void)
|
|||||||
pass = http_trim(pass);
|
pass = http_trim(pass);
|
||||||
|
|
||||||
if (!valid_name(name) || !valid_pass(pass)) {
|
if (!valid_name(name) || !valid_pass(pass)) {
|
||||||
http_response_builder_status(builder, 400);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid credentials
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = user_exists(name, pass);
|
int ret = user_exists(dbcache, name, pass);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
http_response_builder_status(builder, -ret);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid credentials
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -702,7 +518,12 @@ int main(void)
|
|||||||
|
|
||||||
int sess_id = session_set_add(&sessions, user_id);
|
int sess_id = session_set_add(&sessions, user_id);
|
||||||
if (sess_id < 0) {
|
if (sess_id < 0) {
|
||||||
http_response_builder_status(builder, 500);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -710,28 +531,48 @@ int main(void)
|
|||||||
char cookie[1<<9];
|
char cookie[1<<9];
|
||||||
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%d; Path=/; HttpOnly", sess_id);
|
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%d; Path=/; HttpOnly", sess_id);
|
||||||
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
||||||
http_response_builder_status(builder, 500);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
|
http_response_builder_status(builder, 200); // TODO: Whats the correct code here?
|
||||||
http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len });
|
http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len });
|
||||||
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index"));
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="success">
|
||||||
|
Welcome back!
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/api/signup"))) {
|
} else if (http_streq(path, HTTP_STR("/api/signup"))) {
|
||||||
|
|
||||||
if (req->method != HTTP_METHOD_POST) {
|
if (req->method != HTTP_METHOD_POST) {
|
||||||
http_response_builder_status(builder, 405);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid request method
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user_id != -1) {
|
if (user_id != -1) {
|
||||||
// Already logged in
|
// Already logged in
|
||||||
http_response_builder_status(builder, 303);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index"));
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
You are already logged in
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -742,20 +583,35 @@ int main(void)
|
|||||||
HTTP_String pass2 = http_getparam(req->body, HTTP_STR("password2"), &arena);
|
HTTP_String pass2 = http_getparam(req->body, HTTP_STR("password2"), &arena);
|
||||||
|
|
||||||
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
|
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
|
||||||
http_response_builder_status(builder, 400);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid credentials
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!http_streq(pass1, pass2)) {
|
if (!http_streq(pass1, pass2)) {
|
||||||
http_response_builder_status(builder, 400);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
The password was repeated incorrectly
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = create_user(name, email, pass1);
|
int ret = create_user(dbcache, name, email, pass1);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
http_response_builder_status(builder, -ret);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -763,7 +619,12 @@ int main(void)
|
|||||||
|
|
||||||
int sess_id = session_set_add(&sessions, user_id);
|
int sess_id = session_set_add(&sessions, user_id);
|
||||||
if (sess_id < 0) {
|
if (sess_id < 0) {
|
||||||
http_response_builder_status(builder, 500);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -771,14 +632,24 @@ int main(void)
|
|||||||
char cookie[1<<9];
|
char cookie[1<<9];
|
||||||
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%d; Path=/; HttpOnly", sess_id);
|
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%d; Path=/; HttpOnly", sess_id);
|
||||||
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
||||||
http_response_builder_status(builder, 500);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
|
http_response_builder_status(builder, 200); // TODO: Whats the correct code here?
|
||||||
http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len });
|
http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len });
|
||||||
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index"));
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="success">
|
||||||
|
Welcome!
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/api/logout"))) {
|
} else if (http_streq(path, HTTP_STR("/api/logout"))) {
|
||||||
@@ -814,12 +685,37 @@ int main(void)
|
|||||||
link = http_trim(link);
|
link = http_trim(link);
|
||||||
content = http_trim(content);
|
content = http_trim(content);
|
||||||
|
|
||||||
if (!valid_post_title(title) || !valid_link(link) || !valid_post_content(content)) {
|
if (!valid_post_title(title)) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid title
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.len == 0 && link.len == 0) {
|
if (!valid_link(link)) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid link
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid_post_content(content)) {
|
||||||
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid content
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_link = false;
|
bool is_link = false;
|
||||||
@@ -829,31 +725,68 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
int ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content);
|
int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sqlite3_step(stmt);
|
ret = sqlite3_step(stmt);
|
||||||
if (ret != SQLITE_DONE) {
|
if (ret != SQLITE_DONE) {
|
||||||
assert(0); // TODO
|
sqlite3_reset(stmt);
|
||||||
}
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
ret = sqlite3_finalize(stmt);
|
<div class="error">
|
||||||
if (ret != SQLITE_OK) {
|
Internal error
|
||||||
assert(0); // TODO
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t tmp = sqlite3_last_insert_rowid(db);
|
int64_t tmp = sqlite3_last_insert_rowid(db);
|
||||||
if (tmp < 0 || tmp > INT_MAX) {
|
if (tmp < 0 || tmp > INT_MAX) {
|
||||||
assert(0); // TODO
|
sqlite3_reset(stmt);
|
||||||
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
int post_id = (int) tmp;
|
int post_id = (int) tmp;
|
||||||
|
|
||||||
|
ret = sqlite3_reset(stmt);
|
||||||
|
if (ret != SQLITE_OK) {
|
||||||
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
char location[1<<9];
|
char location[1<<9];
|
||||||
ret = snprintf(location, sizeof(location), "Location: /post?id=%d", post_id);
|
ret = snprintf(location, sizeof(location), "Location: /post?id=%d", post_id);
|
||||||
if (ret < 0 || ret >= (int) sizeof(location)) {
|
if (ret < 0 || ret >= (int) sizeof(location)) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_builder_status(builder, 303);
|
http_response_builder_status(builder, 303);
|
||||||
@@ -863,85 +796,96 @@ int main(void)
|
|||||||
} else if (http_streq(path, HTTP_STR("/api/comment"))) {
|
} else if (http_streq(path, HTTP_STR("/api/comment"))) {
|
||||||
|
|
||||||
if (req->method != HTTP_METHOD_POST) {
|
if (req->method != HTTP_METHOD_POST) {
|
||||||
http_response_builder_status(builder, 405);
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Invalid request method
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user_id == -1) {
|
if (user_id == -1) {
|
||||||
http_response_builder_status(builder, 303);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
You are not logged in
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTP_String parent_post_str = http_getparam(req->body, HTTP_STR("parent_post"), &arena);
|
int parent_post = http_getparami(req->body, HTTP_STR("parent_post"), &arena);
|
||||||
HTTP_String parent_comment_str = http_getparam(req->body, HTTP_STR("parent_comment"), &arena);
|
int parent_comment = http_getparami(req->body, HTTP_STR("parent_comment"), &arena);
|
||||||
HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena);
|
HTTP_String content = http_getparam (req->body, HTTP_STR("content"), &arena);
|
||||||
|
|
||||||
int parent_post;
|
|
||||||
{
|
|
||||||
char buf[32];
|
|
||||||
if (parent_post_str.len >= (int) sizeof(buf)) {
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
memcpy(buf, parent_post_str.ptr, parent_post_str.len);
|
|
||||||
buf[parent_post_str.len] = '\0';
|
|
||||||
|
|
||||||
parent_post = atoi(buf);
|
|
||||||
if (parent_post == 0) {
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int parent_comment;
|
|
||||||
{
|
|
||||||
char buf[32];
|
|
||||||
if (parent_comment_str.len >= (int) sizeof(buf))
|
|
||||||
parent_comment = -1;
|
|
||||||
else {
|
|
||||||
memcpy(buf, parent_comment_str.ptr, parent_comment_str.len);
|
|
||||||
buf[parent_comment_str.len] = '\0';
|
|
||||||
|
|
||||||
parent_comment = atoi(buf);
|
|
||||||
if (parent_comment == 0)
|
|
||||||
parent_comment = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content = http_trim(content);
|
content = http_trim(content);
|
||||||
if (!valid_comment_content(content)) {
|
if (!valid_comment_content(content)) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
}
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
if (content.len == 0) {
|
Invalid content
|
||||||
assert(0); // TODO
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
if (parent_comment == -1)
|
if (parent_comment == -1)
|
||||||
ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post);
|
ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post);
|
||||||
else
|
else
|
||||||
ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment);
|
ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sqlite3_step(stmt);
|
ret = sqlite3_step(stmt);
|
||||||
if (ret != SQLITE_DONE) {
|
if (ret != SQLITE_DONE) {
|
||||||
assert(0); // TODO
|
sqlite3_reset(stmt);
|
||||||
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sqlite3_finalize(stmt);
|
ret = sqlite3_reset(stmt);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
char location[1<<9];
|
char location[1<<9];
|
||||||
ret = snprintf(location, sizeof(location), "Location: /post?id=%d", parent_post);
|
ret = snprintf(location, sizeof(location), "Location: /post?id=%d", parent_post);
|
||||||
if (ret < 0 || ret >= (int) sizeof(location)) {
|
if (ret < 0 || ret >= (int) sizeof(location)) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTML_STR((
|
||||||
|
<div class="error">
|
||||||
|
Internal error
|
||||||
|
</div>
|
||||||
|
)));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_builder_status(builder, 303);
|
http_response_builder_status(builder, 303);
|
||||||
@@ -950,7 +894,7 @@ int main(void)
|
|||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/index"))) {
|
} else if (http_streq(path, HTTP_STR("/index"))) {
|
||||||
|
|
||||||
evaluate_template_2(builder, arena, "pages/index.wl", user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/index.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/write"))) {
|
} else if (http_streq(path, HTTP_STR("/write"))) {
|
||||||
|
|
||||||
@@ -962,7 +906,7 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate_template_2(builder, arena, "pages/write.wl", user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/write.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/login"))) {
|
} else if (http_streq(path, HTTP_STR("/login"))) {
|
||||||
|
|
||||||
@@ -974,7 +918,7 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate_template_2(builder, arena, "pages/login.wl", user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/login.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/signup"))) {
|
} else if (http_streq(path, HTTP_STR("/signup"))) {
|
||||||
|
|
||||||
@@ -986,60 +930,61 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate_template_2(builder, arena, "pages/signup.wl", user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/signup.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/post"))) {
|
} else if (http_streq(path, HTTP_STR("/post"))) {
|
||||||
|
|
||||||
HTTP_String idstr = http_getparam(req->url.query, HTTP_STR("id"), &arena);
|
int post_id = http_getparami(req->url.query, HTTP_STR("id"), &arena);
|
||||||
|
if (post_id < 0) {
|
||||||
char buf[32];
|
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
if (idstr.len == 0 || idstr.len >= (int) sizeof(buf)) {
|
|
||||||
printf("post id [%.*s] is not defined\n", idstr.len, idstr.ptr); // TODO
|
|
||||||
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
memcpy(buf, idstr.ptr, idstr.len);
|
|
||||||
buf[idstr.len] = '\0';
|
|
||||||
|
|
||||||
int post_id = atoi(buf);
|
|
||||||
if (post_id == 0) {
|
|
||||||
printf("Invalid post id [%s]\n", buf);
|
|
||||||
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
int ret = sqlite3utils_prepare(db, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
|
int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 500);
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sqlite3_step(stmt);
|
ret = sqlite3_step(stmt);
|
||||||
if (ret != SQLITE_ROW) {
|
if (ret != SQLITE_ROW) {
|
||||||
assert(0); // TODO
|
sqlite3_reset(stmt);
|
||||||
|
http_response_builder_status(builder, 500);
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
int64_t num = sqlite3_column_int64(stmt, 0);
|
int64_t num = sqlite3_column_int64(stmt, 0);
|
||||||
|
|
||||||
ret = sqlite3_finalize(stmt);
|
ret = sqlite3_reset(stmt);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 500);
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num < 0) {
|
if (num < 0) {
|
||||||
assert(0); // TODO
|
http_response_builder_status(builder, 500);
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num == 0)
|
if (num == 0) {
|
||||||
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
|
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
else
|
continue;
|
||||||
evaluate_template_2(builder, arena, "pages/post.wl", user_id, post_id);
|
}
|
||||||
|
|
||||||
|
template_eval(builder, 200, WL_STR("pages/post.wl"), tpcache, &arena, dbcache, user_id, post_id);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
|
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlite_cache_free(dbcache);
|
||||||
|
template_cache_free(tpcache);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
http_server_free(server);
|
http_server_free(server);
|
||||||
http_global_free();
|
http_global_free();
|
||||||
|
|||||||
+139
-26
@@ -1,42 +1,155 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include "sqlite3utils.h"
|
#include "sqlite3utils.h"
|
||||||
|
|
||||||
int sqlite3utils_prepare_impl(sqlite3 *db, sqlite3_stmt **pstmt, const char *fmt, VArgs args)
|
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, const 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, const char *fmt, VArgs args)
|
||||||
{
|
{
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
int ret = sqlite3_prepare_v2(db, fmt, -1, &stmt, NULL);
|
int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt));
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK)
|
||||||
fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < args.len; i++) {
|
for (int i = 0; i < args.len; i++) {
|
||||||
VArg arg = args.ptr[i];
|
VArg arg = args.ptr[i];
|
||||||
switch (arg.type) {
|
switch (arg.type) {
|
||||||
case VARG_TYPE_C : ret = sqlite3_bind_text(stmt, i+1, &arg.c, 1, NULL); break;
|
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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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;
|
case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break;
|
||||||
}
|
}
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
|
sqlite3_reset(stmt);
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -1,5 +1,21 @@
|
|||||||
|
#ifndef SQLITE3UTILS_INCLUDED
|
||||||
|
#define SQLITE3UTILS_INCLUDED
|
||||||
|
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "variadic.h"
|
#include "variadic.h"
|
||||||
|
|
||||||
int sqlite3utils_prepare_impl(sqlite3 *db, sqlite3_stmt **pstmt, const char *fmt, VArgs args);
|
#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__))
|
||||||
#define sqlite3utils_prepare(db, pstmt, fmt, ...) sqlite3utils_prepare_impl((db), (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, const char *fmt, int fmtlen);
|
||||||
|
|
||||||
|
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
|
||||||
|
sqlite3_stmt **pstmt, const char *fmt, VArgs args);
|
||||||
|
|
||||||
|
#endif // SQLITE3UTILS_INCLUDED
|
||||||
+407
@@ -0,0 +1,407 @@
|
|||||||
|
#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, WL_Arena *arena)
|
||||||
|
{
|
||||||
|
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, arena);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +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);
|
||||||
|
|
||||||
|
#endif // TEMPLATE_INCLUDED
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#ifndef VARIADIC_INCLUDED
|
||||||
|
#define VARIADIC_INCLUDED
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "chttp.h"
|
#include "chttp.h"
|
||||||
|
|
||||||
@@ -103,3 +106,5 @@ typedef struct {
|
|||||||
|
|
||||||
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME
|
#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__)
|
#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