ref: 1fe76cb758c2be655ff778499e002b66d756e4dc
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;
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;
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 */
print("=> gemini://%s:%s/%s\n", r->url->server, r->url->port, u);
else
print("%s\n", s);
}else{
print("%s\n", s);
}
free(s);
}
}
freeresponse(r);
}else{
fprint(2, "%s: %r\n", argv[1]);
exits("failed");
}
exits(nil);
}