Commit Diff


commit - ea8874f15243d7c4d6a28836a26ce3724aa1e037
commit + c9dc4bc68ef81f382a23f0ce29b0bada6dacc9c1
blob - ef100583d3b7bd999204b147ad690a6d65a212a5
blob + 5876ccc00df7b8ed5658abe9d9b655d5d7260bb0
--- fcgi.c
+++ fcgi.c
@@ -590,6 +590,18 @@ clt_write(struct client *clt, const uint8_t *buf, size
 	}
 
 	return (0);
+}
+
+int
+clt_putc(struct client *clt, char ch)
+{
+	return (clt_write(clt, &ch, 1));
+}
+
+int
+clt_puts(struct client *clt, const char *str)
+{
+	return (clt_write(clt, str, strlen(str)));
 }
 
 int
blob - fc08077639750af2413c0d69504d7e94fe50fb01
blob + 7507ff23526f5708d749d5b7341b5e50181f38ab
--- galileo.h
+++ galileo.h
@@ -65,6 +65,8 @@ struct client {
 	struct tls		*clt_ctx;
 	struct bufferevent	*clt_bev;
 	int			 clt_headersdone;
+	int			 clt_translate;
+	int			 clt_inpre;
 
 	char			 clt_buf[1024];
 	size_t			 clt_buflen;
@@ -140,6 +142,8 @@ void	 fcgi_accept(int, short, void *);
 void	 fcgi_read(struct bufferevent *, void *);
 void	 fcgi_write(struct bufferevent *, void *);
 void	 fcgi_error(struct bufferevent *, short error, void *);
+int	 clt_putc(struct client *, char);
+int	 clt_puts(struct client *, const char *);
 int	 clt_write_bufferevent(struct client *, struct bufferevent *);
 int	 clt_flush(struct client *);
 int	 clt_write(struct client *, const uint8_t *, size_t);
blob - 52a332a32bb1b4a8aee7973e0430318390c3945e
blob + c4d84f709240345cf2bdbc495effb636c7eaa297
--- proxy.c
+++ proxy.c
@@ -53,6 +53,7 @@ void	proxy_init(struct privsep *, struct privsep_proc 
 int	proxy_launch(struct galileo *);
 void	proxy_inflight_dec(const char *);
 int	proxy_dispatch_parent(int, struct privsep_proc *, struct imsg *);
+void	proxy_translate_gemtext(struct client *);
 void	proxy_resolved(struct asr_result *, void *);
 void	proxy_connect(int, short, void *);
 void	proxy_read(struct bufferevent *, void *);
@@ -146,11 +147,174 @@ proxy_dispatch_parent(int fd, struct privsep_proc *p, 
 	default:
 		log_warnx("unknown message %d", imsg->hdr.type);
 		return (-1);
+	}
+
+	return (0);
+}
+
+static inline int
+printurl(struct client *clt, const char *str)
+{
+	for (; *str; ++str) {
+		switch (*str) {
+		case ' ':
+		case '\t':
+		case '\'':
+		case '\\':
+			if (clt_printf(clt, "%2X", (unsigned char)*str) == -1)
+				return (-1);
+			break;
+		default:
+			if (clt_putc(clt, *str) == -1)
+				return (-1);
+			break;
+		}
+	}
+
+	return (0);
+}
+
+static inline int
+htmlescape(struct client *clt, const char *str)
+{
+	int r;
+
+	for (; *str; ++str) {
+		switch (*str) {
+		case '<':
+			r = clt_puts(clt, "&lt;");
+			break;
+		case '>':
+			r = clt_puts(clt, "&gt;");
+			break;
+		case '&':
+			r = clt_puts(clt, "&amp;");
+			break;
+		default:
+			r = clt_putc(clt, *str);
+			break;
+		}
+
+		if (r == -1)
+			return (-1);
+	}
+
+	return (0);
+}
+
+static int
+gemtext_translate_line(struct client *clt, char *line)
+{
+	/* preformatted line / closing */
+	if (clt->clt_inpre) {
+		if (!strncmp(line, "```", 3)) {
+			clt->clt_inpre = 0;
+			return (clt_puts(clt, "</pre>"));
+		}
+
+		if (htmlescape(clt, line) == -1)
+			return (-1);
+		return (clt_putc(clt, '\n'));
+	}
+
+	/* pre opening */
+	if (!strncmp(line, "```", 3)) {
+		clt->clt_inpre = 1;
+		return (clt_puts(clt, "<pre>"));
+	}
+
+	/* citation block */
+	if (*line == '>') {
+		if (clt_puts(clt, "<blockquote>") == -1 ||
+		    htmlescape(clt, line + 1) == -1 ||
+		    clt_puts(clt, "</blockquote>") == -1)
+			return (-1);
+		return (0);
+	}
+
+	/* headings */
+	if (!strncmp(line, "###", 3)) {
+		if (clt_puts(clt, "<h3>") == -1 ||
+		    htmlescape(clt, line + 3) == -1 ||
+		    clt_puts(clt, "</h3>") == -1)
+			return (-1);
+		return (0);
+	}
+	if (!strncmp(line, "##", 2)) {
+		if (clt_puts(clt, "<h2>") == -1 ||
+		    htmlescape(clt, line + 2) == -1 ||
+		    clt_puts(clt, "</h2>") == -1)
+			return (-1);
+		return (0);
 	}
+	if (!strncmp(line, "#", 1)) {
+		if (clt_puts(clt, "<h1>") == -1 ||
+		    htmlescape(clt, line + 1) == -1 ||
+		    clt_puts(clt, "</h1>") == -1)
+			return (-1);
+		return (0);
+	}
 
+	/* bullet -- XXX: group */
+	if (!strncmp(line, "* ", 2)) {
+		if (clt_puts(clt, "<ul><li>") == -1 ||
+		    htmlescape(clt, line + 2) == -1 ||
+		    clt_puts(clt, "</li></ul>") == -1)
+			return (-1);
+		return (0);
+	}
+
+	/* link -- XXX: group */
+	if (!strncmp(line, "=>", 2)) {
+		char *label;
+
+		line += 2;
+		line += strspn(line, " \t");
+
+		label = line + strcspn(line, " \t");
+		if (*label == '\0')
+			label = line;
+		else
+			*label++ = '\0';
+
+		if (clt_puts(clt, "<p><a href='") == -1 ||
+		    printurl(clt, line) == -1 ||
+		    clt_puts(clt, "'>") == -1 ||
+		    htmlescape(clt, label) == -1 ||
+		    clt_puts(clt, "</a></p>") == -1)
+			return (-1);
+		return (0);
+	}
+
+	/* paragraph */
+	if (clt_puts(clt, "<p>") == -1 ||
+	    htmlescape(clt, line) == -1 ||
+	    clt_puts(clt, "</p>") == -1)
+		return (-1);
 	return (0);
 }
 
+void
+proxy_translate_gemtext(struct client *clt)
+{
+	struct bufferevent	*bev = clt->clt_bev;
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	char			*line;
+	size_t			 len;
+	int			 r;
+
+	for (;;) {
+		line = evbuffer_readln(src, &len, EVBUFFER_EOL_ANY);
+		if (line == NULL)
+			return;
+
+		r = gemtext_translate_line(clt, line);
+		free(line);
+		if (r == -1)
+			return;
+	}
+}
+
 static struct proxy_config *
 proxy_server_match(struct galileo *env, struct client *clt)
 {
@@ -346,13 +510,17 @@ proxy_read(struct bufferevent *bev, void *d)
 {
 	struct client		*clt = d;
 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	const char		*ctype;
 	char			*hdr;
 	size_t			 len;
 	int			 code;
 
 	if (clt->clt_headersdone) {
 	copy:
-		clt_write_bufferevent(clt, bev);
+		if (clt->clt_translate)
+			proxy_translate_gemtext(clt);
+		else
+			clt_write_bufferevent(clt, bev);
 		return;
 	}
 
@@ -381,12 +549,25 @@ proxy_read(struct bufferevent *bev, void *d)
 		return;
 	}
 
-	if (clt_printf(clt, "Content-Type: %s\r\n", &hdr[4]) == -1)
+	if (!strncmp(&hdr[3], "text/gemini", 11)) {
+		ctype = "text/html; charset=utf8";
+		clt->clt_translate = 1;
+	} else
+		ctype = &hdr[3];
+
+	if (clt_printf(clt, "Content-Type: %s\r\n", ctype) == -1)
 		return;
 	if (clt_printf(clt, "\r\n") == -1)
 		return;
 
 	clt->clt_headersdone = 1;
+
+	if (clt->clt_translate) {
+		if (clt_puts(clt, "<!doctype html>"
+		    "<html><head></head><body>") == -1)
+			return;
+	}
+
 	goto copy;
 }
 
@@ -412,6 +593,9 @@ proxy_error(struct bufferevent *bev, short err, void *
 			return;
 		if (clt_printf(clt, "Proxy error\n") == -1)
 			return;
+	} else if (status == 0) {
+		if (clt_puts(clt, "</body></html>") == -1)
+			return;
 	}
 
 	fcgi_end_request(clt, status);