1
0
Fork 0
forked from fun/fun

Added more documentation to kcgi opcodes. No code changes. (0.41.10)

This commit is contained in:
Johannes Findeisen 2026-05-10 02:07:06 +02:00
commit 303e48d37e
2 changed files with 125 additions and 3 deletions

View file

@ -48,7 +48,7 @@ PROJECT_NAME = "Fun API Documentation"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 0.41.8 PROJECT_NUMBER = 0.41.10
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewers a # for a project that appears at the top of each page and should give viewers a
@ -1131,7 +1131,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which Doxygen is # Note that relative paths are relative to the directory from which Doxygen is
# run. # run.
EXCLUDE = EXCLUDE = src/vm
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded # directories that are symbolic links (a Unix file system feature) are excluded

View file

@ -9,7 +9,46 @@
/** /**
* @file kcgi.c * @file kcgi.c
* @brief kcgi helpers for Fun VM KCGI-related opcodes (conditional build). * @brief Thin kcgi integration helpers used by VM opcodes under src/vm/kcgi/.
*
* This module centralizes compact helpers around the kcgi API so the VM
* opcode snippets included by vm.c can remain minimal and focus on stack
* marshalling. The helpers here do not depend on VM internals beyond the
* Value conversion utilities used to represent HTTP requests as Fun values.
* Keeping the external-API specifics in src/extensions/ mirrors other
* integrations (OpenSSL, PCRE2, SQLite, XML2, JSON, INI) and improves
* maintainability.
*
* Build-time feature flag:
* - All code in this file is compiled only when FUN_WITH_KCGI is enabled.
* When disabled, the file provides no symbols and VM opcodes compiled
* from src/vm/kcgi/*.c will fall back to no-op/falsey behaviors as
* documented in those opcode snippets.
*
* Ownership and memory model:
* - kcgi_parse_request() allocates a struct kreq via khttp_parse() and, on
* success, returns it through the out-parameter; the caller takes ownership
* and must later release it with kcgi_free_request().
* - kcgi_free_request() calls khttp_free() and then frees the allocation
* wrapper used by this module.
* - kcgi_write_str() does not take ownership of the input string; it may be
* NULL, which is treated as an empty string.
* - kreq_to_fun() and kcgi_fields_to_map() create Fun Values following the
* VM's normal ownership semantics (returned by value to the caller).
*
* Global state and lifetime:
* - A thread-local pointer g_kcgi_req holds the current request for the
* active CGI handling context. VM opcodes are responsible for setting and
* clearing this pointer by calling the helpers exposed here.
*
* Error handling:
* - Functions return 0/NULL on failure and non-zero/valid objects on success.
* Callers should check return values before use. No errno is set.
*
* Thread-safety:
* - The request handle is stored in a thread-local variable to isolate state
* between concurrent executions. Helpers otherwise maintain no global
* mutable state.
*/ */
#ifdef FUN_WITH_KCGI #ifdef FUN_WITH_KCGI
@ -25,6 +64,24 @@ static __declspec(thread) struct kreq *g_kcgi_req = NULL;
static __thread struct kreq *g_kcgi_req = NULL; static __thread struct kreq *g_kcgi_req = NULL;
#endif #endif
/**
* Convert kcgi form/query fields to a Fun map Value.
*
* Each entry from r->fields is copied into a newly created Fun map where
* the kcgi key becomes the map key and the kcgi value becomes a Fun string.
*
* Notes:
* - If the request pointer is NULL, an empty map is returned.
* - When multiple kcgi fields share the same key, later occurrences will
* overwrite earlier ones ("last write wins").
* - Empty or NULL keys/values are converted to empty strings ("").
*
* Ownership:
* - The returned Value is owned by the caller (normal VM semantics).
*
* @param r Parsed kcgi request pointer (may be NULL).
* @return A Fun map Value containing string keys and values.
*/
static Value kcgi_fields_to_map(const struct kreq *r) { static Value kcgi_fields_to_map(const struct kreq *r) {
Value m = make_map_empty(); Value m = make_map_empty();
if (!r) return m; if (!r) return m;
@ -36,6 +93,22 @@ static Value kcgi_fields_to_map(const struct kreq *r) {
return m; return m;
} }
/**
* Convert a kcgi request handle into a structured Fun Value.
*
* The resulting map contains at least the following keys:
* - "host": string; empty if unavailable
* - "port": int; TCP port
* - "path": string; request path
* - "suffix": string; request suffix (kcgi notion)
* - "fields": map; key/value pairs from form/query fields
*
* Missing fields from kcgi are converted to empty strings where applicable.
* If r is NULL, an empty map is returned.
*
* @param r Parsed kcgi request pointer (may be NULL).
* @return A Fun map Value describing the request.
*/
static Value kreq_to_fun(const struct kreq *r) { static Value kreq_to_fun(const struct kreq *r) {
Value out = make_map_empty(); Value out = make_map_empty();
if (!r) return out; if (!r) return out;
@ -51,6 +124,24 @@ static Value kreq_to_fun(const struct kreq *r) {
} }
/* Lifecycle helpers used by VM opcodes */ /* Lifecycle helpers used by VM opcodes */
/**
* Parse the current CGI/FCGI request using kcgi.
*
* On success, this allocates and initializes a struct kreq by calling
* khttp_parse() and stores it in *out. The caller takes ownership of the
* returned handle and must later free it with kcgi_free_request().
*
* Behavior details:
* - All keys are accepted (kvalid array with a single {NULL,NULL} entry).
* - On allocation or parse failure, *out is not modified and 0 is returned.
*
* Threading:
* - This function is independent of the thread-local g_kcgi_req; setting that
* pointer is left to the caller/opcode.
*
* @param[out] out Where to store the newly allocated struct kreq on success.
* @return 1 on success, 0 on failure.
*/
static int kcgi_parse_request(struct kreq **out) { static int kcgi_parse_request(struct kreq **out) {
static const struct kvalid keys[] = { { NULL, NULL } }; /* accept all */ static const struct kvalid keys[] = { { NULL, NULL } }; /* accept all */
struct kreq *r = (struct kreq *)calloc(1, sizeof(*r)); struct kreq *r = (struct kreq *)calloc(1, sizeof(*r));
@ -61,12 +152,34 @@ static int kcgi_parse_request(struct kreq **out) {
return 1; return 1;
} }
/**
* Free a request previously returned by kcgi_parse_request().
*
* Safe to call with NULL.
*
* @param r Request handle to free (may be NULL).
*/
static void kcgi_free_request(struct kreq *r) { static void kcgi_free_request(struct kreq *r) {
if (!r) return; if (!r) return;
khttp_free(r); khttp_free(r);
free(r); free(r);
} }
/**
* Begin the HTTP response for the current request.
*
* This emits an optional Content-Type header and then switches into body
* mode by calling khttp_body(). If g_kcgi_req is not set, the call fails.
*
* Notes:
* - The status code parameter is currently not passed to kcgi; unless set
* elsewhere, kcgi will default the status to 200 OK.
* - If ctype is NULL or empty, no Content-Type header is emitted here.
*
* @param code Suggested HTTP status code (reserved for future use).
* @param ctype MIME type to emit as Content-Type (may be NULL/empty).
* @return 1 on success, 0 on failure.
*/
static int kcgi_reply_start(int code, const char *ctype) { static int kcgi_reply_start(int code, const char *ctype) {
if (!g_kcgi_req) return 0; if (!g_kcgi_req) return 0;
/* Emit Content-Type header; Status defaults to 200 if not set */ /* Emit Content-Type header; Status defaults to 200 if not set */
@ -79,6 +192,15 @@ static int kcgi_reply_start(int code, const char *ctype) {
return 1; return 1;
} }
/**
* Write a UTF-8 string to the HTTP response body.
*
* Requires an active request in g_kcgi_req and an initialized response body
* (kcgi_reply_start() or equivalent must have been called).
*
* @param s NUL-terminated string to write; NULL is treated as "".
* @return 1 on success, 0 on failure.
*/
static int kcgi_write_str(const char *s) { static int kcgi_write_str(const char *s) {
if (!g_kcgi_req) return 0; if (!g_kcgi_req) return 0;
if (!s) s = ""; if (!s) s = "";