about summary refs log tree commit diff stats
path: root/bonus
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-12-14 21:51:48 +0100
committerbptato <nincsnevem662@gmail.com>2023-12-14 21:51:48 +0100
commit3adc85aebcb0458fe946731f4ee8e43cbd3fd093 (patch)
tree287ebccb6faca280db643353cd744ca87d8326ae /bonus
parent600058a19e58aee247ca90b9eb2a8271b458b2d4 (diff)
downloadchawan-3adc85aebcb0458fe946731f4ee8e43cbd3fd093.tar.gz
bonus: add libfetch HTTP backend
It has roughly zero utility, but maybe it's a good demonstration
of local CGI?

(TODO: add libfetch FTP too, that might actually be useful.)
Diffstat (limited to 'bonus')
-rw-r--r--bonus/libfetch/Makefile6
-rw-r--r--bonus/libfetch/cha-http-libfetch.c157
2 files changed, 163 insertions, 0 deletions
diff --git a/bonus/libfetch/Makefile b/bonus/libfetch/Makefile
new file mode 100644
index 00000000..304ac206
--- /dev/null
+++ b/bonus/libfetch/Makefile
@@ -0,0 +1,6 @@
+cha-http-libfetch: cha-http-libfetch.c
+	$(CC) -Wall -O2 -g -std=c89 cha-http-libfetch.c -lfetch -o cha-http-libfetch
+
+.PHONY: clean
+clean:
+	rm -f cha-http-libfetch
diff --git a/bonus/libfetch/cha-http-libfetch.c b/bonus/libfetch/cha-http-libfetch.c
new file mode 100644
index 00000000..38b73c30
--- /dev/null
+++ b/bonus/libfetch/cha-http-libfetch.c
@@ -0,0 +1,157 @@
+/* This file is dedicated to the public domain.
+ *
+ * FreeBSD libfetch adapter for Chawan local-CGI. Not much more than a
+ * proof of concept, as its functionality is very limited:
+ * - Only a few HTTP headers are supported: Accept, Referer, User-Agent.
+ *   So e.g. cookies do not work.
+ * - No HTTP headers are returned at all.
+ * - Content-Type is deduced from the extension in a very simplistic manner.
+ * - Redirects are respected, but not reported.
+ * - See also: BUGS section in fetch(3).
+ *
+ * Use cases:
+ * - You are upset about having to download a huge HTTP library even
+ *   though your system already has one that kind of works sometimes.
+ * - You are stranded on a desert island with nothing but the Chawan
+ *   sources on a FreeBSD system without libcurl.
+ * - ???
+ */
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <fetch.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define FDIE(x) \
+	do { \
+		puts("Content-Type: text/plain\r\n"); \
+		puts(x); \
+		puts(fetchLastErrString); \
+		exit(1); \
+	} while (0)
+
+#define PDIE(x) \
+	do { \
+		puts("Content-Type: text/plain\r\n"); \
+		puts(x); \
+		puts(strerror(errno)); \
+		exit(1); \
+	} while (0)
+
+#define DIE(x) \
+	do { \
+		puts("Content-Type: text/plain\r\n\r\n" x); \
+		exit(1); \
+	} while (0)
+
+int hasext(const char *path)
+{
+	const char *p = path, *q;
+
+	while ((q = strchr(p, '/')))
+		p = q + 1;
+	q = strchr(p, '.');
+	return !!q;
+}
+
+int main(int argc, char **argv)
+{
+	struct url *u;
+	const char *method, *content_type, *p, *np;
+	const char *scheme, *username, *password, *host, *port, *path, *query;
+	FILE *f;
+	size_t len;
+	int n, iport = 0;
+	char buf[4096];
+	size_t prev_inbuf_len = 0;
+	size_t inbuf_len = 65536;
+	size_t read_len;
+	char *inbuf = malloc(inbuf_len);
+	char docbuf[4097];
+	int has_file_ext = 0;
+
+	if (!inbuf)
+		DIE("out of memory");
+	if (!(method = getenv("REQUEST_METHOD")))
+		DIE("REQUEST_METHOD was not set");
+	scheme = getenv("MAPPED_URI_SCHEME");
+	host = getenv("MAPPED_URI_HOST");
+	if (!scheme || !host)
+		DIE("Scheme or host expected");
+	username = getenv("MAPPED_URI_USERNAME");
+	password = getenv("MAPPED_URI_PASSWORD");
+	port = getenv("MAPPED_URI_PORT");
+	if (port)
+		iport = atoi(port);
+	else if (!strcmp(scheme, "http"))
+		iport = 80;
+	else if (!strcmp(scheme, "https"))
+		iport = 443;
+	docbuf[0] = '\0';
+	path = getenv("MAPPED_URI_PATH");
+	if (path && *path)
+		strncat(docbuf, path, sizeof(docbuf) - 1);
+	else
+		strcat(docbuf, "/");
+	has_file_ext = hasext(docbuf);
+	query = getenv("MAPPED_URI_QUERY");
+	if (query && *query) {
+		strncat(docbuf, "?", sizeof(docbuf) - 1);
+		strncat(docbuf, query, sizeof(docbuf) - 1);
+	}
+	u = fetchMakeURL(scheme, host, iport, docbuf, username, password);
+	if (!u)
+		DIE("Failed to create URL");
+	content_type = getenv("CONTENT_TYPE");
+	np = getenv("REQUEST_HEADERS");
+	while (np) {
+		p = np;
+		np = strstr(p, "\r\n");
+		if (!strncasecmp(p, "User-Agent: ", strlen("User-Agent: "))) {
+			p += strlen("User-Agent: ");
+			len = np ? np - p : strlen(p);
+			if (len >= sizeof(buf))
+				DIE("User agent string too long");
+			memcpy(&buf[0], p, len);
+			buf[len] = '\0';
+			setenv("HTTP_USER_AGENT", buf, 1);
+		} else if (!strncasecmp(p, "Accept: ", strlen("Accept: "))) {
+			p += strlen("Accept: ");
+			len = np ? np - p : strlen(p);
+			if (len >= sizeof(buf))
+				DIE("Accept string too long");
+			memcpy(&buf[0], p, len);
+			buf[len] = '\0';
+			setenv("HTTP_ACCEPT", buf, 1);
+		}
+		if (np)
+			np += 2; /* skip CRLF */
+	}
+	if (!isatty(STDIN_FILENO)) {
+		read_len = inbuf_len - 1 - prev_inbuf_len;
+		while ((n = fread(&inbuf[prev_inbuf_len], 1, read_len,
+				stdin) == read_len)) {
+			if (inbuf_len >= ((size_t)-1) / 2)
+				DIE("out of address space");
+			prev_inbuf_len = inbuf_len;
+			inbuf_len *= 2;
+			inbuf = realloc(inbuf, inbuf_len);
+			if (!inbuf)
+				DIE("out of memory");
+			read_len = inbuf_len - 1 - prev_inbuf_len;
+		}
+	} else {
+		n = 0;
+	}
+	inbuf[n] = '\0';
+	f = fetchReqHTTP(u, method, "", content_type, inbuf);
+	if (!f)
+		FDIE("Failed to open request");
+	/* Hack: print a HTML content type if there is no file extension.
+	 * If there is one, just let the buffer take care of detecting it. */
+	puts(has_file_ext ? "\n" : "Content-Type: text/html\n\n");
+	while ((n = fread(buf, 1, sizeof(buf), f)))
+		fwrite(buf, 1, n, stdout);
+}