Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176953877
D48390.1793651556.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None
D48390.1793651556.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D48390: flua: Add PUT, POST, and header support to lua fetch
Attached
Detach File
Event Timeline