Add HTML escaping to post.wl

This commit is contained in:
2025-09-17 23:39:18 +02:00
parent 6dc9a10628
commit 9ddb744fdd
2 changed files with 257 additions and 89 deletions
+251 -83
View File
@@ -34,6 +34,7 @@ typedef struct {
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
#define SIZEOF(X) (int) sizeof(X)
#define ALIGNOF(X) (int) _Alignof(X)
#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0]))
#ifndef NDEBUG
#define UNREACHABLE __builtin_trap()
@@ -68,14 +69,12 @@ static bool is_hex_digit(char c)
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
#if 0
static char to_lower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
return c;
}
#endif
static int hex_digit_to_int(char c)
{
@@ -98,7 +97,6 @@ static bool streq(String a, String b)
return true;
}
#if 0
static bool streqcase(String a, String b)
{
if (a.len != b.len)
@@ -108,7 +106,7 @@ static bool streqcase(String a, String b)
return false;
return true;
}
#endif
#define REPORT(err, fmt, ...) report((err), __FILE__, __LINE__, fmt, ## __VA_ARGS__)
static void report(Error *err, char *file, int line, char *fmt, ...)
@@ -161,7 +159,6 @@ static bool grow_alloc(WL_Arena *a, char *p, int new_len)
return true;
}
#if 0
static String copystr(String s, WL_Arena *a)
{
char *p = alloc(a, s.len, 1);
@@ -170,7 +167,6 @@ static String copystr(String s, WL_Arena *a)
memcpy(p, s.ptr, s.len);
return (String) { p, s.len };
}
#endif
/////////////////////////////////////////////////////////////////////////
// WRITER
@@ -193,14 +189,14 @@ static void write_raw_mem(Writer *w, void *ptr, int len)
}
static void write_raw_u8 (Writer *w, uint8_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
//static void write_raw_u16(Writer *w, uint16_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_u16(Writer *w, uint16_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_u32(Writer *w, uint32_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
//static void write_raw_u64(Writer *w, uint64_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
//static void write_raw_s8 (Writer *w, int8_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
//static void write_raw_s16(Writer *w, int16_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
//static void write_raw_s32(Writer *w, int32_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_u64(Writer *w, uint64_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s8 (Writer *w, int8_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s16(Writer *w, int16_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s32(Writer *w, int32_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_s64(Writer *w, int64_t x) { write_raw_mem(w, &x, SIZEOF(x)); }
//static void write_raw_f32(Writer *w, float x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_f32(Writer *w, float x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_raw_f64(Writer *w, double x) { write_raw_mem(w, &x, SIZEOF(x)); }
static void write_text(Writer *w, String str)
@@ -265,6 +261,7 @@ typedef enum {
TOKEN_KWORD_FALSE,
TOKEN_KWORD_INCLUDE,
TOKEN_KWORD_LEN,
TOKEN_KWORD_ESCAPE,
TOKEN_VALUE_FLOAT,
TOKEN_VALUE_INT,
TOKEN_VALUE_STR,
@@ -312,6 +309,7 @@ typedef enum {
NODE_INCLUDE,
NODE_SELECT,
NODE_NESTED,
NODE_OPER_ESCAPE,
NODE_OPER_LEN,
NODE_OPER_POS,
NODE_OPER_NEG,
@@ -415,7 +413,6 @@ static bool consume_str(Scanner *s, String x)
return true;
}
#if 0
static void write_token(Writer *w, Token token)
{
switch (token.type) {
@@ -436,6 +433,7 @@ static void write_token(Writer *w, Token token)
case TOKEN_KWORD_FALSE : write_text(w, S("false")); break;
case TOKEN_KWORD_INCLUDE: write_text(w, S("include")); break;
case TOKEN_KWORD_LEN : write_text(w, S("len")); break;
case TOKEN_KWORD_ESCAPE: write_text(w, S("escape")); break;
case TOKEN_VALUE_FLOAT : write_text_f64(w, token.fval); break;
case TOKEN_VALUE_INT : write_text_s64(w, token.ival); break;
case TOKEN_OPER_ASS : write_text(w, S("=")); break;
@@ -469,7 +467,6 @@ static void write_token(Writer *w, Token token)
}
}
#endif
static void parser_report(Parser *p, char *fmt, ...)
{
@@ -552,6 +549,7 @@ static Token next_token(Parser *p)
if (streq(kword, S("false"))) return (Token) { .type=TOKEN_KWORD_FALSE };
if (streq(kword, S("include"))) return (Token) { .type=TOKEN_KWORD_INCLUDE };
if (streq(kword, S("len"))) return (Token) { .type=TOKEN_KWORD_LEN };
if (streq(kword, S("escape"))) return (Token) { .type=TOKEN_KWORD_ESCAPE };
return (Token) { .type=TOKEN_IDENT, .sval=kword };
}
@@ -778,8 +776,7 @@ static Node *parse_html(Parser *p)
}
if (s->cur == s->len) {
parser_report(p, "Invaid end of source inside HTML tag");
return NULL;
ASSERT(0); // TODO
}
s->cur++;
@@ -790,8 +787,7 @@ static Node *parse_html(Parser *p)
while (s->cur < s->len && is_space(s->src[s->cur]))
s->cur++;
if (s->cur == s->len || s->src[s->cur] != '>') {
parser_report(p, "Invalid character inside HTML closing tag");
return NULL;
ASSERT(0); // TODO
}
s->cur++;
no_body = true;
@@ -835,8 +831,7 @@ static Node *parse_html(Parser *p)
}
if (s->cur == s->len) {
parser_report(p, "Missing closing HTML tag </%.*s>", tagname.len, tagname.ptr);
return NULL;
ASSERT(0); // TODO
}
s->cur++;
@@ -848,20 +843,17 @@ static Node *parse_html(Parser *p)
t = next_token(p);
if (t.type != TOKEN_IDENT) {
parser_report(p, "Missing tag name after '</'");
return NULL;
ASSERT(0); // TODO
}
String closing_tagname = t.sval;
if (!streq(closing_tagname, tagname)) {
parser_report(p, "HTML tag mismatch (%.*s != %.*s)", tagname.len, tagname.ptr, closing_tagname.len, closing_tagname.ptr);
return NULL;
ASSERT(0); // TODO
}
t = next_token(p);
if (t.type != TOKEN_OPER_GRT) {
parser_report(p, "Missing '>' in closing HTML tag");
return NULL;
ASSERT(0);
}
break;
@@ -1134,6 +1126,23 @@ static Node *parse_atom(Parser *p)
}
break;
case TOKEN_KWORD_ESCAPE:
{
Node *child = parse_atom(p);
if (child == NULL)
return NULL;
Node *parent = alloc_node(p);
if (parent == NULL)
return NULL;
parent->type = NODE_OPER_ESCAPE;
parent->left = child;
ret = parent;
}
break;
case TOKEN_IDENT:
{
Node *node = alloc_node(p);
@@ -1910,6 +1919,12 @@ static void write_node(Writer *w, Node *node)
write_text(w, S(")"));
break;
case NODE_OPER_ESCAPE:
write_text(w, S("(escape "));
write_node(w, node->left);
write_text(w, S(")"));
break;
case NODE_OPER_POS:
write_text(w, S("(+"));
write_node(w, node->left);
@@ -2003,14 +2018,6 @@ static void write_node(Writer *w, Node *node)
write_text(w, S(")"));
break;
case NODE_OPER_SHOVEL:
write_text(w, S("("));
write_node(w, node->left);
write_text(w, S("<<"));
write_node(w, node->right);
write_text(w, S(")"));
break;
case NODE_VALUE_INT:
write_text_s64(w, node->ival);
break;
@@ -2206,6 +2213,7 @@ enum {
OPCODE_CALL,
OPCODE_RET,
OPCODE_GROUP,
OPCODE_ESCAPE,
OPCODE_PACK,
OPCODE_GPOP,
OPCODE_FOR,
@@ -2516,8 +2524,6 @@ static void cg_pop_scope(Codegen *cg)
ASSERT(cg->num_scopes > 0);
Scope *scope = &cg->scopes[cg->num_scopes-1];
ASSERT(scope->type == SCOPE_PROC || scope->type == SCOPE_GLOBAL || scope->max_vars == 0);
Scope *parent_scope = NULL;
if (cg->num_scopes > 1)
parent_scope = &cg->scopes[cg->num_scopes-2];
@@ -2552,6 +2558,9 @@ static void cg_pop_scope(Codegen *cg)
call->next = cg->free_list_calls;
cg->free_list_calls = call;
// TODO: remove
ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS));
}
cg->num_syms = scope->idx_syms;
@@ -2620,10 +2629,13 @@ static void cg_write_pushs(Codegen *cg, String str, bool dont_group)
}
}
static void walk_node(Codegen *cg, Node *node);
static void walk_node(Codegen *cg, Node *node, bool inside_html);
static void walk_expr_node(Codegen *cg, Node *node, bool one)
{
// TODO: remove
ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS));
switch (node->type) {
case NODE_NESTED:
@@ -2635,6 +2647,12 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one)
cg_write_opcode(cg, OPCODE_LEN);
break;
case NODE_OPER_ESCAPE:
cg_write_opcode(cg, OPCODE_GROUP);
walk_expr_node(cg, node->left, false);
cg_write_opcode(cg, OPCODE_ESCAPE);
break;
case NODE_OPER_POS:
walk_expr_node(cg, node->left, one);
break;
@@ -2699,6 +2717,9 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one)
case NODE_OPER_SHOVEL:
{
Node *dst = node->left;
Node *src = node->right;
walk_expr_node(cg, node->left, true);
cg_push_scope(cg, SCOPE_ASSIGNMENT);
@@ -2824,7 +2845,7 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one)
Node *child = node->html_attr;
while (child) {
walk_node(cg, child);
walk_node(cg, child, true);
child = child->next;
}
@@ -2834,7 +2855,7 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one)
cg_write_pushs(cg, S(">"), false);
Node *child = node->html_child;
while (child) {
walk_node(cg, child);
walk_node(cg, child, true);
child = child->next;
}
cg_write_pushs(cg, S("</"), false);
@@ -2925,14 +2946,17 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one)
}
}
static void walk_node(Codegen *cg, Node *node)
static void walk_node(Codegen *cg, Node *node, bool inside_html)
{
// TODO: remove
ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS));
switch (node->type) {
case NODE_GLOBAL:
for (Node *child = node->left;
child; child = child->next) {
walk_node(cg, child);
walk_node(cg, child, false);
}
break;
@@ -2940,7 +2964,7 @@ static void walk_node(Codegen *cg, Node *node)
cg_push_scope(cg, SCOPE_COMPOUND);
for (Node *child = node->left;
child; child = child->next)
walk_node(cg, child);
walk_node(cg, child, inside_html);
cg_pop_scope(cg);
break;
@@ -2972,10 +2996,10 @@ static void walk_node(Codegen *cg, Node *node)
int off1 = cg_write_opcode(cg, OPCODE_VARS);
int off2 = cg_write_u8(cg, 0);
walk_node(cg, node->proc_body);
walk_node(cg, node->proc_body, false);
cg_write_opcode(cg, OPCODE_RET);
cg_patch_u8 (cg, off2, cg->scopes[cg->num_scopes-1].max_vars);
cg_patch_u8 (cg, off2, count_function_vars(cg));
cg_patch_u32(cg, off0, cg_current_offset(cg));
cg_pop_scope(cg);
@@ -3028,7 +3052,7 @@ static void walk_node(Codegen *cg, Node *node)
int p1 = cg_write_u32(cg, 0);
cg_push_scope(cg, SCOPE_IF);
walk_node(cg, node->if_branch1);
walk_node(cg, node->if_branch1, inside_html);
cg_pop_scope(cg);
cg_write_opcode(cg, OPCODE_JUMP);
@@ -3038,7 +3062,7 @@ static void walk_node(Codegen *cg, Node *node)
cg_patch_u32(cg, p1, cg_current_offset(cg));
cg_push_scope(cg, SCOPE_ELSE);
walk_node(cg, node->if_branch2);
walk_node(cg, node->if_branch2, inside_html);
cg_pop_scope(cg);
cg_flush_pushs(cg);
@@ -3052,7 +3076,7 @@ static void walk_node(Codegen *cg, Node *node)
int p1 = cg_write_u32(cg, 0);
cg_push_scope(cg, SCOPE_IF);
walk_node(cg, node->if_branch1);
walk_node(cg, node->if_branch1, inside_html);
cg_pop_scope(cg);
cg_flush_pushs(cg);
@@ -3086,7 +3110,7 @@ static void walk_node(Codegen *cg, Node *node)
cg_write_u8(cg, var_2);
int p = cg_write_u32(cg, 0);
walk_node(cg, node->left);
walk_node(cg, node->left, inside_html);
cg_write_opcode(cg, OPCODE_JUMP);
cg_write_u32(cg, start);
@@ -3115,7 +3139,7 @@ static void walk_node(Codegen *cg, Node *node)
int p = cg_write_u32(cg, 0);
cg_push_scope(cg, SCOPE_WHILE);
walk_node(cg, node->left);
walk_node(cg, node->left, inside_html);
cg_pop_scope(cg);
cg_write_opcode(cg, OPCODE_JUMP);
@@ -3126,12 +3150,12 @@ static void walk_node(Codegen *cg, Node *node)
break;
case NODE_INCLUDE:
walk_node(cg, node->include_root);
walk_node(cg, node->include_root, false);
break;
default:
walk_expr_node(cg, node, false);
if (cg_global_scope(cg) && !inside_assignment(cg))
if (cg_global_scope(cg) && !inside_assignment(cg) && !inside_html)
cg_write_opcode(cg, OPCODE_OUTPUT);
break;
}
@@ -3168,7 +3192,7 @@ static int codegen(Node *node, char *dst, int cap, char *errmsg, int errcap)
cg_push_scope(&cg, SCOPE_GLOBAL);
cg_write_opcode(&cg, OPCODE_VARS);
int off = cg_write_u8(&cg, 0);
walk_node(&cg, node);
walk_node(&cg, node, false);
cg_write_opcode(&cg, OPCODE_EXIT);
cg_patch_u8(&cg, off, cg.scopes[0].max_vars);
cg_pop_scope(&cg);
@@ -3271,6 +3295,10 @@ static int write_instr(Writer *w, char *src, int len, String data)
write_text(w, S("GROUP\n"));
return 1;
case OPCODE_ESCAPE:
write_text(w, S("ESCAPE\n"));
return 1;
case OPCODE_PACK:
write_text(w, S("PACK\n"));
return 1;
@@ -3452,7 +3480,7 @@ static int write_instr(Writer *w, char *src, int len, String data)
static int write_program(WL_Program program, char *dst, int cap)
{
if ((uint32_t) program.len < 3 * sizeof(uint32_t))
if (program.len < 3 * sizeof(uint32_t))
return -1;
uint32_t magic;
@@ -3466,7 +3494,7 @@ static int write_program(WL_Program program, char *dst, int cap)
if (magic != WL_MAGIC)
return -1;
if (code_len + data_len + 3 * sizeof(uint32_t) != (uint32_t) program.len)
if (code_len + data_len + 3 * sizeof(uint32_t) != program.len)
return -1;
String code = { program.ptr + 3 * sizeof(uint32_t) , code_len };
@@ -3583,9 +3611,7 @@ WL_AddResult wl_compiler_add(WL_Compiler *compiler, WL_String content)
if (include->include_root == NULL) {
if (compiler->num_files == FILE_LIMIT) {
snprintf(compiler->err, sizeof(compiler->err), "File limit reached");
compiler->err = true;
return (WL_AddResult) { .type=WL_ADD_ERROR };
ASSERT(0); // TODO
}
// TODO: Make the path relative to the compiled file
@@ -4449,29 +4475,7 @@ static void value_convert_to_str_inner(Writer *w, Value v)
break;
case TYPE_MAP:
{
write_text(w, S("{"));
AggregateValue *agg = (void*) (v & ~(Value) 7);
for (int i = 0; i < agg->count; i += 2) {
value_convert_to_str_inner(w, agg->vals[i+0]);
write_text(w, S(": "));
value_convert_to_str_inner(w, agg->vals[i+1]);
if (i+2 < agg->count || agg->ext)
write_text(w, S(", "));
}
Extension *ext = agg->ext;
while (ext) {
for (int i = 0; i < ext->count; i += 2) {
value_convert_to_str_inner(w, ext->vals[i+0]);
write_text(w, S(": "));
value_convert_to_str_inner(w, ext->vals[i+1]);
if (i+2 < ext->count || ext->next)
write_text(w, S(", "));
}
ext = ext->next;
}
write_text(w, S("}"));
}
write_text(w, S("<map>"));
break;
case TYPE_ERROR:
@@ -4486,6 +4490,140 @@ static int value_convert_to_str(Value v, char *dst, int cap)
return w.len;
}
static Value value_escape_packed(Value v, WL_Arena *arena, Error *err);
static int array_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err)
{
Value v2 = value_empty_array(value_length(v), arena, err);
if (v2 == VALUE_ERROR) return -1;
AggregateValue *src = (void*) (v & ~(Value) 7);
for (int i = 0; i < src->count; i++) {
Value child = src->vals[i];
Value escaped_child = value_escape_packed(child, arena, err);
if (escaped_child == VALUE_ERROR)
return -1;
if (!value_append(v2, escaped_child, arena, err))
return -1;
}
Extension *ext = src->ext;
while (ext) {
for (int i = 0; i < ext->count; i++) {
Value child = src->vals[i];
Value escaped_child = value_escape_packed(child, arena, err);
if (escaped_child == VALUE_ERROR)
return -1;
if (!value_append(v2, escaped_child, arena, err))
return -1;
}
ext = ext->next;
}
if (max == 0)
return -1;
out[0] = v2;
return 1;
}
static int string_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err)
{
String s = value_to_str(v);
int i = 0;
int num = 0;
for (;;) {
int off = i;
while (i < s.len
&& s.ptr[i] != '<'
&& s.ptr[i] != '>'
&& s.ptr[i] != '&'
&& s.ptr[i] != '"'
&& s.ptr[i] != '\'')
i++;
String substr = { s.ptr + off, i - off };
Value escaped_v = value_from_str(substr, arena, err); // TODO: don't copy the string
if (escaped_v == VALUE_ERROR) return -1;
if (num == max) {
REPORT(err, "Escape buffer limit reached");
return -1;
}
out[num++] = escaped_v;
if (i == s.len) break;
switch (s.ptr[i++]) {
case '<' : escaped_v = value_from_str(S("&lt;"), arena, err); break; // TODO: don't come these strings
case '>' : escaped_v = value_from_str(S("&gt;"), arena, err); break;
case '&' : escaped_v = value_from_str(S("&amp;"), arena, err); break;
case '"' : escaped_v = value_from_str(S("&quot;"), arena, err); break;
case '\'': escaped_v = value_from_str(S("&#x27;"), arena, err); break;
}
if (escaped_v == VALUE_ERROR) return -1;
if (num == max) {
REPORT(err, "Escape buffer limit reached");
return -1;
}
out[num++] = escaped_v;
}
return num;
}
static int value_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err)
{
Type t = value_type(v);
if (t == TYPE_ARRAY)
return array_escape(v, out, max, arena, err);
if (t == TYPE_STRING)
return string_escape(v, out, max, arena, err);
if (max < 1)
return -1;
out[0] = v;
return 1;
}
static Value value_escape_packed(Value v, WL_Arena *arena, Error *err)
{
Value tmp[32];
int num = value_escape(v, tmp, COUNT(tmp), arena, err);
if (num < 0) return VALUE_ERROR;
Value escaped_v;
if (num > 1) {
Value packed = value_empty_array(num, arena, err);
if (packed == VALUE_ERROR)
return VALUE_ERROR;
for (int j = 0; j < num; j++)
if (!value_append(packed, tmp[j], arena, err))
return VALUE_ERROR;
escaped_v = packed;
} else {
ASSERT(num == 1);
escaped_v = tmp[0];
}
return escaped_v;
}
#undef TYPE_PAIR
/////////////////////////////////////////////////////////////////////////
@@ -4543,7 +4681,7 @@ struct WL_Runtime {
WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program)
{
if ((uint32_t) program.len < 3 * sizeof(uint32_t))
if (program.len < 3 * sizeof(uint32_t))
return NULL;
uint32_t magic;
@@ -4645,7 +4783,7 @@ static String rt_read_str(WL_Runtime *rt)
ASSERT(rt->state == RUNTIME_LOOP);
uint32_t off = rt_read_u32(rt);
uint32_t len = rt_read_u32(rt);
ASSERT(off + len <= (uint32_t) rt->data.len);
ASSERT(off + len <= rt->data.len);
return (String) { rt->data.ptr + off, len };
}
@@ -4857,6 +4995,37 @@ static void step(WL_Runtime *rt)
rt_push_group(rt);
break;
case OPCODE_ESCAPE:
{
ASSERT(rt->num_groups > 0);
int start = rt->groups[--rt->num_groups];
int end = rt->stack;
Value escaped[256];
int num_escaped = 0;
for (int i = start; i < end; i++) {
Value v = rt->values[i];
int num = value_escape(v, escaped + num_escaped, COUNT(escaped) - num_escaped, rt->arena, &rt->err);
if (num < 0) break;
num_escaped += num;
}
if (num_escaped > COUNT(escaped)) {
REPORT(&rt->err, "Escape buffer limit reached");
rt->state = RUNTIME_ERROR;
break;
}
rt->stack = start;
if (!rt_check_stack(rt, num_escaped)) break;
for (int i = 0; i < num_escaped; i++)
rt->values[rt->stack + i] = escaped[i];
rt->stack += num_escaped;
}
break;
case OPCODE_PACK:
rt_pack_group(rt);
break;
@@ -5156,7 +5325,6 @@ WL_EvalResult wl_runtime_eval(WL_Runtime *rt)
switch (rt->state) {
case RUNTIME_BEGIN:
case RUNTIME_LOOP:
UNREACHABLE;