ref: 3987505e1b20d271b2838211dc2631d380dcf68c
dir: /main.c/
#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, *t, *u; int len, wait; Biobuf out; wait = 0; ARGBEGIN{ case 'w': wait = 1; break; }ARGEND; if(argc < 1){ fprint(2, "usage: gemnine [-w] URL\n"); exits("usage"); } quotefmtinstall(); Binit(&out, 1, OWRITE); if((r = request(argv[0])) != 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; if(s[0] == '=' && s[1] == '>'){ t = s + 2; while(isspace(*t)) t++; u = t; if((t = strpbrk(t, " :/")) == nil || t[0] != ':' || t[1] != '/' || t[2] != '/') /* relative URL */ Bprint(&out, "=> gemini://%s:%s/%s\n", r->url->server, r->url->port, u); else Bprint(&out, "%s\n", s); }else{ Bprint(&out, "%s\n", s); } free(s); } } freeresponse(r); }else{ fprint(2, "%s: %r\n", argv[1]); if(wait) read(0, &wait, 4); exits("failed"); } Bflush(&out); if(wait) read(0, &wait, 4); exits(nil); }