summary refs log tree commit diff
diff options
context:
space:
mode:
authorwrmr2024-11-03 18:09:00 -0500
committerwrmr2024-11-03 18:09:00 -0500
commitcfcab548584953c1a7e40eef7b9a209d4e5d1402 (patch)
treed217fb57401e201f5cf8ca4039b0c013987aa2ba
parent7d43ad487e7358b3a013d223a8e3cb0bb5304f64 (diff)
add fetch_gopher
-rw-r--r--doc.h2
-rw-r--r--net.c119
-rw-r--r--net.h1
3 files changed, 119 insertions, 3 deletions
diff --git a/doc.h b/doc.h
index 972134b..01a8235 100644
--- a/doc.h
+++ b/doc.h
@@ -12,7 +12,7 @@ struct doc_line {
 
 enum doc_type {
 	DOC_UNKNOWN,
-	DOC_GOPHERDOC,
+	DOC_GOPHERMAP,
 	DOC_GEMTEXT,
 	DOC_PLAIN,
 };
diff --git a/net.c b/net.c
index 5d68714..6fbfa97 100644
--- a/net.c
+++ b/net.c
@@ -1,3 +1,11 @@
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include <stdlib.h>
+#include <errno.h>
 #include <string.h>
 #include <stdio.h>
 
@@ -41,6 +49,29 @@ int net_addr(const char *url, struct addr *adr, enum protocol prot_default) {
 			adr->host[adr->host_len++] = *url++;
 		}
 		adr->host[adr->host_len] = 0;
+
+		char *colon = strchr(adr->host, ':');
+		if (colon) {
+			errno = 0;
+			int port = strtoul(colon, NULL, 10);
+			if (errno) {
+				return -1;
+			} else {
+				adr->port = port;
+			}
+			*colon = 0;
+			adr->host_len = colon - adr->host;
+		} else {
+			switch (adr->prot) {
+			case PROT_GOPHER:
+				adr->port = 70;
+				break;
+			default:
+				adr->port = -1;
+				break;
+			}
+		}
+
 		if (*url && *url != '/') {
 			perr("hostname too long");
 			return -1;
@@ -60,7 +91,39 @@ int net_addr(const char *url, struct addr *adr, enum protocol prot_default) {
 	return 0;
 }
 
-static int file_fetch(const struct addr *adr, struct buf *buf, enum doc_type *doct) {
+static long
+net_hosttoaddr(const char *hostname)
+{
+	struct hostent *h;
+	struct in_addr **al;
+	h = gethostbyname(hostname);
+	if (!h) {
+		perr("host not found");
+		return -1;
+	}
+	al = (struct in_addr **)h->h_addr_list;
+	if (!al || !(*al))
+		perr("empty host address");
+	return (*al)->s_addr;
+}
+
+static int
+net_conntohost(const char *hostname, int port)
+{
+	struct sockaddr_in sai;
+	int s;
+	s = socket(AF_INET, SOCK_STREAM, 0);
+	if (!s)
+		return -1;
+	sai.sin_family = AF_INET;
+	sai.sin_port = htons(port);
+	sai.sin_addr.s_addr = net_hosttoaddr(hostname);
+	if (connect(s, (struct sockaddr *)&sai, sizeof(sai)))
+		return -1;
+	return s;
+}
+
+static int fetch_file(const struct addr *adr, struct buf *buf, enum doc_type *doct) {
 	FILE *f = fopen(adr->path, "r/o");
 	if (!f) {
 		perr("file not found");
@@ -77,10 +140,62 @@ static int file_fetch(const struct addr *adr, struct buf *buf, enum doc_type *do
 	return 0;
 }
 
+static int fetch_gopher(const struct addr *adr, struct buf *buf, enum doc_type *doct) {
+	const char *sel = adr->path;
+	size_t sel_len = adr->path_len;
+	if (sel_len > 0 && sel[0] == '/') {
+		sel_len--;
+		sel++;
+	}
+	if (sel_len > 0) {
+		switch (*sel) {
+		case '0':
+			*doct = DOC_PLAIN;
+			break;
+		case '1':
+			*doct = DOC_GOPHERMAP;
+			break;
+		default:
+			perr("invalid item type");
+			return -1;
+		}
+		sel++;
+		sel_len--;
+	} else {
+		*doct = DOC_GOPHERMAP;
+	}
+	int s = net_conntohost(adr->host, adr->port);
+	if (s < 0) {
+		return -1;
+	}
+	if (write(s, sel, sel_len) < 0
+	 || write(s, "\r\n", 2) < 0) {
+		perr("write failure");
+		return -1;
+	}
+	buf_init(buf, 64);
+	char inbuf[256];
+	for (;;) {
+		ssize_t n = read(s, inbuf, sizeof inbuf);
+		if (n < 0) {
+			perr("read error");
+			buf_free(buf);
+			close(s);
+			return -1;
+		}
+		if (!n) break;
+		buf_cat(buf, inbuf, n);
+	}
+	close(s);
+	return 0;
+}
+
 int net_fetch(const struct addr *adr, struct buf *buf, enum doc_type *doct) {
 	switch (adr->prot) {
 	case PROT_FILE:
-		return file_fetch(adr, buf, doct);
+		return fetch_file(adr, buf, doct);
+	case PROT_GOPHER:
+		return fetch_gopher(adr, buf, doct);
 	default:
 		perr("unsupported protocol");
 		return -1;
diff --git a/net.h b/net.h
index 64048be..a2ad569 100644
--- a/net.h
+++ b/net.h
@@ -19,6 +19,7 @@ struct addr {
 	char path[PATH_MAX + 1];
 	size_t host_len, path_len;
 	enum protocol prot;
+	int port;
 };
 
 int net_addr(const char *url, struct addr *adr, enum protocol prot_default);