Add CSRF tokens

This commit is contained in:
2025-08-17 18:59:13 +02:00
parent e67eb4175b
commit 2ee58c2b0a
35 changed files with 10241 additions and 10128 deletions
+4 -4
View File
@@ -1,4 +1,4 @@
*.o *.o
*.exe *.exe
*.out *.out
+5650 -5650
View File
File diff suppressed because it is too large Load Diff
+90 -90
View File
@@ -1,90 +1,90 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.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;
typedef struct { typedef struct {
char *ptr; char *ptr;
int len; int len;
} WL_String; } WL_String;
typedef struct { typedef struct {
char *ptr; char *ptr;
int len; int len;
int cur; int cur;
} WL_Arena; } WL_Arena;
typedef struct { typedef struct {
char *ptr; char *ptr;
int len; int len;
} WL_Program; } WL_Program;
typedef enum { typedef enum {
WL_ADD_ERROR, WL_ADD_ERROR,
WL_ADD_AGAIN, WL_ADD_AGAIN,
WL_ADD_LINK, WL_ADD_LINK,
} WL_AddResultType; } WL_AddResultType;
typedef struct { typedef struct {
WL_AddResultType type; WL_AddResultType type;
WL_String path; WL_String path;
} WL_AddResult; } WL_AddResult;
typedef enum { typedef enum {
WL_EVAL_NONE, WL_EVAL_NONE,
WL_EVAL_DONE, WL_EVAL_DONE,
WL_EVAL_ERROR, WL_EVAL_ERROR,
WL_EVAL_OUTPUT, WL_EVAL_OUTPUT,
WL_EVAL_SYSVAR, WL_EVAL_SYSVAR,
WL_EVAL_SYSCALL, WL_EVAL_SYSCALL,
} WL_EvalResultType; } WL_EvalResultType;
typedef struct { typedef struct {
WL_EvalResultType type; WL_EvalResultType type;
WL_String str; WL_String str;
} WL_EvalResult; } WL_EvalResult;
WL_Compiler* wl_compiler_init (WL_Arena *arena); WL_Compiler* wl_compiler_init (WL_Arena *arena);
WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content); WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content);
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program); int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
WL_String wl_compiler_error (WL_Compiler *compiler); WL_String wl_compiler_error (WL_Compiler *compiler);
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap); int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
void wl_dump_program (WL_Program program); void wl_dump_program (WL_Program program);
WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program); WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program);
WL_EvalResult wl_runtime_eval (WL_Runtime *rt); WL_EvalResult wl_runtime_eval (WL_Runtime *rt);
WL_String wl_runtime_error (WL_Runtime *rt); WL_String wl_runtime_error (WL_Runtime *rt);
void wl_runtime_dump (WL_Runtime *rt); void wl_runtime_dump (WL_Runtime *rt);
bool wl_streq (WL_String a, char *b, int blen); bool wl_streq (WL_String a, char *b, int blen);
int wl_arg_count (WL_Runtime *rt); int wl_arg_count (WL_Runtime *rt);
bool wl_arg_none (WL_Runtime *rt, int idx); bool wl_arg_none (WL_Runtime *rt, int idx);
bool wl_arg_bool (WL_Runtime *rt, int idx, bool *x); bool wl_arg_bool (WL_Runtime *rt, int idx, bool *x);
bool wl_arg_s64 (WL_Runtime *rt, int idx, int64_t *x); bool wl_arg_s64 (WL_Runtime *rt, int idx, int64_t *x);
bool wl_arg_f64 (WL_Runtime *rt, int idx, double *x); bool wl_arg_f64 (WL_Runtime *rt, int idx, double *x);
bool wl_arg_str (WL_Runtime *rt, int idx, WL_String *x); bool wl_arg_str (WL_Runtime *rt, int idx, WL_String *x);
bool wl_arg_array (WL_Runtime *rt, int idx); bool wl_arg_array (WL_Runtime *rt, int idx);
bool wl_arg_map (WL_Runtime *rt, int idx); bool wl_arg_map (WL_Runtime *rt, int idx);
bool wl_peek_none (WL_Runtime *rt, int off); bool wl_peek_none (WL_Runtime *rt, int off);
bool wl_peek_bool (WL_Runtime *rt, int off, bool *x); bool wl_peek_bool (WL_Runtime *rt, int off, bool *x);
bool wl_peek_s64 (WL_Runtime *rt, int off, int64_t *x); bool wl_peek_s64 (WL_Runtime *rt, int off, int64_t *x);
bool wl_peek_f64 (WL_Runtime *rt, int off, double *x); bool wl_peek_f64 (WL_Runtime *rt, int off, double *x);
bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x); bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x);
bool wl_pop_any (WL_Runtime *rt); bool wl_pop_any (WL_Runtime *rt);
bool wl_pop_none (WL_Runtime *rt); bool wl_pop_none (WL_Runtime *rt);
bool wl_pop_bool (WL_Runtime *rt, bool *x); bool wl_pop_bool (WL_Runtime *rt, bool *x);
bool wl_pop_s64 (WL_Runtime *rt, int64_t *x); bool wl_pop_s64 (WL_Runtime *rt, int64_t *x);
bool wl_pop_f64 (WL_Runtime *rt, double *x); bool wl_pop_f64 (WL_Runtime *rt, double *x);
bool wl_pop_str (WL_Runtime *rt, WL_String *x); bool wl_pop_str (WL_Runtime *rt, WL_String *x);
void wl_push_none (WL_Runtime *rt); void wl_push_none (WL_Runtime *rt);
void wl_push_true (WL_Runtime *rt); void wl_push_true (WL_Runtime *rt);
void wl_push_false (WL_Runtime *rt); void wl_push_false (WL_Runtime *rt);
void wl_push_s64 (WL_Runtime *rt, int64_t x); void wl_push_s64 (WL_Runtime *rt, int64_t x);
void wl_push_f64 (WL_Runtime *rt, double x); void wl_push_f64 (WL_Runtime *rt, double x);
void wl_push_str (WL_Runtime *rt, WL_String x); void wl_push_str (WL_Runtime *rt, WL_String x);
void wl_push_array (WL_Runtime *rt, int cap); void wl_push_array (WL_Runtime *rt, int cap);
void wl_push_map (WL_Runtime *rt, int cap); void wl_push_map (WL_Runtime *rt, int cap);
void wl_push_arg (WL_Runtime *rt, int idx); void wl_push_arg (WL_Runtime *rt, int idx);
void wl_insert (WL_Runtime *rt); void wl_insert (WL_Runtime *rt);
void wl_append (WL_Runtime *rt); void wl_append (WL_Runtime *rt);
+907 -907
View File
File diff suppressed because it is too large Load Diff
+27 -27
View File
@@ -1,27 +1,27 @@
/* /*
* Written by Solar Designer <solar at openwall.com> in 2000-2011. * Written by Solar Designer <solar at openwall.com> in 2000-2011.
* No copyright is claimed, and the software is hereby placed in the public * No copyright is claimed, and the software is hereby placed in the public
* domain. In case this attempt to disclaim copyright and place the software * domain. In case this attempt to disclaim copyright and place the software
* in the public domain is deemed null and void, then the software is * in the public domain is deemed null and void, then the software is
* Copyright (c) 2000-2011 Solar Designer and it is hereby released to the * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
* general public under the following terms: * general public under the following terms:
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted. * modification, are permitted.
* *
* There's ABSOLUTELY NO WARRANTY, express or implied. * There's ABSOLUTELY NO WARRANTY, express or implied.
* *
* See crypt_blowfish.c for more information. * See crypt_blowfish.c for more information.
*/ */
#ifndef _CRYPT_BLOWFISH_H #ifndef _CRYPT_BLOWFISH_H
#define _CRYPT_BLOWFISH_H #define _CRYPT_BLOWFISH_H
extern int _crypt_output_magic(const char *setting, char *output, int size); extern int _crypt_output_magic(const char *setting, char *output, int size);
extern char *_crypt_blowfish_rn(const char *key, const char *setting, extern char *_crypt_blowfish_rn(const char *key, const char *setting,
char *output, int size); char *output, int size);
extern char *_crypt_gensalt_blowfish_rn(const char *prefix, extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
unsigned long count, unsigned long count,
const char *input, int size, char *output, int output_size); const char *input, int size, char *output, int output_size);
#endif #endif
+23 -23
View File
@@ -1,23 +1,23 @@
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
EXT = .exe EXT = .exe
LFLAGS = -lws2_32 -lbcrypt LFLAGS = -lws2_32 -lbcrypt
else else
EXT = .out EXT = .out
LFLAGS = LFLAGS =
endif endif
CFLAGS = -Wall -Wextra -O0 -g3 -I3p CFLAGS = -Wall -Wextra -O0 -g3 -I3p -fsanitize=address,undefined
HFILES = $(shell find src 3p -name "*.h") HFILES = $(shell find src 3p -name "*.h")
CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c")) CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c"))
all: cozisnews$(EXT) all: cozisnews$(EXT)
cozisnews$(EXT): $(CFILES) $(HFILES) sqlite3.o cozisnews$(EXT): $(CFILES) $(HFILES) sqlite3.o
gcc -o $@ $(CFILES) sqlite3.o $(CFLAGS) $(LFLAGS) gcc -o $@ $(CFILES) sqlite3.o $(CFLAGS) $(LFLAGS)
sqlite3.o: 3p/sqlite3.c sqlite3.o: 3p/sqlite3.c
gcc -o $@ -c $< gcc -o $@ -c $<
clean: clean:
rm *.o *.out *.exe rm *.o *.out *.exe
+4 -5
View File
@@ -1,5 +1,4 @@
bcrypt passwords input validation to avoid XSS and injection
session management using proper random tokens only allow signup, login ecc over HTTPS
CSRF protection session expiration
input validation to avoid XSS and injection mark cookies as secure
only allow signup, login ecc over HTTPS
+29 -29
View File
@@ -1,29 +1,29 @@
CREATE TABLE IF NOT EXISTS Users ( CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE,
hash TEXT NOT NULL, hash TEXT NOT NULL,
email TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE,
signup_time DATETIME DEFAULT CURRENT_TIMESTAMP signup_time DATETIME DEFAULT CURRENT_TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS Posts ( CREATE TABLE IF NOT EXISTS Posts (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
author INTEGER NOT NULL, author INTEGER NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
is_link BOOLEAN NOT NULL, is_link BOOLEAN NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author) REFERENCES Users(id) FOREIGN KEY (author) REFERENCES Users(id)
); );
CREATE TABLE IF NOT EXISTS Comments ( CREATE TABLE IF NOT EXISTS Comments (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
parent_post INTEGER NOT NULL, parent_post INTEGER NOT NULL,
parent_comment INTEGER, parent_comment INTEGER,
author INTEGER NOT NULL, author INTEGER NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author) REFERENCES Users(id), FOREIGN KEY (author) REFERENCES Users(id),
FOREIGN KEY (parent_post) REFERENCES Posts(id), FOREIGN KEY (parent_post) REFERENCES Posts(id),
FOREIGN KEY (parent_comment) REFERENCES Comments(id) FOREIGN KEY (parent_comment) REFERENCES Comments(id)
); );
+147 -147
View File
@@ -1,147 +1,147 @@
<html> <html>
<head> <head>
<title>cozis news - login</title> <title>cozis news - login</title>
<style> <style>
body { body {
line-height: 200%; line-height: 200%;
font-family: monospace; font-family: monospace;
max-width: 800px; max-width: 800px;
margin: 20px auto; margin: 20px auto;
} }
nav { nav {
overflow: auto; overflow: auto;
background: #5780C9; background: #5780C9;
color: #6E93D4; color: #6E93D4;
padding: 5px 10px; padding: 5px 10px;
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
} }
nav div { nav div {
float: left float: left
} }
nav div:last-child { nav div:last-child {
float: right float: right
} }
nav a { nav a {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
} }
nav a.current { nav a.current {
font-weight: bold; font-weight: bold;
} }
main { main {
padding: 5px 10px; padding: 5px 10px;
background: #F7E6C0; background: #F7E6C0;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
footer { footer {
padding: 5px 10px; padding: 5px 10px;
} }
/* Login form styles */ /* Login form styles */
.login-container { .login-container {
max-width: 400px; max-width: 400px;
margin: 40px auto; margin: 40px auto;
padding: 30px; padding: 30px;
background: #E8D4A9; background: #E8D4A9;
border-radius: 5px; border-radius: 5px;
border: 1px solid #D4C298; border: 1px solid #D4C298;
} }
.login-container h2 { .login-container h2 {
text-align: center; text-align: center;
margin-bottom: 30px; margin-bottom: 30px;
color: #1D2B42; color: #1D2B42;
font-size: 18px; font-size: 18px;
} }
.form-group { .form-group {
margin-bottom: 20px; margin-bottom: 20px;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
color: #7A5F2A; color: #7A5F2A;
font-size: 14px; font-size: 14px;
} }
.form-group input { .form-group input {
width: 100%; width: 100%;
padding: 8px; padding: 8px;
font-family: monospace; font-family: monospace;
font-size: 14px; font-size: 14px;
background: #F7E6C0; background: #F7E6C0;
border: 1px solid #D4C298; border: 1px solid #D4C298;
border-radius: 3px; border-radius: 3px;
box-sizing: border-box; box-sizing: border-box;
} }
.form-group input:focus { .form-group input:focus {
outline: none; outline: none;
border-color: #5780C9; border-color: #5780C9;
background: #fff; background: #fff;
} }
.login-button { .login-button {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
background: #5780C9; background: #5780C9;
color: #F7E6C0; color: #F7E6C0;
border: none; border: none;
border-radius: 3px; border-radius: 3px;
font-family: monospace; font-family: monospace;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
margin-bottom: 15px; margin-bottom: 15px;
} }
.login-button:hover { .login-button:hover {
background: #1D2B42; background: #1D2B42;
} }
.form-links { .form-links {
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
} }
.form-links a { .form-links a {
color: #7A5F2A; color: #7A5F2A;
margin: 0 10px; margin: 0 10px;
} }
.form-links a:hover { .form-links a:hover {
color: #1D2B42; color: #1D2B42;
} }
</style> </style>
</head> </head>
<body> <body>
<nav> <nav>
<div> <div>
<a href="/new">new</a> <a href="/new">new</a>
| |
<a href="">hot</a> <a href="">hot</a>
</div> </div>
<div> <div>
<a href="">settings</a> <a href="">settings</a>
| |
<a href="/login" class="current">login</a> <a href="/login" class="current">login</a>
</div> </div>
</nav> </nav>
<main> <main>
<div class="login-container"> <div class="login-container">
<h2>Welcome back!</h2> <h2>Welcome back!</h2>
<form action="/api/login" method="POST"> <form action="/api/login" method="POST">
<div class="form-group"> <div class="form-group">
<label for_="username">Username:</label> <label for_="username">Username:</label>
<input type="text" id="username" name="username" required /> <input type="text" id="username" name="username" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for_="password">Password:</label> <label for_="password">Password:</label>
<input type="password" id="password" name="password" required /> <input type="password" id="password" name="password" required />
</div> </div>
<button type="submit" class="login-button">log in</button> <button type="submit" class="login-button">log in</button>
<div class="form-links"> <div class="form-links">
<a href="">forgot password?</a> <a href="">forgot password?</a>
| |
<a href="">create account</a> <a href="">create account</a>
</div> </div>
</form> </form>
</div> </div>
</main> </main>
<footer> <footer>
Made with love by cozis Made with love by cozis
</footer> </footer>
</body> </body>
</html> </html>
+121 -121
View File
@@ -1,121 +1,121 @@
fun page(title, style, content) fun page(title, style, content)
<html> <html>
<head> <head>
<title>CN - \title</title> <title>CN - \title</title>
<style> <style>
body { body {
line-height: 200%; line-height: 200%;
font-family: monospace; font-family: monospace;
max-width: 800px; max-width: 800px;
margin: 20px auto; margin: 20px auto;
} }
nav { nav {
overflow: auto; overflow: auto;
background: #5780C9; background: #5780C9;
color: #6E93D4; color: #6E93D4;
padding: 5px 10px; padding: 5px 10px;
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
} }
nav div { nav div {
float: left float: left
} }
nav div:last-child { nav div:last-child {
float: right float: right
} }
nav a { nav a {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
} }
nav a.current { nav a.current {
font-weight: bold; font-weight: bold;
} }
main { main {
padding: 5px 10px; padding: 5px 10px;
background: #F7E6C0; background: #F7E6C0;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
footer { footer {
padding: 5px 10px; padding: 5px 10px;
} }
</style> </style>
\(style) \(style)
</head> </head>
<body> <body>
<nav> <nav>
<div> <div>
<a href="/new" class="current">new</a> <a href="/new" class="current">new</a>
| |
<a href="">hot</a> <a href="">hot</a>
</div> </div>
\if $login_username == none: \if $login_username == none:
<div> <div>
<a href="/login">log-in</a> <a href="/login">log-in</a>
| |
<a href="/signup">sign-up</a> <a href="/signup">sign-up</a>
</div> </div>
else else
<div> <div>
<a href="">settings</a> <a href="">settings</a>
| |
<a href="/logout">log-out</a> <a href="/logout">log-out</a>
</div> </div>
</nav> </nav>
<main> <main>
\content \content
</main> </main>
<footer> <footer>
Made with love by cozis Made with love by cozis
</footer> </footer>
</body> </body>
</html> </html>
let posts = [ let posts = [
{ title: "Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model", date: "3 hours ago", num_comments: 127, link: "https://github.com/KittenML/KittenTTS" }, { title: "Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model", date: "3 hours ago", num_comments: 127, link: "https://github.com/KittenML/KittenTTS" },
{ title: "Open models by OpenAI", date: "15 hours ago", num_comments: 651, link: "https://openai.com/open-models/" }, { title: "Open models by OpenAI", date: "15 hours ago", num_comments: 651, link: "https://openai.com/open-models/" },
{ title: "Anthropic rejects the main developer of the library they use", date: "40 minutes ago", num_comments: 437, link: "https://grell.dev/blog/ai_rejection" }, { title: "Anthropic rejects the main developer of the library they use", date: "40 minutes ago", num_comments: 437, link: "https://grell.dev/blog/ai_rejection" },
] ]
let style = let style =
<style> <style>
.item { .item {
overflow: auto; overflow: auto;
border-bottom: 1px solid #E8D4A9; border-bottom: 1px solid #E8D4A9;
font-size: 14px; font-size: 14px;
} }
.item:last-child { .item:last-child {
border-bottom: 0; border-bottom: 0;
} }
.item span { .item span {
color: #7A5F2A; color: #7A5F2A;
text-decoration: none; text-decoration: none;
} }
.item div { .item div {
color: #E8D4A9; color: #E8D4A9;
float: left float: left
} }
.item div:first-child { .item div:first-child {
width: calc(100% - 250px); width: calc(100% - 250px);
} }
.item div:last-child { .item div:last-child {
width: 250px; width: 250px;
text-align: right; text-align: right;
} }
</style> </style>
let content = let content =
<div> <div>
\for post in posts: \for post in posts:
<div class="item"> <div class="item">
<div> <div>
<a href=post.link>\post.title</a> <a href=post.link>\post.title</a>
</div> </div>
<div> <div>
<span>\post.date</span> | <span><a href="/thread">\post.num_comments comments</a></span> <span>\post.date</span> | <span><a href="/thread">\post.num_comments comments</a></span>
</div> </div>
</div> </div>
</div> </div>
page("new", style, content) page("new", style, content)
+113 -113
View File
@@ -1,114 +1,114 @@
<html> <html>
<head> <head>
<title>cozis news - not found</title> <title>cozis news - not found</title>
<style> <style>
body { body {
line-height: 200%; line-height: 200%;
font-family: monospace; font-family: monospace;
max-width: 800px; max-width: 800px;
margin: 20px auto; margin: 20px auto;
} }
nav { nav {
overflow: auto; overflow: auto;
background: #5780C9; background: #5780C9;
color: #6E93D4; color: #6E93D4;
padding: 5px 10px; padding: 5px 10px;
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
} }
nav div { nav div {
float: left float: left
} }
nav div:last-child { nav div:last-child {
float: right float: right
} }
nav a { nav a {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
} }
nav a.current { nav a.current {
font-weight: bold; font-weight: bold;
} }
main { main {
padding: 5px 10px; padding: 5px 10px;
background: #F7E6C0; background: #F7E6C0;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
footer { footer {
padding: 5px 10px; padding: 5px 10px;
} }
/* 404 page styles */ /* 404 page styles */
.not-found-container { .not-found-container {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
} }
.error-code { .error-code {
font-size: 72px; font-size: 72px;
font-weight: bold; font-weight: bold;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 20px; margin-bottom: 20px;
line-height: 100%; line-height: 100%;
} }
.error-message { .error-message {
font-size: 24px; font-size: 24px;
color: #1D2B42; color: #1D2B42;
margin-bottom: 15px; margin-bottom: 15px;
} }
.error-description { .error-description {
font-size: 14px; font-size: 14px;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 40px; margin-bottom: 40px;
max-width: 500px; max-width: 500px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
line-height: 160%; line-height: 160%;
} }
.home-button { .home-button {
display: inline-block; display: inline-block;
background: #5780C9; background: #5780C9;
color: #F7E6C0; color: #F7E6C0;
padding: 12px 24px; padding: 12px 24px;
text-decoration: none; text-decoration: none;
border-radius: 5px; border-radius: 5px;
font-size: 14px; font-size: 14px;
margin-top: 20px; margin-top: 20px;
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
.home-button:hover { .home-button:hover {
background: #1D2B42; background: #1D2B42;
color: #F7E6C0; color: #F7E6C0;
} }
</style> </style>
</head> </head>
<body> <body>
<nav> <nav>
<div> <div>
<a href="/new">new</a> <a href="/new">new</a>
| |
<a href="">hot</a> <a href="">hot</a>
</div> </div>
<div> <div>
<a href="">settings</a> <a href="">settings</a>
| |
<a href="/login">log-out</a> <a href="/login">log-out</a>
</div> </div>
</nav> </nav>
<main> <main>
<div class="not-found-container"> <div class="not-found-container">
<div class="error-code">404</div> <div class="error-code">404</div>
<div class="error-message">Page Not Found</div> <div class="error-message">Page Not Found</div>
<div class="error-description"> <div class="error-description">
Looks like this page wandered off somewhere. Looks like this page wandered off somewhere.
</div> </div>
</div> </div>
</main> </main>
<footer> <footer>
Made with love by cozis Made with love by cozis
</footer> </footer>
</body> </body>
</html> </html>
+21 -21
View File
@@ -1,21 +1,21 @@
include "pages/page.wl" include "pages/page.wl"
include "pages/login_and_signup_style.wl" 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"> <form action="/api/signup" method="POST">
<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" />
<input type="password" name="password2" placeholder="repeat password" /> <input type="password" name="password2" placeholder="repeat password" />
<input type="submit" value="Sign-Up" /> <input type="submit" value="Sign-Up" />
<div class="form-links"> <div class="form-links">
<a href="">forgot password?</a> <a href="">forgot password?</a>
| |
<a href="">already have an account?</a> <a href="">already have an account?</a>
</div> </div>
</form> </form>
</main> </main>
page("Log-In", none, style, main) page("Log-In", none, style, main)
+339 -339
View File
@@ -1,340 +1,340 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>cozis news - thread</title> <title>cozis news - thread</title>
<style> <style>
body { body {
line-height: 200%; line-height: 200%;
font-family: monospace; font-family: monospace;
max-width: 800px; max-width: 800px;
margin: 20px auto; margin: 20px auto;
} }
nav { nav {
overflow: auto; overflow: auto;
background: #5780C9; background: #5780C9;
color: #6E93D4; color: #6E93D4;
padding: 5px 10px; padding: 5px 10px;
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
} }
nav div { nav div {
float: left float: left
} }
nav div:last-child { nav div:last-child {
float: right float: right
} }
nav a { nav a {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
} }
nav a.current { nav a.current {
font-weight: bold; font-weight: bold;
} }
main { main {
padding: 5px 10px; padding: 5px 10px;
background: #F7E6C0; background: #F7E6C0;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
footer { footer {
padding: 5px 10px; padding: 5px 10px;
} }
/* Thread styles */ /* Thread styles */
.thread-header { .thread-header {
padding: 15px 0; padding: 15px 0;
border-bottom: 2px solid #E8D4A9; border-bottom: 2px solid #E8D4A9;
margin-bottom: 20px; margin-bottom: 20px;
} }
.thread-title { .thread-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
margin-bottom: 8px; margin-bottom: 8px;
} }
.thread-title a { .thread-title a {
color: #1D2B42; color: #1D2B42;
text-decoration: none; text-decoration: none;
} }
.thread-title a:hover { .thread-title a:hover {
text-decoration: underline; text-decoration: underline;
} }
.thread-meta { .thread-meta {
font-size: 12px; font-size: 12px;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 10px; margin-bottom: 10px;
} }
.thread-meta a { .thread-meta a {
color: #7A5F2A; color: #7A5F2A;
} }
.thread-text { .thread-text {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
line-height: 160%; line-height: 160%;
margin-bottom: 10px; margin-bottom: 10px;
} }
.thread-actions { .thread-actions {
font-size: 12px; font-size: 12px;
} }
.thread-actions a { .thread-actions a {
color: #7A5F2A; color: #7A5F2A;
margin-right: 10px; margin-right: 10px;
} }
/* Comment styles */ /* Comment styles */
.comment { .comment {
margin-bottom: 15px; margin-bottom: 15px;
border-left: 1px solid #E8D4A9; border-left: 1px solid #E8D4A9;
padding-left: 10px; padding-left: 10px;
} }
.comment.level-0 { margin-left: 0px; } .comment.level-0 { margin-left: 0px; }
.comment.level-1 { margin-left: 20px; } .comment.level-1 { margin-left: 20px; }
.comment.level-2 { margin-left: 40px; } .comment.level-2 { margin-left: 40px; }
.comment.level-3 { margin-left: 60px; } .comment.level-3 { margin-left: 60px; }
.comment.level-4 { margin-left: 80px; } .comment.level-4 { margin-left: 80px; }
.comment-meta { .comment-meta {
font-size: 11px; font-size: 11px;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 5px; margin-bottom: 5px;
} }
.comment-meta a { .comment-meta a {
color: #7A5F2A; color: #7A5F2A;
} }
.comment-text { .comment-text {
font-size: 13px; font-size: 13px;
color: #1D2B42; color: #1D2B42;
line-height: 150%; line-height: 150%;
margin-bottom: 5px; margin-bottom: 5px;
} }
.comment-actions { .comment-actions {
font-size: 11px; font-size: 11px;
} }
.comment-actions a { .comment-actions a {
color: #7A5F2A; color: #7A5F2A;
margin-right: 8px; margin-right: 8px;
} }
.comment-actions a:hover { .comment-actions a:hover {
color: #1D2B42; color: #1D2B42;
} }
.vote-buttons { .vote-buttons {
float: left; float: left;
width: 15px; width: 15px;
margin-right: 8px; margin-right: 8px;
font-size: 10px; font-size: 10px;
text-align: center; text-align: center;
} }
.vote-buttons a { .vote-buttons a {
display: block; display: block;
color: #7A5F2A; color: #7A5F2A;
text-decoration: none; text-decoration: none;
line-height: 100%; line-height: 100%;
} }
.vote-buttons a:hover { .vote-buttons a:hover {
color: #1D2B42; color: #1D2B42;
} }
.comment-content { .comment-content {
margin-left: 23px; margin-left: 23px;
} }
.add-comment { .add-comment {
margin: 20px 0; margin: 20px 0;
padding: 15px; padding: 15px;
background: #E8D4A9; background: #E8D4A9;
border-radius: 3px; border-radius: 3px;
border: 1px solid #D4C298; border: 1px solid #D4C298;
} }
.add-comment textarea { .add-comment textarea {
width: 100%; width: 100%;
height: 80px; height: 80px;
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
background: #F7E6C0; background: #F7E6C0;
border: 1px solid #D4C298; border: 1px solid #D4C298;
border-radius: 3px; border-radius: 3px;
padding: 8px; padding: 8px;
box-sizing: border-box; box-sizing: border-box;
resize: vertical; resize: vertical;
} }
.add-comment button { .add-comment button {
background: #5780C9; background: #5780C9;
color: #F7E6C0; color: #F7E6C0;
border: none; border: none;
border-radius: 3px; border-radius: 3px;
padding: 6px 12px; padding: 6px 12px;
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
margin-top: 8px; margin-top: 8px;
} }
.add-comment button:hover { .add-comment button:hover {
background: #1D2B42; background: #1D2B42;
} }
.collapsed { .collapsed {
color: #7A5F2A; color: #7A5F2A;
font-size: 11px; font-size: 11px;
cursor: pointer; cursor: pointer;
} }
.collapsed:hover { .collapsed:hover {
color: #1D2B42; color: #1D2B42;
} }
</style> </style>
</head> </head>
<body> <body>
<nav> <nav>
<div> <div>
<a href="/new" class="current">new</a> <a href="/new" class="current">new</a>
| |
<a href="">hot</a> <a href="">hot</a>
</div> </div>
<div> <div>
<a href="">settings</a> <a href="">settings</a>
| |
<a href="/login">log-out</a> <a href="/login">log-out</a>
</div> </div>
</nav> </nav>
<main> <main>
<div class="thread-header"> <div class="thread-header">
<div class="thread-title"> <div class="thread-title">
<a href="https://github.com/KittenML/KittenTTS">Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model</a> <a href="https://github.com/KittenML/KittenTTS">Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model</a>
</div> </div>
<div class="thread-meta"> <div class="thread-meta">
submitted 3 hours ago by <a href="">kittenlover42</a> | <a href="">127 comments</a> submitted 3 hours ago by <a href="">kittenlover42</a> | <a href="">127 comments</a>
</div> </div>
<div class="thread-text"> <div class="thread-text">
Hey HN! I've been working on Kitten TTS for the past 6 months - a tiny text-to-speech model that runs entirely on CPU. At just 25MB, it's perfect for embedded applications and privacy-focused projects where you don't want to send text to external services. Hey HN! I've been working on Kitten TTS for the past 6 months - a tiny text-to-speech model that runs entirely on CPU. At just 25MB, it's perfect for embedded applications and privacy-focused projects where you don't want to send text to external services.
<br/><br/> <br/><br/>
The model is trained on a diverse dataset and supports multiple languages. Performance is surprisingly good for the size - would love to hear your thoughts! The model is trained on a diverse dataset and supports multiple languages. Performance is surprisingly good for the size - would love to hear your thoughts!
</div> </div>
<div class="thread-actions"> <div class="thread-actions">
<a href="">reply</a> <a href="">reply</a>
</div> </div>
</div> </div>
<div class="add-comment"> <div class="add-comment">
<textarea placeholder="Add a comment..."></textarea> <textarea placeholder="Add a comment..."></textarea>
<button>add comment</button> <button>add comment</button>
</div> </div>
<div class="comment level-0"> <div class="comment level-0">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<a href="">techgeek99</a> 2 hours ago <a href="">techgeek99</a> 2 hours ago
</div> </div>
<div class="comment-text"> <div class="comment-text">
This is incredible! I've been looking for something exactly like this for my IoT project. The fact that it's only 25MB and runs on CPU is a game changer. How does the voice quality compare to larger models? This is incredible! I've been looking for something exactly like this for my IoT project. The fact that it's only 25MB and runs on CPU is a game changer. How does the voice quality compare to larger models?
</div> </div>
<div class="comment-actions"> <div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a> <a href="">reply</a> | <a href="">flag</a>
</div> </div>
</div> </div>
</div> </div>
<div class="comment level-1"> <div class="comment level-1">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<a href="">kittenlover42</a> 2 hours ago <a href="">kittenlover42</a> 2 hours ago
</div> </div>
<div class="comment-text"> <div class="comment-text">
Thanks! The voice quality is obviously not as good as larger models like Tacotron2 or the commercial APIs, but it's surprisingly decent for most use cases. I'd say it's about 75% of the quality at 1% of the size. Perfect for things like reading notifications aloud or simple voice interfaces. Thanks! The voice quality is obviously not as good as larger models like Tacotron2 or the commercial APIs, but it's surprisingly decent for most use cases. I'd say it's about 75% of the quality at 1% of the size. Perfect for things like reading notifications aloud or simple voice interfaces.
</div> </div>
<div class="comment-actions"> <div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a> <a href="">reply</a> | <a href="">flag</a>
</div> </div>
</div> </div>
</div> </div>
<div class="comment level-2"> <div class="comment level-2">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<a href="">embedded_dev</a> 1 hour ago <a href="">embedded_dev</a> 1 hour ago
</div> </div>
<div class="comment-text"> <div class="comment-text">
That sounds perfect for embedded applications! What's the inference speed like on something like a Raspberry Pi? That sounds perfect for embedded applications! What's the inference speed like on something like a Raspberry Pi?
</div> </div>
<div class="comment-actions"> <div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a> <a href="">reply</a> | <a href="">flag</a>
</div> </div>
</div> </div>
</div> </div>
<div class="comment level-0"> <div class="comment level-0">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<a href="">ml_researcher</a> 2 hours ago <a href="">ml_researcher</a> 2 hours ago
</div> </div>
<div class="comment-text"> <div class="comment-text">
Very impressive work! I'm curious about the architecture - are you using knowledge distillation from a larger model, or did you train this from scratch? The 25MB constraint must have required some clever optimizations. Very impressive work! I'm curious about the architecture - are you using knowledge distillation from a larger model, or did you train this from scratch? The 25MB constraint must have required some clever optimizations.
</div> </div>
<div class="comment-actions"> <div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a> <a href="">reply</a> | <a href="">flag</a>
</div> </div>
</div> </div>
</div> </div>
<div class="comment level-1"> <div class="comment level-1">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<a href="">kittenlover42</a> 1 hour ago <a href="">kittenlover42</a> 1 hour ago
</div> </div>
<div class="comment-text"> <div class="comment-text">
Great question! It's a combination approach. I started with a modified Tacotron architecture but with much smaller hidden dimensions, then used knowledge distillation from WaveNet to get the vocoder down to size. Also heavily quantized the weights and used some pruning techniques. Happy to share more technical details if you're interested! Great question! It's a combination approach. I started with a modified Tacotron architecture but with much smaller hidden dimensions, then used knowledge distillation from WaveNet to get the vocoder down to size. Also heavily quantized the weights and used some pruning techniques. Happy to share more technical details if you're interested!
</div> </div>
<div class="comment-actions"> <div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a> <a href="">reply</a> | <a href="">flag</a>
</div> </div>
</div> </div>
</div> </div>
<div class="comment level-0"> <div class="comment level-0">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<a href="">privacy_advocate</a> 1 hour ago <a href="">privacy_advocate</a> 1 hour ago
</div> </div>
<div class="comment-text"> <div class="comment-text">
Love that this runs locally! So tired of having to send my text to Google/Amazon/etc just to get speech synthesis. This is exactly what we need more of in the open source community. Love that this runs locally! So tired of having to send my text to Google/Amazon/etc just to get speech synthesis. This is exactly what we need more of in the open source community.
</div> </div>
<div class="comment-actions"> <div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a> <a href="">reply</a> | <a href="">flag</a>
</div> </div>
</div> </div>
</div> </div>
<div class="comment level-0"> <div class="comment level-0">
<div class="collapsed"> <div class="collapsed">
[5 more comments] [5 more comments]
</div> </div>
</div> </div>
</main> </main>
<footer> <footer>
Made with love by cozis Made with love by cozis
</footer> </footer>
</body> </body>
</html> </html>
+66 -66
View File
@@ -1,67 +1,67 @@
include "pages/page.wl" include "pages/page.wl"
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") 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 = []
let style = let style =
<style> <style>
.item { .item {
overflow: auto; overflow: auto;
border-bottom: 1px solid #E8D4A9; border-bottom: 1px solid #E8D4A9;
font-size: 14px; font-size: 14px;
} }
.item:last-child { .item:last-child {
border-bottom: 0; border-bottom: 0;
} }
.item span { .item span {
color: #7A5F2A; color: #7A5F2A;
text-decoration: none; text-decoration: none;
} }
.item div { .item div {
color: #E8D4A9; color: #E8D4A9;
float: left float: left
} }
.item div:first-child { .item div:first-child {
width: calc(100% - 250px); width: calc(100% - 250px);
} }
.item div:last-child { .item div:last-child {
width: 250px; width: 250px;
text-align: right; text-align: right;
} }
#no-posts { #no-posts {
margin: 60px auto; margin: 60px auto;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: #7A5F2A; color: #7A5F2A;
} }
</style> </style>
let main = let main =
<main> <main>
\if len(posts) == 0: \if len(posts) == 0:
<div id="no-posts">There are no posts yet!</div> <div id="no-posts">There are no posts yet!</div>
\for post in posts: { \for post in posts: {
let link let link
if post.is_link != 0: if post.is_link != 0:
link = post.content link = post.content
else else
link = ["/post?id=", post.id] link = ["/post?id=", post.id]
<div class="item"> <div class="item">
<div> <div>
<a href=\'"'\link\'"'>\post.title</a> <a href=\'"'\link\'"'>\post.title</a>
</div> </div>
<div> <div>
<span>\post.date</span> | <span>\post.num_comments comments</span> <span>\post.date</span> | <span>\post.num_comments comments</span>
</div> </div>
</div> </div>
} }
</main> </main>
page("Index", $login_user_id, style, main) page("Index", $login_user_id, style, main)
+22 -22
View File
@@ -1,22 +1,22 @@
include "pages/page.wl" include "pages/page.wl"
include "pages/login_and_signup_style.wl" include "pages/login_and_signup_style.wl"
let main = let main =
<main> <main>
<span>Welcome back!</span> <span>Welcome back!</span>
<div id="response"></div> <div id="response"></div>
<form hx-post="/api/login" hx-target="#response"> <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" />
<div class="form-links"> <div class="form-links">
<a href="">forgot password?</a> <a href="">forgot password?</a>
| |
<a href="">create account</a> <a href="">create account</a>
</div> </div>
</form> </form>
</main> </main>
page("Log-In", none, style, main) page("Log-In", none, style, main)
+73 -73
View File
@@ -1,74 +1,74 @@
let style = let style =
<style> <style>
span { span {
text-align: center; text-align: center;
color: #1D2B42; color: #1D2B42;
font-size: 18px; font-size: 18px;
display: inline-block; display: inline-block;
width: 100%; width: 100%;
margin-top: 30px; margin-top: 30px;
} }
form { form {
max-width: 300px; max-width: 300px;
margin: 30px auto; margin: 30px auto;
} }
form input { form input {
border: 0; border: 0;
outline: 0; outline: 0;
width: 100%; width: 100%;
border-radius: 3px; border-radius: 3px;
padding: 8px; padding: 8px;
margin-bottom: 15px; margin-bottom: 15px;
} }
form input[type=text], form input[type=text],
form input[type=email], form input[type=email],
form input[type=password] { form input[type=password] {
background: #E8D4A9; background: #E8D4A9;
border: 1px solid #D4C298; border: 1px solid #D4C298;
} }
form input[type=text]:focus, form input[type=text]:focus,
form input[type=email]:focus, form input[type=email]:focus,
form input[type=password]:focus { form input[type=password]:focus {
border-color: #5780C9; border-color: #5780C9;
background: #fff; background: #fff;
} }
form input[type=submit] { form input[type=submit] {
cursor: pointer; cursor: pointer;
background: #5780C9; background: #5780C9;
color: #F7E6C0; color: #F7E6C0;
} }
form input[type=submit]:hover { form input[type=submit]:hover {
background: #1D2B42; background: #1D2B42;
} }
.form-links { .form-links {
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
} }
.form-links a { .form-links a {
color: #7A5F2A; color: #7A5F2A;
margin: 0 10px; margin: 0 10px;
} }
.form-links a:hover { .form-links a:hover {
color: #1D2B42; color: #1D2B42;
} }
#response { #response {
max-width: 300px; max-width: 300px;
margin: auto; margin: auto;
} }
#response .error { #response .error {
margin-top: 30px; margin-top: 30px;
border-radius: 3px; border-radius: 3px;
border: 1px solid #E44C82; border: 1px solid #E44C82;
background: #F295B5; background: #F295B5;
padding: 5px 10px; padding: 5px 10px;
} }
</style> </style>
+60 -60
View File
@@ -1,60 +1,60 @@
include "pages/page.wl" include "pages/page.wl"
let style = let style =
<style> <style>
.not-found-container { .not-found-container {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
} }
.error-code { .error-code {
font-size: 72px; font-size: 72px;
font-weight: bold; font-weight: bold;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 20px; margin-bottom: 20px;
line-height: 100%; line-height: 100%;
} }
.error-message { .error-message {
font-size: 24px; font-size: 24px;
color: #1D2B42; color: #1D2B42;
margin-bottom: 15px; margin-bottom: 15px;
} }
.error-description { .error-description {
font-size: 14px; font-size: 14px;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 40px; margin-bottom: 40px;
max-width: 500px; max-width: 500px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
line-height: 160%; line-height: 160%;
} }
.home-button { .home-button {
display: inline-block; display: inline-block;
background: #5780C9; background: #5780C9;
color: #F7E6C0; color: #F7E6C0;
padding: 12px 24px; padding: 12px 24px;
text-decoration: none; text-decoration: none;
border-radius: 5px; border-radius: 5px;
font-size: 14px; font-size: 14px;
margin-top: 20px; margin-top: 20px;
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
.home-button:hover { .home-button:hover {
background: #1D2B42; background: #1D2B42;
color: #F7E6C0; color: #F7E6C0;
} }
</style> </style>
let main = let main =
<main> <main>
<div class="not-found-container"> <div class="not-found-container">
<div class="error-code">404</div> <div class="error-code">404</div>
<div class="error-message">Page Not Found</div> <div class="error-message">Page Not Found</div>
<div class="error-description"> <div class="error-description">
Looks like this page wandered off somewhere. Looks like this page wandered off somewhere.
</div> </div>
</div> </div>
</main> </main>
page("Not Found", $login_user_id, style, main) page("Not Found", $login_user_id, style, main)
+75 -75
View File
@@ -1,75 +1,75 @@
procedure page(title, login_user_id, style, main) procedure page(title, login_user_id, style, main)
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>CN - \title</title> <title>CN - \title</title>
<style> <style>
body { body {
line-height: 200%; line-height: 200%;
font-family: monospace; font-family: monospace;
max-width: 800px; max-width: 800px;
margin: 20px auto; margin: 20px auto;
} }
nav { nav {
overflow: auto; overflow: auto;
background: #5780C9; background: #5780C9;
color: #6E93D4; color: #6E93D4;
padding: 5px 10px; padding: 5px 10px;
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
} }
nav div { nav div {
float: left float: left
} }
nav div:last-child { nav div:last-child {
float: right float: right
} }
nav a { nav a {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
} }
nav a.current { nav a.current {
font-weight: bold; font-weight: bold;
} }
main { main {
padding: 5px 10px; padding: 5px 10px;
background: #F7E6C0; background: #F7E6C0;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
footer { footer {
padding: 5px 10px; padding: 5px 10px;
} }
</style> </style>
\(style) \(style)
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script> <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>
<a href="/login">log-in</a> <a href="/login">log-in</a>
| |
<a href="/signup">sign-up</a> <a href="/signup">sign-up</a>
</div> </div>
else else
<div> <div>
<a href="">settings</a> <a href="">settings</a>
| |
<a href="/api/logout">log-out</a> <a href="/api/logout">log-out</a>
</div> </div>
</nav> </nav>
\main \main
<footer> <footer>
Made with love by cozis Made with love by cozis
</footer> </footer>
</body> </body>
</html> </html>
+245 -245
View File
@@ -1,246 +1,246 @@
include "pages/page.wl" include "pages/page.wl"
let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", $post_id) let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", $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 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 = {} let lookup = {}
for comment in comments: { for comment in comments: {
comment.child = [] comment.child = []
lookup[comment.id] = comment lookup[comment.id] = comment
} }
let root_comments = [] let root_comments = []
for comment in comments: { for comment in comments: {
if comment.parent_comment == none: if comment.parent_comment == none:
root_comments << comment root_comments << comment
else else
lookup[comment.parent_comment].child << comment lookup[comment.parent_comment].child << comment
} }
let post = posts[0] let post = posts[0]
let style = let style =
<style> <style>
.thread-header { .thread-header {
padding: 15px 0; padding: 15px 0;
border-bottom: 2px solid #E8D4A9; border-bottom: 2px solid #E8D4A9;
margin-bottom: 20px; margin-bottom: 20px;
} }
.thread-title { .thread-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
margin-bottom: 8px; margin-bottom: 8px;
} }
.thread-title a { .thread-title a {
color: #1D2B42; color: #1D2B42;
text-decoration: none; text-decoration: none;
} }
.thread-title a:hover { .thread-title a:hover {
text-decoration: underline; text-decoration: underline;
} }
.thread-meta { .thread-meta {
font-size: 12px; font-size: 12px;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 10px; margin-bottom: 10px;
} }
.thread-meta a { .thread-meta a {
color: #7A5F2A; color: #7A5F2A;
} }
.thread-text { .thread-text {
font-size: 14px; font-size: 14px;
color: #1D2B42; color: #1D2B42;
line-height: 160%; line-height: 160%;
margin-bottom: 10px; margin-bottom: 10px;
} }
.thread-actions { .thread-actions {
font-size: 12px; font-size: 12px;
} }
.thread-actions a { .thread-actions a {
color: #7A5F2A; color: #7A5F2A;
margin-right: 10px; margin-right: 10px;
} }
/* Comment styles */ /* Comment styles */
.comment { .comment {
margin-bottom: 15px; margin-bottom: 15px;
border-left: 1px solid #E8D4A9; border-left: 1px solid #E8D4A9;
padding-left: 10px; padding-left: 10px;
} }
.comment-meta { .comment-meta {
font-size: 11px; font-size: 11px;
color: #7A5F2A; color: #7A5F2A;
margin-bottom: 5px; margin-bottom: 5px;
} }
.comment-meta a { .comment-meta a {
color: #7A5F2A; color: #7A5F2A;
} }
.comment-text { .comment-text {
font-size: 13px; font-size: 13px;
color: #1D2B42; color: #1D2B42;
line-height: 150%; line-height: 150%;
margin-bottom: 5px; margin-bottom: 5px;
} }
.comment-actions { .comment-actions {
font-size: 11px; font-size: 11px;
} }
.comment-actions a { .comment-actions a {
color: #7A5F2A; color: #7A5F2A;
margin-right: 8px; margin-right: 8px;
} }
.comment-actions a:hover { .comment-actions a:hover {
color: #1D2B42; color: #1D2B42;
} }
.vote-buttons { .vote-buttons {
float: left; float: left;
width: 15px; width: 15px;
margin-right: 8px; margin-right: 8px;
font-size: 10px; font-size: 10px;
text-align: center; text-align: center;
} }
.vote-buttons a { .vote-buttons a {
display: block; display: block;
color: #7A5F2A; color: #7A5F2A;
text-decoration: none; text-decoration: none;
line-height: 100%; line-height: 100%;
} }
.vote-buttons a:hover { .vote-buttons a:hover {
color: #1D2B42; color: #1D2B42;
} }
.comment-content { .comment-content {
margin-left: 23px; margin-left: 23px;
} }
.comment-child { .comment-child {
margin-top: 10px; margin-top: 10px;
margin-left: 10px; margin-left: 10px;
} }
.add-comment { .add-comment {
} }
.add-comment form { .add-comment form {
margin: 0; margin: 0;
overflow: auto; 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: white; background: white;
border: 1px solid #D4C298; border: 1px solid #D4C298;
border-radius: 3px; border-radius: 3px;
padding: 8px; padding: 8px;
box-sizing: border-box; box-sizing: border-box;
resize: vertical; resize: vertical;
} }
.add-comment form input[type=submit] { .add-comment form input[type=submit] {
background: #5780C9; background: #5780C9;
color: white; color: white;
border: none; border: none;
border-radius: 3px; border-radius: 3px;
padding: 6px 12px; padding: 6px 12px;
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
margin-top: 8px; margin-top: 8px;
float: right; float: right;
} }
.add-comment input[type=submit]:hover { .add-comment input[type=submit]:hover {
background: #1D2B42; background: #1D2B42;
} }
summary { summary {
list-style: none; list-style: none;
text-decoration: underline; text-decoration: underline;
color: #7A5F2A; color: #7A5F2A;
cursor: pointer; cursor: pointer;
} }
::-webkit-details-marker { ::-webkit-details-marker {
display: none; display: none;
} }
#no-comments { #no-comments {
margin: 30px 0; margin: 30px 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: #7A5F2A; color: #7A5F2A;
} }
pre { pre {
white-space: pre-wrap; /* Since CSS 2.1 */ white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */ white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */ white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */ word-wrap: break-word; /* Internet Explorer 5.5+ */
} }
</style> </style>
let main = let main =
<main> <main>
<div class="thread-header"> <div class="thread-header">
<div class="thread-title"> <div class="thread-title">
<span>\post.title</span> <span>\post.title</span>
</div> </div>
<div class="thread-meta"> <div class="thread-meta">
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">
<pre>\post.content</pre> <pre>\post.content</pre>
</div> </div>
<details> <details>
<summary> <summary>
reply reply
</summary> </summary>
<div class="add-comment"> <div class="add-comment">
<form action="/api/comment" method="POST"> <form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' /> <input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea> <textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" /> <input type="submit" vaue="Publish" />
</form> </form>
</div> </div>
</details> </details>
</div> </div>
\procedure render_comment(comment) \procedure render_comment(comment)
<div class="comment"> <div class="comment">
<div class="vote-buttons"> <div class="vote-buttons">
<a href=""></a> <a href=""></a>
<a href=""></a> <a href=""></a>
</div> </div>
<div class="comment-content"> <div class="comment-content">
<div class="comment-meta"> <div class="comment-meta">
<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">
<pre>\comment.content</pre> <pre>\comment.content</pre>
</div> </div>
\if $login_user_id != none: \if $login_user_id != none:
<details> <details>
<summary> <summary>
reply reply
</summary> </summary>
<div class="add-comment"> <div class="add-comment">
<form action="/api/comment" method="POST"> <form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' /> <input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' /> <input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea> <textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" /> <input type="submit" vaue="Publish" />
</form> </form>
</div> </div>
</details> </details>
</div> </div>
<div class="comment-child"> <div class="comment-child">
\for child in comment.child: \for child in comment.child:
render_comment(child) render_comment(child)
</div> </div>
</div> </div>
\if len root_comments == 0: \if len root_comments == 0:
<div id="no-comments"> <div id="no-comments">
No comments No comments
</div> </div>
else for comment in root_comments: else for comment in root_comments:
render_comment(comment) render_comment(comment)
</main> </main>
page(post.title, $login_user_id, style, main) page(post.title, $login_user_id, style, main)
+72 -72
View File
@@ -1,73 +1,73 @@
include "pages/page.wl" include "pages/page.wl"
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id) 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 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 = {} let lookup = {}
for comment in comments: { for comment in comments: {
comment.child = [] comment.child = []
lookup[comment.id] = comment lookup[comment.id] = comment
} }
let roots = [] let roots = []
for comment in comments: { for comment in comments: {
if comment.parent_comment == none: if comment.parent_comment == none:
roots << comment roots << comment
else else
lookup[comment.parent_comment].child << comment lookup[comment.parent_comment].child << comment
} }
comments = roots comments = roots
let post = posts[0] let post = posts[0]
let style = let style =
<style> <style>
.child { .child {
border-left: 3px solid #ccc; border-left: 3px solid #ccc;
padding-left: 10px; padding-left: 10px;
} }
form textarea { form textarea {
width: 100%; width: 100%;
} }
</style> </style>
procedure render_comment(parent) procedure render_comment(parent)
<div> <div>
<a href="">\parent.username</a> <a href="">\parent.username</a>
<p> <p>
\parent.content \parent.content
</p> </p>
<div class="child"> <div class="child">
\if $login_user_id != none: \if $login_user_id != none:
<form action="/api/comment" method="POST"> <form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' /> <input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' /> <input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
<textarea name="content"></textarea> <textarea name="content"></textarea>
<input type="submit" vaue="Publish" /> <input type="submit" vaue="Publish" />
</form> </form>
\for child in parent.child: \for child in parent.child:
render_comment(child) render_comment(child)
</div> </div>
</div> </div>
let main = let main =
<main> <main>
<h3>\post.title</h3> <h3>\post.title</h3>
<p>\post.content</p> <p>\post.content</p>
<div> <div>
<form action="/api/comment" method="POST"> <form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' /> <input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content"></textarea> <textarea name="content"></textarea>
<input type="submit" vaue="Publish" /> <input type="submit" vaue="Publish" />
</form> </form>
</div> </div>
\if len comments == 0: \if len comments == 0:
<span>No comments yet!</span> <span>No comments yet!</span>
else for comment in comments: else for comment in comments:
render_comment(comment) render_comment(comment)
</main> </main>
page(post.title, $login_user_id, style, main) page(post.title, $login_user_id, style, main)
+24 -24
View File
@@ -1,24 +1,24 @@
include "pages/page.wl" include "pages/page.wl"
include "pages/login_and_signup_style.wl" include "pages/login_and_signup_style.wl"
let main = let main =
<main> <main>
<span>Welcome!</span> <span>Welcome!</span>
<div id="response"></div> <div id="response"></div>
<form hx-post="/api/signup" hx-target="#response"> <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" />
<input type="password" name="password2" placeholder="repeat password" /> <input type="password" name="password2" placeholder="repeat password" />
<input type="submit" value="Sign-Up" /> <input type="submit" value="Sign-Up" />
<div class="form-links"> <div class="form-links">
<a href="">forgot password?</a> <a href="">forgot password?</a>
| |
<a href="">already have an account?</a> <a href="">already have an account?</a>
</div> </div>
</form> </form>
</main> </main>
page("Log-In", none, style, main) page("Log-In", none, style, main)
+94 -92
View File
@@ -1,93 +1,95 @@
include "pages/page.wl" include "pages/page.wl"
let style = let style =
<style> <style>
form { form {
max-width: 400px; max-width: 400px;
margin: 30px auto; margin: 30px auto;
} }
form input, form input,
form textarea { form textarea {
border: 0; border: 0;
outline: 0; outline: 0;
width: 100%; width: 100%;
border-radius: 3px; border-radius: 3px;
padding: 8px; padding: 8px;
margin-bottom: 15px; margin-bottom: 15px;
background: #E8D4A9; background: #E8D4A9;
border: 1px solid #D4C298; border: 1px solid #D4C298;
} }
form input:focus, form input:focus,
form textarea:focus { form textarea:focus {
border-color: #5780C9; border-color: #5780C9;
background: #fff; background: #fff;
} }
form textarea { form textarea {
height: 120px; height: 120px;
resize: vertical; resize: vertical;
} }
form input[type=submit] { form input[type=submit] {
cursor: pointer; cursor: pointer;
background: #5780C9; background: #5780C9;
color: #F7E6C0; color: #F7E6C0;
} }
form input[type=submit]:hover { form input[type=submit]:hover {
background: #1D2B42; background: #1D2B42;
} }
.checkbox-row { .checkbox-row {
margin-bottom: 15px; margin-bottom: 15px;
} }
.checkbox-row input[type="checkbox"] { .checkbox-row input[type="checkbox"] {
width: auto; width: auto;
margin-right: 8px; margin-right: 8px;
} }
.checkbox-row label { .checkbox-row label {
color: #1D2B42; color: #1D2B42;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
} }
</style> </style>
let main = let main =
<main> <main>
<form action="/api/post" method="POST"> <form action="/api/post" method="POST">
<input type="text" id="title" name="title" placeholder="Title" required />
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
<div class="checkbox-row"> <input type="text" id="title" name="title" placeholder="Title" required />
<input type="checkbox" id="is_link" name="is_link" onchange="togglePostType()" />
<label>This is a link post</label> <div class="checkbox-row">
</div> <input type="checkbox" id="is_link" name="is_link" onchange="togglePostType()" />
<label>This is a link post</label>
<input type="url" id="url" name="link" placeholder="URL" style="display: none;" /> </div>
<textarea id="content" name="content" placeholder="Write your post here..."></textarea> <input type="url" id="url" name="link" placeholder="URL" style="display: none;" />
<input type="submit" value="Submit Post" /> <textarea id="content" name="content" placeholder="Write your post here..."></textarea>
</form>
<input type="submit" value="Submit Post" />
<script> </form>
function togglePostType() {
const checkbox = document.getElementById('is_link'); <script>
const urlInput = document.getElementById('url'); function togglePostType() {
const contentTextarea = document.getElementById('content'); const checkbox = document.getElementById('is_link');
const urlInput = document.getElementById('url');
if (checkbox.checked) { const contentTextarea = document.getElementById('content');
urlInput.style.display = 'block';
contentTextarea.style.display = 'none'; if (checkbox.checked) {
} else { urlInput.style.display = 'block';
urlInput.style.display = 'none'; contentTextarea.style.display = 'none';
contentTextarea.style.display = 'block'; } else {
} urlInput.style.display = 'none';
} contentTextarea.style.display = 'block';
</script> }
</main> }
</script>
</main>
page("Write Post", $login_user_id, style, main) page("Write Post", $login_user_id, style, main)
+45 -45
View File
@@ -1,45 +1,45 @@
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include <crypt_blowfish.h> #include <crypt_blowfish.h>
#include "bcrypt.h" #include "bcrypt.h"
#include "random.h" #include "random.h"
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash) int hash_password(char *pass, int passlen, int cost, PasswordHash *hash)
{ {
char passzt[128]; char passzt[128];
if (passlen >= (int) sizeof(passzt)) if (passlen >= (int) sizeof(passzt))
return -1; return -1;
memcpy(passzt, pass, passlen); memcpy(passzt, pass, passlen);
passzt[passlen] = '\0'; passzt[passlen] = '\0';
char random[16]; char random[16];
int ret = generate_random_bytes(random, (int) sizeof(random)); int ret = generate_random_bytes(random, (int) sizeof(random));
if (ret) return -1; if (ret) return -1;
char salt[30]; char salt[30];
if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL) if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL)
return -1; return -1;
if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL) if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL)
return -1; return -1;
return 0; return 0;
} }
int check_password(char *pass, int passlen, PasswordHash hash) int check_password(char *pass, int passlen, PasswordHash hash)
{ {
char passzt[128]; char passzt[128];
if (passlen >= (int) sizeof(passzt)) if (passlen >= (int) sizeof(passzt))
return -1; return -1;
memcpy(passzt, pass, passlen); memcpy(passzt, pass, passlen);
passzt[passlen] = '\0'; passzt[passlen] = '\0';
PasswordHash new_hash; 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)
return -1; return -1;
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
return 1; return 1;
return 0; return 0;
} }
+11 -11
View File
@@ -1,11 +1,11 @@
#ifndef BCRYPT_INCLUDED #ifndef BCRYPT_INCLUDED
#define BCRYPT_INCLUDED #define BCRYPT_INCLUDED
typedef struct { typedef struct {
char data[61]; char data[61];
} PasswordHash; } PasswordHash;
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash); int hash_password(char *pass, int passlen, int cost, PasswordHash *hash);
int check_password(char *pass, int passlen, PasswordHash hash); int check_password(char *pass, int passlen, PasswordHash hash);
#endif // BCRYPT_INCLUDED #endif // BCRYPT_INCLUDED
+987 -1095
View File
File diff suppressed because it is too large Load Diff
+32 -32
View File
@@ -1,32 +1,32 @@
#ifdef __linux__ #ifdef __linux__
#include <errno.h> #include <errno.h>
#include <sys/random.h> #include <sys/random.h>
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#endif #endif
#include "random.h" #include "random.h"
int generate_random_bytes(char *dst, int cap) int generate_random_bytes(char *dst, int cap)
{ {
#ifdef __linux__ #ifdef __linux__
int copied = 0; int copied = 0;
while (copied < cap) { while (copied < cap) {
int ret = getrandom(dst, (size_t) cap, 0); int ret = getrandom(dst, (size_t) cap, 0);
if (ret < 0) { if (ret < 0) {
if (errno == EINTR) if (errno == EINTR)
continue; continue;
return -1; return -1;
} }
copied += ret; copied += ret;
} }
return 0; return 0;
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG); NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return BCRYPT_SUCCESS(status) ? 0 : -1; return BCRYPT_SUCCESS(status) ? 0 : -1;
#endif #endif
} }
+5 -5
View File
@@ -1,6 +1,6 @@
#ifndef RANDOM_INCLUDED #ifndef RANDOM_INCLUDED
#define RANDOM_INCLUDED #define RANDOM_INCLUDED
int generate_random_bytes(char *dst, int cap); int generate_random_bytes(char *dst, int cap);
#endif // RANDOM_INCLUDED #endif // RANDOM_INCLUDED
+198
View File
@@ -0,0 +1,198 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "random.h"
#include "session.h"
#define CSRF_RAW_TOKEN_SIZE 32
#define SESS_RAW_TOKEN_SIZE 32
#define CSRF_TOKEN_SIZE (2 * CSRF_RAW_TOKEN_SIZE)
#define SESS_TOKEN_SIZE (2 * SESS_RAW_TOKEN_SIZE)
typedef struct {
int user;
char csrf[CSRF_TOKEN_SIZE];
char sess[SESS_TOKEN_SIZE];
} Session;
struct SessionStorage {
int count;
int capacity;
Session items[];
};
static bool is_hex_digit(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
static int hex_digit_to_int(char c)
{
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return c - '0';
}
static void unpack_token(char *src, int srclen, char *dst, int dstlen)
{
assert(2 * srclen == dstlen);
for (int i = 0; i < srclen; i++) {
static const char table[] = "0123456789abcdef";
int low = (src[i] & 0x0F) >> 0;
int high = (src[i] & 0xF0) >> 4;
dst[(i << 1) | 0] = table[high];
dst[(i << 1) | 1] = table[low];
}
}
static int pack_token(char *src, int srclen, char *dst, int dstlen)
{
if (srclen & 1)
return -1;
assert(srclen == 2 * dstlen);
for (int i = 0; i < srclen; i += 2) {
int high = src[i+0];
int low = src[i+1];
if (!is_hex_digit(high) || !is_hex_digit(low))
return -1;
dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0);
}
return 0;
}
SessionStorage *session_storage_init(int max_sessions)
{
int capacity = 2 * max_sessions;
SessionStorage *storage = malloc(sizeof(SessionStorage) + capacity * sizeof(Session));
if (storage == NULL)
return NULL;
storage->count = 0;
storage->capacity = capacity;
for (int i = 0; i < capacity; i++)
storage->items[i].user = -1;
return storage;
}
void session_storage_free(SessionStorage *storage)
{
free(storage);
}
#include <stdio.h> // TODO
static Session *lookup_session_slot(SessionStorage *storage, HTTP_String sess, bool find_unused)
{
if (find_unused && 2 * storage->count + 2 > storage->capacity)
return NULL;
if (sess.len != SESS_TOKEN_SIZE)
return NULL;
uint64_t key;
if (sess.len < (int) (2 * sizeof(key)))
return NULL;
for (int i = 0; i < (int) sizeof(key); i++) {
int high = sess.ptr[(i << 1) | 0];
int low = sess.ptr[(i << 1) | 1];
if (!is_hex_digit(sess.ptr[i+0]) ||
!is_hex_digit(sess.ptr[i+1]))
return NULL;
key <<= 4;
key |= hex_digit_to_int(high);
key <<= 4;
key |= hex_digit_to_int(low);
}
int i = key % storage->capacity;
for (int j = 0; j < storage->capacity; j++) {
if (find_unused) {
if (storage->items[i].user < 0)
return &storage->items[i]; // Unused slot
} else {
if (storage->items[i].user == -1)
return NULL;
if (storage->items[i].user != -2)
if (!memcmp(storage->items[i].sess, sess.ptr, SESS_TOKEN_SIZE))
return &storage->items[i];
}
i++;
if (i == storage->capacity)
i = 0;
}
return NULL;
}
int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf)
{
int ret;
char raw_sess[SESS_RAW_TOKEN_SIZE];
char raw_csrf[CSRF_RAW_TOKEN_SIZE];
ret = generate_random_bytes(raw_sess, SESS_RAW_TOKEN_SIZE);
if (ret) return -1;
ret = generate_random_bytes(raw_csrf, CSRF_RAW_TOKEN_SIZE);
if (ret) return -1;
char sess[SESS_TOKEN_SIZE];
char csrf[CSRF_TOKEN_SIZE];
unpack_token(raw_sess, SESS_RAW_TOKEN_SIZE, sess, SESS_TOKEN_SIZE);
unpack_token(raw_csrf, CSRF_RAW_TOKEN_SIZE, csrf, CSRF_TOKEN_SIZE);
Session *found = lookup_session_slot(storage, (HTTP_String) { sess, SESS_TOKEN_SIZE }, true);
if (found == NULL) return -1;
found->user = user;
memcpy(found->sess, sess, SESS_TOKEN_SIZE);
memcpy(found->csrf, csrf, CSRF_TOKEN_SIZE);
*psess = (HTTP_String) { found->sess, SESS_TOKEN_SIZE };
*pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE };
storage->count++;
return 0;
}
int delete_session(SessionStorage *storage, HTTP_String sess)
{
char raw_sess[SESS_RAW_TOKEN_SIZE];
if (sess.len != SESS_TOKEN_SIZE || pack_token(sess.ptr, sess.len, raw_sess, (int) sizeof(raw_sess)) < 0)
return -1;
Session *found = lookup_session_slot(storage, sess, false);
if (found == NULL)
return false;
assert(found->user >= 0);
found->user = -2;
storage->count--;
return 0;
}
int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser)
{
Session *found = lookup_session_slot(storage, sess, false);
if (found == NULL)
return -1;
assert(found->user >= 0);
*pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE };
*puser = found->user;
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef SESSION_INCLUDED
#define SESSION_INCLUDED
#include <chttp.h> // Only for HTTP_String
typedef struct SessionStorage SessionStorage;
SessionStorage *session_storage_init(int max_sessions);
void session_storage_free(SessionStorage *storage);
int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf);
int delete_session(SessionStorage *storage, HTTP_String sess);
int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser);
#endif // SESSION_INCLUDED
+158 -158
View File
@@ -1,159 +1,159 @@
#include <stdio.h> #include <stdio.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "sqlite3utils.h" #include "sqlite3utils.h"
typedef struct { typedef struct {
char *str; char *str;
int len; int len;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
} Prepped; } Prepped;
struct SQLiteCache { struct SQLiteCache {
sqlite3 *db; sqlite3 *db;
int count; int count;
int capacity_log2; int capacity_log2;
Prepped items[]; Prepped items[];
}; };
SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2) SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2)
{ {
SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped)); SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped));
if (cache == NULL) if (cache == NULL)
return NULL; return NULL;
cache->db = db; cache->db = db;
cache->count = 0; cache->count = 0;
cache->capacity_log2 = capacity_log2; cache->capacity_log2 = capacity_log2;
for (int i = 0; i < (1 << capacity_log2); i++) for (int i = 0; i < (1 << capacity_log2); i++)
cache->items[i].stmt = NULL; cache->items[i].stmt = NULL;
return cache; return cache;
} }
void sqlite_cache_free(SQLiteCache *cache) void sqlite_cache_free(SQLiteCache *cache)
{ {
for (int i = 0; i < (1 << cache->capacity_log2); i++) { for (int i = 0; i < (1 << cache->capacity_log2); i++) {
sqlite3_stmt *stmt = cache->items[i].stmt; sqlite3_stmt *stmt = cache->items[i].stmt;
if (stmt) { if (stmt) {
free(cache->items[i].str); free(cache->items[i].str);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
} }
free(cache); free(cache);
} }
sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) sqlite3 *sqlite_cache_getdb(SQLiteCache *cache)
{ {
return cache->db; return cache->db;
} }
static unsigned long djb2(char *src, int len) static unsigned long djb2(char *src, int len)
{ {
char *ptr = src; char *ptr = src;
char *end = src + len; char *end = src + len;
unsigned long hash = 5381; unsigned long hash = 5381;
int c; int c;
while (ptr < end && (c = *ptr++)) while (ptr < end && (c = *ptr++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash; return hash;
} }
static int lookup(SQLiteCache *cache, char *fmt, int fmtlen) static int lookup(SQLiteCache *cache, char *fmt, int fmtlen)
{ {
int mask = (1 << cache->capacity_log2) - 1; int mask = (1 << cache->capacity_log2) - 1;
int hash = djb2(fmt, fmtlen); int hash = djb2(fmt, fmtlen);
int i = hash & mask; int i = hash & mask;
int perturb = hash; int perturb = hash;
for (;;) { for (;;) {
if (cache->items[i].stmt == NULL) if (cache->items[i].stmt == NULL)
return i; return i;
if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen)) if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen))
return i; return i;
perturb >>= 5; perturb >>= 5;
i = (i * 5 + 1 + perturb) & mask; i = (i * 5 + 1 + perturb) & mask;
} }
return -1; return -1;
} }
int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen) int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen)
{ {
if (fmtlen < 0) if (fmtlen < 0)
fmtlen = strlen(fmt); fmtlen = strlen(fmt);
int i = lookup(cache, fmt, fmtlen); int i = lookup(cache, fmt, fmtlen);
if (cache->items[i].stmt == NULL) { if (cache->items[i].stmt == NULL) {
printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL); int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
//fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__); //fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
return ret; return ret;
} }
char *cpy = malloc(fmtlen); char *cpy = malloc(fmtlen);
if (cpy == NULL) { if (cpy == NULL) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return SQLITE_NOMEM; return SQLITE_NOMEM;
} }
memcpy(cpy, fmt, fmtlen); memcpy(cpy, fmt, fmtlen);
cache->items[i].str = cpy; cache->items[i].str = cpy;
cache->items[i].len = fmtlen; cache->items[i].len = fmtlen;
cache->items[i].stmt = stmt; cache->items[i].stmt = stmt;
} }
sqlite3_stmt *stmt = cache->items[i].stmt; sqlite3_stmt *stmt = cache->items[i].stmt;
*pstmt = stmt; *pstmt = stmt;
return SQLITE_OK; return SQLITE_OK;
} }
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, VArgs args) sqlite3_stmt **pstmt, char *fmt, VArgs args)
{ {
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt)); int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt));
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
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) {
sqlite3_reset(stmt); sqlite3_reset(stmt);
return ret; return ret;
} }
} }
*pstmt = stmt; *pstmt = stmt;
return SQLITE_OK; return SQLITE_OK;
} }
+20 -20
View File
@@ -1,21 +1,21 @@
#ifndef SQLITE3UTILS_INCLUDED #ifndef SQLITE3UTILS_INCLUDED
#define SQLITE3UTILS_INCLUDED #define SQLITE3UTILS_INCLUDED
#include "sqlite3.h" #include "sqlite3.h"
#include "variadic.h" #include "variadic.h"
#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__)) #define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__))
typedef struct SQLiteCache SQLiteCache; typedef struct SQLiteCache SQLiteCache;
SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2); SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2);
void sqlite_cache_free(SQLiteCache *cache); void sqlite_cache_free(SQLiteCache *cache);
sqlite3* sqlite_cache_getdb(SQLiteCache *cache); sqlite3* sqlite_cache_getdb(SQLiteCache *cache);
int sqlite3utils_prepare(SQLiteCache *cache, int sqlite3utils_prepare(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, int fmtlen); sqlite3_stmt **pstmt, char *fmt, int fmtlen);
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, VArgs args); sqlite3_stmt **pstmt, char *fmt, VArgs args);
#endif // SQLITE3UTILS_INCLUDED #endif // SQLITE3UTILS_INCLUDED
+419 -412
View File
@@ -1,412 +1,419 @@
#include <stdio.h> #include <stdio.h>
#include <limits.h> #include <limits.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include "template.h" #include "template.h"
#include "sqlite3utils.h" #include "sqlite3utils.h"
#define TRACE(...) {} #define TRACE(...) {}
//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__); //#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__);
typedef struct CachedProgram CachedProgram; typedef struct CachedProgram CachedProgram;
struct CachedProgram { struct CachedProgram {
char path[1<<8]; char path[1<<8];
int pathlen; int pathlen;
WL_Program program; WL_Program program;
}; };
struct TemplateCache { struct TemplateCache {
int count; int count;
int capacity_log2; int capacity_log2;
CachedProgram pool[]; CachedProgram pool[];
}; };
TemplateCache *template_cache_init(int capacity_log2) TemplateCache *template_cache_init(int capacity_log2)
{ {
TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram)); TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram));
if (cache == NULL) if (cache == NULL)
return NULL; return NULL;
cache->count = 0; cache->count = 0;
cache->capacity_log2 = capacity_log2; cache->capacity_log2 = capacity_log2;
for (int i = 0; i < (1 << capacity_log2); i++) for (int i = 0; i < (1 << capacity_log2); i++)
cache->pool[i].pathlen = -1; cache->pool[i].pathlen = -1;
return cache; return cache;
} }
void template_cache_free(TemplateCache *cache) void template_cache_free(TemplateCache *cache)
{ {
free(cache); free(cache);
} }
static unsigned long djb2(WL_String str) static unsigned long djb2(WL_String str)
{ {
char *ptr = str.ptr; char *ptr = str.ptr;
char *end = str.ptr + str.len; char *end = str.ptr + str.len;
unsigned long hash = 5381; unsigned long hash = 5381;
int c; int c;
while (ptr < end && (c = *ptr++)) while (ptr < end && (c = *ptr++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash; return hash;
} }
static int lookup(TemplateCache *cache, WL_String path) static int lookup(TemplateCache *cache, WL_String path)
{ {
int mask = (1 << cache->capacity_log2) - 1; int mask = (1 << cache->capacity_log2) - 1;
int hash = djb2(path); int hash = djb2(path);
int i = hash & mask; int i = hash & mask;
int perturb = hash; int perturb = hash;
for (;;) { for (;;) {
if (cache->pool[i].pathlen == -1) if (cache->pool[i].pathlen == -1)
return i; return i;
if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen)) if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen))
return i; return i;
perturb >>= 5; perturb >>= 5;
i = (i * 5 + 1 + perturb) & mask; i = (i * 5 + 1 + perturb) & mask;
} }
return -1; return -1;
} }
typedef struct LoadedFile LoadedFile; typedef struct LoadedFile LoadedFile;
struct LoadedFile { struct LoadedFile {
LoadedFile* next; LoadedFile* next;
int len; int len;
char data[]; char data[];
}; };
static LoadedFile *load_file(WL_String path) static LoadedFile *load_file(WL_String path)
{ {
char buf[1<<10]; char buf[1<<10];
if (path.len >= (int) sizeof(buf)) if (path.len >= (int) sizeof(buf))
return NULL; return NULL;
memcpy(buf, path.ptr, path.len); memcpy(buf, path.ptr, path.len);
buf[path.len] = '\0'; buf[path.len] = '\0';
FILE *stream = fopen(buf, "rb"); FILE *stream = fopen(buf, "rb");
if (stream == NULL) if (stream == NULL)
return NULL; return NULL;
int ret = fseek(stream, 0, SEEK_END); int ret = fseek(stream, 0, SEEK_END);
if (ret) { if (ret) {
fclose(stream); fclose(stream);
return NULL; return NULL;
} }
long tmp = ftell(stream); long tmp = ftell(stream);
if (tmp < 0 || tmp > INT_MAX) { if (tmp < 0 || tmp > INT_MAX) {
fclose(stream); fclose(stream);
return NULL; return NULL;
} }
int len = (int) tmp; int len = (int) tmp;
ret = fseek(stream, 0, SEEK_SET); ret = fseek(stream, 0, SEEK_SET);
if (ret) { if (ret) {
fclose(stream); fclose(stream);
return NULL; return NULL;
} }
LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1);
if (result == NULL) { if (result == NULL) {
fclose(stream); fclose(stream);
return NULL; return NULL;
} }
result->next = NULL; result->next = NULL;
result->len = len; result->len = len;
int read_len = fread(result->data, 1, len+1, stream); int read_len = fread(result->data, 1, len+1, stream);
if (read_len != len || ferror(stream) || !feof(stream)) { if (read_len != len || ferror(stream) || !feof(stream)) {
fclose(stream); fclose(stream);
free(result); free(result);
return NULL; return NULL;
} }
fclose(stream); fclose(stream);
return result; return result;
} }
static void free_loaded_files(LoadedFile *loaded_file) static void free_loaded_files(LoadedFile *loaded_file)
{ {
while (loaded_file) { while (loaded_file) {
LoadedFile *next = loaded_file->next; LoadedFile *next = loaded_file->next;
free(loaded_file); free(loaded_file);
loaded_file = next; loaded_file = next;
} }
} }
static int compile(WL_String path, WL_Program *program, WL_Arena *arena) static int compile(WL_String path, WL_Program *program, WL_Arena *arena)
{ {
WL_Compiler *compiler = wl_compiler_init(arena); WL_Compiler *compiler = wl_compiler_init(arena);
if (compiler == NULL) { if (compiler == NULL) {
TRACE("Couldn't initialize WL compiler object"); TRACE("Couldn't initialize WL compiler object");
return -1; return -1;
} }
LoadedFile *loaded_file_head = NULL; LoadedFile *loaded_file_head = NULL;
LoadedFile **loaded_file_tail = &loaded_file_head; LoadedFile **loaded_file_tail = &loaded_file_head;
for (int i = 0;; i++) { for (int i = 0;; i++) {
LoadedFile *loaded_file = load_file(path); LoadedFile *loaded_file = load_file(path);
if (loaded_file == NULL) { if (loaded_file == NULL) {
TRACE("Couldn't load file '%.*s'", path.len, path.ptr); TRACE("Couldn't load file '%.*s'", path.len, path.ptr);
free_loaded_files(loaded_file_head); free_loaded_files(loaded_file_head);
return -1; return -1;
} }
*loaded_file_tail = loaded_file; *loaded_file_tail = loaded_file;
loaded_file_tail = &loaded_file->next; loaded_file_tail = &loaded_file->next;
WL_String content = { loaded_file->data, loaded_file->len }; WL_String content = { loaded_file->data, loaded_file->len };
WL_AddResult result = wl_compiler_add(compiler, content); WL_AddResult result = wl_compiler_add(compiler, content);
if (result.type == WL_ADD_ERROR) { if (result.type == WL_ADD_ERROR) {
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
free_loaded_files(loaded_file_head); free_loaded_files(loaded_file_head);
return -1; return -1;
} }
if (result.type == WL_ADD_LINK) break; if (result.type == WL_ADD_LINK) break;
assert(result.type == WL_ADD_AGAIN); assert(result.type == WL_ADD_AGAIN);
path = result.path; path = result.path;
} }
int ret = wl_compiler_link(compiler, program); int ret = wl_compiler_link(compiler, program);
if (ret < 0) { if (ret < 0) {
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
return -1; return -1;
} }
free_loaded_files(loaded_file_head); free_loaded_files(loaded_file_head);
TRACE("Compilation succeded"); TRACE("Compilation succeded");
return 0; return 0;
} }
static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache)
{ {
int num_args = wl_arg_count(rt); int num_args = wl_arg_count(rt);
if (num_args == 0) if (num_args == 0)
return 0; return 0;
WL_String format; WL_String format;
if (!wl_arg_str(rt, 0, &format)) if (!wl_arg_str(rt, 0, &format))
return -1; return -1;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len); int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
return -1; return -1;
for (int i = 1; i < num_args; i++) { for (int i = 1; i < num_args; i++) {
int64_t ival; int64_t ival;
double fval; double fval;
WL_String str; WL_String str;
if (wl_arg_none(rt, i)) if (wl_arg_none(rt, i))
ret = sqlite3_bind_null (stmt, i); ret = sqlite3_bind_null (stmt, i);
else if (wl_arg_s64(rt, i, &ival)) else if (wl_arg_s64(rt, i, &ival))
ret = sqlite3_bind_int64 (stmt, i, ival); ret = sqlite3_bind_int64 (stmt, i, ival);
else if (wl_arg_f64(rt, i, &fval)) else if (wl_arg_f64(rt, i, &fval))
ret = sqlite3_bind_double(stmt, i, fval); ret = sqlite3_bind_double(stmt, i, fval);
else if (wl_arg_str(rt, i, &str)) else if (wl_arg_str(rt, i, &str))
ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL); ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL);
else assert(0); else assert(0);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
sqlite3_reset(stmt); sqlite3_reset(stmt);
return -1; return -1;
} }
} }
wl_push_array(rt, 0); wl_push_array(rt, 0);
while (sqlite3_step(stmt) == SQLITE_ROW) { while (sqlite3_step(stmt) == SQLITE_ROW) {
int num_cols = sqlite3_column_count(stmt); int num_cols = sqlite3_column_count(stmt);
if (num_cols < 0) { if (num_cols < 0) {
sqlite3_reset(stmt); sqlite3_reset(stmt);
return -1; return -1;
} }
wl_push_map(rt, num_cols); wl_push_map(rt, num_cols);
for (int i = 0; i < num_cols; i++) { for (int i = 0; i < num_cols; i++) {
ret = sqlite3_column_type(stmt, i); ret = sqlite3_column_type(stmt, i);
switch (ret) { switch (ret) {
case SQLITE_INTEGER: case SQLITE_INTEGER:
{ {
int64_t x = sqlite3_column_int64(stmt, i); int64_t x = sqlite3_column_int64(stmt, i);
wl_push_s64(rt, x); wl_push_s64(rt, x);
} }
break; break;
case SQLITE_FLOAT: case SQLITE_FLOAT:
{ {
double x = sqlite3_column_double(stmt, i); double x = sqlite3_column_double(stmt, i);
wl_push_f64(rt, x); wl_push_f64(rt, x);
} }
break; break;
case SQLITE_TEXT: case SQLITE_TEXT:
{ {
const void *x = sqlite3_column_text(stmt, i); const void *x = sqlite3_column_text(stmt, i);
int n = sqlite3_column_bytes(stmt, i); int n = sqlite3_column_bytes(stmt, i);
wl_push_str(rt, (WL_String) { (char*) x, n }); wl_push_str(rt, (WL_String) { (char*) x, n });
} }
break; break;
case SQLITE_BLOB: case SQLITE_BLOB:
{ {
const void *x = sqlite3_column_blob(stmt, i); const void *x = sqlite3_column_blob(stmt, i);
int n = sqlite3_column_bytes(stmt, i); int n = sqlite3_column_bytes(stmt, i);
wl_push_str(rt, (WL_String) { (char*) x, n }); wl_push_str(rt, (WL_String) { (char*) x, n });
} }
break; break;
case SQLITE_NULL: case SQLITE_NULL:
{ {
wl_push_none(rt); wl_push_none(rt);
} }
break; break;
} }
const char *name = sqlite3_column_name(stmt, i); const char *name = sqlite3_column_name(stmt, i);
wl_push_str(rt, (WL_String) { (char*) name, strlen(name) }); wl_push_str(rt, (WL_String) { (char*) name, strlen(name) });
wl_insert(rt); wl_insert(rt);
} }
wl_append(rt); wl_append(rt);
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
return 0; return 0;
} }
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, int user_id, int post_id) static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
{ {
(void) dbcache; (void) dbcache;
if (wl_streq(name, "login_user_id", -1)) { if (wl_streq(name, "login_user_id", -1)) {
if (user_id < 0) if (user_id < 0)
wl_push_none(rt); wl_push_none(rt);
else else
wl_push_s64(rt, user_id); wl_push_s64(rt, user_id);
} else if (wl_streq(name, "post_id", -1)) { } else if (wl_streq(name, "post_id", -1)) {
if (post_id < 0) if (post_id < 0)
wl_push_none(rt); wl_push_none(rt);
else else
wl_push_s64(rt, post_id); wl_push_s64(rt, post_id);
}
} } else if (wl_streq(name, "csrf", -1)) {
static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache) if (csrf.len == 0)
{ wl_push_none(rt);
if (wl_streq(name, "query", -1)) { else
query_routine(rt, dbcache); wl_push_str(rt, (WL_String) { csrf.ptr, csrf.len });
return; }
} }
}
static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache)
static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program) {
{ if (wl_streq(name, "query", -1)) {
if (cache == NULL) query_routine(rt, dbcache);
return -1; return;
}
int i = lookup(cache, path); }
if (cache->pool[i].pathlen == -1) {
static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program)
WL_Program program; {
int ret = compile(path, &program, arena); if (cache == NULL)
if (ret < 0) return -1; return -1;
void *p = malloc(program.len); int i = lookup(cache, path);
if (p == NULL) if (cache->pool[i].pathlen == -1) {
return -1;
memcpy(p, program.ptr, program.len); WL_Program program;
program.ptr = p; int ret = compile(path, &program, arena);
if (ret < 0) return -1;
if ((int) sizeof(cache->pool->path) <= path.len)
return -1; void *p = malloc(program.len);
memcpy(cache->pool[i].path, path.ptr, path.len); if (p == NULL)
cache->pool[i].path[path.len] = '\0'; return -1;
cache->pool[i].pathlen = path.len; memcpy(p, program.ptr, program.len);
cache->pool[i].program = program; program.ptr = p;
}
if ((int) sizeof(cache->pool->path) <= path.len)
*program = cache->pool[i].program; return -1;
return 0; memcpy(cache->pool[i].path, path.ptr, path.len);
} cache->pool[i].path[path.len] = '\0';
cache->pool[i].pathlen = path.len;
void template_eval(HTTP_ResponseBuilder builder, int status, cache->pool[i].program = program;
WL_String path, TemplateCache *cache, WL_Arena *arena, }
SQLiteCache *dbcache, int user_id, int post_id)
{ *program = cache->pool[i].program;
http_response_builder_status(builder, status); return 0;
}
WL_Program program;
int ret = get_or_create_program(cache, path, arena, &program); void template_eval(HTTP_ResponseBuilder builder, int status,
if (ret < 0) { WL_String path, TemplateCache *cache, WL_Arena *arena,
http_response_builder_undo(builder); SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
http_response_builder_status(builder, 500); {
http_response_builder_done(builder); http_response_builder_status(builder, status);
return;
} WL_Program program;
int ret = get_or_create_program(cache, path, arena, &program);
//wl_dump_program(program); if (ret < 0) {
http_response_builder_undo(builder);
WL_Runtime *rt = wl_runtime_init(arena, program); http_response_builder_status(builder, 500);
if (rt == NULL) { http_response_builder_done(builder);
http_response_builder_undo(builder); return;
http_response_builder_status(builder, 500); }
http_response_builder_done(builder);
return; //wl_dump_program(program);
}
WL_Runtime *rt = wl_runtime_init(arena, program);
for (bool done = false; !done; ) { if (rt == NULL) {
http_response_builder_undo(builder);
WL_EvalResult result = wl_runtime_eval(rt); http_response_builder_status(builder, 500);
switch (result.type) { http_response_builder_done(builder);
return;
case WL_EVAL_DONE: }
http_response_builder_done(builder);
done = true; for (bool done = false; !done; ) {
break;
WL_EvalResult result = wl_runtime_eval(rt);
case WL_EVAL_ERROR: switch (result.type) {
// wl_runtime_error(rt)
http_response_builder_undo(builder); case WL_EVAL_DONE:
http_response_builder_status(builder, 500); http_response_builder_done(builder);
http_response_builder_done(builder); done = true;
return; break;
case WL_EVAL_SYSVAR: case WL_EVAL_ERROR:
push_sysvar(rt, result.str, dbcache, user_id, post_id); // wl_runtime_error(rt)
break; http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
case WL_EVAL_SYSCALL: http_response_builder_done(builder);
push_syscall(rt, result.str, dbcache); return;
break;
case WL_EVAL_SYSVAR:
case WL_EVAL_OUTPUT: push_sysvar(rt, result.str, dbcache, csrf, user_id, post_id);
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len }); break;
break;
case WL_EVAL_SYSCALL:
default: push_syscall(rt, result.str, dbcache);
break; break;
}
} case WL_EVAL_OUTPUT:
} http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
break;
default:
break;
}
}
}
+15 -15
View File
@@ -1,16 +1,16 @@
#ifndef TEMPLATE_INCLUDED #ifndef TEMPLATE_INCLUDED
#define TEMPLATE_INCLUDED #define TEMPLATE_INCLUDED
#include "wl.h" #include "wl.h"
#include "chttp.h" #include "chttp.h"
#include "sqlite3utils.h" #include "sqlite3utils.h"
typedef struct TemplateCache TemplateCache; typedef struct TemplateCache TemplateCache;
TemplateCache *template_cache_init(int capacity_log2); TemplateCache *template_cache_init(int capacity_log2);
void template_cache_free(TemplateCache *cache); void template_cache_free(TemplateCache *cache);
void template_eval(HTTP_ResponseBuilder builder, int status, void template_eval(HTTP_ResponseBuilder builder, int status,
WL_String path, TemplateCache *cache, WL_Arena *arena, WL_String path, TemplateCache *cache, WL_Arena *arena,
SQLiteCache *dbcache, int user_id, int post_id); SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id);
#endif // TEMPLATE_INCLUDED #endif // TEMPLATE_INCLUDED
+21 -21
View File
@@ -1,21 +1,21 @@
#include "variadic.h" #include "variadic.h"
VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; } VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; }
VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; } VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; }
VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; } VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; }
VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; } VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; }
VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; } VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; }
VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; } VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; }
VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; } VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; }
VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; } VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; }
VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; } VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; }
VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; } VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; }
VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; } VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; }
VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; } VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; }
VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; } VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; }
VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; } VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; }
VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; } VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; }
VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; } VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; }
VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; } VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; }
VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; } VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; }
VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; } VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; }
+109 -109
View File
@@ -1,110 +1,110 @@
#ifndef VARIADIC_INCLUDED #ifndef VARIADIC_INCLUDED
#define VARIADIC_INCLUDED #define VARIADIC_INCLUDED
#include <stdbool.h> #include <stdbool.h>
#include "chttp.h" #include "chttp.h"
typedef enum { typedef enum {
VARG_TYPE_C, VARG_TYPE_C,
VARG_TYPE_S, VARG_TYPE_S,
VARG_TYPE_I, VARG_TYPE_I,
VARG_TYPE_L, VARG_TYPE_L,
VARG_TYPE_LL, VARG_TYPE_LL,
VARG_TYPE_SC, VARG_TYPE_SC,
VARG_TYPE_SS, VARG_TYPE_SS,
VARG_TYPE_SI, VARG_TYPE_SI,
VARG_TYPE_SL, VARG_TYPE_SL,
VARG_TYPE_SLL, VARG_TYPE_SLL,
VARG_TYPE_UC, VARG_TYPE_UC,
VARG_TYPE_US, VARG_TYPE_US,
VARG_TYPE_UI, VARG_TYPE_UI,
VARG_TYPE_UL, VARG_TYPE_UL,
VARG_TYPE_ULL, VARG_TYPE_ULL,
VARG_TYPE_F, VARG_TYPE_F,
VARG_TYPE_D, VARG_TYPE_D,
VARG_TYPE_B, VARG_TYPE_B,
VARG_TYPE_STR, VARG_TYPE_STR,
} VArgType; } VArgType;
typedef struct { typedef struct {
VArgType type; VArgType type;
union { union {
char c; char c;
short s; short s;
int i; int i;
long l; long l;
long long ll; long long ll;
signed char sc; signed char sc;
signed short ss; signed short ss;
signed int si; signed int si;
signed long sl; signed long sl;
signed long long sll; signed long long sll;
unsigned char uc; unsigned char uc;
unsigned short us; unsigned short us;
unsigned int ui; unsigned int ui;
unsigned long ul; unsigned long ul;
unsigned long long ull; unsigned long long ull;
float f; float f;
double d; double d;
bool b; bool b;
HTTP_String str; HTTP_String str;
}; };
} VArg; } VArg;
VArg varg_from_c (char c); VArg varg_from_c (char c);
VArg varg_from_s (short s); VArg varg_from_s (short s);
VArg varg_from_i (int i); VArg varg_from_i (int i);
VArg varg_from_l (long l); VArg varg_from_l (long l);
VArg varg_from_ll (long long ll); VArg varg_from_ll (long long ll);
VArg varg_from_sc (char sc); VArg varg_from_sc (char sc);
VArg varg_from_ss (short ss); VArg varg_from_ss (short ss);
VArg varg_from_si (int si); VArg varg_from_si (int si);
VArg varg_from_sl (long sl); VArg varg_from_sl (long sl);
VArg varg_from_sll (long long sll); VArg varg_from_sll (long long sll);
VArg varg_from_uc (char uc); VArg varg_from_uc (char uc);
VArg varg_from_us (short us); VArg varg_from_us (short us);
VArg varg_from_ui (int ui); VArg varg_from_ui (int ui);
VArg varg_from_ul (long ul); VArg varg_from_ul (long ul);
VArg varg_from_ull (long long ull); VArg varg_from_ull (long long ull);
VArg varg_from_f (float f); VArg varg_from_f (float f);
VArg varg_from_d (double d); VArg varg_from_d (double d);
VArg varg_from_b (bool b); VArg varg_from_b (bool b);
VArg varg_from_str (HTTP_String str); VArg varg_from_str (HTTP_String str);
#define VARG(X) (_Generic((X), \ #define VARG(X) (_Generic((X), \
char : varg_from_c, \ char : varg_from_c, \
short : varg_from_s, \ short : varg_from_s, \
int : varg_from_i, \ int : varg_from_i, \
long : varg_from_l, \ long : varg_from_l, \
long long : varg_from_ll, \ long long : varg_from_ll, \
signed char : varg_from_sc, \ signed char : varg_from_sc, \
/*signed short : varg_from_ss,*/ \ /*signed short : varg_from_ss,*/ \
/*signed int : varg_from_si,*/ \ /*signed int : varg_from_si,*/ \
/*signed long : varg_from_sl,*/ \ /*signed long : varg_from_sl,*/ \
/*signed long long : varg_from_sll,*/ \ /*signed long long : varg_from_sll,*/ \
unsigned char : varg_from_uc, \ unsigned char : varg_from_uc, \
unsigned short : varg_from_us, \ unsigned short : varg_from_us, \
unsigned int : varg_from_ui, \ unsigned int : varg_from_ui, \
unsigned long : varg_from_ul, \ unsigned long : varg_from_ul, \
unsigned long long: varg_from_ull, \ unsigned long long: varg_from_ull, \
float : varg_from_f, \ float : varg_from_f, \
double : varg_from_d, \ double : varg_from_d, \
bool : varg_from_b, \ bool : varg_from_b, \
HTTP_String : varg_from_str \ HTTP_String : varg_from_str \
))(X) ))(X)
typedef struct { typedef struct {
int len; int len;
VArg *ptr; VArg *ptr;
} VArgs; } VArgs;
#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } } #define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } }
#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } } #define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } }
#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } } #define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } }
#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } } #define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } }
#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } } #define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } }
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME #define 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 #endif // VARIADIC_INCLUDED