ref: 54f4cf61dada165e94b3cd61f72a7003092e9867
dir: /main.c/
#include <u.h>
#include <libc.h>
#include <libsec.h>
#include <bio.h>
#include <ctype.h>
#include <plumb.h>
#include "gemnine.h"
typedef struct Response Response;
struct Response {
Url *url;
char *mime;
char *prompt;
int status;
int fd;
};
#pragma varargck type "E" char*
void
freeresponse(Response *r)
{
if(r != nil){
close(r->fd);
freeurl(r->url);
free(r->mime);
free(r->prompt);
free(r);
}
}
Response *
request(Url *url)
{
Thumbprint *th;
Response *r;
char *s, buf[1024], *port;
TLSconn conn;
int i, ok, len, oldfd;
Url *u;
r = calloc(1, sizeof(*r));
r->fd = -1;
r->url = url;
if((port = url->port) == nil)
port = "1965";
if((r->fd = dial(netmkaddr(url->host, "tcp", 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->host;
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->full);
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 = estrdup(s);
}else if(r->status >= 20 && r->status < 30){ /* success */
r->mime = estrdup(s[0] ? s : "text/gemini");
}else if(r->status >= 30 && r->status < 40){ /* redirect */
if((u = urlparse(r->url, s)) == nil){
werrstr("invalid redirect url");
goto err;
}
freeresponse(r);
if((r = request(u)) == nil)
freeurl(u);
}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("%U: %r", r->url);
freeresponse(r);
return nil;
}
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 *url, *x;
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");
}
quotefmtinstall();
fmtinstall('U', Ufmt);
fmtinstall('N', Nfmt);
fmtinstall(']', Mfmt);
fmtinstall('E', Efmt);
fmtinstall('[', encodefmt);
fmtinstall('H', encodefmt);
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 = urlparse(nil, estrdup(m->data));
plumbfree(m);
}else{
exits(nil);
}
}else{
sysfatal("plumbopen: %r");
}
}else{
url = urlparse(nil, estrdup(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);
t = smprint("%s?%E", r->url->full, s);
free(s);
url = urlparse(nil, t);
free(t);
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 = "";
x = urlparse(r->url, u);
Bprint(&out, "→ %U %s\n", x, t);
freeurl(x);
}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);
}