Fix issues in demo templates

This commit is contained in:
2025-09-26 13:03:44 +02:00
committed by cozis
parent e50dfd518b
commit 783c0d22b8
25 changed files with 16242 additions and 16238 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
*.o
*.out
*.o
*.out
*.exe
+5559 -5559
View File
File diff suppressed because it is too large Load Diff
+526 -526
View File
File diff suppressed because it is too large Load Diff
+909 -909
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
+5865 -5865
View File
File diff suppressed because it is too large Load Diff
+89 -89
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 path, WL_String content);
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
WL_String wl_compiler_error (WL_Compiler *compiler);
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
void wl_dump_program (WL_Program program);
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);
#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 path, WL_String content);
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
WL_String wl_compiler_error (WL_Compiler *compiler);
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
void wl_dump_program (WL_Program program);
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);
+6 -6
View File
@@ -1,7 +1,7 @@
Copyright © 2025 Francesco Cozzuto
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Copyright © 2025 Francesco Cozzuto
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+25 -25
View File
@@ -1,25 +1,25 @@
FLAGS = -Wall -Wextra -O0 -g3 -I.
ifeq ($(OS),Windows_NT)
EXT = .exe
FLAGS += -lws2_32 -lbcrypt
else
EXT = .out
endif
all: cweb.h cozisnews$(EXT)
cweb.h: src/main.c src/main.h
python amalg.py
cweb.o: cweb.c
gcc $< -o $@ -I3p
sqlite3.o: 3p/sqlite3.c
gcc -c -o $@ $<
cozisnews$(EXT): demo/main.c sqlite3.o
gcc -o $@ demo/main.c sqlite3.o $(FLAGS) -I3p
clean:
rm *.o *.out *.exe
FLAGS = -Wall -Wextra -O0 -g3 -I.
ifeq ($(OS),Windows_NT)
EXT = .exe
FLAGS += -lws2_32 -lbcrypt
else
EXT = .out
endif
all: cweb.h cozisnews$(EXT)
cweb.h: src/main.c src/main.h
python amalg.py
cweb.o: cweb.c
gcc $< -o $@ -I3p
sqlite3.o: 3p/sqlite3.c
gcc -c -o $@ $<
cozisnews$(EXT): demo/main.c sqlite3.o
gcc -o $@ demo/main.c sqlite3.o $(FLAGS) -I3p
clean:
rm *.o *.out *.exe
+6 -6
View File
@@ -1,7 +1,7 @@
# cWEB
cWEB is a framework for web development designed for small scale production environments and tooling. It's distributed as a single header file and has minimal dependencies.
cWEB is single header file web framework for C designed for small scale production environments and tooling :D
# cWEB
cWEB is a framework for web development designed for small scale production environments and tooling. It's distributed as a single header file and has minimal dependencies.
cWEB is single header file web framework for C designed for small scale production environments and tooling :D
(In progress)
+112 -112
View File
@@ -1,112 +1,112 @@
class Amalgamator:
def __init__(self):
self.out = ""
def append_text(self, text):
self.out += text
def append_file(self, file):
self.out += "\n"
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
self.out += "// " + file + "\n"
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
self.out += "\n"
self.out += "#line 1 \"" + file + "\"\n"
self.out += open(file).read()
if len(self.out) > 0 and self.out[len(self.out)-1] != '\n':
self.out += "\n"
def save(self, file):
open(file, 'w').write(self.out)
desc = """
// This file was generated automatically. Do not modify directly!
"""
header = Amalgamator()
header.append_text("""/*
Copyright © 2025 Francesco Cozzuto
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the Software),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
""")
header.append_text("#ifndef CWEB_INCLUDED\n")
header.append_text("#define CWEB_INCLUDED\n")
header.append_file("src/main.h")
header.append_text("#endif // CWEB_INCLUDED\n")
header.append_text("#ifdef CWEB_IMPLEMENTATION\n")
header.append_text("#define CWEB_AMALGAMATION\n")
header.append_text("#define WL_NOINCLUDE\n")
header.append_text("#define HTTP_NOINCLUDE\n")
header.append_text("#define CRYPT_BLOWFISH_NOINCLUDE\n")
header.append_text("#undef MIN\n")
header.append_text("#undef MAX\n")
header.append_text("#undef ASSERT\n")
header.append_text("#undef SIZEOF\n")
header.append_text("#undef TRACE\n")
header.append_file("3p/chttp.h")
header.append_file("3p/chttp.c")
header.append_text("#undef MIN\n")
header.append_text("#undef MAX\n")
header.append_text("#undef ASSERT\n")
header.append_text("#undef SIZEOF\n")
header.append_text("#undef TRACE\n")
header.append_text("#define Scanner WL_Scanner\n")
header.append_text("#define Token WL_Token\n")
header.append_text("#define is_space is_space__wl\n")
header.append_text("#define is_digit is_digit__wl\n")
header.append_text("#define is_alpha is_alpha__wl\n")
header.append_text("#define is_printable is_printable__wl\n")
header.append_text("#define is_hex_digit is_hex_digit__wl\n")
header.append_text("#define hex_digit_to_int hex_digit_to_int__wl\n")
header.append_text("#define consume_str consume_str__wl\n")
header.append_file("3p/wl.h")
header.append_file("3p/wl.c")
header.append_text("#undef Scanner\n")
header.append_text("#undef Token\n")
header.append_text("#undef is_space\n")
header.append_text("#undef is_digit\n")
header.append_text("#undef is_alpha\n")
header.append_text("#undef is_printable\n")
header.append_text("#undef is_hex_digit\n")
header.append_text("#undef hex_digit_to_int\n")
header.append_text("#undef MIN\n")
header.append_text("#undef MAX\n")
header.append_text("#undef ASSERT\n")
header.append_text("#undef SIZEOF\n")
header.append_file("3p/crypt_blowfish.h")
header.append_file("3p/crypt_blowfish.c")
header.append_file("src/main.c")
header.append_text("#endif // CWEB_IMPLEMENTATION\n")
header.save("cweb.h")
class Amalgamator:
def __init__(self):
self.out = ""
def append_text(self, text):
self.out += text
def append_file(self, file):
self.out += "\n"
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
self.out += "// " + file + "\n"
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
self.out += "\n"
self.out += "#line 1 \"" + file + "\"\n"
self.out += open(file).read()
if len(self.out) > 0 and self.out[len(self.out)-1] != '\n':
self.out += "\n"
def save(self, file):
open(file, 'w').write(self.out)
desc = """
// This file was generated automatically. Do not modify directly!
"""
header = Amalgamator()
header.append_text("""/*
Copyright © 2025 Francesco Cozzuto
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the Software),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
""")
header.append_text("#ifndef CWEB_INCLUDED\n")
header.append_text("#define CWEB_INCLUDED\n")
header.append_file("src/main.h")
header.append_text("#endif // CWEB_INCLUDED\n")
header.append_text("#ifdef CWEB_IMPLEMENTATION\n")
header.append_text("#define CWEB_AMALGAMATION\n")
header.append_text("#define WL_NOINCLUDE\n")
header.append_text("#define HTTP_NOINCLUDE\n")
header.append_text("#define CRYPT_BLOWFISH_NOINCLUDE\n")
header.append_text("#undef MIN\n")
header.append_text("#undef MAX\n")
header.append_text("#undef ASSERT\n")
header.append_text("#undef SIZEOF\n")
header.append_text("#undef TRACE\n")
header.append_file("3p/chttp.h")
header.append_file("3p/chttp.c")
header.append_text("#undef MIN\n")
header.append_text("#undef MAX\n")
header.append_text("#undef ASSERT\n")
header.append_text("#undef SIZEOF\n")
header.append_text("#undef TRACE\n")
header.append_text("#define Scanner WL_Scanner\n")
header.append_text("#define Token WL_Token\n")
header.append_text("#define is_space is_space__wl\n")
header.append_text("#define is_digit is_digit__wl\n")
header.append_text("#define is_alpha is_alpha__wl\n")
header.append_text("#define is_printable is_printable__wl\n")
header.append_text("#define is_hex_digit is_hex_digit__wl\n")
header.append_text("#define hex_digit_to_int hex_digit_to_int__wl\n")
header.append_text("#define consume_str consume_str__wl\n")
header.append_file("3p/wl.h")
header.append_file("3p/wl.c")
header.append_text("#undef Scanner\n")
header.append_text("#undef Token\n")
header.append_text("#undef is_space\n")
header.append_text("#undef is_digit\n")
header.append_text("#undef is_alpha\n")
header.append_text("#undef is_printable\n")
header.append_text("#undef is_hex_digit\n")
header.append_text("#undef hex_digit_to_int\n")
header.append_text("#undef MIN\n")
header.append_text("#undef MAX\n")
header.append_text("#undef ASSERT\n")
header.append_text("#undef SIZEOF\n")
header.append_file("3p/crypt_blowfish.h")
header.append_file("3p/crypt_blowfish.c")
header.append_file("src/main.c")
header.append_text("#endif // CWEB_IMPLEMENTATION\n")
header.save("cweb.h")
+34 -34
View File
@@ -1,8 +1,8 @@
/*
Copyright 2025 Francesco Cozzuto
Copyright © 2025 Francesco Cozzuto
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the Software),
copy of this software and associated documentation files (the Software),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
@@ -11,7 +11,7 @@ Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
@@ -26,7 +26,7 @@ OTHER DEALINGS IN THE SOFTWARE.
// src/main.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/main.h"
#line 1 "src/main.h"
#include <stdint.h>
#include <stdbool.h>
@@ -358,7 +358,7 @@ int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash);
// 3p/chttp.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "3p/chttp.h"
#line 1 "3p/chttp.h"
#ifndef HTTP_AMALGAMATION
#define HTTP_AMALGAMATION
@@ -368,7 +368,7 @@ int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash);
// src/basic.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/basic.h"
#line 1 "src/basic.h"
#ifndef CHTTP_BASIC_INCLUDED
#define CHTTP_BASIC_INCLUDED
@@ -455,7 +455,7 @@ void print_bytes(HTTP_String prefix, HTTP_String src);
// src/parse.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/parse.h"
#line 1 "src/parse.h"
#ifndef PARSE_INCLUDED
#define PARSE_INCLUDED
@@ -561,7 +561,7 @@ int http_get_param_i (HTTP_String body, HTTP_String str);
// src/engine.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/engine.h"
#line 1 "src/engine.h"
#ifndef HTTP_ENGINE_INCLUDED
#define HTTP_ENGINE_INCLUDED
@@ -689,7 +689,7 @@ void http_engine_undo (HTTP_Engine *eng);
// src/cert.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/cert.h"
#line 1 "src/cert.h"
#ifndef CERT_INCLUDED
#define CERT_INCLUDED
@@ -720,7 +720,7 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
// src/client.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/client.h"
#line 1 "src/client.h"
#ifndef CLIENT_INCLUDED
#define CLIENT_INCLUDED
@@ -824,7 +824,7 @@ HTTP_Response *http_post(HTTP_String url,
// src/server.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/server.h"
#line 1 "src/server.h"
#ifndef HTTP_SERVER_INCLUDED
#define HTTP_SERVER_INCLUDED
@@ -865,7 +865,7 @@ void http_response_builder_done (HTTP_ResponseBuilder res);
// src/router.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/router.h"
#line 1 "src/router.h"
#ifndef HTTP_ROUTER_INCLUDED
#define HTTP_ROUTER_INCLUDED
@@ -890,7 +890,7 @@ int http_serve (char *addr, int port, HTTP_Router *router);
// 3p/chttp.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "3p/chttp.c"
#line 1 "3p/chttp.c"
#ifndef HTTP_NOINCLUDE
#include "chttp.h"
#endif
@@ -899,7 +899,7 @@ int http_serve (char *addr, int port, HTTP_Router *router);
// src/sec.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/sec.h"
#line 1 "src/sec.h"
#ifndef SEC_INCLUDED
#define SEC_INCLUDED
@@ -960,7 +960,7 @@ void secure_context_free(SecureContext *sec);
// src/socket_raw.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/socket_raw.h"
#line 1 "src/socket_raw.h"
#ifndef SOCKET_RAW_INCLUDED
#define SOCKET_RAW_INCLUDED
@@ -1001,7 +1001,7 @@ RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int b
// src/socket.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/socket.h"
#line 1 "src/socket.h"
#ifndef SOCKET_INCLUDED
#define SOCKET_INCLUDED
@@ -1082,7 +1082,7 @@ void* socket_get_user_data(Socket *sock);
// src/socket_pool.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/socket_pool.h"
#line 1 "src/socket_pool.h"
#ifndef SOCKET_POOL_INCLUDED
#define SOCKET_POOL_INCLUDED
@@ -1148,7 +1148,7 @@ bool socket_pool_secure(SocketPool *pool, SocketHandle handle);
// src/basic.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/basic.c"
#line 1 "src/basic.c"
#include <stddef.h>
#include <string.h>
@@ -1257,7 +1257,7 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
// src/parse.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/parse.c"
#line 1 "src/parse.c"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
@@ -2564,7 +2564,7 @@ int http_get_param_i(HTTP_String body, HTTP_String str)
// src/engine.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/engine.c"
#line 1 "src/engine.c"
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
@@ -3558,7 +3558,7 @@ void http_engine_undo(HTTP_Engine *eng)
// src/cert.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/cert.c"
#line 1 "src/cert.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -3733,7 +3733,7 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
// src/sec.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/sec.c"
#line 1 "src/sec.c"
#ifndef HTTP_AMALGAMATION
#include "sec.h"
#endif
@@ -3959,7 +3959,7 @@ int secure_context_add_cert(SecureContext *sec,
// src/socket_raw.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/socket_raw.c"
#line 1 "src/socket_raw.c"
#include <string.h>
#ifdef _WIN32
@@ -4071,7 +4071,7 @@ RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int b
// src/socket.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/socket.c"
#line 1 "src/socket.c"
#include <stdio.h> // snprintf
#include <assert.h>
#include <string.h>
@@ -4867,7 +4867,7 @@ void *socket_get_user_data(Socket *sock)
// src/socket_pool.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/socket_pool.c"
#line 1 "src/socket_pool.c"
#include <assert.h>
#include <stdlib.h>
@@ -5230,7 +5230,7 @@ bool socket_pool_secure(SocketPool *pool, SocketHandle handle)
// src/client.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/client.c"
#line 1 "src/client.c"
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
@@ -5689,7 +5689,7 @@ HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers,
// src/server.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/server.c"
#line 1 "src/server.c"
#include <stdint.h>
#include <stdarg.h>
#include <stdlib.h>
@@ -6002,7 +6002,7 @@ void http_response_builder_done(HTTP_ResponseBuilder res)
// src/router.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/router.c"
#line 1 "src/router.c"
#include <string.h>
#include <stdlib.h>
#include <limits.h>
@@ -6469,7 +6469,7 @@ int http_serve(char *addr, int port, HTTP_Router *router)
// 3p/wl.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "3p/wl.h"
#line 1 "3p/wl.h"
#include <stdint.h>
#include <stdbool.h>
@@ -6565,7 +6565,7 @@ void wl_append (WL_Runtime *rt);
// 3p/wl.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "3p/wl.c"
#line 1 "3p/wl.c"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
@@ -12448,7 +12448,7 @@ void wl_runtime_dump(WL_Runtime *rt)
// 3p/crypt_blowfish.h
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "3p/crypt_blowfish.h"
#line 1 "3p/crypt_blowfish.h"
/*
* Written by Solar Designer <solar at openwall.com> in 2000-2011.
* No copyright is claimed, and the software is hereby placed in the public
@@ -12481,7 +12481,7 @@ extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
// 3p/crypt_blowfish.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "3p/crypt_blowfish.c"
#line 1 "3p/crypt_blowfish.c"
/*
* The crypt_blowfish homepage is:
*
@@ -13396,7 +13396,7 @@ char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count,
// src/main.c
////////////////////////////////////////////////////////////////////////////////////////
//#line 1 "src/main.c"
#line 1 "src/main.c"
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
@@ -14072,7 +14072,7 @@ static int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *
if (cache->items[i].stmt == NULL) {
sqlite3_stmt *stmt;
int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL);
int ret = sqlite3_prepare_v2(cache->db, fmt, fmtlen, &stmt, NULL);
if (ret != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(cache->db), __FILE__, __LINE__); // TODO
return ret;
+374 -373
View File
@@ -1,373 +1,374 @@
#include <stddef.h>
#define CWEB_IMPLEMENTATION
#include "../cweb.h"
#define USERNAME_LIMIT 64
bool valid_name(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_email(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_pass(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_post_title(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_post_content(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_link(CWEB_String str)
{
(void) str;
return true;
}
bool valid_comment_content(CWEB_String str)
{
(void) str;
return true;
}
// TODO: check for the correct methods at each endpoint
static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass)
{
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT id, hash FROM Users WHERE username=?", name);
int64_t user_id;
CWEB_PasswordHash hash;
int ret = cweb_next_query_row(&res, &user_id, &hash);
if (ret < 0) {
cweb_free_query_result(&res);
return -1;
}
if (ret == 0) {
cweb_free_query_result(&res);
return 0;
}
ret = cweb_check_password(pass, hash);
if (ret < 0) {
cweb_free_query_result(&res);
return -1;
}
if (ret > 0) {
cweb_free_query_result(&res);
return 0;
}
return user_id;
}
static void endpoint_api_login(CWEB *cweb, CWEB_Request *req)
{
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
CWEB_String pass = cweb_get_param_s(req, CWEB_STR("password"));
if (!valid_name(name) || !valid_pass(pass)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
return;
}
int ret = user_exists(cweb, name, pass);
if (ret < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
if (ret == 0) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
return;
}
if (cweb_set_user_id(req, ret) < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
cweb_respond_redirect(req, CWEB_STR("/index"));
}
static void endpoint_api_signup(CWEB *cweb, CWEB_Request *req)
{
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
CWEB_String email = cweb_get_param_s(req, CWEB_STR("email"));
CWEB_String pass1 = cweb_get_param_s(req, CWEB_STR("password1"));
CWEB_String pass2 = cweb_get_param_s(req, CWEB_STR("password2"));
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
return;
}
if (!cweb_streq(pass1, pass2)) {
cweb_respond_basic(req, 400, CWEB_STR("The password was repeated incorrectly"));
return;
}
CWEB_PasswordHash hash;
int ret = cweb_hash_password(pass1, 12, &hash);
if (ret) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
int64_t insert_id = cweb_database_insert(cweb, "INSERT INTO Users(username, email, hash) VALUES (?, ?, ?)", name, email, hash);
if (insert_id < 0) {
// TODO: What if the user exists?
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
if (cweb_set_user_id(req, insert_id) < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
cweb_respond_redirect(req, CWEB_STR("/index"));
}
static void endpoint_api_logout(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
cweb_set_user_id(req, -1);
cweb_respond_redirect(req, CWEB_STR("/index"));
}
static void endpoint_api_post(CWEB *cweb, CWEB_Request *req)
{
int user_id = cweb_get_user_id(req);
if (user_id == -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
CWEB_String title = cweb_get_param_s(req, CWEB_STR("title"));
CWEB_String link = cweb_get_param_s(req, CWEB_STR("link"));
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
CWEB_String csrf = cweb_get_param_s(req, CWEB_STR("csrf"));
if (!cweb_streq(cweb_get_csrf(req), csrf)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
return;
}
if (!valid_post_title(title)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid title"));
return;
}
if (!valid_link(link)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid link"));
return;
}
if (!valid_post_content(content)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
return;
}
bool is_link = false;
if (link.len > 0) {
is_link = true;
content = link;
}
int64_t insert_id = cweb_database_insert(cweb,
"INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)",
user_id, title, is_link, content);
if (insert_id < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
int post_id = (int) insert_id;
CWEB_String target = cweb_format(req, "/post?id={}", post_id);
cweb_respond_redirect(req, target);
}
static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req)
{
int user_id = cweb_get_user_id(req);
if (user_id == -1) {
cweb_respond_basic(req, 400, CWEB_STR("You are not logged in"));
return;
}
int parent_post = cweb_get_param_i(req, CWEB_STR("parent_post"));
int parent_comment = cweb_get_param_i(req, CWEB_STR("parent_comment"));
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
CWEB_String csrf2 = cweb_get_param_s(req, CWEB_STR("csrf"));
if (!cweb_streq(cweb_get_csrf(req), csrf2)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
return;
}
content = cweb_trim(content);
if (!valid_comment_content(content)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
return;
}
int64_t insert_id;
if (parent_comment == -1) insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post);
else insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment);
if (insert_id < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
CWEB_String target = cweb_format(req, "/post?id={}", parent_post);
cweb_respond_redirect(req, target);
}
static void endpoint_index(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
cweb_respond_template(req, 200, CWEB_STR("demo/pages/index.wl"), -1);
}
static void endpoint_write(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
if (cweb_get_user_id(req) == -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/write.wl"), -1);
}
static void endpoint_login(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/login.wl"), -1);
}
static void endpoint_signup(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/signup.wl"), -1);
}
static void endpoint_post(CWEB *cweb, CWEB_Request *req)
{
int post_id = cweb_get_param_i(req, CWEB_STR("id"));
if (post_id < 0) {
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
return;
}
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
int num;
if (cweb_next_query_row(&res, &num) != 1) {
cweb_respond_basic(req, 500, CWEB_STR(""));
cweb_free_query_result(&res);
return;
}
cweb_free_query_result(&res);
if (num < 0) {
cweb_respond_basic(req, 500, CWEB_STR(""));
return;
}
if (num == 0) {
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/post.wl"), post_id);
}
static void endpoint_fallback(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
}
int main(void)
{
CWEB_String addr = CWEB_STR("127.0.0.1");
uint16_t port = 8080;
CWEB_String database_file = CWEB_STR(":memory:");
CWEB_String schema_file = CWEB_STR("demo/schema.sql");
if (cweb_global_init() < 0)
return -1;
CWEB *cweb = cweb_init(addr, port);
if (cweb == NULL) {
cweb_global_free();
return -1;
}
if (cweb_enable_database(cweb, database_file, schema_file) < 0) {
cweb_free(cweb);
cweb_global_free();
return -1;
}
cweb_trace_sql(cweb, true);
for (;;) {
CWEB_Request *req = cweb_wait(cweb);
if (0) {}
else if (cweb_match_endpoint(req, CWEB_STR("/api/login"))) endpoint_api_login(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/signup"))) endpoint_api_signup(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/logout"))) endpoint_api_logout(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/post"))) endpoint_api_post(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/comment"))) endpoint_api_comment(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/index"))) endpoint_index(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/write"))) endpoint_write(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/login"))) endpoint_login(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/signup"))) endpoint_signup(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/post"))) endpoint_post(cweb, req);
else endpoint_fallback(cweb, req);
}
cweb_free(cweb);
cweb_global_free();
return 0;
}
#include <stddef.h>
#define CWEB_IMPLEMENTATION
#include "../cweb.h"
#define USERNAME_LIMIT 64
bool valid_name(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_email(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_pass(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_post_title(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_post_content(CWEB_String str)
{
(void) str; // TODO
return true;
}
bool valid_link(CWEB_String str)
{
(void) str;
return true;
}
bool valid_comment_content(CWEB_String str)
{
(void) str;
return true;
}
// TODO: check for the correct methods at each endpoint
static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass)
{
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT id, hash FROM Users WHERE username=?", name);
int64_t user_id;
CWEB_PasswordHash hash;
int ret = cweb_next_query_row(&res, &user_id, &hash);
if (ret < 0) {
cweb_free_query_result(&res);
return -1;
}
if (ret == 0) {
cweb_free_query_result(&res);
return 0;
}
ret = cweb_check_password(pass, hash);
if (ret < 0) {
cweb_free_query_result(&res);
return -1;
}
if (ret > 0) {
cweb_free_query_result(&res);
return 0;
}
return user_id;
}
static void endpoint_api_login(CWEB *cweb, CWEB_Request *req)
{
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
CWEB_String pass = cweb_get_param_s(req, CWEB_STR("password"));
if (!valid_name(name) || !valid_pass(pass)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
return;
}
int ret = user_exists(cweb, name, pass);
if (ret < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
if (ret == 0) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
return;
}
if (cweb_set_user_id(req, ret) < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
cweb_respond_redirect(req, CWEB_STR("/index"));
}
static void endpoint_api_signup(CWEB *cweb, CWEB_Request *req)
{
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
CWEB_String name = cweb_get_param_s(req, CWEB_STR("username"));
CWEB_String email = cweb_get_param_s(req, CWEB_STR("email"));
CWEB_String pass1 = cweb_get_param_s(req, CWEB_STR("password1"));
CWEB_String pass2 = cweb_get_param_s(req, CWEB_STR("password2"));
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid credentials"));
return;
}
if (!cweb_streq(pass1, pass2)) {
cweb_respond_basic(req, 400, CWEB_STR("The password was repeated incorrectly"));
return;
}
CWEB_PasswordHash hash;
int ret = cweb_hash_password(pass1, 12, &hash);
if (ret) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
int64_t insert_id = cweb_database_insert(cweb, "INSERT INTO Users(username, email, hash) VALUES (?, ?, ?)", name, email, hash);
if (insert_id < 0) {
// TODO: What if the user exists?
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
if (cweb_set_user_id(req, insert_id) < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
cweb_respond_redirect(req, CWEB_STR("/index"));
}
static void endpoint_api_logout(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
cweb_set_user_id(req, -1);
cweb_respond_redirect(req, CWEB_STR("/index"));
}
static void endpoint_api_post(CWEB *cweb, CWEB_Request *req)
{
int user_id = cweb_get_user_id(req);
if (user_id == -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
CWEB_String title = cweb_get_param_s(req, CWEB_STR("title"));
CWEB_String link = cweb_get_param_s(req, CWEB_STR("link"));
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
CWEB_String csrf = cweb_get_param_s(req, CWEB_STR("csrf"));
if (!cweb_streq(cweb_get_csrf(req), csrf)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
return;
}
if (!valid_post_title(title)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid title"));
return;
}
if (!valid_link(link)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid link"));
return;
}
if (!valid_post_content(content)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
return;
}
bool is_link = false;
if (link.len > 0) {
is_link = true;
content = link;
}
int64_t insert_id = cweb_database_insert(cweb,
"INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)",
user_id, title, is_link, content);
if (insert_id < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
int post_id = (int) insert_id;
CWEB_String target = cweb_format(req, "/post?id={}", post_id);
cweb_respond_redirect(req, target);
}
static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req)
{
int user_id = cweb_get_user_id(req);
if (user_id == -1) {
cweb_respond_basic(req, 400, CWEB_STR("You are not logged in"));
return;
}
int parent_post = cweb_get_param_i(req, CWEB_STR("parent_post"));
int parent_comment = cweb_get_param_i(req, CWEB_STR("parent_comment"));
CWEB_String content = cweb_get_param_s(req, CWEB_STR("content"));
CWEB_String csrf2 = cweb_get_param_s(req, CWEB_STR("csrf"));
if (!cweb_streq(cweb_get_csrf(req), csrf2)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid request"));
return;
}
content = cweb_trim(content);
if (!valid_comment_content(content)) {
cweb_respond_basic(req, 400, CWEB_STR("Invalid content"));
return;
}
int64_t insert_id;
if (parent_comment == -1) insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post);
else insert_id = cweb_database_insert(cweb, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment);
if (insert_id < 0) {
cweb_respond_basic(req, 500, CWEB_STR("Internal error"));
return;
}
CWEB_String target = cweb_format(req, "/post?id={}", parent_post);
cweb_respond_redirect(req, target);
}
static void endpoint_index(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
cweb_respond_template(req, 200, CWEB_STR("demo/pages/index.wl"), -1);
}
static void endpoint_write(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
if (cweb_get_user_id(req) == -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/write.wl"), -1);
}
static void endpoint_login(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/login.wl"), -1);
}
static void endpoint_signup(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
if (cweb_get_user_id(req) != -1) {
cweb_respond_redirect(req, CWEB_STR("/index"));
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/signup.wl"), -1);
}
static void endpoint_post(CWEB *cweb, CWEB_Request *req)
{
int post_id = cweb_get_param_i(req, CWEB_STR("id"));
if (post_id < 0) {
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
return;
}
CWEB_QueryResult res = cweb_database_select(cweb, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
int num;
if (cweb_next_query_row(&res, &num) != 1) {
cweb_respond_basic(req, 500, CWEB_STR(""));
cweb_free_query_result(&res);
return;
}
cweb_free_query_result(&res);
if (num < 0) {
cweb_respond_basic(req, 500, CWEB_STR(""));
return;
}
if (num == 0) {
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
return;
}
cweb_respond_template(req, 200, CWEB_STR("demo/pages/post.wl"), post_id);
}
static void endpoint_fallback(CWEB *cweb, CWEB_Request *req)
{
(void) cweb;
cweb_respond_template(req, 404, CWEB_STR("demo/pages/notfound.wl"), -1);
}
int main(void)
{
CWEB_String addr = CWEB_STR("127.0.0.1");
uint16_t port = 8080;
CWEB_String database_file = CWEB_STR(":memory:");
CWEB_String schema_file = CWEB_STR("demo/schema.sql");
if (cweb_global_init() < 0)
return -1;
CWEB *cweb = cweb_init(addr, port);
if (cweb == NULL) {
cweb_global_free();
return -1;
}
if (cweb_enable_database(cweb, database_file, schema_file) < 0) {
cweb_free(cweb);
cweb_global_free();
return -1;
}
cweb_trace_sql(cweb, true);
cweb_enable_template_cache(cweb, false);
for (;;) {
CWEB_Request *req = cweb_wait(cweb);
if (0) {}
else if (cweb_match_endpoint(req, CWEB_STR("/api/login"))) endpoint_api_login(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/signup"))) endpoint_api_signup(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/logout"))) endpoint_api_logout(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/post"))) endpoint_api_post(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/api/comment"))) endpoint_api_comment(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/index"))) endpoint_index(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/write"))) endpoint_write(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/login"))) endpoint_login(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/signup"))) endpoint_signup(cweb, req);
else if (cweb_match_endpoint(req, CWEB_STR("/post"))) endpoint_post(cweb, req);
else endpoint_fallback(cweb, req);
}
cweb_free(cweb);
cweb_global_free();
return 0;
}
+66 -66
View File
@@ -1,67 +1,67 @@
include "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\'"'>\escape(post.title)</a>
</div>
<div>
<span>\escape(post.date)</span> | <span>\escape(post.num_comments) comments</span>
</div>
</div>
}
</main>
include "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\'"'>\escape(post.title)</a>
</div>
<div>
<span>\escape(post.date)</span> | <span>\escape(post.num_comments) comments</span>
</div>
</div>
}
</main>
page("Index", $login_user_id, style, main)
+22 -22
View File
@@ -1,22 +1,22 @@
include "page.wl"
include "login_and_signup_style.wl"
let main =
<main>
<span>Welcome back!</span>
<div id="response"></div>
<form action="/api/login" method="POST">
<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 "page.wl"
include "login_and_signup_style.wl"
let main =
<main>
<span>Welcome back!</span>
<div id="response"></div>
<form action="/api/login" method="POST">
<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 "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 "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 - \escape(title)</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
</style>
\(style)
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
</head>
<body>
<nav>
<div>
<a href="/index">index</a>
\if login_user_id != none: {
"|\n"
<a href="/write">write</a>
}
</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 - \escape(title)</title>
<style>
body {
line-height: 200%;
font-family: monospace;
max-width: 800px;
margin: 20px auto;
}
nav {
overflow: auto;
background: #5780C9;
color: #6E93D4;
padding: 5px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
nav div {
float: left
}
nav div:last-child {
float: right
}
nav a {
font-size: 14px;
color: #1D2B42;
}
nav a.current {
font-weight: bold;
}
main {
padding: 5px 10px;
background: #F7E6C0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
footer {
padding: 5px 10px;
}
</style>
\(style)
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.2/htmx.min.js"></script>
</head>
<body>
<nav>
<div>
<a href="/index">index</a>
\if login_user_id != none: {
"|\n"
<a href="/write">write</a>
}
</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>
+248 -245
View File
@@ -1,246 +1,249 @@
include "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", $resource_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", $resource_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>\escape(post.title)</span>
</div>
<div class="thread-meta">
submitted 3 hours ago by <a href="">\escape(post.username)</a> | <a href="">\len(comments)</a>
</div>
<div class="thread-text">
<pre>\escape(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=\'"'\$resource_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="">\escape(comment.username)</a> 2 hours ago
</div>
<div class="comment-text">
<pre>\escape(comment.content)</pre>
</div>
\if $login_user_id != none:
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$resource_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 "page.wl"
let post_id = $args(0)
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>\escape(post.title)</span>
</div>
<div class="thread-meta">
submitted 3 hours ago by <a href="">\escape(post.username)</a> | <a href="">\len(comments)</a>
</div>
<div class="thread-text">
<pre>\escape(post.content)</pre>
</div>
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
<input type="hidden" name="parent_post" value=\'"'\post_id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
</details>
</div>
\procedure render_comment(post_id, comment)
<div class="comment">
<div class="vote-buttons">
<a href=""></a>
<a href=""></a>
</div>
<div class="comment-content">
<div class="comment-meta">
<a href="">\escape(comment.username)</a> 2 hours ago
</div>
<div class="comment-text">
<pre>\escape(comment.content)</pre>
</div>
\if $login_user_id != none:
<details>
<summary>
reply
</summary>
<div class="add-comment">
<form action="/api/comment" method="POST">
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
<input type="hidden" name="parent_post" value=\'"'\post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\comment.id\'"' />
<textarea name="content" placeholder="Add a comment..."></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
</details>
</div>
<div class="comment-child">
\for child in comment.child:
render_comment(post_id, child)
</div>
</div>
\if len(root_comments) == 0:
<div id="no-comments">
No comments
</div>
else for comment in root_comments:
render_comment(post_id, comment)
</main>
page(post.title, $login_user_id, style, main)
+72 -72
View File
@@ -1,73 +1,73 @@
include "page.wl"
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
let lookup = {}
for comment in comments: {
comment.child = []
lookup[comment.id] = comment
}
let roots = []
for comment in comments: {
if comment.parent_comment == none:
roots << comment
else
lookup[comment.parent_comment].child << comment
}
comments = roots
let post = posts[0]
let style =
<style>
.child {
border-left: 3px solid #ccc;
padding-left: 10px;
}
form textarea {
width: 100%;
}
</style>
procedure render_comment(parent)
<div>
<a href="">\parent.username</a>
<p>
\parent.content
</p>
<div class="child">
\if $login_user_id != none:
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
\for child in parent.child:
render_comment(child)
</div>
</div>
let main =
<main>
<h3>\post.title</h3>
<p>\post.content</p>
<div>
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
\if len comments == 0:
<span>No comments yet!</span>
else for comment in comments:
render_comment(comment)
</main>
include "page.wl"
let posts = $query("SELECT title, content FROM Posts WHERE id=?", $post_id)
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id == C.author", $post_id)
let lookup = {}
for comment in comments: {
comment.child = []
lookup[comment.id] = comment
}
let roots = []
for comment in comments: {
if comment.parent_comment == none:
roots << comment
else
lookup[comment.parent_comment].child << comment
}
comments = roots
let post = posts[0]
let style =
<style>
.child {
border-left: 3px solid #ccc;
padding-left: 10px;
}
form textarea {
width: 100%;
}
</style>
procedure render_comment(parent)
<div>
<a href="">\parent.username</a>
<p>
\parent.content
</p>
<div class="child">
\if $login_user_id != none:
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<input type="hidden" name="parent_comment" value=\'"'\parent.id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
\for child in parent.child:
render_comment(child)
</div>
</div>
let main =
<main>
<h3>\post.title</h3>
<p>\post.content</p>
<div>
<form action="/api/comment" method="POST">
<input type="hidden" name="parent_post" value=\'"'\$post_id\'"' />
<textarea name="content"></textarea>
<input type="submit" vaue="Publish" />
</form>
</div>
\if len comments == 0:
<span>No comments yet!</span>
else for comment in comments:
render_comment(comment)
</main>
page(post.title, $login_user_id, style, main)
+24 -24
View File
@@ -1,24 +1,24 @@
include "page.wl"
include "login_and_signup_style.wl"
let main =
<main>
<span>Welcome!</span>
<div id="response"></div>
<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 "page.wl"
include "login_and_signup_style.wl"
let main =
<main>
<span>Welcome!</span>
<div id="response"></div>
<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)
+94 -94
View File
@@ -1,95 +1,95 @@
include "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>
include "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)
+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)
);
+1630 -1630
View File
File diff suppressed because it is too large Load Diff
+315 -315
View File
@@ -1,315 +1,315 @@
#include <stdint.h>
#include <stdbool.h>
#define CWEB_ENABLE_DATABASE
#define CWEB_ENABLE_TEMPLATE
#define CWEB_STR(X) ((CWEB_String) { (X), (int) sizeof(X)-1 })
typedef struct {
char *ptr;
int len;
} CWEB_String;
typedef struct {
char data[61];
} CWEB_PasswordHash;
CWEB_String cweb_trim(CWEB_String s);
bool cweb_streq(CWEB_String a, CWEB_String b);
typedef enum {
CWEB_VARG_TYPE_C,
CWEB_VARG_TYPE_S,
CWEB_VARG_TYPE_I,
CWEB_VARG_TYPE_L,
CWEB_VARG_TYPE_LL,
CWEB_VARG_TYPE_SC,
CWEB_VARG_TYPE_SS,
CWEB_VARG_TYPE_SI,
CWEB_VARG_TYPE_SL,
CWEB_VARG_TYPE_SLL,
CWEB_VARG_TYPE_UC,
CWEB_VARG_TYPE_US,
CWEB_VARG_TYPE_UI,
CWEB_VARG_TYPE_UL,
CWEB_VARG_TYPE_ULL,
CWEB_VARG_TYPE_F,
CWEB_VARG_TYPE_D,
CWEB_VARG_TYPE_B,
CWEB_VARG_TYPE_STR,
CWEB_VARG_TYPE_HASH,
CWEB_VARG_TYPE_PC,
CWEB_VARG_TYPE_PS,
CWEB_VARG_TYPE_PI,
CWEB_VARG_TYPE_PL,
CWEB_VARG_TYPE_PLL,
CWEB_VARG_TYPE_PSC,
CWEB_VARG_TYPE_PSS,
CWEB_VARG_TYPE_PSI,
CWEB_VARG_TYPE_PSL,
CWEB_VARG_TYPE_PSLL,
CWEB_VARG_TYPE_PUC,
CWEB_VARG_TYPE_PUS,
CWEB_VARG_TYPE_PUI,
CWEB_VARG_TYPE_PUL,
CWEB_VARG_TYPE_PULL,
CWEB_VARG_TYPE_PF,
CWEB_VARG_TYPE_PD,
CWEB_VARG_TYPE_PB,
CWEB_VARG_TYPE_PSTR,
CWEB_VARG_TYPE_PHASH,
} CWEB_VArgType;
typedef struct {
CWEB_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;
CWEB_String str;
CWEB_PasswordHash hash;
char *pc;
short *ps;
int *pi;
long *pl;
long long *pll;
signed char *psc;
signed short *pss;
signed int *psi;
signed long *psl;
signed long long *psll;
unsigned char *puc;
unsigned short *pus;
unsigned int *pui;
unsigned long *pul;
unsigned long long *pull;
float *pf;
double *pd;
bool *pb;
CWEB_String *pstr;
CWEB_PasswordHash *phash;
};
} CWEB_VArg;
typedef struct {
int len;
CWEB_VArg *ptr;
} CWEB_VArgs;
CWEB_VArg cweb_varg_from_c (char c);
CWEB_VArg cweb_varg_from_s (short s);
CWEB_VArg cweb_varg_from_i (int i);
CWEB_VArg cweb_varg_from_l (long l);
CWEB_VArg cweb_varg_from_ll (long long ll);
CWEB_VArg cweb_varg_from_sc (char sc);
CWEB_VArg cweb_varg_from_ss (short ss);
CWEB_VArg cweb_varg_from_si (int si);
CWEB_VArg cweb_varg_from_sl (long sl);
CWEB_VArg cweb_varg_from_sll (long long sll);
CWEB_VArg cweb_varg_from_uc (char uc);
CWEB_VArg cweb_varg_from_us (short us);
CWEB_VArg cweb_varg_from_ui (int ui);
CWEB_VArg cweb_varg_from_ul (long ul);
CWEB_VArg cweb_varg_from_ull (long long ull);
CWEB_VArg cweb_varg_from_f (float f);
CWEB_VArg cweb_varg_from_d (double d);
CWEB_VArg cweb_varg_from_b (bool b);
CWEB_VArg cweb_varg_from_str (CWEB_String str);
CWEB_VArg cweb_varg_from_hash (CWEB_PasswordHash hash);
CWEB_VArg cweb_varg_from_pc (char *pc);
CWEB_VArg cweb_varg_from_ps (short *ps);
CWEB_VArg cweb_varg_from_pi (int *pi);
CWEB_VArg cweb_varg_from_pl (long *pl);
CWEB_VArg cweb_varg_from_pll (long long *pll);
CWEB_VArg cweb_varg_from_psc (signed char *psc);
CWEB_VArg cweb_varg_from_pss (signed short *pss);
CWEB_VArg cweb_varg_from_psi (signed int *psi);
CWEB_VArg cweb_varg_from_psl (signed long *psl);
CWEB_VArg cweb_varg_from_psll (signed long long *psll);
CWEB_VArg cweb_varg_from_puc (unsigned char *puc);
CWEB_VArg cweb_varg_from_pus (unsigned short *pus);
CWEB_VArg cweb_varg_from_pui (unsigned int *pui);
CWEB_VArg cweb_varg_from_pul (unsigned long *pul);
CWEB_VArg cweb_varg_from_pull (unsigned long long *pull);
CWEB_VArg cweb_varg_from_pf (float *pf);
CWEB_VArg cweb_varg_from_pd (double *pd);
CWEB_VArg cweb_varg_from_pb (bool *pb);
CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr);
CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash);
#define __CWEB_HELPER_ARG(X) (_Generic((X), \
char : cweb_varg_from_c, \
short : cweb_varg_from_s, \
int : cweb_varg_from_i, \
long : cweb_varg_from_l, \
long long : cweb_varg_from_ll, \
signed char : cweb_varg_from_sc, \
/*signed short : cweb_varg_from_ss,*/ \
/*signed int : cweb_varg_from_si,*/ \
/*signed long : cweb_varg_from_sl,*/ \
/*signed long long : cweb_varg_from_sll,*/ \
unsigned char : cweb_varg_from_uc, \
unsigned short : cweb_varg_from_us, \
unsigned int : cweb_varg_from_ui, \
unsigned long : cweb_varg_from_ul, \
unsigned long long: cweb_varg_from_ull, \
float : cweb_varg_from_f, \
double : cweb_varg_from_d, \
bool : cweb_varg_from_b, \
CWEB_String : cweb_varg_from_str, \
CWEB_PasswordHash : cweb_varg_from_hash, \
char* : cweb_varg_from_pc, \
short* : cweb_varg_from_ps, \
int* : cweb_varg_from_pi, \
long* : cweb_varg_from_pl, \
long long* : cweb_varg_from_pll, \
signed char* : cweb_varg_from_psc, \
/*signed short* : cweb_varg_from_pss,*/ \
/*signed int* : cweb_varg_from_psi,*/ \
/*signed long* : cweb_varg_from_psl,*/ \
/*signed long long* : cweb_varg_from_psll,*/ \
unsigned char* : cweb_varg_from_puc, \
unsigned short* : cweb_varg_from_pus, \
unsigned int* : cweb_varg_from_pui, \
unsigned long* : cweb_varg_from_pul, \
unsigned long long*: cweb_varg_from_pull, \
float* : cweb_varg_from_pf, \
double* : cweb_varg_from_pd, \
bool* : cweb_varg_from_pb, \
CWEB_String* : cweb_varg_from_pstr, \
CWEB_PasswordHash* : cweb_varg_from_phash \
))(X)
// Helper macros
#define __CWEB_HELPER_DISPATCH_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
#define __CWEB_HELPER_CONCAT_0(A, B) A ## B
#define __CWEB_HELPER_CONCAT_1(A, B) __CWEB_HELPER_CONCAT_0(A, B)
#define __CWEB_HELPER_ARGS_0() (CWEB_VArgs) { 0, (CWEB_VArg[]) {}}
#define __CWEB_HELPER_ARGS_1(a) (CWEB_VArgs) { 1, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a) }}
#define __CWEB_HELPER_ARGS_2(a, b) (CWEB_VArgs) { 2, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b) }}
#define __CWEB_HELPER_ARGS_3(a, b, c) (CWEB_VArgs) { 3, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c) }}
#define __CWEB_HELPER_ARGS_4(a, b, c, d) (CWEB_VArgs) { 4, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d) }}
#define __CWEB_HELPER_ARGS_5(a, b, c, d, e) (CWEB_VArgs) { 5, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e) }}
#define __CWEB_HELPER_ARGS_6(a, b, c, d, e, f) (CWEB_VArgs) { 6, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f) }}
#define __CWEB_HELPER_ARGS_7(a, b, c, d, e, f, g) (CWEB_VArgs) { 7, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g) }}
#define __CWEB_HELPER_ARGS_8(a, b, c, d, e, f, g, h) (CWEB_VArgs) { 8, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g), __CWEB_HELPER_ARG(h) }}
#define __CWEB_COUNT_ARGS(...) __CWEB_HELPER_DISPATCH_N(DUMMY, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define CWEB_VARGS(...) __CWEB_HELPER_CONCAT_1(__CWEB_HELPER_ARGS_, __CWEB_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)
typedef struct CWEB CWEB;
typedef struct CWEB_Request CWEB_Request;
int cweb_global_init(void);
void cweb_global_free(void);
CWEB *cweb_init(CWEB_String addr, uint16_t port);
void cweb_free(CWEB *cweb);
// Open an SQLite instance in file "database_file" and run the DDL script at "schema_file".
// Note that "database_file" may be ":memory:".
int cweb_enable_database(CWEB *cweb, CWEB_String database_file, CWEB_String schema_file);
// Log all evaluated SQL statements to stdout
void cweb_trace_sql(CWEB *cweb, bool enable);
// TODO: comment
void cweb_enable_template_cache(CWEB *cweb, bool enable);
// Pause execution until a request is available.
// TODO: When does this function return NULL?
CWEB_Request *cweb_wait(CWEB *cweb);
// Returns true iff the request matches the specified endpoint
bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str);
// Returns the CSRF token associated to the current session
CWEB_String cweb_get_csrf(CWEB_Request *req);
// Returns the user ID for the current session, or -1 if there is no session
int cweb_get_user_id(CWEB_Request *req);
// Sets the user ID for the current session (it must be a positive integer).
// If the ID is -1, the session is deleted.
int cweb_set_user_id(CWEB_Request *req, int user_id);
// TODO: comment
CWEB_String cweb_get_path(CWEB_Request *req);
// Returns the request parameter with the specified name
// If the request uses POST, the parameter is taken from the body,
// else it's taken from the URL. If the parameter is not present,
// an empty string is returned.
CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name);
// Like cweb_get_param_s, but also parser the argument as an integer.
// If parsing fails or the parameter is missing, -1 is returned.
int cweb_get_param_i(CWEB_Request *req, CWEB_String name);
// Create a string by evaluating a format. Memory is allocated from the arena of the request.
// If the arena is full, an empty string is returned.
CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args);
// Helper
#define cweb_format(req, fmt, ...) cweb_format_impl((req), (fmt), CWEB_VARGS(__VA_ARGS__))
// Responds to the specified request with the given status code and content
void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content);
// Responds to the request by redirecting the client to the given target
void cweb_respond_redirect(CWEB_Request *req, CWEB_String target);
// Responds to the request by evaluating a WL template file
void cweb_respond_template_impl(CWEB_Request *req, int status, CWEB_String template_file, CWEB_VArgs args);
// Helper
#define cweb_respond_template(req, status, template_file, ...) \
cweb_respond_template_impl((req), (status), (template_file), CWEB_VARGS(__VA_ARGS__))
// Evaluates an SQL INSERT statement and returns the ID of the last inserted row. On error -1 is returned
int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args);
// Helper
#define cweb_database_insert(cweb, fmt, ...) \
cweb_database_insert_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))
// Iterator over database rows
typedef struct { void *handle; } CWEB_QueryResult;
// Evaluates an SQL SELECT statement, returning a scanner over the returned rows.
// You don't have to check for errors with this function
CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args);
// Helper
#define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))
// Returns the next row from the query result iterator.
int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args);
// Helper
#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__))
// Frees the result of a database query
void cweb_free_query_result(CWEB_QueryResult *res);
// Calculates the bcrypt hash of the specified password
int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash);
// Checks whether the password matches the given hash
int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash);
#include <stdint.h>
#include <stdbool.h>
#define CWEB_ENABLE_DATABASE
#define CWEB_ENABLE_TEMPLATE
#define CWEB_STR(X) ((CWEB_String) { (X), (int) sizeof(X)-1 })
typedef struct {
char *ptr;
int len;
} CWEB_String;
typedef struct {
char data[61];
} CWEB_PasswordHash;
CWEB_String cweb_trim(CWEB_String s);
bool cweb_streq(CWEB_String a, CWEB_String b);
typedef enum {
CWEB_VARG_TYPE_C,
CWEB_VARG_TYPE_S,
CWEB_VARG_TYPE_I,
CWEB_VARG_TYPE_L,
CWEB_VARG_TYPE_LL,
CWEB_VARG_TYPE_SC,
CWEB_VARG_TYPE_SS,
CWEB_VARG_TYPE_SI,
CWEB_VARG_TYPE_SL,
CWEB_VARG_TYPE_SLL,
CWEB_VARG_TYPE_UC,
CWEB_VARG_TYPE_US,
CWEB_VARG_TYPE_UI,
CWEB_VARG_TYPE_UL,
CWEB_VARG_TYPE_ULL,
CWEB_VARG_TYPE_F,
CWEB_VARG_TYPE_D,
CWEB_VARG_TYPE_B,
CWEB_VARG_TYPE_STR,
CWEB_VARG_TYPE_HASH,
CWEB_VARG_TYPE_PC,
CWEB_VARG_TYPE_PS,
CWEB_VARG_TYPE_PI,
CWEB_VARG_TYPE_PL,
CWEB_VARG_TYPE_PLL,
CWEB_VARG_TYPE_PSC,
CWEB_VARG_TYPE_PSS,
CWEB_VARG_TYPE_PSI,
CWEB_VARG_TYPE_PSL,
CWEB_VARG_TYPE_PSLL,
CWEB_VARG_TYPE_PUC,
CWEB_VARG_TYPE_PUS,
CWEB_VARG_TYPE_PUI,
CWEB_VARG_TYPE_PUL,
CWEB_VARG_TYPE_PULL,
CWEB_VARG_TYPE_PF,
CWEB_VARG_TYPE_PD,
CWEB_VARG_TYPE_PB,
CWEB_VARG_TYPE_PSTR,
CWEB_VARG_TYPE_PHASH,
} CWEB_VArgType;
typedef struct {
CWEB_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;
CWEB_String str;
CWEB_PasswordHash hash;
char *pc;
short *ps;
int *pi;
long *pl;
long long *pll;
signed char *psc;
signed short *pss;
signed int *psi;
signed long *psl;
signed long long *psll;
unsigned char *puc;
unsigned short *pus;
unsigned int *pui;
unsigned long *pul;
unsigned long long *pull;
float *pf;
double *pd;
bool *pb;
CWEB_String *pstr;
CWEB_PasswordHash *phash;
};
} CWEB_VArg;
typedef struct {
int len;
CWEB_VArg *ptr;
} CWEB_VArgs;
CWEB_VArg cweb_varg_from_c (char c);
CWEB_VArg cweb_varg_from_s (short s);
CWEB_VArg cweb_varg_from_i (int i);
CWEB_VArg cweb_varg_from_l (long l);
CWEB_VArg cweb_varg_from_ll (long long ll);
CWEB_VArg cweb_varg_from_sc (char sc);
CWEB_VArg cweb_varg_from_ss (short ss);
CWEB_VArg cweb_varg_from_si (int si);
CWEB_VArg cweb_varg_from_sl (long sl);
CWEB_VArg cweb_varg_from_sll (long long sll);
CWEB_VArg cweb_varg_from_uc (char uc);
CWEB_VArg cweb_varg_from_us (short us);
CWEB_VArg cweb_varg_from_ui (int ui);
CWEB_VArg cweb_varg_from_ul (long ul);
CWEB_VArg cweb_varg_from_ull (long long ull);
CWEB_VArg cweb_varg_from_f (float f);
CWEB_VArg cweb_varg_from_d (double d);
CWEB_VArg cweb_varg_from_b (bool b);
CWEB_VArg cweb_varg_from_str (CWEB_String str);
CWEB_VArg cweb_varg_from_hash (CWEB_PasswordHash hash);
CWEB_VArg cweb_varg_from_pc (char *pc);
CWEB_VArg cweb_varg_from_ps (short *ps);
CWEB_VArg cweb_varg_from_pi (int *pi);
CWEB_VArg cweb_varg_from_pl (long *pl);
CWEB_VArg cweb_varg_from_pll (long long *pll);
CWEB_VArg cweb_varg_from_psc (signed char *psc);
CWEB_VArg cweb_varg_from_pss (signed short *pss);
CWEB_VArg cweb_varg_from_psi (signed int *psi);
CWEB_VArg cweb_varg_from_psl (signed long *psl);
CWEB_VArg cweb_varg_from_psll (signed long long *psll);
CWEB_VArg cweb_varg_from_puc (unsigned char *puc);
CWEB_VArg cweb_varg_from_pus (unsigned short *pus);
CWEB_VArg cweb_varg_from_pui (unsigned int *pui);
CWEB_VArg cweb_varg_from_pul (unsigned long *pul);
CWEB_VArg cweb_varg_from_pull (unsigned long long *pull);
CWEB_VArg cweb_varg_from_pf (float *pf);
CWEB_VArg cweb_varg_from_pd (double *pd);
CWEB_VArg cweb_varg_from_pb (bool *pb);
CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr);
CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash);
#define __CWEB_HELPER_ARG(X) (_Generic((X), \
char : cweb_varg_from_c, \
short : cweb_varg_from_s, \
int : cweb_varg_from_i, \
long : cweb_varg_from_l, \
long long : cweb_varg_from_ll, \
signed char : cweb_varg_from_sc, \
/*signed short : cweb_varg_from_ss,*/ \
/*signed int : cweb_varg_from_si,*/ \
/*signed long : cweb_varg_from_sl,*/ \
/*signed long long : cweb_varg_from_sll,*/ \
unsigned char : cweb_varg_from_uc, \
unsigned short : cweb_varg_from_us, \
unsigned int : cweb_varg_from_ui, \
unsigned long : cweb_varg_from_ul, \
unsigned long long: cweb_varg_from_ull, \
float : cweb_varg_from_f, \
double : cweb_varg_from_d, \
bool : cweb_varg_from_b, \
CWEB_String : cweb_varg_from_str, \
CWEB_PasswordHash : cweb_varg_from_hash, \
char* : cweb_varg_from_pc, \
short* : cweb_varg_from_ps, \
int* : cweb_varg_from_pi, \
long* : cweb_varg_from_pl, \
long long* : cweb_varg_from_pll, \
signed char* : cweb_varg_from_psc, \
/*signed short* : cweb_varg_from_pss,*/ \
/*signed int* : cweb_varg_from_psi,*/ \
/*signed long* : cweb_varg_from_psl,*/ \
/*signed long long* : cweb_varg_from_psll,*/ \
unsigned char* : cweb_varg_from_puc, \
unsigned short* : cweb_varg_from_pus, \
unsigned int* : cweb_varg_from_pui, \
unsigned long* : cweb_varg_from_pul, \
unsigned long long*: cweb_varg_from_pull, \
float* : cweb_varg_from_pf, \
double* : cweb_varg_from_pd, \
bool* : cweb_varg_from_pb, \
CWEB_String* : cweb_varg_from_pstr, \
CWEB_PasswordHash* : cweb_varg_from_phash \
))(X)
// Helper macros
#define __CWEB_HELPER_DISPATCH_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
#define __CWEB_HELPER_CONCAT_0(A, B) A ## B
#define __CWEB_HELPER_CONCAT_1(A, B) __CWEB_HELPER_CONCAT_0(A, B)
#define __CWEB_HELPER_ARGS_0() (CWEB_VArgs) { 0, (CWEB_VArg[]) {}}
#define __CWEB_HELPER_ARGS_1(a) (CWEB_VArgs) { 1, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a) }}
#define __CWEB_HELPER_ARGS_2(a, b) (CWEB_VArgs) { 2, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b) }}
#define __CWEB_HELPER_ARGS_3(a, b, c) (CWEB_VArgs) { 3, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c) }}
#define __CWEB_HELPER_ARGS_4(a, b, c, d) (CWEB_VArgs) { 4, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d) }}
#define __CWEB_HELPER_ARGS_5(a, b, c, d, e) (CWEB_VArgs) { 5, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e) }}
#define __CWEB_HELPER_ARGS_6(a, b, c, d, e, f) (CWEB_VArgs) { 6, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f) }}
#define __CWEB_HELPER_ARGS_7(a, b, c, d, e, f, g) (CWEB_VArgs) { 7, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g) }}
#define __CWEB_HELPER_ARGS_8(a, b, c, d, e, f, g, h) (CWEB_VArgs) { 8, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g), __CWEB_HELPER_ARG(h) }}
#define __CWEB_COUNT_ARGS(...) __CWEB_HELPER_DISPATCH_N(DUMMY, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define CWEB_VARGS(...) __CWEB_HELPER_CONCAT_1(__CWEB_HELPER_ARGS_, __CWEB_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)
typedef struct CWEB CWEB;
typedef struct CWEB_Request CWEB_Request;
int cweb_global_init(void);
void cweb_global_free(void);
CWEB *cweb_init(CWEB_String addr, uint16_t port);
void cweb_free(CWEB *cweb);
// Open an SQLite instance in file "database_file" and run the DDL script at "schema_file".
// Note that "database_file" may be ":memory:".
int cweb_enable_database(CWEB *cweb, CWEB_String database_file, CWEB_String schema_file);
// Log all evaluated SQL statements to stdout
void cweb_trace_sql(CWEB *cweb, bool enable);
// TODO: comment
void cweb_enable_template_cache(CWEB *cweb, bool enable);
// Pause execution until a request is available.
// TODO: When does this function return NULL?
CWEB_Request *cweb_wait(CWEB *cweb);
// Returns true iff the request matches the specified endpoint
bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str);
// Returns the CSRF token associated to the current session
CWEB_String cweb_get_csrf(CWEB_Request *req);
// Returns the user ID for the current session, or -1 if there is no session
int cweb_get_user_id(CWEB_Request *req);
// Sets the user ID for the current session (it must be a positive integer).
// If the ID is -1, the session is deleted.
int cweb_set_user_id(CWEB_Request *req, int user_id);
// TODO: comment
CWEB_String cweb_get_path(CWEB_Request *req);
// Returns the request parameter with the specified name
// If the request uses POST, the parameter is taken from the body,
// else it's taken from the URL. If the parameter is not present,
// an empty string is returned.
CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name);
// Like cweb_get_param_s, but also parser the argument as an integer.
// If parsing fails or the parameter is missing, -1 is returned.
int cweb_get_param_i(CWEB_Request *req, CWEB_String name);
// Create a string by evaluating a format. Memory is allocated from the arena of the request.
// If the arena is full, an empty string is returned.
CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args);
// Helper
#define cweb_format(req, fmt, ...) cweb_format_impl((req), (fmt), CWEB_VARGS(__VA_ARGS__))
// Responds to the specified request with the given status code and content
void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content);
// Responds to the request by redirecting the client to the given target
void cweb_respond_redirect(CWEB_Request *req, CWEB_String target);
// Responds to the request by evaluating a WL template file
void cweb_respond_template_impl(CWEB_Request *req, int status, CWEB_String template_file, CWEB_VArgs args);
// Helper
#define cweb_respond_template(req, status, template_file, ...) \
cweb_respond_template_impl((req), (status), (template_file), CWEB_VARGS(__VA_ARGS__))
// Evaluates an SQL INSERT statement and returns the ID of the last inserted row. On error -1 is returned
int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args);
// Helper
#define cweb_database_insert(cweb, fmt, ...) \
cweb_database_insert_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))
// Iterator over database rows
typedef struct { void *handle; } CWEB_QueryResult;
// Evaluates an SQL SELECT statement, returning a scanner over the returned rows.
// You don't have to check for errors with this function
CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args);
// Helper
#define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))
// Returns the next row from the query result iterator.
int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args);
// Helper
#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__))
// Frees the result of a database query
void cweb_free_query_result(CWEB_QueryResult *res);
// Calculates the bcrypt hash of the specified password
int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash);
// Checks whether the password matches the given hash
int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash);