diff --git a/libexec/flua/Makefile b/libexec/flua/Makefile --- a/libexec/flua/Makefile +++ b/libexec/flua/Makefile @@ -1,5 +1,6 @@ .include +SUBDIR+= libfetch SUBDIR+= libfreebsd SUBDIR+= libhash SUBDIR+= libjail diff --git a/libexec/flua/libfetch/Makefile b/libexec/flua/libfetch/Makefile new file mode 100644 --- /dev/null +++ b/libexec/flua/libfetch/Makefile @@ -0,0 +1,9 @@ +SHLIB_NAME= fetch.so + +SRCS+= lfetch.c + +LIBADD+= fetch + +MAN= fetch.3lua + +.include diff --git a/libexec/flua/libfetch/fetch.3lua b/libexec/flua/libfetch/fetch.3lua new file mode 100644 --- /dev/null +++ b/libexec/flua/libfetch/fetch.3lua @@ -0,0 +1,113 @@ +.\" +.\" Copyright (c) 2024 SkunkWerks, GmbH. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd October 6, 2024 +.Dt FETCH 3lua +.Os +.Sh NAME +.Nm fetch +.Nd Lua bindings to +.Xr fetch 3 +library +.Sh DESCRIPTION +The bindings allow Lua scripts to parse URI strings and download from URLs, +including http and file schemes supported by +.Xr fetch 3 +.Pp +The module exports the following functions: +.Bl -tag -width Ds +.It Fn fetch.get_url url outfile +Downloads the content from the specified +.Fa url +and saves it to +.Fa outfile . +Returns +.Dv true +on success, or +.Dv nil +and an error message on failure. +.It Fn fetch.parse_url url +Parses the given +.Fa url +and returns a table containing its components. +The table has the following fields: +.Bl -bullet -compact +.It +scheme +.It +user +.It +password +.It +host +.It +port +.It +doc (path and query string for HTTP/HTTPS URLs) +.El +.Pp +Returns +.Dv nil +and an error message if the URL cannot be parsed. +.El +.Sh RETURN VALUES +The +.Fn luaopen_fetch +function returns 1, indicating that it has pushed one value, the table, +onto the Lua stack. +.Sh EXAMPLES +The following Lua code demonstrates how to use the +.Nm +module: +.Bd -literal -offset indent +/usr/libexec/flua -l f=fetch +Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio + +> f.parse_url("http:\\") +nil Failed to parse URL + +> r = f.parse_url("http://localhost/") +> for k,v in pairs(r) do; print(' ', k, v); end + user + scheme http + host localhost + doc / + password + +> r = f.parse_url("http://user:pass@host:1999/rain?beret=raspberry") +> for k,v in pairs(r) do; print(' ', k, v); end + user user + doc /rain?beret=raspberry + scheme http + host host + port 1999 + password pass + +> f.get_url("file:///var/run/motd", "/tmp/m") +true + +> f.get_url("http://w3.org/", "/tmp/w") +true + +> f.get_url("https://freebsd.org/", "/tmp/f") +true + +> f.get_url("https://invalid.site/", "/tmp/i") +nil Failed to read from URL: No error: 0 + +> 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 +.Ed +.Sh SEE ALSO +.Xr fetch 3 , +.Xr fetchGet 3 , +.Xr fetchParseURL 3 , +.Xr intro 3lua +.Sh AUTHORS +The +.Nm +man page was written by +.An Dave Cottlehuber Aq Mt dch@FreeBSD.org . diff --git a/libexec/flua/libfetch/lfetch.h b/libexec/flua/libfetch/lfetch.h new file mode 100644 --- /dev/null +++ b/libexec/flua/libfetch/lfetch.h @@ -0,0 +1,10 @@ +/*- + * + * This file is in the public domain. + */ + +#pragma once + +#include + +int luaopen_fetch(lua_State *L); diff --git a/libexec/flua/libfetch/lfetch.c b/libexec/flua/libfetch/lfetch.c new file mode 100644 --- /dev/null +++ b/libexec/flua/libfetch/lfetch.c @@ -0,0 +1,129 @@ +/*- + * Copyright (c) 2023, 2024 Dave Cottlehuber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* #include */ + +#include +#include +#include +#include +#include + +#include +#include "lauxlib.h" +#include "lfetch.h" + +#define CHUNK_SIZE 4096 + +/* + * Minimal implementation of libfetch + */ + +static int +lfetch_parse_url(lua_State *L) +{ + const char *url = luaL_checkstring(L, 1); + struct url *u; + + u = fetchParseURL(url); + if (u == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to parse URL"); + return (2); + } + + lua_newtable(L); + lua_pushstring(L, u->scheme); + lua_setfield(L, -2, "scheme"); + lua_pushstring(L, u->user); + lua_setfield(L, -2, "user"); + lua_pushstring(L, u->pwd); + lua_setfield(L, -2, "password"); + lua_pushstring(L, u->host); + lua_setfield(L, -2, "host"); + /* if port is not explicitly set, it defaults to 0, not scheme */ + if (u->port != 0) { + lua_pushinteger(L, u->port); + } else { + lua_pushnil(L); + } + lua_setfield(L, -2, "port"); + /* for http(s), doc is the combined path & query string */ + lua_pushstring(L, u->doc); + lua_setfield(L, -2, "doc"); + fetchFreeURL(u); + return (1); +} + +static int +lfetch_get_url(lua_State *L) +{ + const char *url = luaL_checkstring(L, 1); + const char *out = luaL_checkstring(L, 2); + struct url *u; + FILE *fetch; + FILE *file; + + u = fetchParseURL(url); + if (u == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to parse URL"); + return (2); + } + + file = fopen(out, "w"); + if (file == NULL) { + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to open output file: %s", strerror(errno)); + return 2; + } + + fetch = fetchGet(u, ""); + if (fetch == NULL) { + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to read from URL: %s", strerror(errno)); + return 2; + } + + char buf[CHUNK_SIZE]; + size_t bytes; + + while ((bytes = fread(buf, 1, CHUNK_SIZE, fetch)) > 0) { + if (fwrite(buf, 1, bytes, file) != bytes) { + fclose(fetch); + fclose(file); + fetchFreeURL(u); + lua_pushnil(L); + lua_pushfstring(L, "Failed to write to file: %s", strerror(errno)); + return 2; + } + } + + fclose(fetch); + fclose(file); + fetchFreeURL(u); + lua_pushboolean(L, 1); + return 1; +} + +static const struct +luaL_Reg fetchlib[] = { + {"get_url", lfetch_get_url}, + {"parse_url", lfetch_parse_url}, + {NULL, NULL} +}; + +int +luaopen_fetch(lua_State *L) +{ + luaL_newlib(L, fetchlib); + return (1); +} +