Added some basic CGI support using kcgi plus a lot of fixes and content updates. (0.41.9)
This commit is contained in:
parent
9698074279
commit
604c415c0c
25 changed files with 407 additions and 13 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.41.8 LANGUAGES C)
|
project(fun VERSION 0.41.9 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
@ -430,6 +430,26 @@ if(BUILD_TESTING)
|
||||||
else()
|
else()
|
||||||
message(WARNING "include_line_mapping_test.fun not found; skipping include_line_mapping CTest")
|
message(WARNING "include_line_mapping_test.fun not found; skipping include_line_mapping CTest")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# KCGI example smoke test (only when the KCGI extension is enabled)
|
||||||
|
# We run the CGI example with minimal environment to avoid RFC warnings
|
||||||
|
# and assert the body contains the expected greeting.
|
||||||
|
if(FUN_WITH_KCGI)
|
||||||
|
set(_kcgi_example "${CMAKE_SOURCE_DIR}/examples/cgi/hello_kcgi.fun")
|
||||||
|
if(EXISTS "${_kcgi_example}")
|
||||||
|
add_test(NAME kcgi_hello
|
||||||
|
COMMAND $<TARGET_FILE:fun> "${_kcgi_example}"
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
)
|
||||||
|
# Provide standard library path and minimal CGI environment
|
||||||
|
set_tests_properties(kcgi_hello PROPERTIES
|
||||||
|
ENVIRONMENT "FUN_LIB_DIR=${CMAKE_SOURCE_DIR}/lib;REQUEST_METHOD=GET;QUERY_STRING=name=Fun;SERVER_NAME=localhost;SERVER_PORT=80;SCRIPT_NAME=/hello_kcgi.fun;REMOTE_ADDR=127.0.0.1"
|
||||||
|
PASS_REGULAR_EXPRESSION "Hello, Fun!"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(WARNING "KCGI example not found: ${_kcgi_example}; skipping kcgi_hello CTest")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# --- Doxygen docs target (optional) ---
|
# --- Doxygen docs target (optional) ---
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the stand
|
||||||
|
|
||||||
### Optional extensions (build-time selectable / only testing this on Linux actually):
|
### Optional extensions (build-time selectable / only testing this on Linux actually):
|
||||||
|
|
||||||
- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) ☐
|
- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) — see [docs](./web/documentation/extensions/kcgi/kcgi.md) ☑
|
||||||
- [cURL (libcurl)](./web/documentation/extensions/curl/curl.md) (optional) ☑
|
- [cURL (libcurl)](./web/documentation/extensions/curl/curl.md) (optional) ☑
|
||||||
- [INI (iniparser)](./web/documentation/extensions/ini/ini.md) (optional) ☑
|
- [INI (iniparser)](./web/documentation/extensions/ini/ini.md) (optional) ☑
|
||||||
- [JSON (json-c)](./web/documentation/extensions/json/json.md) (optional) ☑
|
- [JSON (json-c)](./web/documentation/extensions/json/json.md) (optional) ☑
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,18 @@ include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCRE2.cmake)
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/CURL.cmake)
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/CURL.cmake)
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake)
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake)
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/OPENSSL.cmake)
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/OPENSSL.cmake)
|
||||||
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/KCGI.cmake)
|
||||||
|
|
||||||
# Summary of extension toggles
|
# Summary of extension toggles
|
||||||
message(STATUS "---- Fun extension summary ----")
|
message(STATUS "---- Fun extension summary ----")
|
||||||
_fun_print_feature("SQLite (FUN_WITH_SQLITE)" FUN_WITH_SQLITE)
|
_fun_print_feature("SQLite (FUN_WITH_SQLITE)" FUN_WITH_SQLITE)
|
||||||
_fun_print_feature("PCSC (FUN_WITH_PCSC)" FUN_WITH_PCSC)
|
_fun_print_feature("PCSC (FUN_WITH_PCSC)" FUN_WITH_PCSC)
|
||||||
_fun_print_feature("JSON-C (FUN_WITH_JSON)" FUN_WITH_JSON)
|
_fun_print_feature("json-c (FUN_WITH_JSON)" FUN_WITH_JSON)
|
||||||
_fun_print_feature("INI/iniparser (FUN_WITH_INI)" FUN_WITH_INI)
|
_fun_print_feature("INI/iniparser (FUN_WITH_INI)" FUN_WITH_INI)
|
||||||
_fun_print_feature("libxml2 (FUN_WITH_XML2)" FUN_WITH_XML2)
|
_fun_print_feature("libxml2 (FUN_WITH_XML2)" FUN_WITH_XML2)
|
||||||
## libsql extension removed
|
|
||||||
_fun_print_feature("PCRE2 (FUN_WITH_PCRE2)" FUN_WITH_PCRE2)
|
_fun_print_feature("PCRE2 (FUN_WITH_PCRE2)" FUN_WITH_PCRE2)
|
||||||
_fun_print_feature("libcurl (FUN_WITH_CURL)" FUN_WITH_CURL)
|
_fun_print_feature("curl (FUN_WITH_CURL)" FUN_WITH_CURL)
|
||||||
_fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL)
|
_fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL)
|
||||||
_fun_print_feature("OpenSSL (FUN_WITH_OPENSSL)" FUN_WITH_OPENSSL)
|
_fun_print_feature("OpenSSL (FUN_WITH_OPENSSL)" FUN_WITH_OPENSSL)
|
||||||
|
_fun_print_feature("kcgi (FUN_WITH_KCGI)" FUN_WITH_KCGI)
|
||||||
message(STATUS "--------------------------------")
|
message(STATUS "--------------------------------")
|
||||||
|
|
|
||||||
19
cmake/Extensions/KCGI.cmake
Normal file
19
cmake/Extensions/KCGI.cmake
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# KCGI (kcgi)
|
||||||
|
option(FUN_WITH_KCGI "Enable kcgi support (CGI/FastCGI via kcgi)" OFF)
|
||||||
|
set(KCGI_INCLUDE_DIRS "")
|
||||||
|
set(KCGI_LINK_LIBS "")
|
||||||
|
|
||||||
|
if(FUN_WITH_KCGI)
|
||||||
|
add_definitions(-DFUN_WITH_KCGI)
|
||||||
|
find_package(PkgConfig QUIET)
|
||||||
|
if(PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(KCGI QUIET kcgi)
|
||||||
|
endif()
|
||||||
|
if(KCGI_FOUND)
|
||||||
|
list(APPEND KCGI_INCLUDE_DIRS ${KCGI_INCLUDE_DIRS} ${KCGI_INCLUDE_DIRS})
|
||||||
|
list(APPEND KCGI_LINK_LIBS ${KCGI_LINK_LIBS} ${KCGI_LIBRARIES})
|
||||||
|
else()
|
||||||
|
# Fallback libs commonly required for kcgi on Linux
|
||||||
|
list(APPEND KCGI_LINK_LIBS kcgi z)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
@ -42,7 +42,8 @@ foreach(var_pair
|
||||||
INIPARSER
|
INIPARSER
|
||||||
LIBSQL
|
LIBSQL
|
||||||
LIBXML2
|
LIBXML2
|
||||||
OPENSSL)
|
OPENSSL
|
||||||
|
KCGI)
|
||||||
if(${var_pair}_INCLUDE_DIRS)
|
if(${var_pair}_INCLUDE_DIRS)
|
||||||
target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS})
|
target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -93,6 +94,10 @@ if(FUN_WITH_OPENSSL)
|
||||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1)
|
target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(FUN_WITH_KCGI)
|
||||||
|
target_compile_definitions(fun_core PUBLIC FUN_WITH_KCGI=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
# LibreSSL toggle: ensure compile definitions are applied to fun_core so
|
# LibreSSL toggle: ensure compile definitions are applied to fun_core so
|
||||||
# the LibreSSL code paths are compiled, and linking vars are handled above.
|
# the LibreSSL code paths are compiled, and linking vars are handled above.
|
||||||
|
|
||||||
|
|
|
||||||
14
examples/cgi/hello_kcgi.fun
Normal file
14
examples/cgi/hello_kcgi.fun
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimal example using KCGI wrapper.
|
||||||
|
* Run under a CGI/FastCGI environment where kcgi can parse the request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <net/kcgi.fun>
|
||||||
|
|
||||||
|
cgi = KCGI()
|
||||||
|
name = cgi.get_or("name", "Fun")
|
||||||
|
|
||||||
|
body = "<h1>Hello, " + to_string(name) + "!</h1>"
|
||||||
|
cgi.reply(200, "text/html; charset=utf-8", body)
|
||||||
46
lib/net/kcgi.fun
Normal file
46
lib/net/kcgi.fun
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Thin wrapper around VM kcgi intrinsics
|
||||||
|
|
||||||
|
class KCGI()
|
||||||
|
fun _construct(this)
|
||||||
|
this.req = kcgi_parse()
|
||||||
|
|
||||||
|
fun get(this, key)
|
||||||
|
if (typeof(this.req) == "Map")
|
||||||
|
fields = this.req["fields"]
|
||||||
|
if (typeof(fields) == "Map")
|
||||||
|
return fields[key]
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Returns the field value if present and non-nil, otherwise returns 'def'.
|
||||||
|
fun get_or(this, key, def)
|
||||||
|
v = this.get(key)
|
||||||
|
if (v == nil)
|
||||||
|
return def
|
||||||
|
// Some code may stringify nil to "nil"; treat that as missing too.
|
||||||
|
if (typeof(v) == "String" && v == "nil")
|
||||||
|
return def
|
||||||
|
return v
|
||||||
|
|
||||||
|
fun reply(this, code, content_type, body)
|
||||||
|
if (kcgi_reply_start(code, content_type) == 1)
|
||||||
|
_ = kcgi_write(to_string(body))
|
||||||
|
_ = kcgi_end()
|
||||||
|
|
||||||
|
// streaming API
|
||||||
|
fun start(this, code, content_type)
|
||||||
|
return kcgi_reply_start(code, content_type)
|
||||||
|
|
||||||
|
fun write(this, chunk)
|
||||||
|
return kcgi_write(to_string(chunk))
|
||||||
|
|
||||||
|
fun end(this)
|
||||||
|
return kcgi_end()
|
||||||
1
make
1
make
|
|
@ -29,6 +29,7 @@ if [ "$target" = "all" ]; then
|
||||||
-DFUN_WITH_JSON=ON \
|
-DFUN_WITH_JSON=ON \
|
||||||
-DFUN_WITH_INI=ON \
|
-DFUN_WITH_INI=ON \
|
||||||
-DFUN_WITH_CPP=ON \
|
-DFUN_WITH_CPP=ON \
|
||||||
|
-DFUN_WITH_KCGI=ON \
|
||||||
-DFUN_WITH_OPENSSL=ON \
|
-DFUN_WITH_OPENSSL=ON \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "all_debug" ]; then
|
elif [ "$target" = "all_debug" ]; then
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,12 @@ typedef enum {
|
||||||
OP_PROC_SYSTEM, // pops command string; pushes exit code number
|
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_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)
|
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_DATE_FORMAT, // pops fmt string, ms epoch (int); pushes formatted date string using strftime
|
||||||
OP_ENV_ALL, // pushes map of all environment variables
|
OP_ENV_ALL, // pushes map of all environment variables
|
||||||
OP_FUN_VERSION, // pushes version string
|
OP_FUN_VERSION, // pushes version string
|
||||||
|
|
|
||||||
89
src/extensions/kcgi.c
Normal file
89
src/extensions/kcgi.c
Normal 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 */
|
||||||
65
src/parser.c
65
src/parser.c
|
|
@ -1351,6 +1351,71 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
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) {
|
if (strcmp(name, "rust_hello") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
if (!consume_char(src, len, pos, ')')) {
|
if (!consume_char(src, len, pos, ')')) {
|
||||||
|
|
|
||||||
9
src/vm.c
9
src/vm.c
|
|
@ -72,6 +72,7 @@
|
||||||
#include "extensions/pcsc.c"
|
#include "extensions/pcsc.c"
|
||||||
#include "extensions/sqlite.c"
|
#include "extensions/sqlite.c"
|
||||||
#include "extensions/xml2.c"
|
#include "extensions/xml2.c"
|
||||||
|
#include "extensions/kcgi.c"
|
||||||
|
|
||||||
/* forward declarations for include mapping used in error reporting */
|
/* forward declarations for include mapping used in error reporting */
|
||||||
extern char *preprocess_includes(const char *src);
|
extern char *preprocess_includes(const char *src);
|
||||||
|
|
@ -1089,6 +1090,14 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/curl/post.c"
|
#include "vm/curl/post.c"
|
||||||
#endif
|
#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) */
|
/* OpenSSL ops (md5/sha256/sha512/ripemd160) */
|
||||||
#ifdef FUN_WITH_OPENSSL
|
#ifdef FUN_WITH_OPENSSL
|
||||||
#include "vm/openssl/md5.c"
|
#include "vm/openssl/md5.c"
|
||||||
|
|
|
||||||
10
src/vm/kcgi/end.c
Normal file
10
src/vm/kcgi/end.c
Normal 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
20
src/vm/kcgi/parse.c
Normal 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
31
src/vm/kcgi/reply_start.c
Normal 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
15
src/vm/kcgi/write.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,7 @@ This section documents Fun's optional, build-time selectable extensions. Each pa
|
||||||
- [PCRE2 (Perl-compatible regex)](./pcre2/)
|
- [PCRE2 (Perl-compatible regex)](./pcre2/)
|
||||||
- [PC/SC (Smart cards)](./pcsc/)
|
- [PC/SC (Smart cards)](./pcsc/)
|
||||||
- [OpenSSL](./openssl/)
|
- [OpenSSL](./openssl/)
|
||||||
|
- [kcgi](./kcgi/)
|
||||||
|
|
||||||
## Notes:
|
## Notes:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ tags:
|
||||||
- optional
|
- optional
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_INI=ON
|
- CMake option: FUN_WITH_INI=ON
|
||||||
- Purpose: Read/write simple INI configuration files.
|
- Purpose: Read/write simple INI configuration files.
|
||||||
- Homepage: [https://github.com/ndevilla/iniparser](https://github.com/ndevilla/iniparser){:class="ext"}
|
- Homepage: [https://github.com/ndevilla/iniparser](https://github.com/ndevilla/iniparser){:class="ext"}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ tags:
|
||||||
- optional
|
- optional
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_JSON=ON
|
- CMake option: FUN_WITH_JSON=ON
|
||||||
- Purpose: JSON parse/stringify and file helpers via json-c.
|
- Purpose: JSON parse/stringify and file helpers via json-c.
|
||||||
- Homepage: [https://json-c.github.io/json-c/](https://json-c.github.io/json-c/){:class="ext"}
|
- Homepage: [https://json-c.github.io/json-c/](https://json-c.github.io/json-c/){:class="ext"}
|
||||||
|
|
|
||||||
49
web/documentation/extensions/kcgi/kcgi.md
Normal file
49
web/documentation/extensions/kcgi/kcgi.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
layout: page
|
||||||
|
published: true
|
||||||
|
noToc: false
|
||||||
|
noComments: false
|
||||||
|
noDate: false
|
||||||
|
title: Fun - KCGI extension (optional)
|
||||||
|
subtitle: Documentation for KCGI (kcgi) extension (optional)
|
||||||
|
description: Documentation for KCGI (kcgi) extension (optional)
|
||||||
|
permalink: /documentation/extensions/kcgi/
|
||||||
|
lang: en
|
||||||
|
tags:
|
||||||
|
- extension
|
||||||
|
- kcgi
|
||||||
|
- optional
|
||||||
|
---
|
||||||
|
|
||||||
|
- CMake option: FUN_WITH_KCGI=ON
|
||||||
|
- Purpose: integrate the kcgi (CGI/FastCGI) C library; provide request parsing and response helpers for building CGI apps in Fun.
|
||||||
|
- Homepage: [https://kristaps.bsd.lv/kcgi/](https://kristaps.bsd.lv/kcgi/){:class="ext"}
|
||||||
|
|
||||||
|
## Build notes:
|
||||||
|
|
||||||
|
- Requires system kcgi development headers and libraries (pkg-config name: kcgi).
|
||||||
|
- If pkg-config is not available, build falls back to linking against libkcgi and zlib (as configured in cmake/Extensions/KCGI.cmake).
|
||||||
|
|
||||||
|
## Provided helper/opcodes:
|
||||||
|
|
||||||
|
- Function: kcgi_parse(data: none) -> Map | Nil. Parses the current CGI/FastCGI request via kcgi and returns a Map with keys like method, scheme, host, port, path, suffix, query, and fields (GET/POST map). Returns Nil on failure or when the extension is disabled.
|
||||||
|
- Function: kcgi_reply_start(code:int, content_type:string) -> 1/0. Starts the HTTP reply (sets Content-Type and opens the body).
|
||||||
|
- Function: kcgi_write(text:string) -> 1/0. Writes a chunk to the response body.
|
||||||
|
- Function: kcgi_end() -> 1/0. Finalizes the response and frees request resources.
|
||||||
|
- Opcodes: OP_KCGI_PARSE, OP_KCGI_REPLY_START, OP_KCGI_WRITE, OP_KCGI_END (internal mappings for the functions above).
|
||||||
|
|
||||||
|
## Quickstart:
|
||||||
|
|
||||||
|
- Configure: cmake -S . -B build -DFUN_WITH_KCGI=ON
|
||||||
|
- Build: cmake --build build --target fun
|
||||||
|
- Run example:
|
||||||
|
- FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/cgi/hello_kcgi.fun
|
||||||
|
|
||||||
|
## Example output:
|
||||||
|
|
||||||
|
- Content-Type: text/html; charset=utf-8
|
||||||
|
- <h1>Hello, Fun!</h1>
|
||||||
|
|
||||||
|
## Notes:
|
||||||
|
|
||||||
|
- Running outside a real CGI/FastCGI environment may emit RFC warnings (e.g., missing REMOTE_ADDR); these are benign for local testing.
|
||||||
|
- When FUN_WITH_KCGI is OFF, the helpers behave as no-ops (returning Nil/0) to keep scripts portable.
|
||||||
|
|
@ -15,7 +15,6 @@ tags:
|
||||||
- optional
|
- optional
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_OPENSSL=ON
|
- CMake option: FUN_WITH_OPENSSL=ON
|
||||||
- Purpose: provide small crypto helpers backed by OpenSSL. Includes md5, sha256, sha512, ripemd160 helpers.
|
- Purpose: provide small crypto helpers backed by OpenSSL. Includes md5, sha256, sha512, ripemd160 helpers.
|
||||||
- Homepage: [https://www.openssl.org/](https://www.openssl.org/){:class="ext"}
|
- Homepage: [https://www.openssl.org/](https://www.openssl.org/){:class="ext"}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ tags:
|
||||||
- regex
|
- regex
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_PCRE2=ON
|
- CMake option: FUN_WITH_PCRE2=ON
|
||||||
- Purpose: Advanced regular expressions via PCRE2.
|
- Purpose: Advanced regular expressions via PCRE2.
|
||||||
- Homepage: [https://www.pcre.org/](https://www.pcre.org/){:class="ext"}
|
- Homepage: [https://www.pcre.org/](https://www.pcre.org/){:class="ext"}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ tags:
|
||||||
- smart
|
- smart
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_PCSC=ON
|
- CMake option: FUN_WITH_PCSC=ON
|
||||||
- Purpose: Access smart card readers/cards via PC/SC (pcsclite).
|
- Purpose: Access smart card readers/cards via PC/SC (pcsclite).
|
||||||
- Homepage: [https://pcsclite.apdu.fr/](https://pcsclite.apdu.fr/){:class="ext"}
|
- Homepage: [https://pcsclite.apdu.fr/](https://pcsclite.apdu.fr/){:class="ext"}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ tags:
|
||||||
- sqlite
|
- sqlite
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_SQLITE=ON
|
- CMake option: FUN_WITH_SQLITE=ON
|
||||||
- Purpose: Access SQLite databases via the native C API.
|
- Purpose: Access SQLite databases via the native C API.
|
||||||
- Homepage: [https://www.sqlite.org/](https://www.sqlite.org/){:class="ext"}
|
- Homepage: [https://www.sqlite.org/](https://www.sqlite.org/){:class="ext"}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ tags:
|
||||||
- xml
|
- xml
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
- CMake option: FUN_WITH_XML2=ON
|
- CMake option: FUN_WITH_XML2=ON
|
||||||
- Purpose: Minimal XML parsing helpers using libxml2.
|
- Purpose: Minimal XML parsing helpers using libxml2.
|
||||||
- Homepage: [http://xmlsoft.org/](http://xmlsoft.org/){:class="ext"}
|
- Homepage: [http://xmlsoft.org/](http://xmlsoft.org/){:class="ext"}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue