Page MenuHomeFreeBSD

D48390.1793651556.diff
No OneTemporary

Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None

D48390.1793651556.diff

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
@@ -3,7 +3,7 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
-.Dd October 6, 2024
+.Dd October 6, 2026
.Dt FETCH 3lua
.Os
.Sh NAME
@@ -18,11 +18,45 @@
.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; see
+.Sx HEADERS .
+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.
+An optional
+.Fa headers
+table can be provided; see
+.Sx HEADERS .
+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.
+An optional
+.Fa headers
+table can be provided; see
+.Sx HEADERS .
Returns
.Dv true
on success, or
@@ -52,6 +86,36 @@
.Dv nil
and an error message if the URL cannot be parsed.
.El
+.Sh HEADERS
+The optional
+.Fa headers
+argument is a table whose keys are header names and whose values are
+strings or numbers.
+Each entry is sent as a request header.
+.Dv Accept ,
+.Dv Referer
+and
+.Dv User-Agent
+replace the defaults that
+.Xr fetch 3
+would otherwise send;
+.Dv Content-Type
+is sent together with the body of a PUT or POST request and is ignored
+for GET.
+Names and values may not contain newlines, and names may not contain
+colons; passing either raises an error.
+.Pp
+The headers are passed to
+.Xr fetch 3
+through the
+.Ev HTTP_ACCEPT ,
+.Ev HTTP_REFERER
+and
+.Ev HTTP_USER_AGENT
+environment variables for the duration of the request, after which their
+previous values are restored.
+.Pp
+The body of a PUT or POST request is sent up to its first NUL byte.
.Sh RETURN VALUES
The
.Fn luaopen_fetch
@@ -100,6 +164,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
@@ -4,24 +4,184 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
-/* #include <sys/cdefs.h> */
+#include <sys/param.h>
+#include <sys/stat.h>
#include <errno.h>
-#include <sys/param.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <strings.h>
+
#include <fetch.h>
#include <lua.h>
#include "lauxlib.h"
#include "lfetch.h"
-#define CHUNK_SIZE 4096
+#define CHUNK_SIZE 4096
/*
* Minimal implementation of libfetch
+ *
+ * libfetch has no interface for arbitrary request headers. It does let the
+ * caller supply the Accept, Referer and User-Agent headers through the
+ * environment, and the Content-Type header through fetchReqHTTP(). Accept is
+ * sent on every request, so any other header the caller asks for is appended
+ * as additional lines following the Accept value.
*/
+struct lfetch_headers {
+ char *accept;
+ char *referer;
+ char *user_agent;
+ char *content_type;
+ char *extra; /* "\r\nName: value" for the rest */
+};
+
+struct lfetch_saved_env {
+ const char *name;
+ char *value; /* NULL if it was unset */
+};
+
+static const char *const header_envs[] = {
+ "HTTP_ACCEPT", "HTTP_REFERER", "HTTP_USER_AGENT",
+};
+
+static void
+free_headers(struct lfetch_headers *h)
+{
+ free(h->accept);
+ free(h->referer);
+ free(h->user_agent);
+ free(h->content_type);
+ free(h->extra);
+ memset(h, 0, sizeof(*h));
+}
+
+static int
+append_header(char **dst, const char *name, const char *value)
+{
+ char *s;
+
+ if (asprintf(&s, "%s\r\n%s: %s", *dst != NULL ? *dst : "", name,
+ value) == -1)
+ return (-1);
+ free(*dst);
+ *dst = s;
+ return (0);
+}
+
+/*
+ * Collect the headers table at index into h. Raises a Lua error on malformed
+ * input; returns -1 on allocation failure.
+ */
+static int
+build_headers(lua_State *L, int index, struct lfetch_headers *h)
+{
+ const char *name, *value;
+ char **slot;
+
+ memset(h, 0, sizeof(*h));
+ if (lua_isnoneornil(L, index))
+ return (0);
+ luaL_checktype(L, index, LUA_TTABLE);
+
+ lua_pushnil(L);
+ while (lua_next(L, index) != 0) {
+ if (lua_type(L, -2) != LUA_TSTRING)
+ return (luaL_argerror(L, index,
+ "header names must be strings"));
+ if (!lua_isstring(L, -1))
+ return (luaL_argerror(L, index,
+ "header values must be strings or numbers"));
+ name = lua_tostring(L, -2);
+ value = lua_tostring(L, -1);
+ if (name[strcspn(name, "\r\n:")] != '\0' ||
+ value[strcspn(value, "\r\n")] != '\0')
+ return (luaL_argerror(L, index,
+ "header names and values may not contain "
+ "newlines or colons"));
+
+ if (strcasecmp(name, "Accept") == 0)
+ slot = &h->accept;
+ else if (strcasecmp(name, "Referer") == 0)
+ slot = &h->referer;
+ else if (strcasecmp(name, "User-Agent") == 0)
+ slot = &h->user_agent;
+ else if (strcasecmp(name, "Content-Type") == 0)
+ slot = &h->content_type;
+ else
+ slot = NULL;
+
+ if (slot != NULL) {
+ free(*slot);
+ *slot = strdup(value);
+ if (*slot == NULL)
+ return (-1);
+ } else if (append_header(&h->extra, name, value) == -1)
+ return (-1);
+ lua_pop(L, 1);
+ }
+ return (0);
+}
+
+/*
+ * Export h through the environment variables libfetch reads, remembering
+ * their previous values so restore_env() can put them back. Any extra
+ * headers ride along after the Accept value, which libfetch always sends.
+ */
+static int
+apply_env(const struct lfetch_headers *h, struct lfetch_saved_env *saved)
+{
+ const char *values[nitems(header_envs)];
+ const char *old;
+ char *accept = NULL;
+ size_t i;
+ int error;
+
+ if (h->extra != NULL) {
+ if (asprintf(&accept, "%s%s",
+ h->accept != NULL ? h->accept : "*/*", h->extra) == -1)
+ return (-1);
+ values[0] = accept;
+ } else
+ values[0] = h->accept;
+ values[1] = h->referer;
+ values[2] = h->user_agent;
+
+ error = 0;
+ for (i = 0; i < nitems(header_envs) && error == 0; i++) {
+ saved[i].name = header_envs[i];
+ old = getenv(header_envs[i]);
+ saved[i].value = old != NULL ? strdup(old) : NULL;
+ if (old != NULL && saved[i].value == NULL)
+ error = -1;
+ else if (values[i] != NULL &&
+ setenv(header_envs[i], values[i], 1) == -1)
+ error = -1;
+ }
+ free(accept);
+ return (error);
+}
+
+static void
+restore_env(struct lfetch_saved_env *saved)
+{
+ size_t i;
+
+ for (i = 0; i < nitems(header_envs); i++) {
+ if (saved[i].name == NULL)
+ continue;
+ if (saved[i].value != NULL)
+ setenv(saved[i].name, saved[i].value, 1);
+ else
+ unsetenv(saved[i].name);
+ free(saved[i].value);
+ saved[i].value = NULL;
+ }
+}
+
static int
lfetch_parse_url(lua_State *L)
{
@@ -45,11 +205,10 @@
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);
- }
+ 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);
@@ -63,12 +222,25 @@
{
const char *url = luaL_checkstring(L, 1);
const char *out = luaL_checkstring(L, 2);
+ struct lfetch_headers headers;
+ struct lfetch_saved_env saved[nitems(header_envs)] = { 0 };
struct url *u;
FILE *fetch;
FILE *file;
+ char buf[CHUNK_SIZE];
+ size_t bytes;
+ int error;
+
+ if (build_headers(L, 3, &headers) == -1) {
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushstring(L, "Failed to allocate memory for headers");
+ return (2);
+ }
u = fetchParseURL(url);
if (u == NULL) {
+ free_headers(&headers);
lua_pushnil(L);
lua_pushstring(L, "Failed to parse URL");
return (2);
@@ -76,48 +248,182 @@
file = fopen(out, "w");
if (file == NULL) {
- fclose(file);
+ error = errno;
fetchFreeURL(u);
+ free_headers(&headers);
lua_pushnil(L);
- lua_pushfstring(L, "Failed to open output file: %s", strerror(errno));
- return 2;
+ lua_pushfstring(L, "Failed to open output file: %s",
+ strerror(error));
+ return (2);
}
+ if (apply_env(&headers, saved) == -1) {
+ restore_env(saved);
+ fclose(file);
+ fetchFreeURL(u);
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushstring(L, "Failed to set request headers");
+ return (2);
+ }
fetch = fetchGet(u, "");
+ error = errno;
+ restore_env(saved);
+ free_headers(&headers);
+ fetchFreeURL(u);
if (fetch == NULL) {
fclose(file);
- fetchFreeURL(u);
lua_pushnil(L);
- lua_pushfstring(L, "Failed to read from URL: %s", strerror(errno));
- return 2;
+ lua_pushfstring(L, "Failed to read from URL: %s",
+ strerror(error));
+ 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) {
+ error = errno;
fclose(fetch);
fclose(file);
- fetchFreeURL(u);
lua_pushnil(L);
- lua_pushfstring(L, "Failed to write to file: %s", strerror(errno));
- return 2;
+ lua_pushfstring(L, "Failed to write to file: %s",
+ strerror(error));
+ return (2);
}
}
fclose(fetch);
fclose(file);
- fetchFreeURL(u);
lua_pushboolean(L, 1);
- return 1;
+ return (1);
}
-static const struct
-luaL_Reg fetchlib[] = {
- {"get_url", lfetch_get_url},
- {"parse_url", lfetch_parse_url},
- {NULL, NULL}
+/*
+ * Common implementation of put_url and post_url: send the contents of the
+ * file named by argument 2 as the body of a request with the given method.
+ * libfetch takes the body as a C string, so it is sent up to its first NUL.
+ */
+static int
+lfetch_upload(lua_State *L, const char *method)
+{
+ const char *url = luaL_checkstring(L, 1);
+ const char *in = luaL_checkstring(L, 2);
+ struct lfetch_headers headers;
+ struct lfetch_saved_env saved[nitems(header_envs)] = { 0 };
+ struct url *u;
+ FILE *fetch;
+ FILE *file;
+ struct stat sb;
+ char *buf;
+ size_t size;
+ int error;
+
+ if (build_headers(L, 3, &headers) == -1) {
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushstring(L, "Failed to allocate memory for headers");
+ return (2);
+ }
+
+ u = fetchParseURL(url);
+ if (u == NULL) {
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushstring(L, "Failed to parse URL");
+ return (2);
+ }
+
+ file = fopen(in, "r");
+ if (file == NULL) {
+ error = errno;
+ fetchFreeURL(u);
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushfstring(L, "Failed to open input file: %s",
+ strerror(error));
+ return (2);
+ }
+
+ if (fstat(fileno(file), &sb) == -1) {
+ error = errno;
+ fclose(file);
+ fetchFreeURL(u);
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushfstring(L, "Failed to stat input file: %s",
+ strerror(error));
+ return (2);
+ }
+
+ size = sb.st_size;
+ buf = malloc(size + 1);
+ if (buf == NULL) {
+ fclose(file);
+ fetchFreeURL(u);
+ free_headers(&headers);
+ 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);
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushstring(L, "Failed to read input file");
+ return (2);
+ }
+ buf[size] = '\0';
+ fclose(file);
+
+ if (apply_env(&headers, saved) == -1) {
+ restore_env(saved);
+ free(buf);
+ fetchFreeURL(u);
+ free_headers(&headers);
+ lua_pushnil(L);
+ lua_pushstring(L, "Failed to set request headers");
+ return (2);
+ }
+ fetch = fetchReqHTTP(u, method, "", headers.content_type, buf);
+ error = errno;
+ restore_env(saved);
+ free(buf);
+ free_headers(&headers);
+ fetchFreeURL(u);
+ if (fetch == NULL) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "Failed to %s to URL: %s", method,
+ strerror(error));
+ return (2);
+ }
+
+ fclose(fetch);
+ lua_pushboolean(L, 1);
+ return (1);
+}
+
+static int
+lfetch_put_url(lua_State *L)
+{
+
+ return (lfetch_upload(L, "PUT"));
+}
+
+static int
+lfetch_post_url(lua_State *L)
+{
+
+ return (lfetch_upload(L, "POST"));
+}
+
+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 }
};
int
@@ -126,4 +432,3 @@
luaL_newlib(L, fetchlib);
return (1);
}
-

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 2, 8:32 PM (5 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41306717
Default Alt Text
D48390.1793651556.diff (13 KB)

Event Timeline