1
0
Fork 0
forked from fun/fun

Added libcurl (cURL) support. (0.30.0)

This commit is contained in:
Johannes Findeisen 2025-11-26 00:35:22 +01:00
commit 4d82d73515
15 changed files with 304 additions and 8 deletions

View file

@ -145,6 +145,9 @@ static const char *opcode_name(OpCode op) {
case OP_JSON_STRINGIFY: return "JSON_STRINGIFY";
case OP_JSON_FROM_FILE: return "JSON_FROM_FILE";
case OP_JSON_TO_FILE: return "JSON_TO_FILE";
case OP_CURL_GET: return "CURL_GET";
case OP_CURL_POST: return "CURL_POST";
case OP_CURL_DOWNLOAD: return "CURL_DOWNLOAD";
case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH";
case OP_PCSC_RELEASE: return "PCSC_RELEASE";
case OP_PCSC_LIST_READERS: return "PCSC_LIST_READERS";

View file

@ -147,6 +147,11 @@ typedef enum {
OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil)
OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0
// CURL (libcurl)
OP_CURL_GET, // pops [headers map?], url; pushes response string (or "")
OP_CURL_POST, // pops [headers map?], body string, url; pushes response string (or "")
OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0
// PCSC (smart card) opcodes
OP_PCSC_ESTABLISH, // returns context id (>0) or 0
OP_PCSC_RELEASE, // pops ctx id; returns 1/0

View file

@ -778,6 +778,35 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
/* CURL builtins (minimal interface like JSON) */
if (strcmp(name, "curl_get") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_get expects (url)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_get arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_CURL_GET, 0);
free(name);
return 1;
}
if (strcmp(name, "curl_post") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_post args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_CURL_POST, 0);
free(name);
return 1;
}
if (strcmp(name, "curl_download") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_download args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_CURL_DOWNLOAD, 0);
free(name);
return 1;
}
/* PCSC builtins */
if (strcmp(name, "pcsc_establish") == 0) {
(*pos)++; /* '(' */

View file

@ -32,6 +32,25 @@
#include <pcre2.h>
#endif
/* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */
#ifdef FUN_WITH_CURL
#include <curl/curl.h>
typedef struct { char *d; size_t n; } FunCurlBuf;
static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
size_t add = sz * nm;
FunCurlBuf *b = (FunCurlBuf*)ud;
char *p = (char*)realloc(b->d, b->n + add + 1);
if (!p) return 0;
memcpy(p + b->n, ptr, add);
b->d = p; b->n += add; b->d[b->n] = '\0';
return add;
}
static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
FILE *f = (FILE*)ud;
return fwrite(ptr, sz, nm, f);
}
#endif
/* forward declarations for include mapping used in error reporting */
extern char *preprocess_includes(const char *src);
static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
@ -679,6 +698,11 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/json/from_file.c"
#include "vm/json/to_file.c"
/* CURL ops */
#include "vm/curl/get.c"
#include "vm/curl/post.c"
#include "vm/curl/download.c"
/* PCRE2 ops */
#include "vm/pcre2/test.c"
#include "vm/pcre2/match.c"

View file

@ -39,6 +39,7 @@ static const char *opcode_names[] = {
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
"CURL_GET","CURL_POST","CURL_DOWNLOAD",
"PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT",
"PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL",
"SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT",

47
src/vm/curl/download.c Normal file
View file

@ -0,0 +1,47 @@
/**
* libcurl DOWNLOAD builtin
*/
case OP_CURL_DOWNLOAD: {
#ifdef FUN_WITH_CURL
Value vpath = pop_value(vm);
Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl);
char *path = value_to_string_alloc(&vpath);
free_value(vurl);
free_value(vpath);
if (!url || !path) {
if (url) free(url);
if (path) free(path);
push_value(vm, make_int(0));
break;
}
FILE *fp = fopen(path, "wb");
if (!fp) {
free(url); free(path);
push_value(vm, make_int(0));
break;
}
CURL *h = curl_easy_init();
if (!h) {
fclose(fp);
free(url); free(path);
push_value(vm, make_int(0));
break;
}
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_file_write_cb);
curl_easy_setopt(h, CURLOPT_WRITEDATA, fp);
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
fclose(fp);
free(url); free(path);
if (rc != CURLE_OK) { push_value(vm, make_int(0)); break; }
push_value(vm, make_int(1));
#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;
}

33
src/vm/curl/get.c Normal file
View file

@ -0,0 +1,33 @@
/**
* libcurl GET builtin
*/
case OP_CURL_GET: {
#ifdef FUN_WITH_CURL
Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl);
free_value(vurl);
if (!url) { push_value(vm, make_string("")); break; }
FunCurlBuf buf = { NULL, 0 };
CURL *h = curl_easy_init();
if (!h) { free(url); push_value(vm, make_string("")); break; }
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb);
curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf);
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
free(url);
if (rc != CURLE_OK) {
if (buf.d) free(buf.d);
push_value(vm, make_string(""));
break;
}
Value s = make_string(buf.d ? buf.d : "");
if (buf.d) free(buf.d);
push_value(vm, s);
#else
Value v = pop_value(vm); free_value(v);
push_value(vm, make_string(""));
#endif
break;
}

41
src/vm/curl/post.c Normal file
View file

@ -0,0 +1,41 @@
/**
* libcurl POST builtin
*/
case OP_CURL_POST: {
#ifdef FUN_WITH_CURL
Value vbody = pop_value(vm);
Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl);
char *body = value_to_string_alloc(&vbody);
free_value(vurl);
free_value(vbody);
if (!url) { if (body) free(body); push_value(vm, make_string("")); break; }
if (!body) body = strdup("");
FunCurlBuf buf = { NULL, 0 };
CURL *h = curl_easy_init();
if (!h) { free(url); free(body); push_value(vm, make_string("")); break; }
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(h, CURLOPT_POST, 1L);
curl_easy_setopt(h, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb);
curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf);
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
free(url);
free(body);
if (rc != CURLE_OK) {
if (buf.d) free(buf.d);
push_value(vm, make_string(""));
break;
}
Value s = make_string(buf.d ? buf.d : "");
if (buf.d) free(buf.d);
push_value(vm, s);
#else
Value a = pop_value(vm); free_value(a);
Value b = pop_value(vm); free_value(b);
push_value(vm, make_string(""));
#endif
break;
}