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
*.exe
*.out
*.o
*.exe
*.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 <stdbool.h>
typedef struct WL_Runtime WL_Runtime;
typedef struct WL_Compiler WL_Compiler;
typedef struct {
char *ptr;
int len;
} WL_String;
typedef struct {
char *ptr;
int len;
int cur;
} WL_Arena;
typedef struct {
char *ptr;
int len;
} WL_Program;
typedef enum {
WL_ADD_ERROR,
WL_ADD_AGAIN,
WL_ADD_LINK,
} WL_AddResultType;
typedef struct {
WL_AddResultType type;
WL_String path;
} WL_AddResult;
typedef enum {
WL_EVAL_NONE,
WL_EVAL_DONE,
WL_EVAL_ERROR,
WL_EVAL_OUTPUT,
WL_EVAL_SYSVAR,
WL_EVAL_SYSCALL,
} WL_EvalResultType;
typedef struct {
WL_EvalResultType type;
WL_String str;
} WL_EvalResult;
WL_Compiler* wl_compiler_init (WL_Arena *arena);
WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content);
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
WL_String wl_compiler_error (WL_Compiler *compiler);
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
void wl_dump_program (WL_Program program);
WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program);
WL_EvalResult wl_runtime_eval (WL_Runtime *rt);
WL_String wl_runtime_error (WL_Runtime *rt);
void wl_runtime_dump (WL_Runtime *rt);
bool wl_streq (WL_String a, char *b, int blen);
int wl_arg_count (WL_Runtime *rt);
bool wl_arg_none (WL_Runtime *rt, int idx);
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_f64 (WL_Runtime *rt, int idx, double *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_map (WL_Runtime *rt, int idx);
bool wl_peek_none (WL_Runtime *rt, int off);
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_f64 (WL_Runtime *rt, int off, double *x);
bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x);
bool wl_pop_any (WL_Runtime *rt);
bool wl_pop_none (WL_Runtime *rt);
bool wl_pop_bool (WL_Runtime *rt, bool *x);
bool wl_pop_s64 (WL_Runtime *rt, int64_t *x);
bool wl_pop_f64 (WL_Runtime *rt, double *x);
bool wl_pop_str (WL_Runtime *rt, WL_String *x);
void wl_push_none (WL_Runtime *rt);
void wl_push_true (WL_Runtime *rt);
void wl_push_false (WL_Runtime *rt);
void wl_push_s64 (WL_Runtime *rt, int64_t x);
void wl_push_f64 (WL_Runtime *rt, double x);
void wl_push_str (WL_Runtime *rt, WL_String x);
void wl_push_array (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_insert (WL_Runtime *rt);
void wl_append (WL_Runtime *rt);
#include <stdint.h>
#include <stdbool.h>
typedef struct WL_Runtime WL_Runtime;
typedef struct WL_Compiler WL_Compiler;
typedef struct {
char *ptr;
int len;
} WL_String;
typedef struct {
char *ptr;
int len;
int cur;
} WL_Arena;
typedef struct {
char *ptr;
int len;
} WL_Program;
typedef enum {
WL_ADD_ERROR,
WL_ADD_AGAIN,
WL_ADD_LINK,
} WL_AddResultType;
typedef struct {
WL_AddResultType type;
WL_String path;
} WL_AddResult;
typedef enum {
WL_EVAL_NONE,
WL_EVAL_DONE,
WL_EVAL_ERROR,
WL_EVAL_OUTPUT,
WL_EVAL_SYSVAR,
WL_EVAL_SYSCALL,
} WL_EvalResultType;
typedef struct {
WL_EvalResultType type;
WL_String str;
} WL_EvalResult;
WL_Compiler* wl_compiler_init (WL_Arena *arena);
WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content);
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
WL_String wl_compiler_error (WL_Compiler *compiler);
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
void wl_dump_program (WL_Program program);
WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program);
WL_EvalResult wl_runtime_eval (WL_Runtime *rt);
WL_String wl_runtime_error (WL_Runtime *rt);
void wl_runtime_dump (WL_Runtime *rt);
bool wl_streq (WL_String a, char *b, int blen);
int wl_arg_count (WL_Runtime *rt);
bool wl_arg_none (WL_Runtime *rt, int idx);
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_f64 (WL_Runtime *rt, int idx, double *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_map (WL_Runtime *rt, int idx);
bool wl_peek_none (WL_Runtime *rt, int off);
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_f64 (WL_Runtime *rt, int off, double *x);
bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x);
bool wl_pop_any (WL_Runtime *rt);
bool wl_pop_none (WL_Runtime *rt);
bool wl_pop_bool (WL_Runtime *rt, bool *x);
bool wl_pop_s64 (WL_Runtime *rt, int64_t *x);
bool wl_pop_f64 (WL_Runtime *rt, double *x);
bool wl_pop_str (WL_Runtime *rt, WL_String *x);
void wl_push_none (WL_Runtime *rt);
void wl_push_true (WL_Runtime *rt);
void wl_push_false (WL_Runtime *rt);
void wl_push_s64 (WL_Runtime *rt, int64_t x);
void wl_push_f64 (WL_Runtime *rt, double x);
void wl_push_str (WL_Runtime *rt, WL_String x);
void wl_push_array (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_insert (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.
* 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
* 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
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* See crypt_blowfish.c for more information.
*/
#ifndef _CRYPT_BLOWFISH_H
#define _CRYPT_BLOWFISH_H
extern int _crypt_output_magic(const char *setting, char *output, int size);
extern char *_crypt_blowfish_rn(const char *key, const char *setting,
char *output, int size);
extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
unsigned long count,
const char *input, int size, char *output, int output_size);
#endif
/*
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
* 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
* 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
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* See crypt_blowfish.c for more information.
*/
#ifndef _CRYPT_BLOWFISH_H
#define _CRYPT_BLOWFISH_H
extern int _crypt_output_magic(const char *setting, char *output, int size);
extern char *_crypt_blowfish_rn(const char *key, const char *setting,
char *output, int size);
extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
unsigned long count,
const char *input, int size, char *output, int output_size);
#endif
+23 -23
View File
@@ -1,23 +1,23 @@
ifeq ($(OS),Windows_NT)
EXT = .exe
LFLAGS = -lws2_32 -lbcrypt
else
EXT = .out
LFLAGS =
endif
CFLAGS = -Wall -Wextra -O0 -g3 -I3p
HFILES = $(shell find src 3p -name "*.h")
CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c"))
all: cozisnews$(EXT)
cozisnews$(EXT): $(CFILES) $(HFILES) sqlite3.o
gcc -o $@ $(CFILES) sqlite3.o $(CFLAGS) $(LFLAGS)
sqlite3.o: 3p/sqlite3.c
gcc -o $@ -c $<
clean:
rm *.o *.out *.exe
ifeq ($(OS),Windows_NT)
EXT = .exe
LFLAGS = -lws2_32 -lbcrypt
else
EXT = .out
LFLAGS =
endif
CFLAGS = -Wall -Wextra -O0 -g3 -I3p -fsanitize=address,undefined
HFILES = $(shell find src 3p -name "*.h")
CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c"))
all: cozisnews$(EXT)
cozisnews$(EXT): $(CFILES) $(HFILES) sqlite3.o
gcc -o $@ $(CFILES) sqlite3.o $(CFLAGS) $(LFLAGS)
sqlite3.o: 3p/sqlite3.c
gcc -o $@ -c $<
clean:
rm *.o *.out *.exe
+4 -5
View File
@@ -1,5 +1,4 @@
bcrypt passwords
session management using proper random tokens
CSRF protection
input validation to avoid XSS and injection
only allow signup, login ecc over HTTPS
input validation to avoid XSS and injection
only allow signup, login ecc over HTTPS
session expiration
mark cookies as secure
+29 -29
View File
@@ -1,29 +1,29 @@
CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
hash TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
signup_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS Posts (
id INTEGER PRIMARY KEY,
author INTEGER NOT NULL,
title TEXT NOT NULL,
is_link BOOLEAN NOT NULL,
content TEXT NOT NULL,
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author) REFERENCES Users(id)
);
CREATE TABLE IF NOT EXISTS Comments (
id INTEGER PRIMARY KEY,
parent_post INTEGER NOT NULL,
parent_comment INTEGER,
author INTEGER NOT NULL,
content TEXT NOT NULL,
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author) REFERENCES Users(id),
FOREIGN KEY (parent_post) REFERENCES Posts(id),
FOREIGN KEY (parent_comment) REFERENCES Comments(id)
);
CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
hash TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
signup_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS Posts (
id INTEGER PRIMARY KEY,
author INTEGER NOT NULL,
title TEXT NOT NULL,
is_link BOOLEAN NOT NULL,
content TEXT NOT NULL,
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author) REFERENCES Users(id)
);
CREATE TABLE IF NOT EXISTS Comments (
id INTEGER PRIMARY KEY,
parent_post INTEGER NOT NULL,
parent_comment INTEGER,
author INTEGER NOT NULL,
content TEXT NOT NULL,
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author) REFERENCES Users(id),
FOREIGN KEY (parent_post) REFERENCES Posts(id),
FOREIGN KEY (parent_comment) REFERENCES Comments(id)
);
+147 -147
View File
@@ -1,147 +1,147 @@
<html>
<head>
<title>cozis news - login</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
/* Login form styles */
.login-container {
max-width: 400px;
margin: 40px auto;
padding: 30px;
background: #E8D4A9;
border-radius: 5px;
border: 1px solid #D4C298;
}
.login-container h2 {
text-align: center;
margin-bottom: 30px;
color: #1D2B42;
font-size: 18px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #7A5F2A;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 8px;
font-family: monospace;
font-size: 14px;
background: #F7E6C0;
border: 1px solid #D4C298;
border-radius: 3px;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #5780C9;
background: #fff;
}
.login-button {
width: 100%;
padding: 10px;
background: #5780C9;
color: #F7E6C0;
border: none;
border-radius: 3px;
font-family: monospace;
font-size: 14px;
cursor: pointer;
margin-bottom: 15px;
}
.login-button:hover {
background: #1D2B42;
}
.form-links {
text-align: center;
font-size: 12px;
}
.form-links a {
color: #7A5F2A;
margin: 0 10px;
}
.form-links a:hover {
color: #1D2B42;
}
</style>
</head>
<body>
<nav>
<div>
<a href="/new">new</a>
|
<a href="">hot</a>
</div>
<div>
<a href="">settings</a>
|
<a href="/login" class="current">login</a>
</div>
</nav>
<main>
<div class="login-container">
<h2>Welcome back!</h2>
<form action="/api/login" method="POST">
<div class="form-group">
<label for_="username">Username:</label>
<input type="text" id="username" name="username" required />
</div>
<div class="form-group">
<label for_="password">Password:</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit" class="login-button">log in</button>
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">create account</a>
</div>
</form>
</div>
</main>
<footer>
Made with love by cozis
</footer>
</body>
</html>
<html>
<head>
<title>cozis news - login</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
/* Login form styles */
.login-container {
max-width: 400px;
margin: 40px auto;
padding: 30px;
background: #E8D4A9;
border-radius: 5px;
border: 1px solid #D4C298;
}
.login-container h2 {
text-align: center;
margin-bottom: 30px;
color: #1D2B42;
font-size: 18px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #7A5F2A;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 8px;
font-family: monospace;
font-size: 14px;
background: #F7E6C0;
border: 1px solid #D4C298;
border-radius: 3px;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #5780C9;
background: #fff;
}
.login-button {
width: 100%;
padding: 10px;
background: #5780C9;
color: #F7E6C0;
border: none;
border-radius: 3px;
font-family: monospace;
font-size: 14px;
cursor: pointer;
margin-bottom: 15px;
}
.login-button:hover {
background: #1D2B42;
}
.form-links {
text-align: center;
font-size: 12px;
}
.form-links a {
color: #7A5F2A;
margin: 0 10px;
}
.form-links a:hover {
color: #1D2B42;
}
</style>
</head>
<body>
<nav>
<div>
<a href="/new">new</a>
|
<a href="">hot</a>
</div>
<div>
<a href="">settings</a>
|
<a href="/login" class="current">login</a>
</div>
</nav>
<main>
<div class="login-container">
<h2>Welcome back!</h2>
<form action="/api/login" method="POST">
<div class="form-group">
<label for_="username">Username:</label>
<input type="text" id="username" name="username" required />
</div>
<div class="form-group">
<label for_="password">Password:</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit" class="login-button">log in</button>
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">create account</a>
</div>
</form>
</div>
</main>
<footer>
Made with love by cozis
</footer>
</body>
</html>
+121 -121
View File
@@ -1,121 +1,121 @@
fun page(title, style, content)
<html>
<head>
<title>CN - \title</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
</style>
\(style)
</head>
<body>
<nav>
<div>
<a href="/new" class="current">new</a>
|
<a href="">hot</a>
</div>
\if $login_username == none:
<div>
<a href="/login">log-in</a>
|
<a href="/signup">sign-up</a>
</div>
else
<div>
<a href="">settings</a>
|
<a href="/logout">log-out</a>
</div>
</nav>
<main>
\content
</main>
<footer>
Made with love by cozis
</footer>
</body>
</html>
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: "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" },
]
let style =
<style>
.item {
overflow: auto;
border-bottom: 1px solid #E8D4A9;
font-size: 14px;
}
.item:last-child {
border-bottom: 0;
}
.item span {
color: #7A5F2A;
text-decoration: none;
}
.item div {
color: #E8D4A9;
float: left
}
.item div:first-child {
width: calc(100% - 250px);
}
.item div:last-child {
width: 250px;
text-align: right;
}
</style>
let content =
<div>
\for post in posts:
<div class="item">
<div>
<a href=post.link>\post.title</a>
</div>
<div>
<span>\post.date</span> | <span><a href="/thread">\post.num_comments comments</a></span>
</div>
</div>
</div>
page("new", style, content)
fun page(title, style, content)
<html>
<head>
<title>CN - \title</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
</style>
\(style)
</head>
<body>
<nav>
<div>
<a href="/new" class="current">new</a>
|
<a href="">hot</a>
</div>
\if $login_username == none:
<div>
<a href="/login">log-in</a>
|
<a href="/signup">sign-up</a>
</div>
else
<div>
<a href="">settings</a>
|
<a href="/logout">log-out</a>
</div>
</nav>
<main>
\content
</main>
<footer>
Made with love by cozis
</footer>
</body>
</html>
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: "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" },
]
let style =
<style>
.item {
overflow: auto;
border-bottom: 1px solid #E8D4A9;
font-size: 14px;
}
.item:last-child {
border-bottom: 0;
}
.item span {
color: #7A5F2A;
text-decoration: none;
}
.item div {
color: #E8D4A9;
float: left
}
.item div:first-child {
width: calc(100% - 250px);
}
.item div:last-child {
width: 250px;
text-align: right;
}
</style>
let content =
<div>
\for post in posts:
<div class="item">
<div>
<a href=post.link>\post.title</a>
</div>
<div>
<span>\post.date</span> | <span><a href="/thread">\post.num_comments comments</a></span>
</div>
</div>
</div>
page("new", style, content)
+113 -113
View File
@@ -1,114 +1,114 @@
<html>
<head>
<title>cozis news - not found</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
/* 404 page styles */
.not-found-container {
text-align: center;
padding: 60px 20px;
}
.error-code {
font-size: 72px;
font-weight: bold;
color: #7A5F2A;
margin-bottom: 20px;
line-height: 100%;
}
.error-message {
font-size: 24px;
color: #1D2B42;
margin-bottom: 15px;
}
.error-description {
font-size: 14px;
color: #7A5F2A;
margin-bottom: 40px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
line-height: 160%;
}
.home-button {
display: inline-block;
background: #5780C9;
color: #F7E6C0;
padding: 12px 24px;
text-decoration: none;
border-radius: 5px;
font-size: 14px;
margin-top: 20px;
border: none;
cursor: pointer;
}
.home-button:hover {
background: #1D2B42;
color: #F7E6C0;
}
</style>
</head>
<body>
<nav>
<div>
<a href="/new">new</a>
|
<a href="">hot</a>
</div>
<div>
<a href="">settings</a>
|
<a href="/login">log-out</a>
</div>
</nav>
<main>
<div class="not-found-container">
<div class="error-code">404</div>
<div class="error-message">Page Not Found</div>
<div class="error-description">
Looks like this page wandered off somewhere.
</div>
</div>
</main>
<footer>
Made with love by cozis
</footer>
</body>
<html>
<head>
<title>cozis news - not found</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
/* 404 page styles */
.not-found-container {
text-align: center;
padding: 60px 20px;
}
.error-code {
font-size: 72px;
font-weight: bold;
color: #7A5F2A;
margin-bottom: 20px;
line-height: 100%;
}
.error-message {
font-size: 24px;
color: #1D2B42;
margin-bottom: 15px;
}
.error-description {
font-size: 14px;
color: #7A5F2A;
margin-bottom: 40px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
line-height: 160%;
}
.home-button {
display: inline-block;
background: #5780C9;
color: #F7E6C0;
padding: 12px 24px;
text-decoration: none;
border-radius: 5px;
font-size: 14px;
margin-top: 20px;
border: none;
cursor: pointer;
}
.home-button:hover {
background: #1D2B42;
color: #F7E6C0;
}
</style>
</head>
<body>
<nav>
<div>
<a href="/new">new</a>
|
<a href="">hot</a>
</div>
<div>
<a href="">settings</a>
|
<a href="/login">log-out</a>
</div>
</nav>
<main>
<div class="not-found-container">
<div class="error-code">404</div>
<div class="error-message">Page Not Found</div>
<div class="error-description">
Looks like this page wandered off somewhere.
</div>
</div>
</main>
<footer>
Made with love by cozis
</footer>
</body>
</html>
+21 -21
View File
@@ -1,21 +1,21 @@
include "pages/page.wl"
include "pages/login_and_signup_style.wl"
let main =
<main>
<span>Welcome!</span>
<form action="/api/signup" method="POST">
<input type="text" name="username" placeholder="username" />
<input type="email" name="email" placeholder="email" />
<input type="password" name="password1" placeholder="password" />
<input type="password" name="password2" placeholder="repeat password" />
<input type="submit" value="Sign-Up" />
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">already have an account?</a>
</div>
</form>
</main>
page("Log-In", none, style, main)
include "pages/page.wl"
include "pages/login_and_signup_style.wl"
let main =
<main>
<span>Welcome!</span>
<form action="/api/signup" method="POST">
<input type="text" name="username" placeholder="username" />
<input type="email" name="email" placeholder="email" />
<input type="password" name="password1" placeholder="password" />
<input type="password" name="password2" placeholder="repeat password" />
<input type="submit" value="Sign-Up" />
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">already have an account?</a>
</div>
</form>
</main>
page("Log-In", none, style, main)
+339 -339
View File
@@ -1,340 +1,340 @@
<html>
<head>
<meta charset="utf-8" />
<title>cozis news - thread</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
/* Thread styles */
.thread-header {
padding: 15px 0;
border-bottom: 2px solid #E8D4A9;
margin-bottom: 20px;
}
.thread-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
}
.thread-title a {
color: #1D2B42;
text-decoration: none;
}
.thread-title a:hover {
text-decoration: underline;
}
.thread-meta {
font-size: 12px;
color: #7A5F2A;
margin-bottom: 10px;
}
.thread-meta a {
color: #7A5F2A;
}
.thread-text {
font-size: 14px;
color: #1D2B42;
line-height: 160%;
margin-bottom: 10px;
}
.thread-actions {
font-size: 12px;
}
.thread-actions a {
color: #7A5F2A;
margin-right: 10px;
}
/* Comment styles */
.comment {
margin-bottom: 15px;
border-left: 1px solid #E8D4A9;
padding-left: 10px;
}
.comment.level-0 { margin-left: 0px; }
.comment.level-1 { margin-left: 20px; }
.comment.level-2 { margin-left: 40px; }
.comment.level-3 { margin-left: 60px; }
.comment.level-4 { margin-left: 80px; }
.comment-meta {
font-size: 11px;
color: #7A5F2A;
margin-bottom: 5px;
}
.comment-meta a {
color: #7A5F2A;
}
.comment-text {
font-size: 13px;
color: #1D2B42;
line-height: 150%;
margin-bottom: 5px;
}
.comment-actions {
font-size: 11px;
}
.comment-actions a {
color: #7A5F2A;
margin-right: 8px;
}
.comment-actions a:hover {
color: #1D2B42;
}
.vote-buttons {
float: left;
width: 15px;
margin-right: 8px;
font-size: 10px;
text-align: center;
}
.vote-buttons a {
display: block;
color: #7A5F2A;
text-decoration: none;
line-height: 100%;
}
.vote-buttons a:hover {
color: #1D2B42;
}
.comment-content {
margin-left: 23px;
}
.add-comment {
margin: 20px 0;
padding: 15px;
background: #E8D4A9;
border-radius: 3px;
border: 1px solid #D4C298;
}
.add-comment textarea {
width: 100%;
height: 80px;
font-family: monospace;
font-size: 12px;
background: #F7E6C0;
border: 1px solid #D4C298;
border-radius: 3px;
padding: 8px;
box-sizing: border-box;
resize: vertical;
}
.add-comment button {
background: #5780C9;
color: #F7E6C0;
border: none;
border-radius: 3px;
padding: 6px 12px;
font-family: monospace;
font-size: 12px;
cursor: pointer;
margin-top: 8px;
}
.add-comment button:hover {
background: #1D2B42;
}
.collapsed {
color: #7A5F2A;
font-size: 11px;
cursor: pointer;
}
.collapsed:hover {
color: #1D2B42;
}
</style>
</head>
<body>
<nav>
<div>
<a href="/new" class="current">new</a>
|
<a href="">hot</a>
</div>
<div>
<a href="">settings</a>
|
<a href="/login">log-out</a>
</div>
</nav>
<main>
<div class="thread-header">
<div class="thread-title">
<a href="https://github.com/KittenML/KittenTTS">Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model</a>
</div>
<div class="thread-meta">
submitted 3 hours ago by <a href="">kittenlover42</a> | <a href="">127 comments</a>
</div>
<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.
<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!
</div>
<div class="thread-actions">
<a href="">reply</a>
</div>
</div>
<div class="add-comment">
<textarea placeholder="Add a comment..."></textarea>
<button>add comment</button>
</div>
<div class="comment level-0">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">techgeek99</a> 2 hours ago
</div>
<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?
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-1">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">kittenlover42</a> 2 hours ago
</div>
<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.
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-2">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">embedded_dev</a> 1 hour ago
</div>
<div class="comment-text">
That sounds perfect for embedded applications! What's the inference speed like on something like a Raspberry Pi?
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-0">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">ml_researcher</a> 2 hours ago
</div>
<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.
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-1">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">kittenlover42</a> 1 hour ago
</div>
<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!
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-0">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">privacy_advocate</a> 1 hour ago
</div>
<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.
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-0">
<div class="collapsed">
[5 more comments]
</div>
</div>
</main>
<footer>
Made with love by cozis
</footer>
</body>
<html>
<head>
<meta charset="utf-8" />
<title>cozis news - thread</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
/* Thread styles */
.thread-header {
padding: 15px 0;
border-bottom: 2px solid #E8D4A9;
margin-bottom: 20px;
}
.thread-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
}
.thread-title a {
color: #1D2B42;
text-decoration: none;
}
.thread-title a:hover {
text-decoration: underline;
}
.thread-meta {
font-size: 12px;
color: #7A5F2A;
margin-bottom: 10px;
}
.thread-meta a {
color: #7A5F2A;
}
.thread-text {
font-size: 14px;
color: #1D2B42;
line-height: 160%;
margin-bottom: 10px;
}
.thread-actions {
font-size: 12px;
}
.thread-actions a {
color: #7A5F2A;
margin-right: 10px;
}
/* Comment styles */
.comment {
margin-bottom: 15px;
border-left: 1px solid #E8D4A9;
padding-left: 10px;
}
.comment.level-0 { margin-left: 0px; }
.comment.level-1 { margin-left: 20px; }
.comment.level-2 { margin-left: 40px; }
.comment.level-3 { margin-left: 60px; }
.comment.level-4 { margin-left: 80px; }
.comment-meta {
font-size: 11px;
color: #7A5F2A;
margin-bottom: 5px;
}
.comment-meta a {
color: #7A5F2A;
}
.comment-text {
font-size: 13px;
color: #1D2B42;
line-height: 150%;
margin-bottom: 5px;
}
.comment-actions {
font-size: 11px;
}
.comment-actions a {
color: #7A5F2A;
margin-right: 8px;
}
.comment-actions a:hover {
color: #1D2B42;
}
.vote-buttons {
float: left;
width: 15px;
margin-right: 8px;
font-size: 10px;
text-align: center;
}
.vote-buttons a {
display: block;
color: #7A5F2A;
text-decoration: none;
line-height: 100%;
}
.vote-buttons a:hover {
color: #1D2B42;
}
.comment-content {
margin-left: 23px;
}
.add-comment {
margin: 20px 0;
padding: 15px;
background: #E8D4A9;
border-radius: 3px;
border: 1px solid #D4C298;
}
.add-comment textarea {
width: 100%;
height: 80px;
font-family: monospace;
font-size: 12px;
background: #F7E6C0;
border: 1px solid #D4C298;
border-radius: 3px;
padding: 8px;
box-sizing: border-box;
resize: vertical;
}
.add-comment button {
background: #5780C9;
color: #F7E6C0;
border: none;
border-radius: 3px;
padding: 6px 12px;
font-family: monospace;
font-size: 12px;
cursor: pointer;
margin-top: 8px;
}
.add-comment button:hover {
background: #1D2B42;
}
.collapsed {
color: #7A5F2A;
font-size: 11px;
cursor: pointer;
}
.collapsed:hover {
color: #1D2B42;
}
</style>
</head>
<body>
<nav>
<div>
<a href="/new" class="current">new</a>
|
<a href="">hot</a>
</div>
<div>
<a href="">settings</a>
|
<a href="/login">log-out</a>
</div>
</nav>
<main>
<div class="thread-header">
<div class="thread-title">
<a href="https://github.com/KittenML/KittenTTS">Show HN: Kitten TTS - 25MB CPU-Only, Open-Source TTS Model</a>
</div>
<div class="thread-meta">
submitted 3 hours ago by <a href="">kittenlover42</a> | <a href="">127 comments</a>
</div>
<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.
<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!
</div>
<div class="thread-actions">
<a href="">reply</a>
</div>
</div>
<div class="add-comment">
<textarea placeholder="Add a comment..."></textarea>
<button>add comment</button>
</div>
<div class="comment level-0">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">techgeek99</a> 2 hours ago
</div>
<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?
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-1">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">kittenlover42</a> 2 hours ago
</div>
<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.
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-2">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">embedded_dev</a> 1 hour ago
</div>
<div class="comment-text">
That sounds perfect for embedded applications! What's the inference speed like on something like a Raspberry Pi?
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-0">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">ml_researcher</a> 2 hours ago
</div>
<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.
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-1">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">kittenlover42</a> 1 hour ago
</div>
<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!
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-0">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">privacy_advocate</a> 1 hour ago
</div>
<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.
</div>
<div class="comment-actions">
<a href="">reply</a> | <a href="">flag</a>
</div>
</div>
</div>
<div class="comment level-0">
<div class="collapsed">
[5 more comments]
</div>
</div>
</main>
<footer>
Made with love by cozis
</footer>
</body>
</html>
+66 -66
View File
@@ -1,67 +1,67 @@
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")
if posts == none:
posts = []
let style =
<style>
.item {
overflow: auto;
border-bottom: 1px solid #E8D4A9;
font-size: 14px;
}
.item:last-child {
border-bottom: 0;
}
.item span {
color: #7A5F2A;
text-decoration: none;
}
.item div {
color: #E8D4A9;
float: left
}
.item div:first-child {
width: calc(100% - 250px);
}
.item div:last-child {
width: 250px;
text-align: right;
}
#no-posts {
margin: 60px auto;
width: 100%;
text-align: center;
color: #7A5F2A;
}
</style>
let main =
<main>
\if len(posts) == 0:
<div id="no-posts">There are no posts yet!</div>
\for post in posts: {
let link
if post.is_link != 0:
link = post.content
else
link = ["/post?id=", post.id]
<div class="item">
<div>
<a href=\'"'\link\'"'>\post.title</a>
</div>
<div>
<span>\post.date</span> | <span>\post.num_comments comments</span>
</div>
</div>
}
</main>
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")
if posts == none:
posts = []
let style =
<style>
.item {
overflow: auto;
border-bottom: 1px solid #E8D4A9;
font-size: 14px;
}
.item:last-child {
border-bottom: 0;
}
.item span {
color: #7A5F2A;
text-decoration: none;
}
.item div {
color: #E8D4A9;
float: left
}
.item div:first-child {
width: calc(100% - 250px);
}
.item div:last-child {
width: 250px;
text-align: right;
}
#no-posts {
margin: 60px auto;
width: 100%;
text-align: center;
color: #7A5F2A;
}
</style>
let main =
<main>
\if len(posts) == 0:
<div id="no-posts">There are no posts yet!</div>
\for post in posts: {
let link
if post.is_link != 0:
link = post.content
else
link = ["/post?id=", post.id]
<div class="item">
<div>
<a href=\'"'\link\'"'>\post.title</a>
</div>
<div>
<span>\post.date</span> | <span>\post.num_comments comments</span>
</div>
</div>
}
</main>
page("Index", $login_user_id, style, main)
+22 -22
View File
@@ -1,22 +1,22 @@
include "pages/page.wl"
include "pages/login_and_signup_style.wl"
let main =
<main>
<span>Welcome back!</span>
<div id="response"></div>
<form hx-post="/api/login" hx-target="#response">
<input type="text" name="username" placeholder="username" />
<input type="password" name="password" placeholder="password" />
<input type="submit" value="Log-In" />
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">create account</a>
</div>
</form>
</main>
page("Log-In", none, style, main)
include "pages/page.wl"
include "pages/login_and_signup_style.wl"
let main =
<main>
<span>Welcome back!</span>
<div id="response"></div>
<form hx-post="/api/login" hx-target="#response">
<input type="text" name="username" placeholder="username" />
<input type="password" name="password" placeholder="password" />
<input type="submit" value="Log-In" />
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">create account</a>
</div>
</form>
</main>
page("Log-In", none, style, main)
+73 -73
View File
@@ -1,74 +1,74 @@
let style =
<style>
span {
text-align: center;
color: #1D2B42;
font-size: 18px;
display: inline-block;
width: 100%;
margin-top: 30px;
}
form {
max-width: 300px;
margin: 30px auto;
}
form input {
border: 0;
outline: 0;
width: 100%;
border-radius: 3px;
padding: 8px;
margin-bottom: 15px;
}
form input[type=text],
form input[type=email],
form input[type=password] {
background: #E8D4A9;
border: 1px solid #D4C298;
}
form input[type=text]:focus,
form input[type=email]:focus,
form input[type=password]:focus {
border-color: #5780C9;
background: #fff;
}
form input[type=submit] {
cursor: pointer;
background: #5780C9;
color: #F7E6C0;
}
form input[type=submit]:hover {
background: #1D2B42;
}
.form-links {
text-align: center;
font-size: 12px;
}
.form-links a {
color: #7A5F2A;
margin: 0 10px;
}
.form-links a:hover {
color: #1D2B42;
}
#response {
max-width: 300px;
margin: auto;
}
#response .error {
margin-top: 30px;
border-radius: 3px;
border: 1px solid #E44C82;
background: #F295B5;
padding: 5px 10px;
}
let style =
<style>
span {
text-align: center;
color: #1D2B42;
font-size: 18px;
display: inline-block;
width: 100%;
margin-top: 30px;
}
form {
max-width: 300px;
margin: 30px auto;
}
form input {
border: 0;
outline: 0;
width: 100%;
border-radius: 3px;
padding: 8px;
margin-bottom: 15px;
}
form input[type=text],
form input[type=email],
form input[type=password] {
background: #E8D4A9;
border: 1px solid #D4C298;
}
form input[type=text]:focus,
form input[type=email]:focus,
form input[type=password]:focus {
border-color: #5780C9;
background: #fff;
}
form input[type=submit] {
cursor: pointer;
background: #5780C9;
color: #F7E6C0;
}
form input[type=submit]:hover {
background: #1D2B42;
}
.form-links {
text-align: center;
font-size: 12px;
}
.form-links a {
color: #7A5F2A;
margin: 0 10px;
}
.form-links a:hover {
color: #1D2B42;
}
#response {
max-width: 300px;
margin: auto;
}
#response .error {
margin-top: 30px;
border-radius: 3px;
border: 1px solid #E44C82;
background: #F295B5;
padding: 5px 10px;
}
</style>
+60 -60
View File
@@ -1,60 +1,60 @@
include "pages/page.wl"
let style =
<style>
.not-found-container {
text-align: center;
padding: 60px 20px;
}
.error-code {
font-size: 72px;
font-weight: bold;
color: #7A5F2A;
margin-bottom: 20px;
line-height: 100%;
}
.error-message {
font-size: 24px;
color: #1D2B42;
margin-bottom: 15px;
}
.error-description {
font-size: 14px;
color: #7A5F2A;
margin-bottom: 40px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
line-height: 160%;
}
.home-button {
display: inline-block;
background: #5780C9;
color: #F7E6C0;
padding: 12px 24px;
text-decoration: none;
border-radius: 5px;
font-size: 14px;
margin-top: 20px;
border: none;
cursor: pointer;
}
.home-button:hover {
background: #1D2B42;
color: #F7E6C0;
}
</style>
let main =
<main>
<div class="not-found-container">
<div class="error-code">404</div>
<div class="error-message">Page Not Found</div>
<div class="error-description">
Looks like this page wandered off somewhere.
</div>
</div>
</main>
page("Not Found", $login_user_id, style, main)
include "pages/page.wl"
let style =
<style>
.not-found-container {
text-align: center;
padding: 60px 20px;
}
.error-code {
font-size: 72px;
font-weight: bold;
color: #7A5F2A;
margin-bottom: 20px;
line-height: 100%;
}
.error-message {
font-size: 24px;
color: #1D2B42;
margin-bottom: 15px;
}
.error-description {
font-size: 14px;
color: #7A5F2A;
margin-bottom: 40px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
line-height: 160%;
}
.home-button {
display: inline-block;
background: #5780C9;
color: #F7E6C0;
padding: 12px 24px;
text-decoration: none;
border-radius: 5px;
font-size: 14px;
margin-top: 20px;
border: none;
cursor: pointer;
}
.home-button:hover {
background: #1D2B42;
color: #F7E6C0;
}
</style>
let main =
<main>
<div class="not-found-container">
<div class="error-code">404</div>
<div class="error-message">Page Not Found</div>
<div class="error-description">
Looks like this page wandered off somewhere.
</div>
</div>
</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)
<html>
<head>
<meta charset="UTF-8" />
<title>CN - \title</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
</style>
\(style)
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
</head>
<body>
<nav>
<div>
<a href="/index">index</a>
\if login_user_id != none: {
"|\n"
<a href="/write">write</a>
}
</div>
\if login_user_id == none:
<div>
<a href="/login">log-in</a>
|
<a href="/signup">sign-up</a>
</div>
else
<div>
<a href="">settings</a>
|
<a href="/api/logout">log-out</a>
</div>
</nav>
\main
<footer>
Made with love by cozis
</footer>
</body>
</html>
procedure page(title, login_user_id, style, main)
<html>
<head>
<meta charset="UTF-8" />
<title>CN - \title</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
</style>
\(style)
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
</head>
<body>
<nav>
<div>
<a href="/index">index</a>
\if login_user_id != none: {
"|\n"
<a href="/write">write</a>
}
</div>
\if login_user_id == none:
<div>
<a href="/login">log-in</a>
|
<a href="/signup">sign-up</a>
</div>
else
<div>
<a href="">settings</a>
|
<a href="/api/logout">log-out</a>
</div>
</nav>
\main
<footer>
Made with love by cozis
</footer>
</body>
</html>
+245 -245
View File
@@ -1,246 +1,246 @@
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 comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id=C.author", $post_id)
let lookup = {}
for comment in comments: {
comment.child = []
lookup[comment.id] = comment
}
let root_comments = []
for comment in comments: {
if comment.parent_comment == none:
root_comments << comment
else
lookup[comment.parent_comment].child << comment
}
let post = posts[0]
let style =
<style>
.thread-header {
padding: 15px 0;
border-bottom: 2px solid #E8D4A9;
margin-bottom: 20px;
}
.thread-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
}
.thread-title a {
color: #1D2B42;
text-decoration: none;
}
.thread-title a:hover {
text-decoration: underline;
}
.thread-meta {
font-size: 12px;
color: #7A5F2A;
margin-bottom: 10px;
}
.thread-meta a {
color: #7A5F2A;
}
.thread-text {
font-size: 14px;
color: #1D2B42;
line-height: 160%;
margin-bottom: 10px;
}
.thread-actions {
font-size: 12px;
}
.thread-actions a {
color: #7A5F2A;
margin-right: 10px;
}
/* Comment styles */
.comment {
margin-bottom: 15px;
border-left: 1px solid #E8D4A9;
padding-left: 10px;
}
.comment-meta {
font-size: 11px;
color: #7A5F2A;
margin-bottom: 5px;
}
.comment-meta a {
color: #7A5F2A;
}
.comment-text {
font-size: 13px;
color: #1D2B42;
line-height: 150%;
margin-bottom: 5px;
}
.comment-actions {
font-size: 11px;
}
.comment-actions a {
color: #7A5F2A;
margin-right: 8px;
}
.comment-actions a:hover {
color: #1D2B42;
}
.vote-buttons {
float: left;
width: 15px;
margin-right: 8px;
font-size: 10px;
text-align: center;
}
.vote-buttons a {
display: block;
color: #7A5F2A;
text-decoration: none;
line-height: 100%;
}
.vote-buttons a:hover {
color: #1D2B42;
}
.comment-content {
margin-left: 23px;
}
.comment-child {
margin-top: 10px;
margin-left: 10px;
}
.add-comment {
}
.add-comment form {
margin: 0;
overflow: auto;
}
.add-comment form textarea {
width: 100%;
height: 80px;
font-family: monospace;
font-size: 12px;
background: white;
border: 1px solid #D4C298;
border-radius: 3px;
padding: 8px;
box-sizing: border-box;
resize: vertical;
}
.add-comment form input[type=submit] {
background: #5780C9;
color: white;
border: none;
border-radius: 3px;
padding: 6px 12px;
font-family: monospace;
font-size: 12px;
cursor: pointer;
margin-top: 8px;
float: right;
}
.add-comment input[type=submit]:hover {
background: #1D2B42;
}
summary {
list-style: none;
text-decoration: underline;
color: #7A5F2A;
cursor: pointer;
}
::-webkit-details-marker {
display: none;
}
#no-comments {
margin: 30px 0;
width: 100%;
text-align: center;
color: #7A5F2A;
}
pre {
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
</style>
let main =
<main>
<div class="thread-header">
<div class="thread-title">
<span>\post.title</span>
</div>
<div class="thread-meta">
submitted 3 hours ago by <a href="">\post.username</a> | <a href="">\len comments</a>
</div>
<div class="thread-text">
<pre>\post.content</pre>
</div>
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
</details>
</div>
\procedure render_comment(comment)
<div class="comment">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">\comment.username</a> 2 hours ago
</div>
<div class="comment-text">
<pre>\comment.content</pre>
</div>
\if $login_user_id != none:
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
</details>
</div>
<div class="comment-child">
\for child in comment.child:
render_comment(child)
</div>
</div>
\if len root_comments == 0:
<div id="no-comments">
No comments
</div>
else for comment in root_comments:
render_comment(comment)
</main>
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 comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id=C.author", $post_id)
let lookup = {}
for comment in comments: {
comment.child = []
lookup[comment.id] = comment
}
let root_comments = []
for comment in comments: {
if comment.parent_comment == none:
root_comments << comment
else
lookup[comment.parent_comment].child << comment
}
let post = posts[0]
let style =
<style>
.thread-header {
padding: 15px 0;
border-bottom: 2px solid #E8D4A9;
margin-bottom: 20px;
}
.thread-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
}
.thread-title a {
color: #1D2B42;
text-decoration: none;
}
.thread-title a:hover {
text-decoration: underline;
}
.thread-meta {
font-size: 12px;
color: #7A5F2A;
margin-bottom: 10px;
}
.thread-meta a {
color: #7A5F2A;
}
.thread-text {
font-size: 14px;
color: #1D2B42;
line-height: 160%;
margin-bottom: 10px;
}
.thread-actions {
font-size: 12px;
}
.thread-actions a {
color: #7A5F2A;
margin-right: 10px;
}
/* Comment styles */
.comment {
margin-bottom: 15px;
border-left: 1px solid #E8D4A9;
padding-left: 10px;
}
.comment-meta {
font-size: 11px;
color: #7A5F2A;
margin-bottom: 5px;
}
.comment-meta a {
color: #7A5F2A;
}
.comment-text {
font-size: 13px;
color: #1D2B42;
line-height: 150%;
margin-bottom: 5px;
}
.comment-actions {
font-size: 11px;
}
.comment-actions a {
color: #7A5F2A;
margin-right: 8px;
}
.comment-actions a:hover {
color: #1D2B42;
}
.vote-buttons {
float: left;
width: 15px;
margin-right: 8px;
font-size: 10px;
text-align: center;
}
.vote-buttons a {
display: block;
color: #7A5F2A;
text-decoration: none;
line-height: 100%;
}
.vote-buttons a:hover {
color: #1D2B42;
}
.comment-content {
margin-left: 23px;
}
.comment-child {
margin-top: 10px;
margin-left: 10px;
}
.add-comment {
}
.add-comment form {
margin: 0;
overflow: auto;
}
.add-comment form textarea {
width: 100%;
height: 80px;
font-family: monospace;
font-size: 12px;
background: white;
border: 1px solid #D4C298;
border-radius: 3px;
padding: 8px;
box-sizing: border-box;
resize: vertical;
}
.add-comment form input[type=submit] {
background: #5780C9;
color: white;
border: none;
border-radius: 3px;
padding: 6px 12px;
font-family: monospace;
font-size: 12px;
cursor: pointer;
margin-top: 8px;
float: right;
}
.add-comment input[type=submit]:hover {
background: #1D2B42;
}
summary {
list-style: none;
text-decoration: underline;
color: #7A5F2A;
cursor: pointer;
}
::-webkit-details-marker {
display: none;
}
#no-comments {
margin: 30px 0;
width: 100%;
text-align: center;
color: #7A5F2A;
}
pre {
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
</style>
let main =
<main>
<div class="thread-header">
<div class="thread-title">
<span>\post.title</span>
</div>
<div class="thread-meta">
submitted 3 hours ago by <a href="">\post.username</a> | <a href="">\len comments</a>
</div>
<div class="thread-text">
<pre>\post.content</pre>
</div>
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
</details>
</div>
\procedure render_comment(comment)
<div class="comment">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">\comment.username</a> 2 hours ago
</div>
<div class="comment-text">
<pre>\comment.content</pre>
</div>
\if $login_user_id != none:
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
</details>
</div>
<div class="comment-child">
\for child in comment.child:
render_comment(child)
</div>
</div>
\if len root_comments == 0:
<div id="no-comments">
No comments
</div>
else for comment in root_comments:
render_comment(comment)
</main>
page(post.title, $login_user_id, style, main)
+72 -72
View File
@@ -1,73 +1,73 @@
include "pages/page.wl"
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
let lookup = {}
for comment in comments: {
comment.child = []
lookup[comment.id] = comment
}
let roots = []
for comment in comments: {
if comment.parent_comment == none:
roots << comment
else
lookup[comment.parent_comment].child << comment
}
comments = roots
let post = posts[0]
let style =
<style>
.child {
border-left: 3px solid #ccc;
padding-left: 10px;
}
form textarea {
width: 100%;
}
</style>
procedure render_comment(parent)
<div>
<a href="">\parent.username</a>
<p>
\parent.content
</p>
<div class="child">
\if $login_user_id != none:
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
\for child in parent.child:
render_comment(child)
</div>
</div>
let main =
<main>
<h3>\post.title</h3>
<p>\post.content</p>
<div>
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
\if len comments == 0:
<span>No comments yet!</span>
else for comment in comments:
render_comment(comment)
</main>
include "pages/page.wl"
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
let lookup = {}
for comment in comments: {
comment.child = []
lookup[comment.id] = comment
}
let roots = []
for comment in comments: {
if comment.parent_comment == none:
roots << comment
else
lookup[comment.parent_comment].child << comment
}
comments = roots
let post = posts[0]
let style =
<style>
.child {
border-left: 3px solid #ccc;
padding-left: 10px;
}
form textarea {
width: 100%;
}
</style>
procedure render_comment(parent)
<div>
<a href="">\parent.username</a>
<p>
\parent.content
</p>
<div class="child">
\if $login_user_id != none:
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
\for child in parent.child:
render_comment(child)
</div>
</div>
let main =
<main>
<h3>\post.title</h3>
<p>\post.content</p>
<div>
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
\if len comments == 0:
<span>No comments yet!</span>
else for comment in comments:
render_comment(comment)
</main>
page(post.title, $login_user_id, style, main)
+24 -24
View File
@@ -1,24 +1,24 @@
include "pages/page.wl"
include "pages/login_and_signup_style.wl"
let main =
<main>
<span>Welcome!</span>
<div id="response"></div>
<form hx-post="/api/signup" hx-target="#response">
<input type="text" name="username" placeholder="username" />
<input type="email" name="email" placeholder="email" />
<input type="password" name="password1" placeholder="password" />
<input type="password" name="password2" placeholder="repeat password" />
<input type="submit" value="Sign-Up" />
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">already have an account?</a>
</div>
</form>
</main>
page("Log-In", none, style, main)
include "pages/page.wl"
include "pages/login_and_signup_style.wl"
let main =
<main>
<span>Welcome!</span>
<div id="response"></div>
<form hx-post="/api/signup" hx-target="#response">
<input type="text" name="username" placeholder="username" />
<input type="email" name="email" placeholder="email" />
<input type="password" name="password1" placeholder="password" />
<input type="password" name="password2" placeholder="repeat password" />
<input type="submit" value="Sign-Up" />
<div class="form-links">
<a href="">forgot password?</a>
|
<a href="">already have an account?</a>
</div>
</form>
</main>
page("Log-In", none, style, main)
+94 -92
View File
@@ -1,93 +1,95 @@
include "pages/page.wl"
let style =
<style>
form {
max-width: 400px;
margin: 30px auto;
}
form input,
form textarea {
border: 0;
outline: 0;
width: 100%;
border-radius: 3px;
padding: 8px;
margin-bottom: 15px;
background: #E8D4A9;
border: 1px solid #D4C298;
}
form input:focus,
form textarea:focus {
border-color: #5780C9;
background: #fff;
}
form textarea {
height: 120px;
resize: vertical;
}
form input[type=submit] {
cursor: pointer;
background: #5780C9;
color: #F7E6C0;
}
form input[type=submit]:hover {
background: #1D2B42;
}
.checkbox-row {
margin-bottom: 15px;
}
.checkbox-row input[type="checkbox"] {
width: auto;
margin-right: 8px;
}
.checkbox-row label {
color: #1D2B42;
font-size: 14px;
cursor: pointer;
}
</style>
let main =
<main>
<form action="/api/post" method="POST">
<input type="text" id="title" name="title" placeholder="Title" required />
<div class="checkbox-row">
<input type="checkbox" id="is_link" name="is_link" onchange="togglePostType()" />
<label>This is a link post</label>
</div>
<input type="url" id="url" name="link" placeholder="URL" style="display: none;" />
<textarea id="content" name="content" placeholder="Write your post here..."></textarea>
<input type="submit" value="Submit Post" />
</form>
<script>
function togglePostType() {
const checkbox = document.getElementById('is_link');
const urlInput = document.getElementById('url');
const contentTextarea = document.getElementById('content');
if (checkbox.checked) {
urlInput.style.display = 'block';
contentTextarea.style.display = 'none';
} else {
urlInput.style.display = 'none';
contentTextarea.style.display = 'block';
}
}
</script>
</main>
include "pages/page.wl"
let style =
<style>
form {
max-width: 400px;
margin: 30px auto;
}
form input,
form textarea {
border: 0;
outline: 0;
width: 100%;
border-radius: 3px;
padding: 8px;
margin-bottom: 15px;
background: #E8D4A9;
border: 1px solid #D4C298;
}
form input:focus,
form textarea:focus {
border-color: #5780C9;
background: #fff;
}
form textarea {
height: 120px;
resize: vertical;
}
form input[type=submit] {
cursor: pointer;
background: #5780C9;
color: #F7E6C0;
}
form input[type=submit]:hover {
background: #1D2B42;
}
.checkbox-row {
margin-bottom: 15px;
}
.checkbox-row input[type="checkbox"] {
width: auto;
margin-right: 8px;
}
.checkbox-row label {
color: #1D2B42;
font-size: 14px;
cursor: pointer;
}
</style>
let main =
<main>
<form action="/api/post" method="POST">
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
<input type="text" id="title" name="title" placeholder="Title" required />
<div class="checkbox-row">
<input type="checkbox" id="is_link" name="is_link" onchange="togglePostType()" />
<label>This is a link post</label>
</div>
<input type="url" id="url" name="link" placeholder="URL" style="display: none;" />
<textarea id="content" name="content" placeholder="Write your post here..."></textarea>
<input type="submit" value="Submit Post" />
</form>
<script>
function togglePostType() {
const checkbox = document.getElementById('is_link');
const urlInput = document.getElementById('url');
const contentTextarea = document.getElementById('content');
if (checkbox.checked) {
urlInput.style.display = 'block';
contentTextarea.style.display = 'none';
} else {
urlInput.style.display = 'none';
contentTextarea.style.display = 'block';
}
}
</script>
</main>
page("Write Post", $login_user_id, style, main)
+45 -45
View File
@@ -1,45 +1,45 @@
#include <stddef.h>
#include <string.h>
#include <crypt_blowfish.h>
#include "bcrypt.h"
#include "random.h"
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash)
{
char passzt[128];
if (passlen >= (int) sizeof(passzt))
return -1;
memcpy(passzt, pass, passlen);
passzt[passlen] = '\0';
char random[16];
int ret = generate_random_bytes(random, (int) sizeof(random));
if (ret) return -1;
char salt[30];
if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL)
return -1;
if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL)
return -1;
return 0;
}
int check_password(char *pass, int passlen, PasswordHash hash)
{
char passzt[128];
if (passlen >= (int) sizeof(passzt))
return -1;
memcpy(passzt, pass, passlen);
passzt[passlen] = '\0';
PasswordHash new_hash;
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL)
return -1;
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
return 1;
return 0;
}
#include <stddef.h>
#include <string.h>
#include <crypt_blowfish.h>
#include "bcrypt.h"
#include "random.h"
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash)
{
char passzt[128];
if (passlen >= (int) sizeof(passzt))
return -1;
memcpy(passzt, pass, passlen);
passzt[passlen] = '\0';
char random[16];
int ret = generate_random_bytes(random, (int) sizeof(random));
if (ret) return -1;
char salt[30];
if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL)
return -1;
if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL)
return -1;
return 0;
}
int check_password(char *pass, int passlen, PasswordHash hash)
{
char passzt[128];
if (passlen >= (int) sizeof(passzt))
return -1;
memcpy(passzt, pass, passlen);
passzt[passlen] = '\0';
PasswordHash new_hash;
if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL)
return -1;
if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time
return 1;
return 0;
}
+11 -11
View File
@@ -1,11 +1,11 @@
#ifndef BCRYPT_INCLUDED
#define BCRYPT_INCLUDED
typedef struct {
char data[61];
} PasswordHash;
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash);
int check_password(char *pass, int passlen, PasswordHash hash);
#endif // BCRYPT_INCLUDED
#ifndef BCRYPT_INCLUDED
#define BCRYPT_INCLUDED
typedef struct {
char data[61];
} PasswordHash;
int hash_password(char *pass, int passlen, int cost, PasswordHash *hash);
int check_password(char *pass, int passlen, PasswordHash hash);
#endif // BCRYPT_INCLUDED
+987 -1095
View File
File diff suppressed because it is too large Load Diff
+32 -32
View File
@@ -1,32 +1,32 @@
#ifdef __linux__
#include <errno.h>
#include <sys/random.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#include "random.h"
int generate_random_bytes(char *dst, int cap)
{
#ifdef __linux__
int copied = 0;
while (copied < cap) {
int ret = getrandom(dst, (size_t) cap, 0);
if (ret < 0) {
if (errno == EINTR)
continue;
return -1;
}
copied += ret;
}
return 0;
#endif
#ifdef _WIN32
NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return BCRYPT_SUCCESS(status) ? 0 : -1;
#endif
}
#ifdef __linux__
#include <errno.h>
#include <sys/random.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#include "random.h"
int generate_random_bytes(char *dst, int cap)
{
#ifdef __linux__
int copied = 0;
while (copied < cap) {
int ret = getrandom(dst, (size_t) cap, 0);
if (ret < 0) {
if (errno == EINTR)
continue;
return -1;
}
copied += ret;
}
return 0;
#endif
#ifdef _WIN32
NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return BCRYPT_SUCCESS(status) ? 0 : -1;
#endif
}
+5 -5
View File
@@ -1,6 +1,6 @@
#ifndef RANDOM_INCLUDED
#define RANDOM_INCLUDED
int generate_random_bytes(char *dst, int cap);
#ifndef RANDOM_INCLUDED
#define RANDOM_INCLUDED
int generate_random_bytes(char *dst, int cap);
#endif // RANDOM_INCLUDED
+198
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 <stddef.h>
#include <string.h>
#include <stdlib.h>
#include "sqlite3utils.h"
typedef struct {
char *str;
int len;
sqlite3_stmt *stmt;
} Prepped;
struct SQLiteCache {
sqlite3 *db;
int count;
int capacity_log2;
Prepped items[];
};
SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2)
{
SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped));
if (cache == NULL)
return NULL;
cache->db = db;
cache->count = 0;
cache->capacity_log2 = capacity_log2;
for (int i = 0; i < (1 << capacity_log2); i++)
cache->items[i].stmt = NULL;
return cache;
}
void sqlite_cache_free(SQLiteCache *cache)
{
for (int i = 0; i < (1 << cache->capacity_log2); i++) {
sqlite3_stmt *stmt = cache->items[i].stmt;
if (stmt) {
free(cache->items[i].str);
sqlite3_finalize(stmt);
}
}
free(cache);
}
sqlite3 *sqlite_cache_getdb(SQLiteCache *cache)
{
return cache->db;
}
static unsigned long djb2(char *src, int len)
{
char *ptr = src;
char *end = src + len;
unsigned long hash = 5381;
int c;
while (ptr < end && (c = *ptr++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
static int lookup(SQLiteCache *cache, char *fmt, int fmtlen)
{
int mask = (1 << cache->capacity_log2) - 1;
int hash = djb2(fmt, fmtlen);
int i = hash & mask;
int perturb = hash;
for (;;) {
if (cache->items[i].stmt == NULL)
return i;
if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen))
return i;
perturb >>= 5;
i = (i * 5 + 1 + perturb) & mask;
}
return -1;
}
int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen)
{
if (fmtlen < 0)
fmtlen = strlen(fmt);
int i = lookup(cache, fmt, fmtlen);
if (cache->items[i].stmt == NULL) {
printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO
sqlite3_stmt *stmt;
int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
//fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
return ret;
}
char *cpy = malloc(fmtlen);
if (cpy == NULL) {
sqlite3_finalize(stmt);
return SQLITE_NOMEM;
}
memcpy(cpy, fmt, fmtlen);
cache->items[i].str = cpy;
cache->items[i].len = fmtlen;
cache->items[i].stmt = stmt;
}
sqlite3_stmt *stmt = cache->items[i].stmt;
*pstmt = stmt;
return SQLITE_OK;
}
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, VArgs args)
{
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt));
if (ret != SQLITE_OK)
return ret;
for (int i = 0; i < args.len; i++) {
VArg arg = args.ptr[i];
switch (arg.type) {
case VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break;
case VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break;
case VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break;
case VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break;
case VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break;
case VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break;
case VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break;
case VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break;
case VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break;
case VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break;
case VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break;
case VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break;
case VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break;
case VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break;
case VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break;
case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break;
case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break;
case VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break;
case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break;
}
if (ret != SQLITE_OK) {
sqlite3_reset(stmt);
return ret;
}
}
*pstmt = stmt;
return SQLITE_OK;
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include "sqlite3utils.h"
typedef struct {
char *str;
int len;
sqlite3_stmt *stmt;
} Prepped;
struct SQLiteCache {
sqlite3 *db;
int count;
int capacity_log2;
Prepped items[];
};
SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2)
{
SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped));
if (cache == NULL)
return NULL;
cache->db = db;
cache->count = 0;
cache->capacity_log2 = capacity_log2;
for (int i = 0; i < (1 << capacity_log2); i++)
cache->items[i].stmt = NULL;
return cache;
}
void sqlite_cache_free(SQLiteCache *cache)
{
for (int i = 0; i < (1 << cache->capacity_log2); i++) {
sqlite3_stmt *stmt = cache->items[i].stmt;
if (stmt) {
free(cache->items[i].str);
sqlite3_finalize(stmt);
}
}
free(cache);
}
sqlite3 *sqlite_cache_getdb(SQLiteCache *cache)
{
return cache->db;
}
static unsigned long djb2(char *src, int len)
{
char *ptr = src;
char *end = src + len;
unsigned long hash = 5381;
int c;
while (ptr < end && (c = *ptr++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
static int lookup(SQLiteCache *cache, char *fmt, int fmtlen)
{
int mask = (1 << cache->capacity_log2) - 1;
int hash = djb2(fmt, fmtlen);
int i = hash & mask;
int perturb = hash;
for (;;) {
if (cache->items[i].stmt == NULL)
return i;
if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen))
return i;
perturb >>= 5;
i = (i * 5 + 1 + perturb) & mask;
}
return -1;
}
int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen)
{
if (fmtlen < 0)
fmtlen = strlen(fmt);
int i = lookup(cache, fmt, fmtlen);
if (cache->items[i].stmt == NULL) {
printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO
sqlite3_stmt *stmt;
int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
//fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
return ret;
}
char *cpy = malloc(fmtlen);
if (cpy == NULL) {
sqlite3_finalize(stmt);
return SQLITE_NOMEM;
}
memcpy(cpy, fmt, fmtlen);
cache->items[i].str = cpy;
cache->items[i].len = fmtlen;
cache->items[i].stmt = stmt;
}
sqlite3_stmt *stmt = cache->items[i].stmt;
*pstmt = stmt;
return SQLITE_OK;
}
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, VArgs args)
{
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt));
if (ret != SQLITE_OK)
return ret;
for (int i = 0; i < args.len; i++) {
VArg arg = args.ptr[i];
switch (arg.type) {
case VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break;
case VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break;
case VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break;
case VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break;
case VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break;
case VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break;
case VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break;
case VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break;
case VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break;
case VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break;
case VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break;
case VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break;
case VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break;
case VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break;
case VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break;
case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break;
case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break;
case VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break;
case VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break;
}
if (ret != SQLITE_OK) {
sqlite3_reset(stmt);
return ret;
}
}
*pstmt = stmt;
return SQLITE_OK;
}
+20 -20
View File
@@ -1,21 +1,21 @@
#ifndef SQLITE3UTILS_INCLUDED
#define SQLITE3UTILS_INCLUDED
#include "sqlite3.h"
#include "variadic.h"
#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__))
typedef struct SQLiteCache SQLiteCache;
SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2);
void sqlite_cache_free(SQLiteCache *cache);
sqlite3* sqlite_cache_getdb(SQLiteCache *cache);
int sqlite3utils_prepare(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, int fmtlen);
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, VArgs args);
#ifndef SQLITE3UTILS_INCLUDED
#define SQLITE3UTILS_INCLUDED
#include "sqlite3.h"
#include "variadic.h"
#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__))
typedef struct SQLiteCache SQLiteCache;
SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2);
void sqlite_cache_free(SQLiteCache *cache);
sqlite3* sqlite_cache_getdb(SQLiteCache *cache);
int sqlite3utils_prepare(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, int fmtlen);
int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache,
sqlite3_stmt **pstmt, char *fmt, VArgs args);
#endif // SQLITE3UTILS_INCLUDED
+419 -412
View File
@@ -1,412 +1,419 @@
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "template.h"
#include "sqlite3utils.h"
#define TRACE(...) {}
//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__);
typedef struct CachedProgram CachedProgram;
struct CachedProgram {
char path[1<<8];
int pathlen;
WL_Program program;
};
struct TemplateCache {
int count;
int capacity_log2;
CachedProgram pool[];
};
TemplateCache *template_cache_init(int capacity_log2)
{
TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram));
if (cache == NULL)
return NULL;
cache->count = 0;
cache->capacity_log2 = capacity_log2;
for (int i = 0; i < (1 << capacity_log2); i++)
cache->pool[i].pathlen = -1;
return cache;
}
void template_cache_free(TemplateCache *cache)
{
free(cache);
}
static unsigned long djb2(WL_String str)
{
char *ptr = str.ptr;
char *end = str.ptr + str.len;
unsigned long hash = 5381;
int c;
while (ptr < end && (c = *ptr++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
static int lookup(TemplateCache *cache, WL_String path)
{
int mask = (1 << cache->capacity_log2) - 1;
int hash = djb2(path);
int i = hash & mask;
int perturb = hash;
for (;;) {
if (cache->pool[i].pathlen == -1)
return i;
if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen))
return i;
perturb >>= 5;
i = (i * 5 + 1 + perturb) & mask;
}
return -1;
}
typedef struct LoadedFile LoadedFile;
struct LoadedFile {
LoadedFile* next;
int len;
char data[];
};
static LoadedFile *load_file(WL_String path)
{
char buf[1<<10];
if (path.len >= (int) sizeof(buf))
return NULL;
memcpy(buf, path.ptr, path.len);
buf[path.len] = '\0';
FILE *stream = fopen(buf, "rb");
if (stream == NULL)
return NULL;
int ret = fseek(stream, 0, SEEK_END);
if (ret) {
fclose(stream);
return NULL;
}
long tmp = ftell(stream);
if (tmp < 0 || tmp > INT_MAX) {
fclose(stream);
return NULL;
}
int len = (int) tmp;
ret = fseek(stream, 0, SEEK_SET);
if (ret) {
fclose(stream);
return NULL;
}
LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1);
if (result == NULL) {
fclose(stream);
return NULL;
}
result->next = NULL;
result->len = len;
int read_len = fread(result->data, 1, len+1, stream);
if (read_len != len || ferror(stream) || !feof(stream)) {
fclose(stream);
free(result);
return NULL;
}
fclose(stream);
return result;
}
static void free_loaded_files(LoadedFile *loaded_file)
{
while (loaded_file) {
LoadedFile *next = loaded_file->next;
free(loaded_file);
loaded_file = next;
}
}
static int compile(WL_String path, WL_Program *program, WL_Arena *arena)
{
WL_Compiler *compiler = wl_compiler_init(arena);
if (compiler == NULL) {
TRACE("Couldn't initialize WL compiler object");
return -1;
}
LoadedFile *loaded_file_head = NULL;
LoadedFile **loaded_file_tail = &loaded_file_head;
for (int i = 0;; i++) {
LoadedFile *loaded_file = load_file(path);
if (loaded_file == NULL) {
TRACE("Couldn't load file '%.*s'", path.len, path.ptr);
free_loaded_files(loaded_file_head);
return -1;
}
*loaded_file_tail = loaded_file;
loaded_file_tail = &loaded_file->next;
WL_String content = { loaded_file->data, loaded_file->len };
WL_AddResult result = wl_compiler_add(compiler, content);
if (result.type == WL_ADD_ERROR) {
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
free_loaded_files(loaded_file_head);
return -1;
}
if (result.type == WL_ADD_LINK) break;
assert(result.type == WL_ADD_AGAIN);
path = result.path;
}
int ret = wl_compiler_link(compiler, program);
if (ret < 0) {
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
return -1;
}
free_loaded_files(loaded_file_head);
TRACE("Compilation succeded");
return 0;
}
static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache)
{
int num_args = wl_arg_count(rt);
if (num_args == 0)
return 0;
WL_String format;
if (!wl_arg_str(rt, 0, &format))
return -1;
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len);
if (ret != SQLITE_OK)
return -1;
for (int i = 1; i < num_args; i++) {
int64_t ival;
double fval;
WL_String str;
if (wl_arg_none(rt, i))
ret = sqlite3_bind_null (stmt, i);
else if (wl_arg_s64(rt, i, &ival))
ret = sqlite3_bind_int64 (stmt, i, ival);
else if (wl_arg_f64(rt, i, &fval))
ret = sqlite3_bind_double(stmt, i, fval);
else if (wl_arg_str(rt, i, &str))
ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL);
else assert(0);
if (ret != SQLITE_OK) {
sqlite3_reset(stmt);
return -1;
}
}
wl_push_array(rt, 0);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int num_cols = sqlite3_column_count(stmt);
if (num_cols < 0) {
sqlite3_reset(stmt);
return -1;
}
wl_push_map(rt, num_cols);
for (int i = 0; i < num_cols; i++) {
ret = sqlite3_column_type(stmt, i);
switch (ret) {
case SQLITE_INTEGER:
{
int64_t x = sqlite3_column_int64(stmt, i);
wl_push_s64(rt, x);
}
break;
case SQLITE_FLOAT:
{
double x = sqlite3_column_double(stmt, i);
wl_push_f64(rt, x);
}
break;
case SQLITE_TEXT:
{
const void *x = sqlite3_column_text(stmt, i);
int n = sqlite3_column_bytes(stmt, i);
wl_push_str(rt, (WL_String) { (char*) x, n });
}
break;
case SQLITE_BLOB:
{
const void *x = sqlite3_column_blob(stmt, i);
int n = sqlite3_column_bytes(stmt, i);
wl_push_str(rt, (WL_String) { (char*) x, n });
}
break;
case SQLITE_NULL:
{
wl_push_none(rt);
}
break;
}
const char *name = sqlite3_column_name(stmt, i);
wl_push_str(rt, (WL_String) { (char*) name, strlen(name) });
wl_insert(rt);
}
wl_append(rt);
}
sqlite3_reset(stmt);
return 0;
}
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, int user_id, int post_id)
{
(void) dbcache;
if (wl_streq(name, "login_user_id", -1)) {
if (user_id < 0)
wl_push_none(rt);
else
wl_push_s64(rt, user_id);
} else if (wl_streq(name, "post_id", -1)) {
if (post_id < 0)
wl_push_none(rt);
else
wl_push_s64(rt, post_id);
}
}
static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache)
{
if (wl_streq(name, "query", -1)) {
query_routine(rt, dbcache);
return;
}
}
static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program)
{
if (cache == NULL)
return -1;
int i = lookup(cache, path);
if (cache->pool[i].pathlen == -1) {
WL_Program program;
int ret = compile(path, &program, arena);
if (ret < 0) return -1;
void *p = malloc(program.len);
if (p == NULL)
return -1;
memcpy(p, program.ptr, program.len);
program.ptr = p;
if ((int) sizeof(cache->pool->path) <= path.len)
return -1;
memcpy(cache->pool[i].path, path.ptr, path.len);
cache->pool[i].path[path.len] = '\0';
cache->pool[i].pathlen = path.len;
cache->pool[i].program = program;
}
*program = cache->pool[i].program;
return 0;
}
void template_eval(HTTP_ResponseBuilder builder, int status,
WL_String path, TemplateCache *cache, WL_Arena *arena,
SQLiteCache *dbcache, int user_id, int post_id)
{
http_response_builder_status(builder, status);
WL_Program program;
int ret = get_or_create_program(cache, path, arena, &program);
if (ret < 0) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
return;
}
//wl_dump_program(program);
WL_Runtime *rt = wl_runtime_init(arena, program);
if (rt == NULL) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
return;
}
for (bool done = false; !done; ) {
WL_EvalResult result = wl_runtime_eval(rt);
switch (result.type) {
case WL_EVAL_DONE:
http_response_builder_done(builder);
done = true;
break;
case WL_EVAL_ERROR:
// wl_runtime_error(rt)
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
return;
case WL_EVAL_SYSVAR:
push_sysvar(rt, result.str, dbcache, user_id, post_id);
break;
case WL_EVAL_SYSCALL:
push_syscall(rt, result.str, dbcache);
break;
case WL_EVAL_OUTPUT:
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
break;
default:
break;
}
}
}
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "template.h"
#include "sqlite3utils.h"
#define TRACE(...) {}
//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__);
typedef struct CachedProgram CachedProgram;
struct CachedProgram {
char path[1<<8];
int pathlen;
WL_Program program;
};
struct TemplateCache {
int count;
int capacity_log2;
CachedProgram pool[];
};
TemplateCache *template_cache_init(int capacity_log2)
{
TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram));
if (cache == NULL)
return NULL;
cache->count = 0;
cache->capacity_log2 = capacity_log2;
for (int i = 0; i < (1 << capacity_log2); i++)
cache->pool[i].pathlen = -1;
return cache;
}
void template_cache_free(TemplateCache *cache)
{
free(cache);
}
static unsigned long djb2(WL_String str)
{
char *ptr = str.ptr;
char *end = str.ptr + str.len;
unsigned long hash = 5381;
int c;
while (ptr < end && (c = *ptr++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
static int lookup(TemplateCache *cache, WL_String path)
{
int mask = (1 << cache->capacity_log2) - 1;
int hash = djb2(path);
int i = hash & mask;
int perturb = hash;
for (;;) {
if (cache->pool[i].pathlen == -1)
return i;
if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen))
return i;
perturb >>= 5;
i = (i * 5 + 1 + perturb) & mask;
}
return -1;
}
typedef struct LoadedFile LoadedFile;
struct LoadedFile {
LoadedFile* next;
int len;
char data[];
};
static LoadedFile *load_file(WL_String path)
{
char buf[1<<10];
if (path.len >= (int) sizeof(buf))
return NULL;
memcpy(buf, path.ptr, path.len);
buf[path.len] = '\0';
FILE *stream = fopen(buf, "rb");
if (stream == NULL)
return NULL;
int ret = fseek(stream, 0, SEEK_END);
if (ret) {
fclose(stream);
return NULL;
}
long tmp = ftell(stream);
if (tmp < 0 || tmp > INT_MAX) {
fclose(stream);
return NULL;
}
int len = (int) tmp;
ret = fseek(stream, 0, SEEK_SET);
if (ret) {
fclose(stream);
return NULL;
}
LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1);
if (result == NULL) {
fclose(stream);
return NULL;
}
result->next = NULL;
result->len = len;
int read_len = fread(result->data, 1, len+1, stream);
if (read_len != len || ferror(stream) || !feof(stream)) {
fclose(stream);
free(result);
return NULL;
}
fclose(stream);
return result;
}
static void free_loaded_files(LoadedFile *loaded_file)
{
while (loaded_file) {
LoadedFile *next = loaded_file->next;
free(loaded_file);
loaded_file = next;
}
}
static int compile(WL_String path, WL_Program *program, WL_Arena *arena)
{
WL_Compiler *compiler = wl_compiler_init(arena);
if (compiler == NULL) {
TRACE("Couldn't initialize WL compiler object");
return -1;
}
LoadedFile *loaded_file_head = NULL;
LoadedFile **loaded_file_tail = &loaded_file_head;
for (int i = 0;; i++) {
LoadedFile *loaded_file = load_file(path);
if (loaded_file == NULL) {
TRACE("Couldn't load file '%.*s'", path.len, path.ptr);
free_loaded_files(loaded_file_head);
return -1;
}
*loaded_file_tail = loaded_file;
loaded_file_tail = &loaded_file->next;
WL_String content = { loaded_file->data, loaded_file->len };
WL_AddResult result = wl_compiler_add(compiler, content);
if (result.type == WL_ADD_ERROR) {
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
free_loaded_files(loaded_file_head);
return -1;
}
if (result.type == WL_ADD_LINK) break;
assert(result.type == WL_ADD_AGAIN);
path = result.path;
}
int ret = wl_compiler_link(compiler, program);
if (ret < 0) {
TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr);
return -1;
}
free_loaded_files(loaded_file_head);
TRACE("Compilation succeded");
return 0;
}
static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache)
{
int num_args = wl_arg_count(rt);
if (num_args == 0)
return 0;
WL_String format;
if (!wl_arg_str(rt, 0, &format))
return -1;
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len);
if (ret != SQLITE_OK)
return -1;
for (int i = 1; i < num_args; i++) {
int64_t ival;
double fval;
WL_String str;
if (wl_arg_none(rt, i))
ret = sqlite3_bind_null (stmt, i);
else if (wl_arg_s64(rt, i, &ival))
ret = sqlite3_bind_int64 (stmt, i, ival);
else if (wl_arg_f64(rt, i, &fval))
ret = sqlite3_bind_double(stmt, i, fval);
else if (wl_arg_str(rt, i, &str))
ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL);
else assert(0);
if (ret != SQLITE_OK) {
sqlite3_reset(stmt);
return -1;
}
}
wl_push_array(rt, 0);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int num_cols = sqlite3_column_count(stmt);
if (num_cols < 0) {
sqlite3_reset(stmt);
return -1;
}
wl_push_map(rt, num_cols);
for (int i = 0; i < num_cols; i++) {
ret = sqlite3_column_type(stmt, i);
switch (ret) {
case SQLITE_INTEGER:
{
int64_t x = sqlite3_column_int64(stmt, i);
wl_push_s64(rt, x);
}
break;
case SQLITE_FLOAT:
{
double x = sqlite3_column_double(stmt, i);
wl_push_f64(rt, x);
}
break;
case SQLITE_TEXT:
{
const void *x = sqlite3_column_text(stmt, i);
int n = sqlite3_column_bytes(stmt, i);
wl_push_str(rt, (WL_String) { (char*) x, n });
}
break;
case SQLITE_BLOB:
{
const void *x = sqlite3_column_blob(stmt, i);
int n = sqlite3_column_bytes(stmt, i);
wl_push_str(rt, (WL_String) { (char*) x, n });
}
break;
case SQLITE_NULL:
{
wl_push_none(rt);
}
break;
}
const char *name = sqlite3_column_name(stmt, i);
wl_push_str(rt, (WL_String) { (char*) name, strlen(name) });
wl_insert(rt);
}
wl_append(rt);
}
sqlite3_reset(stmt);
return 0;
}
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
{
(void) dbcache;
if (wl_streq(name, "login_user_id", -1)) {
if (user_id < 0)
wl_push_none(rt);
else
wl_push_s64(rt, user_id);
} else if (wl_streq(name, "post_id", -1)) {
if (post_id < 0)
wl_push_none(rt);
else
wl_push_s64(rt, post_id);
} else if (wl_streq(name, "csrf", -1)) {
if (csrf.len == 0)
wl_push_none(rt);
else
wl_push_str(rt, (WL_String) { csrf.ptr, csrf.len });
}
}
static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache)
{
if (wl_streq(name, "query", -1)) {
query_routine(rt, dbcache);
return;
}
}
static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program)
{
if (cache == NULL)
return -1;
int i = lookup(cache, path);
if (cache->pool[i].pathlen == -1) {
WL_Program program;
int ret = compile(path, &program, arena);
if (ret < 0) return -1;
void *p = malloc(program.len);
if (p == NULL)
return -1;
memcpy(p, program.ptr, program.len);
program.ptr = p;
if ((int) sizeof(cache->pool->path) <= path.len)
return -1;
memcpy(cache->pool[i].path, path.ptr, path.len);
cache->pool[i].path[path.len] = '\0';
cache->pool[i].pathlen = path.len;
cache->pool[i].program = program;
}
*program = cache->pool[i].program;
return 0;
}
void template_eval(HTTP_ResponseBuilder builder, int status,
WL_String path, TemplateCache *cache, WL_Arena *arena,
SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
{
http_response_builder_status(builder, status);
WL_Program program;
int ret = get_or_create_program(cache, path, arena, &program);
if (ret < 0) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
return;
}
//wl_dump_program(program);
WL_Runtime *rt = wl_runtime_init(arena, program);
if (rt == NULL) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
return;
}
for (bool done = false; !done; ) {
WL_EvalResult result = wl_runtime_eval(rt);
switch (result.type) {
case WL_EVAL_DONE:
http_response_builder_done(builder);
done = true;
break;
case WL_EVAL_ERROR:
// wl_runtime_error(rt)
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
return;
case WL_EVAL_SYSVAR:
push_sysvar(rt, result.str, dbcache, csrf, user_id, post_id);
break;
case WL_EVAL_SYSCALL:
push_syscall(rt, result.str, dbcache);
break;
case WL_EVAL_OUTPUT:
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
break;
default:
break;
}
}
}
+15 -15
View File
@@ -1,16 +1,16 @@
#ifndef TEMPLATE_INCLUDED
#define TEMPLATE_INCLUDED
#include "wl.h"
#include "chttp.h"
#include "sqlite3utils.h"
typedef struct TemplateCache TemplateCache;
TemplateCache *template_cache_init(int capacity_log2);
void template_cache_free(TemplateCache *cache);
void template_eval(HTTP_ResponseBuilder builder, int status,
WL_String path, TemplateCache *cache, WL_Arena *arena,
SQLiteCache *dbcache, int user_id, int post_id);
#ifndef TEMPLATE_INCLUDED
#define TEMPLATE_INCLUDED
#include "wl.h"
#include "chttp.h"
#include "sqlite3utils.h"
typedef struct TemplateCache TemplateCache;
TemplateCache *template_cache_init(int capacity_log2);
void template_cache_free(TemplateCache *cache);
void template_eval(HTTP_ResponseBuilder builder, int status,
WL_String path, TemplateCache *cache, WL_Arena *arena,
SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id);
#endif // TEMPLATE_INCLUDED
+21 -21
View File
@@ -1,21 +1,21 @@
#include "variadic.h"
VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; }
VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; }
VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; }
VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; }
VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; }
VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; }
VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; }
VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; }
VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; }
VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; }
VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; }
VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; }
VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; }
VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; }
VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; }
VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; }
VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; }
VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; }
VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; }
#include "variadic.h"
VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; }
VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; }
VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; }
VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; }
VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; }
VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; }
VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; }
VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; }
VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; }
VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; }
VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; }
VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; }
VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; }
VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; }
VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; }
VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; }
VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; }
VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; }
VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; }
+109 -109
View File
@@ -1,110 +1,110 @@
#ifndef VARIADIC_INCLUDED
#define VARIADIC_INCLUDED
#include <stdbool.h>
#include "chttp.h"
typedef enum {
VARG_TYPE_C,
VARG_TYPE_S,
VARG_TYPE_I,
VARG_TYPE_L,
VARG_TYPE_LL,
VARG_TYPE_SC,
VARG_TYPE_SS,
VARG_TYPE_SI,
VARG_TYPE_SL,
VARG_TYPE_SLL,
VARG_TYPE_UC,
VARG_TYPE_US,
VARG_TYPE_UI,
VARG_TYPE_UL,
VARG_TYPE_ULL,
VARG_TYPE_F,
VARG_TYPE_D,
VARG_TYPE_B,
VARG_TYPE_STR,
} VArgType;
typedef struct {
VArgType type;
union {
char c;
short s;
int i;
long l;
long long ll;
signed char sc;
signed short ss;
signed int si;
signed long sl;
signed long long sll;
unsigned char uc;
unsigned short us;
unsigned int ui;
unsigned long ul;
unsigned long long ull;
float f;
double d;
bool b;
HTTP_String str;
};
} VArg;
VArg varg_from_c (char c);
VArg varg_from_s (short s);
VArg varg_from_i (int i);
VArg varg_from_l (long l);
VArg varg_from_ll (long long ll);
VArg varg_from_sc (char sc);
VArg varg_from_ss (short ss);
VArg varg_from_si (int si);
VArg varg_from_sl (long sl);
VArg varg_from_sll (long long sll);
VArg varg_from_uc (char uc);
VArg varg_from_us (short us);
VArg varg_from_ui (int ui);
VArg varg_from_ul (long ul);
VArg varg_from_ull (long long ull);
VArg varg_from_f (float f);
VArg varg_from_d (double d);
VArg varg_from_b (bool b);
VArg varg_from_str (HTTP_String str);
#define VARG(X) (_Generic((X), \
char : varg_from_c, \
short : varg_from_s, \
int : varg_from_i, \
long : varg_from_l, \
long long : varg_from_ll, \
signed char : varg_from_sc, \
/*signed short : varg_from_ss,*/ \
/*signed int : varg_from_si,*/ \
/*signed long : varg_from_sl,*/ \
/*signed long long : varg_from_sll,*/ \
unsigned char : varg_from_uc, \
unsigned short : varg_from_us, \
unsigned int : varg_from_ui, \
unsigned long : varg_from_ul, \
unsigned long long: varg_from_ull, \
float : varg_from_f, \
double : varg_from_d, \
bool : varg_from_b, \
HTTP_String : varg_from_str \
))(X)
typedef struct {
int len;
VArg *ptr;
} VArgs;
#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } }
#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } }
#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } }
#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } }
#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } }
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME
#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__)
#ifndef VARIADIC_INCLUDED
#define VARIADIC_INCLUDED
#include <stdbool.h>
#include "chttp.h"
typedef enum {
VARG_TYPE_C,
VARG_TYPE_S,
VARG_TYPE_I,
VARG_TYPE_L,
VARG_TYPE_LL,
VARG_TYPE_SC,
VARG_TYPE_SS,
VARG_TYPE_SI,
VARG_TYPE_SL,
VARG_TYPE_SLL,
VARG_TYPE_UC,
VARG_TYPE_US,
VARG_TYPE_UI,
VARG_TYPE_UL,
VARG_TYPE_ULL,
VARG_TYPE_F,
VARG_TYPE_D,
VARG_TYPE_B,
VARG_TYPE_STR,
} VArgType;
typedef struct {
VArgType type;
union {
char c;
short s;
int i;
long l;
long long ll;
signed char sc;
signed short ss;
signed int si;
signed long sl;
signed long long sll;
unsigned char uc;
unsigned short us;
unsigned int ui;
unsigned long ul;
unsigned long long ull;
float f;
double d;
bool b;
HTTP_String str;
};
} VArg;
VArg varg_from_c (char c);
VArg varg_from_s (short s);
VArg varg_from_i (int i);
VArg varg_from_l (long l);
VArg varg_from_ll (long long ll);
VArg varg_from_sc (char sc);
VArg varg_from_ss (short ss);
VArg varg_from_si (int si);
VArg varg_from_sl (long sl);
VArg varg_from_sll (long long sll);
VArg varg_from_uc (char uc);
VArg varg_from_us (short us);
VArg varg_from_ui (int ui);
VArg varg_from_ul (long ul);
VArg varg_from_ull (long long ull);
VArg varg_from_f (float f);
VArg varg_from_d (double d);
VArg varg_from_b (bool b);
VArg varg_from_str (HTTP_String str);
#define VARG(X) (_Generic((X), \
char : varg_from_c, \
short : varg_from_s, \
int : varg_from_i, \
long : varg_from_l, \
long long : varg_from_ll, \
signed char : varg_from_sc, \
/*signed short : varg_from_ss,*/ \
/*signed int : varg_from_si,*/ \
/*signed long : varg_from_sl,*/ \
/*signed long long : varg_from_sll,*/ \
unsigned char : varg_from_uc, \
unsigned short : varg_from_us, \
unsigned int : varg_from_ui, \
unsigned long : varg_from_ul, \
unsigned long long: varg_from_ull, \
float : varg_from_f, \
double : varg_from_d, \
bool : varg_from_b, \
HTTP_String : varg_from_str \
))(X)
typedef struct {
int len;
VArg *ptr;
} VArgs;
#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } }
#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } }
#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } }
#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } }
#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } }
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME
#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__)
#endif // VARIADIC_INCLUDED