ref: fbf4ef21fadd62b526cb83c656c8dfaa4ba5f8b0
dir: /main.c/
#include <u.h> #include <libc.h> #include <libsec.h> #include <bio.h> #include <ctype.h> #include <plumb.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; int fd; }; #pragma varargck type "E" char* char * urlto(Url *url, char *u) { char *e, *trail; int len; if((len = strlen(u)) < 1) return ""; trail = (len > 1 && u[len-1] == '/') ? "/" : ""; if(*u == '/'){ if(u[1] == '/') /* no protocol */ return smprint("gemini://%s%s", cleanname(u+2), trail); /* absolute url, no scheme */ return strcmp(url->port, "1965") == 0 ? smprint("gemini://%s%s%s", url->server, cleanname(u), trail) : smprint("gemini://%s:%s%s%s", url->server, url->port, cleanname(u), trail); } /* with scheme */ if((e = strpbrk(u, ":/")) != nil && e[0] == ':' && e[1] == '/' && e[2] == '/'){ e[2] = 0; e = cleanname(e+3); return smprint("%s/%s%s", u, e, trail); } /* chars not allowed */ if(strpbrk(u, ":") != nil) return strdup(u); /* relative, no scheme */ len = strlen(url->url); if(url->url[len-1] == '/'){ /* easy */ u = smprint("%s%s%s", url->url, u, trail); }else{ /* replace the last element */ if((e = strrchr(url->url, '/')) != nil && e[-1] != '/') len = e - url->url; u = smprint("%.*s/%s%s", len, url->url, u, trail); } if((e = strchr(strchr(u, ':') + 3, '/')) != nil) cleanname(e); return u; } 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, buf[1024]; TLSconn conn; int i, 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){ werrstr("dial: %r"); 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){ werrstr("tls: %r"); 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); for(len = 0; len < sizeof(buf)-1; len++){ if((i = read(r->fd, buf+len, 1)) < 0){ werrstr("read: %r"); goto err; } if(i == 0 || buf[len] == '\n') break; } s = buf; s[len] = 0; for(len--; len >= 0 && (s[len] == '\r' || s[len] == '\n'); len--) s[len] = 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 */ s = urlto(r->url, s); freeresponse(r); r = request(s); free(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: if(r != nil && r->url != nil) werrstr("%q: %r", r->url->url); freeresponse(r); return nil; } int Efmt(Fmt *f) { char *s; s = va_arg(f->args, char*); for(; *s; s++){ if(*s == '%' && isxdigit(s[1]) && isxdigit(s[2])){ fmtprint(f, "%%%c%c", toupper(s[1]), toupper(s[2])); s += 2; }else if(isalnum(*s) || strchr(".-_~!$&'()*,;=/:@ \n", *s) == nil){ fmtprint(f, "%c", *s); }else{ fmtprint(f, "%%%.2X", *s & 0xff); } } return 0; } char * readall(int fd) { char *s; int n, sz, bufsz; bufsz = 1023; s = nil; for(sz = 0;; sz += n){ if(bufsz-sz < 1024){ bufsz *= 2; s = realloc(s, bufsz); } if((n = read(fd, s+sz, bufsz-sz-1)) < 1) break; } s[sz] = 0; if(sz > 1 && s[sz-1] == '\n') s[sz-1] = 0; return s; } void page(Response *r) { if(rfork(RFPROC|RFFDG|RFNOTEG|RFNOWAIT) == 0){ char tmp[32] = "/tmp/gem9XXXXXXXXXXX", *cmd; mktemp(tmp); cmd = smprint("cat >%s >[2]/dev/null; page -w %s; rm %s", tmp, tmp, tmp); dup(r->fd, 0); close(r->fd); execl("/bin/rc", "rc", "-c", cmd, nil); } } void play(Response *r) { int wfd; char *wsys, tmp[64]; if(rfork(RFPROC|RFFDG|RFNOTEG|RFNOWAIT) == 0){ snprint(tmp, sizeof(tmp), "new -pid %d -dx %d -dy %d", getpid(), 640, 480); if ((wsys = getenv("wsys")) == nil) exits("no wsys"); if ((wfd = open(wsys, ORDWR)) < 0 || mount(wfd, -1, "/mnt/wsys", MREPL, tmp) < 0 || bind("/mnt/wsys", "/dev", MBEFORE) < 0){ exits("wsys: %r"); } dup(r->fd, 0); close(r->fd); execl("/bin/play", "play", nil); } } void main(int argc, char **argv) { Response *r; char *s, *t, *u, *url; int len, wait, pl, fd; Plumbmsg *m; Biobuf out, body; wait = 0; ARGBEGIN{ case 'w': wait = 1; break; }ARGEND; if(!wait && argc < 1){ fprint(2, "usage: gemnine [-w] [URL]\n"); exits("usage"); } fmtinstall('E', Efmt); quotefmtinstall(); Binit(&out, 1, OWRITE); pl = -1; nexturl: url = nil; if(wait){ if(pl >= 0 || (pl = plumbopen("gemini", OREAD)) >= 0){ if((m = plumbrecv(pl)) != nil){ url = strdup(m->data); plumbfree(m); }else{ exits(nil); } }else{ sysfatal("plumbopen: %r"); } }else{ url = strdup(argv[0]); } nextreq: if((r = request(url)) != nil){ if(r->mime != nil && strncmp(r->mime, "text/", 5) != 0){ if(strncmp(r->mime, "image/", 6) == 0 || strcmp(r->mime, "application/pdf") == 0) page(r); else if(strncmp(r->mime, "audio/", 6) == 0) play(r); else fprint(2, "unsupported MIME %q\n", r->mime); }else if(r->prompt != nil){ if(wait) close(open("/dev/text", OWRITE|OTRUNC)); if((fd = open("/dev/consctl", OWRITE)) >= 0){ write(fd, "holdon", 6); print("%s\n", r->prompt); s = readall(0); free(url); url = smprint("%s?%E", r->url->url, s); free(s); freeresponse(r); close(fd); goto nextreq; }else{ fprint(2, "%r\n"); } }else{ if(wait) close(open("/dev/text", OWRITE|OTRUNC)); Binit(&body, r->fd, OREAD); while((s = Brdstr(&body, '\n', 1)) != nil){ if((len = Blinelen(&body)) > 0) s[len] = 0; for(len--; len >= 0 && (s[len] == '\r' || s[len] == '\n'); len--) s[len] = 0; if(s[0] == '=' && s[1] == '>'){ u = s + 2; while(isspace(*u)) u++; if((t = strpbrk(u, " \t")) != nil) *t++ = 0; else t = ""; u = urlto(r->url, u); Bprint(&out, "→ %s %s\n", u, t); free(u); }else{ Bprint(&out, "%s\n", s); } free(s); } } freeresponse(r); }else{ fprint(2, "%r\n"); if(!wait) exits("failed"); } Bflush(&out); if(wait) goto nexturl; exits(nil); }