diff --git a/libexec/flua/libfetch/fetch.3lua b/libexec/flua/libfetch/fetch.3lua --- a/libexec/flua/libfetch/fetch.3lua +++ b/libexec/flua/libfetch/fetch.3lua @@ -18,11 +18,36 @@ .Pp The module exports the following functions: .Bl -tag -width Ds -.It Fn fetch.get_url url outfile +.It Fn fetch.get_url url outfile [headers] Downloads the content from the specified .Fa url and saves it to .Fa outfile . +An optional +.Fa headers +table can be provided with HTTP headers as key-value pairs. +Returns +.Dv true +on success, or +.Dv nil +and an error message on failure. +.It Fn fetch.put_url url infile [headers] +Uploads the content from +.Fa infile +to the specified +.Fa url +using HTTP PUT. +Returns +.Dv true +on success, or +.Dv nil +and an error message on failure. +.It Fn fetch.post_url url infile [headers] +Uploads the content from +.Fa infile +to the specified +.Fa url +using HTTP POST. Returns .Dv true on success, or @@ -100,6 +125,29 @@ > f.get_url("https://wrong.host.badssl.com/", "/tmp/i") SSL certificate subject doesn't match host wrong.host.badssl.com nil Failed to read from URL: Authentication error + +> -- Upload a file using PUT +> f.put_url("http://example.com/upload", "/tmp/data.txt") +true + +> -- Submit form data using POST +> f.post_url("http://example.com/submit", "/tmp/form.txt") +true + +> -- GET with custom headers +> headers = { +> ["User-Agent"] = "MyApp/1.0", +> ["Accept"] = "application/json" +> } +> f.get_url("http://api.example.com/data", "/tmp/data", headers) +true + +> -- POST with content type header +> headers = { +> ["Content-Type"] = "application/json" +> } +> f.post_url("http://api.example.com/submit", "/tmp/data.json", headers) +true .Ed .Sh SEE ALSO .Xr fetch 3 , diff --git a/libexec/flua/libfetch/lfetch.c b/libexec/flua/libfetch/lfetch.c --- a/libexec/flua/libfetch/lfetch.c +++ b/libexec/flua/libfetch/lfetch.c @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include #include @@ -17,11 +19,63 @@ #include "lfetch.h" #define CHUNK_SIZE 4096 +#define MAX_HEADERS 32 /* * Minimal implementation of libfetch */ +/* Helper function to build headers string from Lua table */ +static char * +build_headers(lua_State *L, int index) +{ + char *headers = NULL; + size_t total_len = 0; + size_t header_count = 0; + + if (!lua_istable(L, index)) { + return strdup(""); + } + + /* First pass: calculate total length needed */ + lua_pushnil(L); + while (lua_next(L, index) != 0 && header_count < MAX_HEADERS) { + const char *key = lua_tostring(L, -2); + const char *value = lua_tostring(L, -1); + if (key && value) { + total_len += strlen(key) + strlen(value) + 4; /* key: value\n\0 */ + header_count++; + } + lua_pop(L, 1); + } + + if (total_len == 0) { + return strdup(""); + } + + headers = malloc(total_len); + if (headers == NULL) { + return NULL; + } + headers[0] = '\0'; + + /* Second pass: build headers string */ + lua_pushnil(L); + while (lua_next(L, index) != 0) { + const char *key = lua_tostring(L, -2); + const char *value = lua_tostring(L, -1); + if (key && value) { + strcat(headers, key); + strcat(headers, ": "); + strcat(headers, value); + strcat(headers, "\n"); + } + lua_pop(L, 1); + } + + return headers; +} + static int lfetch_parse_url(lua_State *L) { @@ -66,6 +120,19 @@ struct url *u; FILE *fetch; FILE *file; + char *headers = NULL; + + /* Get optional headers table */ + if (lua_gettop(L) >= 3) { + headers = build_headers(L, 3); + if (headers == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate memory for headers"); + return 2; + } + } else { + headers = strdup(""); + } u = fetchParseURL(url); if (u == NULL) { @@ -83,7 +150,8 @@ return 2; } - fetch = fetchGet(u, ""); + fetch = fetchGet(u, headers); + free(headers); if (fetch == NULL) { fclose(file); fetchFreeURL(u); @@ -113,10 +181,182 @@ return 1; } +static int +lfetch_put_url(lua_State *L) +{ + const char *url = luaL_checkstring(L, 1); + const char *in = luaL_checkstring(L, 2); + struct url *u; + FILE *fetch; + FILE *file; + char *headers = NULL; + char *buf; + struct stat sb; + size_t size; + + u = fetchParseURL(url); + if (u == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to parse URL"); + return (2); + } + + file = fopen(in, "r"); + if (file == NULL) { + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to open input file: %s", strerror(errno)); + return 2; + } + + if (fstat(fileno(file), &sb) == -1) { + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to stat input file: %s", strerror(errno)); + return 2; + } + + size = sb.st_size; + buf = malloc(size); + if (buf == NULL) { + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate memory for request"); + return 2; + } + + if (fread(buf, 1, size, file) != size) { + free(buf); + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushstring(L, "Failed to read input file"); + return 2; + } + fclose(file); + + /* Get optional headers table */ + if (lua_gettop(L) >= 3) { + headers = build_headers(L, 3); + if (headers == NULL) { + free(buf); + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate memory for headers"); + return 2; + } + } else { + headers = strdup(""); + } + + fetch = fetchReqHTTP(u, "PUT", buf, headers, ""); + free(headers); + free(buf); + if (fetch == NULL) { + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to PUT to URL: %s", strerror(errno)); + return 2; + } + + fclose(fetch); + fetchFreeURL(u); + lua_pushboolean(L, 1); + return 1; +} + +static int +lfetch_post_url(lua_State *L) +{ + const char *url = luaL_checkstring(L, 1); + const char *in = luaL_checkstring(L, 2); + struct url *u; + FILE *fetch; + FILE *file; + char *buf; + char *headers = NULL; + struct stat sb; + size_t size; + + u = fetchParseURL(url); + if (u == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to parse URL"); + return (2); + } + + file = fopen(in, "r"); + if (file == NULL) { + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to open input file: %s", strerror(errno)); + return 2; + } + + if (fstat(fileno(file), &sb) == -1) { + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to stat input file: %s", strerror(errno)); + return 2; + } + + size = sb.st_size; + buf = malloc(size); + if (buf == NULL) { + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate memory for request"); + return 2; + } + + if (fread(buf, 1, size, file) != size) { + free(buf); + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushstring(L, "Failed to read input file"); + return 2; + } + fclose(file); + + /* Get optional headers table */ + if (lua_gettop(L) >= 3) { + headers = build_headers(L, 3); + if (headers == NULL) { + free(buf); + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate memory for headers"); + return 2; + } + } else { + headers = strdup(""); + } + + fetch = fetchReqHTTP(u, "POST", buf, headers, ""); + free(headers); + free(buf); + if (fetch == NULL) { + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to POST to URL: %s", strerror(errno)); + return 2; + } + + fclose(fetch); + fetchFreeURL(u); + lua_pushboolean(L, 1); + return 1; +} + static const struct luaL_Reg fetchlib[] = { {"get_url", lfetch_get_url}, {"parse_url", lfetch_parse_url}, + {"put_url", lfetch_put_url}, + {"post_url", lfetch_post_url}, {NULL, NULL} };