summary refs log tree commit diff
path: root/net.c
diff options
context:
space:
mode:
Diffstat (limited to 'net.c')
-rw-r--r--net.c119
1 files changed, 117 insertions, 2 deletions
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;