1
0
Fork 0
forked from fun/fun

Added some basic CGI support using kcgi plus a lot of fixes and content updates. (0.41.9)

This commit is contained in:
Johannes Findeisen 2026-05-09 10:47:33 +02:00
commit 604c415c0c
25 changed files with 407 additions and 13 deletions

View file

@ -143,6 +143,12 @@ typedef enum {
OP_PROC_SYSTEM, // pops command string; pushes exit code number
OP_TIME_NOW_MS, // pushes current wall-clock time in milliseconds since Unix epoch
OP_CLOCK_MONO_MS, // pushes monotonic clock in milliseconds (not wall time)
// kcgi (optional)
OP_KCGI_PARSE, // () -> Map | Nil (parse request via kcgi)
OP_KCGI_REPLY_START, // (code:int, content_type:string) -> 1/0
OP_KCGI_WRITE, // (chunk:string) -> 1/0
OP_KCGI_END, // () -> 1/0 (free request)
OP_DATE_FORMAT, // pops fmt string, ms epoch (int); pushes formatted date string using strftime
OP_ENV_ALL, // pushes map of all environment variables
OP_FUN_VERSION, // pushes version string

89
src/extensions/kcgi.c Normal file
View file

@ -0,0 +1,89 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/**
* @file kcgi.c
* @brief kcgi helpers for Fun VM KCGI-related opcodes (conditional build).
*/
#ifdef FUN_WITH_KCGI
#include <kcgi.h>
#include <stdlib.h>
#include <string.h>
/* Thread-local request handle for the current CGI invocation */
#ifdef _WIN32
static __declspec(thread) struct kreq *g_kcgi_req = NULL;
#else
static __thread struct kreq *g_kcgi_req = NULL;
#endif
static Value kcgi_fields_to_map(const struct kreq *r) {
Value m = make_map_empty();
if (!r) return m;
for (size_t i = 0; i < r->fieldsz; i++) {
const char *k = r->fields[i].key ? r->fields[i].key : "";
const char *v = r->fields[i].val ? r->fields[i].val : "";
map_set(&m, k, make_string(v));
}
return m;
}
static Value kreq_to_fun(const struct kreq *r) {
Value out = make_map_empty();
if (!r) return out;
/* Basic request info */
map_set(&out, "host", make_string(r->host ? r->host : ""));
map_set(&out, "port", make_int((int64_t)r->port));
map_set(&out, "path", make_string(r->path ? r->path : ""));
map_set(&out, "suffix", make_string(r->suffix ? r->suffix : ""));
Value fields = kcgi_fields_to_map(r);
map_set(&out, "fields", fields);
return out;
}
/* Lifecycle helpers used by VM opcodes */
static int kcgi_parse_request(struct kreq **out) {
static const struct kvalid keys[] = { { NULL, NULL } }; /* accept all */
struct kreq *r = (struct kreq *)calloc(1, sizeof(*r));
if (!r) return 0;
enum kcgi_err er = khttp_parse(r, keys, 0, NULL, 0, 0);
if (er != KCGI_OK) { free(r); return 0; }
*out = r;
return 1;
}
static void kcgi_free_request(struct kreq *r) {
if (!r) return;
khttp_free(r);
free(r);
}
static int kcgi_reply_start(int code, const char *ctype) {
if (!g_kcgi_req) return 0;
/* Emit Content-Type header; Status defaults to 200 if not set */
if (ctype && *ctype) {
if (khttp_head(g_kcgi_req, "Content-Type", "%s", ctype) != KCGI_OK)
return 0;
}
if (khttp_body(g_kcgi_req) != KCGI_OK)
return 0;
return 1;
}
static int kcgi_write_str(const char *s) {
if (!g_kcgi_req) return 0;
if (!s) s = "";
size_t n = strlen(s);
return khttp_write(g_kcgi_req, s, n) == KCGI_OK;
}
#endif /* FUN_WITH_KCGI */

View file

@ -1351,6 +1351,71 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
/* KCGI intrinsics (optional feature; opcodes are safe no-ops when disabled) */
if (strcmp(name, "kcgi_parse") == 0) {
(*pos)++; /* '(' */
if (!consume_char(src, len, pos, ')')) {
parser_fail(*pos, "kcgi_parse expects ()");
free(name);
return 0;
}
bytecode_add_instruction(bc, OP_KCGI_PARSE, 0);
free(name);
return 1;
}
if (strcmp(name, "kcgi_reply_start") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) {
parser_fail(*pos, "kcgi_reply_start expects (code:int, content_type:string)");
free(name);
return 0;
}
if (!consume_char(src, len, pos, ',')) {
parser_fail(*pos, "kcgi_reply_start expects 2 args");
free(name);
return 0;
}
if (!emit_expression(bc, src, len, pos)) {
parser_fail(*pos, "kcgi_reply_start expects (code:int, content_type:string)");
free(name);
return 0;
}
if (!consume_char(src, len, pos, ')')) {
parser_fail(*pos, "Expected ')' after kcgi_reply_start args");
free(name);
return 0;
}
bytecode_add_instruction(bc, OP_KCGI_REPLY_START, 0);
free(name);
return 1;
}
if (strcmp(name, "kcgi_write") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) {
parser_fail(*pos, "kcgi_write expects (chunk:string)");
free(name);
return 0;
}
if (!consume_char(src, len, pos, ')')) {
parser_fail(*pos, "Expected ')' after kcgi_write arg");
free(name);
return 0;
}
bytecode_add_instruction(bc, OP_KCGI_WRITE, 0);
free(name);
return 1;
}
if (strcmp(name, "kcgi_end") == 0) {
(*pos)++; /* '(' */
if (!consume_char(src, len, pos, ')')) {
parser_fail(*pos, "kcgi_end expects ()");
free(name);
return 0;
}
bytecode_add_instruction(bc, OP_KCGI_END, 0);
free(name);
return 1;
}
if (strcmp(name, "rust_hello") == 0) {
(*pos)++; /* '(' */
if (!consume_char(src, len, pos, ')')) {

View file

@ -72,6 +72,7 @@
#include "extensions/pcsc.c"
#include "extensions/sqlite.c"
#include "extensions/xml2.c"
#include "extensions/kcgi.c"
/* forward declarations for include mapping used in error reporting */
extern char *preprocess_includes(const char *src);
@ -1089,6 +1090,14 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/curl/post.c"
#endif
/* KCGI ops */
#ifdef FUN_WITH_KCGI
#include "vm/kcgi/parse.c"
#include "vm/kcgi/reply_start.c"
#include "vm/kcgi/write.c"
#include "vm/kcgi/end.c"
#endif
/* OpenSSL ops (md5/sha256/sha512/ripemd160) */
#ifdef FUN_WITH_OPENSSL
#include "vm/openssl/md5.c"

10
src/vm/kcgi/end.c Normal file
View file

@ -0,0 +1,10 @@
/* KCGI_END */
case OP_KCGI_END: {
#ifdef FUN_WITH_KCGI
if (g_kcgi_req) { kcgi_free_request(g_kcgi_req); g_kcgi_req = NULL; }
push_value(vm, make_int(1));
#else
push_value(vm, make_int(0));
#endif
break;
}

20
src/vm/kcgi/parse.c Normal file
View file

@ -0,0 +1,20 @@
/* KCGI_PARSE */
case OP_KCGI_PARSE: {
#ifdef FUN_WITH_KCGI
if (g_kcgi_req) { /* safety: free previous if any */
kcgi_free_request(g_kcgi_req);
g_kcgi_req = NULL;
}
struct kreq *r = NULL;
if (!kcgi_parse_request(&r)) {
push_value(vm, make_nil());
break;
}
g_kcgi_req = r;
Value v = kreq_to_fun(r);
push_value(vm, v);
#else
push_value(vm, make_nil());
#endif
break;
}

31
src/vm/kcgi/reply_start.c Normal file
View file

@ -0,0 +1,31 @@
/* KCGI_REPLY_START */
case OP_KCGI_REPLY_START: {
#ifdef FUN_WITH_KCGI
Value vct = pop_value(vm);
Value vcode = pop_value(vm);
int code = 200;
if (vcode.type == VAL_INT) {
code = (int)vcode.i;
} else if (vcode.type == VAL_FLOAT) {
code = (int)vcode.d;
} else if (vcode.type == VAL_STRING) {
if (vcode.s) code = atoi(vcode.s);
}
char *ct = value_to_string_alloc(&vct);
free_value(vcode);
free_value(vct);
if (!ct || ct[0] == '\0') {
/* default content type */
free(ct);
ct = strdup("text/html; charset=utf-8");
}
int ok = kcgi_reply_start(code, ct);
free(ct);
push_value(vm, make_int(ok ? 1 : 0));
#else
Value a = pop_value(vm); free_value(a);
Value b = pop_value(vm); free_value(b);
push_value(vm, make_int(0));
#endif
break;
}

15
src/vm/kcgi/write.c Normal file
View file

@ -0,0 +1,15 @@
/* KCGI_WRITE */
case OP_KCGI_WRITE: {
#ifdef FUN_WITH_KCGI
Value vs = pop_value(vm);
char *s = value_to_string_alloc(&vs);
free_value(vs);
int ok = kcgi_write_str(s ? s : "");
if (s) free(s);
push_value(vm, make_int(ok ? 1 : 0));
#else
Value drop = pop_value(vm); free_value(drop);
push_value(vm, make_int(0));
#endif
break;
}