shithub: gemnine

Download patch

ref: 291e2955f505e3167a725e8b00371e4ef597ecef
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Sun May 17 09:07:27 EDT 2020

a very basic client

--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,1 @@
+Public domain.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,11 @@
+# gemnine
+
+WIP.
+
+Gemini browser for Plan 9.
+
+WTH is Gemini??? See https://gemini.circumlunar.space/
+
+As of now some basics are implemented to handle errors and print the
+body to stdout, the plan is to add a separate GUI program to display
+the pages in a better way.
--- /dev/null
+++ b/main.c
@@ -1,0 +1,201 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+#include <bio.h>
+#include <ctype.h>
+
+typedef struct Url Url;
+typedef struct Response Response;
+
+struct Url {
+	char *url;
+	char *server;
+	char *port;
+};
+
+struct Response {
+	Url *url;
+	char *mime;
+	char *prompt;
+	int status;
+	Biobuf body;
+	int fd;
+};
+
+Url *
+parseurl(char *url)
+{
+	char *server, *port, *s, *e;
+	Url *u;
+
+	url = strdup(url);
+	if((s = strpbrk(url, ":/")) != nil && s[0] == ':' && s[1] == '/' && s[2] == '/'){
+		server = s + 3;
+	}else{
+		s = smprint("gemini://%s", url);
+		free(url);
+		url = s;
+		server = s + 9;
+	}
+
+	port = strdup("1965");
+	if((e = strpbrk(server, ":/")) != nil){
+		s = mallocz(e-server+1, 1);
+		memmove(s, server, e-server);
+		server = s;
+		if(*e == ':'){
+			port = strdup(e+1);
+			if((e = strchr(port, '/')) != nil)
+				*e = 0;
+		}
+	}else{
+		server = strdup(server);
+	}
+
+	u = calloc(1, sizeof(*u));
+	u->url = url;
+	u->server = server;
+	u->port = port;
+
+	return u;
+}
+
+void
+freeurl(Url *u)
+{
+	if(u != nil){
+		free(u->url);
+		free(u->server);
+		free(u->port);
+		free(u);
+	}
+}
+
+void
+freeresponse(Response *r)
+{
+	if(r != nil){
+		close(r->fd);
+		freeurl(r->url);
+		free(r->mime);
+		free(r->prompt);
+		free(r);
+	}
+}
+
+Response *
+request(char *url)
+{
+	Thumbprint *th;
+	Response *r;
+	char *s;
+	TLSconn conn;
+	int ok, len, oldfd;
+
+	r = calloc(1, sizeof(*r));
+	r->fd = -1;
+	if((r->url = parseurl(url)) == nil)
+		goto err;
+
+	if((r->fd = dial(netmkaddr(r->url->server, "tcp", r->url->port), nil, nil, nil)) < 0)
+		goto err;
+	th = initThumbprints("/sys/lib/ssl/gemini", nil, "x509");
+	memset(&conn, 0, sizeof(conn));
+	conn.serverName = r->url->server;
+	oldfd = r->fd;
+	r->fd = tlsClient(oldfd, &conn);
+	close(oldfd);
+	if(r->fd < 0)
+		goto err;
+
+	/* FIXME find a way to trust on the first run */
+	if(th != nil){
+		ok = okCertificate(conn.cert, conn.certlen, th);
+		freeThumbprints(th);
+		if(!ok){
+			//fprint(2, "echo 'x509 %r server=%s' >>/sys/lib/ssl/gemini\n", r->url->server);
+			//werrstr("untrusted cert");
+			//goto err;
+		}
+	}
+
+	fprint(r->fd, "%s\r\n", r->url->url);
+	Binit(&r->body, r->fd, OREAD);
+	if((s = Brdstr(&r->body, '\n', 1)) == nil){
+		werrstr("EOF");
+		goto err;
+	}
+	if((len = Blinelen(&r->body)) > 0)
+		s[len-1] = 0;
+	if(s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9'){
+		werrstr("invalid status");
+		goto err;
+	}
+	r->status = 10*(int)(s[0]-'0') + s[1] - '0';
+	s += 2;
+	while(isspace(*s))
+		s++;
+
+	if(r->status >= 10 && r->status < 20){ /* input */
+		r->prompt = strdup(s);
+	}else if(r->status >= 20 && r->status < 30){ /* success */
+		r->mime = strdup(s[0] ? s : "text/gemini");
+	}else if(r->status >= 30 && r->status < 40){ /* redirect */
+		freeresponse(r);
+		r = request(s);
+	}else if(r->status >= 40 && r->status < 50){
+		werrstr("temporary failure: %s", s);
+		goto err;
+	}else if(r->status >= 50 && r->status < 60){
+		werrstr("permanent failure: %s", s);
+		goto err;
+	}else if(r->status >= 60 && r->status < 70){
+		werrstr("client cert required: %s", s);
+		goto err;
+	}
+
+	return r;
+
+err:
+	freeresponse(r);
+	return nil;
+}
+
+void
+main(int argc, char **argv)
+{
+	Response *r;
+	char *s;
+	int len;
+
+	if(argc < 2){
+		fprint(2, "usage: gemnine URL\n");
+		exits("usage");
+	}
+
+	quotefmtinstall();
+
+	if((r = request(argv[1])) != nil){
+		if(r->mime != nil && strncmp(r->mime, "text/", 5) != 0){
+			/* FIXME handle in a better way */
+			if(r->mime != nil)
+				fprint(2, "MIME %s\n", r->mime);
+		}else if(r->prompt != nil){
+			/* FIXME no idea */
+			fprint(2, "INPUT %s\n", r->prompt);
+		}else{
+			while((s = Brdstr(&r->body, '\n', 1)) != nil){
+				if((len = Blinelen(&r->body)) > 0)
+					s[len] = 0;
+				print("%s\n", s);
+				free(s);
+			}
+		}
+		freeresponse(r);
+	}else{
+		fprint(2, "%s: %r\n", argv[1]);
+		exits("failed");
+	}
+
+	exits(nil);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,17 @@
+</$objtype/mkfile
+
+TARG=gemnine
+
+BIN=/$objtype/bin
+
+OFILES=\
+	main.$O\
+
+UPDATE=\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	mkfile\
+
+default:V:	all
+
+</sys/src/cmd/mkone