Commit Diff


commit - 7527561b1106e58c4f926b259d87fd4e55d6be70
commit + 81839fee39ad184c28b20b13297027be9d10f200
blob - 60d416a534b2e9e358fe933856e74ea20325bcc0
blob + eadce1c536e0178454f2f2be0e5487d97b32b0de
--- Makefile.am
+++ Makefile.am
@@ -27,6 +27,7 @@ telescope_SOURCES =	cmd.c			\
 			parser.c		\
 			parser.h		\
 			parser_gemtext.c	\
+			parser_gophermap.c	\
 			parser_textplain.c	\
 			sandbox.c		\
 			telescope.c		\
blob - 43423133b915661820713d65a7a6f48c4766025b
blob + b5e42ce1ddf4465a8052e9cc624ea371d0b092ff
--- parser.h
+++ parser.h
@@ -27,6 +27,9 @@ int	 parser_foreach_line(struct parser*, const char*, 
 /* parser_gemtext.c */
 void	 gemtext_initparser(struct parser*);
 
+/* parser_gophermap.c */
+void	 gophermap_initparser(struct parser *);
+
 /* parser_textplain.c */
 void	 textplain_initparser(struct parser*);
 
blob - /dev/null
blob + 9b05562e49d6b62f6466864e7b7cbf7057f0bc40 (mode 644)
--- /dev/null
+++ parser_gophermap.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "parser.h"
+
+struct gm_selector {
+	char		 type;
+	const char	*ds;
+	const char	*selector;
+	const char	*addr;
+	const char	*port;
+};
+
+static void	gm_parse_selector(char *, struct gm_selector *);
+
+static int	gm_parse(struct parser *, const char *, size_t);
+static int	gm_foreach_line(struct parser *, const char *, size_t);
+static int	gm_free(struct parser *);
+
+void
+gophermap_initparser(struct parser *p)
+{
+	memset(p, 0, sizeof(*p));
+
+	p->name = "gophermap";
+	p->parse = &gm_parse;
+	p->free = &gm_free;
+}
+
+static void
+gm_parse_selector(char *line, struct gm_selector *s)
+{
+	s->type = *line++;
+	s->ds = line;
+
+	if ((line = strchr(line, '\t')) == NULL)
+		return;
+        *line++ = '\0';
+	s->selector = line;
+
+	if ((line = strchr(line, '\t')) == NULL)
+		return;
+	*line++ = '\0';
+	s->addr = line;
+
+	if ((line = strchr(line, '\t')) == NULL)
+		return;
+	*line++ = '\0';
+	s->port = line;
+}
+
+static int
+gm_parse(struct parser *p, const char *buf, size_t size)
+{
+	return parser_foreach_line(p, buf, size, gm_foreach_line);
+}
+
+static inline int
+emit_line(struct parser *p, enum line_type type, struct gm_selector *s)
+{
+	struct line *l;
+	char buf[LINE_MAX], b[2] = {0};
+
+	if ((l = calloc(1, sizeof(*l))) == NULL)
+                goto err;
+
+	if ((l->line = strdup(s->ds)) == NULL)
+		goto err;
+
+	switch (l->type = type) {
+	case LINE_LINK:
+		if (s->type == 'h' && has_prefix(s->selector, "URL:")) {
+			strlcpy(buf, s->selector+4, sizeof(buf));
+		} else {
+			strlcpy(buf, "gopher://", sizeof(buf));
+			strlcat(buf, s->addr, sizeof(buf));
+			strlcat(buf, ":", sizeof(buf));
+			strlcat(buf, s->port, sizeof(buf));
+			strlcat(buf, "/", sizeof(buf));
+			b[0] = s->type;
+			strlcat(buf, b, sizeof(buf));
+			if (*s->selector != '/')
+				strlcat(buf, "/", sizeof(buf));
+			strlcat(buf, s->selector, sizeof(buf));
+		}
+
+		if ((l->alt = strdup(buf)) == NULL)
+			goto err;
+		break;
+
+	default:
+		break;
+	}
+
+	if (TAILQ_EMPTY(&p->head))
+		TAILQ_INSERT_HEAD(&p->head, l, lines);
+	else
+		TAILQ_INSERT_TAIL(&p->head, l, lines);
+
+	return 1;
+
+err:
+	if (l != NULL) {
+		free(l->line);
+		free(l->alt);
+		free(l);
+	}
+	return 0;
+}
+
+static int
+gm_foreach_line(struct parser *p, const char *line, size_t linelen)
+{
+	char buf[LINE_MAX] = {0};
+	struct gm_selector s = {0};
+
+	memcpy(buf, line, MIN(sizeof(buf)-1, linelen));
+	gm_parse_selector(buf, &s);
+
+	switch (s.type) {
+	case '0':	/* text file */
+	case '1':	/* gopher submenu */
+	case '2':	/* CCSO nameserver */
+	case '4':	/* binhex-encoded file */
+	case '5':	/* DOS file */
+	case '6':	/* uuencoded file */
+	case '7':	/* full-text search */
+	case '8':	/* telnet */
+	case '9':	/* binary file */
+	case '+':	/* mirror or alternate server */
+	case 'g':	/* gif */
+	case 'I':	/* image */
+	case 'T':	/* telnet 3270 */
+	case ':':	/* gopher+: bitmap image */
+	case ';':	/* gopher+: movie file */
+	case 'd':	/* non-canonical: doc */
+	case 'h':	/* non-canonical: html file */
+	case 's':	/* non-canonical: sound file */
+		if (!emit_line(p, LINE_LINK, &s))
+			return 0;
+		break;
+
+		break;
+
+	case 'i':	/* non-canonical: message */
+		if (!emit_line(p, LINE_TEXT, &s))
+			return 0;
+		break;
+
+	case '3':	/* error code */
+		if (!emit_line(p, LINE_QUOTE, &s))
+			return 0;
+		break;
+	}
+
+	return 1;
+}
+
+static int
+gm_free(struct parser *p)
+{
+	/* flush the buffer */
+	if (p->len != 0)
+		gm_foreach_line(p, p->buf, p->len);
+
+	free(p->buf);
+
+	return 1;
+}
blob - 7c61398338fc18e8e7c29d0793db9d97a11daa74
blob + bc3bfcc362be9577969382cb8717cbec7636da58
--- telescope.c
+++ telescope.c
@@ -636,15 +636,29 @@ load_gemini_url(struct tab *tab, const char *url)
 static void
 load_gopher_url(struct tab *tab, const char *url)
 {
-	struct get_req	req;
+	struct get_req	 req;
+	const char	*path;
 
 	memset(&req, 0, sizeof(req));
 	strlcpy(req.host, tab->uri.host, sizeof(req.host));
 	strlcpy(req.port, tab->uri.port, sizeof(req.host));
 
-	/* cheat a bit by considering gophermaps text */
-	textplain_initparser(&tab->buffer.page);
-	make_request(tab, &req, PROTO_GOPHER, tab->uri.path);
+	path = tab->uri.path;
+	if (!strcmp(path, "/") || *path == '\0') {
+		/* expect the top directory to be a gophermap */
+		gophermap_initparser(&tab->buffer.page);
+	} else if (has_prefix(path, "/1/")) {
+		/* gophermap menu/submenu */
+		gophermap_initparser(&tab->buffer.page);
+		path += 3;
+	} else if (has_prefix(path, "/0/")) {
+		textplain_initparser(&tab->buffer.page);
+		path += 3;
+	} else {
+		return;
+	}
+
+	make_request(tab, &req, PROTO_GOPHER, path);
 }
 
 static void