ref: 54f4cf61dada165e94b3cd61f72a7003092e9867
parent: 78425573e39c42b2624834ad832f804996aa32d4
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Mon Aug 24 05:53:59 EDT 2020
use webfs url parsing
--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,2 @@
+[a0125678vqki].out
+*.[o0125678vqki]
--- /dev/null
+++ b/gemnine.h
@@ -1,0 +1,28 @@
+typedef struct Url Url;
+
+struct Url
+{+ char *full;
+ char *scheme;
+ char *user;
+ char *pass;
+ char *host;
+ char *port;
+ char *path;
+ char *query;
+ char *fragment;
+};
+
+#pragma varargck type "U" Url*
+
+int Efmt(Fmt*);
+int Nfmt(Fmt*);
+int Mfmt(Fmt*);
+int Ufmt(Fmt *f);
+Url *urlparse(Url *from, char *s);
+int matchurl(Url *u, Url *s);
+void freeurl(Url *u);
+char *Upath(Url *u);
+
+void *emalloc(int n);
+char *estrdup(char *s);
--- a/main.c
+++ b/main.c
@@ -4,16 +4,10 @@
#include <bio.h>
#include <ctype.h>
#include <plumb.h>
+#include "gemnine.h"
-typedef struct Url Url;
typedef struct Response Response;
-struct Url {- char *url;
- char *server;
- char *port;
-};
-
struct Response {Url *url;
char *mime;
@@ -24,102 +18,7 @@
#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){@@ -132,26 +31,28 @@
}
Response *
-request(char *url)
+request(Url *url)
{Thumbprint *th;
Response *r;
- char *s, buf[1024];
+ char *s, buf[1024], *port;
TLSconn conn;
int i, ok, len, oldfd;
+ Url *u;
r = calloc(1, sizeof(*r));
r->fd = -1;
- if((r->url = parseurl(url)) == nil)
- goto err;
+ r->url = url;
- if((r->fd = dial(netmkaddr(r->url->server, "tcp", r->url->port), nil, nil, nil)) < 0){+ 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->server;
+ conn.serverName = r->url->host;
oldfd = r->fd;
r->fd = tlsClient(oldfd, &conn);
close(oldfd);
@@ -171,7 +72,7 @@
}
}
- fprint(r->fd, "%s\r\n", r->url->url);
+ 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");@@ -195,14 +96,17 @@
s++;
if(r->status >= 10 && r->status < 20){ /* input */- r->prompt = strdup(s);
+ r->prompt = estrdup(s);
}else if(r->status >= 20 && r->status < 30){ /* success */- r->mime = strdup(s[0] ? s : "text/gemini");
+ r->mime = estrdup(s[0] ? s : "text/gemini");
}else if(r->status >= 30 && r->status < 40){ /* redirect */- s = urlto(r->url, s);
+ if((u = urlparse(r->url, s)) == nil){+ werrstr("invalid redirect url");+ goto err;
+ }
freeresponse(r);
- r = request(s);
- free(s);
+ if((r = request(u)) == nil)
+ freeurl(u);
}else if(r->status >= 40 && r->status < 50){ werrstr("temporary failure: %s", s);goto err;
@@ -218,30 +122,11 @@
err:
if(r != nil && r->url != nil)
- werrstr("%q: %r", r->url->url);+ werrstr("%U: %r", r->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)
{@@ -301,7 +186,8 @@
main(int argc, char **argv)
{Response *r;
- char *s, *t, *u, *url;
+ char *s, *t, *u;
+ Url *url, *x;
int len, wait, pl, fd;
Plumbmsg *m;
Biobuf out, body;
@@ -318,8 +204,14 @@
exits("usage");}
- fmtinstall('E', Efmt);quotefmtinstall();
+ fmtinstall('U', Ufmt);+ fmtinstall('N', Nfmt);+ fmtinstall(']', Mfmt);+ fmtinstall('E', Efmt);+ fmtinstall('[', encodefmt);+ fmtinstall('H', encodefmt);+
Binit(&out, 1, OWRITE);
pl = -1;
@@ -328,7 +220,7 @@
if(wait){ if(pl >= 0 || (pl = plumbopen("gemini", OREAD)) >= 0){ if((m = plumbrecv(pl)) != nil){- url = strdup(m->data);
+ url = urlparse(nil, estrdup(m->data));
plumbfree(m);
}else{exits(nil);
@@ -337,7 +229,7 @@
sysfatal("plumbopen: %r");}
}else{- url = strdup(argv[0]);
+ url = urlparse(nil, estrdup(argv[0]));
}
nextreq:
@@ -357,8 +249,10 @@
print("%s\n", r->prompt);s = readall(0);
free(url);
- url = smprint("%s?%E", r->url->url, s);+ t = smprint("%s?%E", r->url->full, s);free(s);
+ url = urlparse(nil, t);
+ free(t);
freeresponse(r);
close(fd);
goto nextreq;
@@ -382,9 +276,9 @@
*t++ = 0;
else
t = "";
- u = urlto(r->url, u);
- Bprint(&out, "→ %s %s\n", u, t);
- free(u);
+ x = urlparse(r->url, u);
+ Bprint(&out, "→ %U %s\n", x, t);
+ freeurl(x);
}else{Bprint(&out, "%s\n", s);
}
--- a/mkfile
+++ b/mkfile
@@ -4,8 +4,13 @@
BIN=/$objtype/bin
+HFILES=\
+ gemnine.h\
+
OFILES=\
main.$O\
+ url.$O\
+ util.$O\
UPDATE=\
$HFILES\
--- /dev/null
+++ b/url.c
@@ -1,0 +1,426 @@
+/* this is a copy from webfs */
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "gemnine.h"
+
+enum {+ Domlen = 256,
+};
+
+typedef struct {+ char *s1;
+ char *s2;
+}Str2;
+
+#pragma varargck type "E" Str2
+#pragma varargck type "N" char*
+#pragma varargck type "]" char*
+
+static char reserved[] = "%:/?#[]@!$&'()*+,;=";
+
+static int
+dhex(char c)
+{+ if('0' <= c && c <= '9')+ return c-'0';
+ if('a' <= c && c <= 'f')+ return c-'a'+10;
+ if('A' <= c && c <= 'F')+ return c-'A'+10;
+ return 0;
+}
+
+static char*
+unescape(char *s, char *spec)
+{+ char *r, *w;
+ uchar x;
+
+ if(s == nil)
+ return s;
+ for(r=w=s; x = *r; r++){+ if(x == '%' && isxdigit(r[1]) && isxdigit(r[2])){+ x = (dhex(r[1])<<4)|dhex(r[2]);
+ if(spec && strchr(spec, x)){+ *w++ = '%';
+ *w++ = toupper(r[1]);
+ *w++ = toupper(r[2]);
+ }
+ else
+ *w++ = x;
+ r += 2;
+ continue;
+ }
+ *w++ = x;
+ }
+ *w = 0;
+ return s;
+}
+
+int
+Efmt(Fmt *f)
+{+ char *s, *spec;
+ Str2 s2;
+
+ s2 = va_arg(f->args, Str2);
+ s = s2.s1;
+ spec = s2.s2;
+ 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(".-_~!$&'()*,;=", *s) || strchr(spec, *s))+ fmtprint(f, "%c", *s);
+ else
+ fmtprint(f, "%%%.2X", *s & 0xff);
+ return 0;
+}
+
+int
+Nfmt(Fmt *f)
+{+ char d[Domlen], *s;
+
+ s = va_arg(f->args, char*);
+ if(utf2idn(s, d, sizeof(d)) >= 0)
+ s = d;
+ fmtprint(f, "%s", s);
+ return 0;
+}
+
+int
+Mfmt(Fmt *f)
+{+ char *s = va_arg(f->args, char*);
+ fmtprint(f, (*s != '[' && strchr(s, ':') != nil)? "[%s]" : "%s", s);
+ return 0;
+}
+
+int
+Ufmt(Fmt *f)
+{+ char *s;
+ Url *u;
+
+ if((u = va_arg(f->args, Url*)) == nil)
+ return fmtprint(f, "nil");
+ if(u->scheme)
+ fmtprint(f, "%s:", u->scheme);
+ if(u->user || u->host)
+ fmtprint(f, "//");
+ if(u->user){+ fmtprint(f, "%E", (Str2){u->user, ""});+ if(u->pass)
+ fmtprint(f, ":%E", (Str2){u->pass, ""});+ fmtprint(f, "@");
+ }
+ if(u->host){+ fmtprint(f, "%]", u->host);
+ if(u->port)
+ fmtprint(f, ":%s", u->port);
+ }
+ if(s = Upath(u))
+ fmtprint(f, "%E", (Str2){s, "/:@+"});+ if(u->query)
+ fmtprint(f, "?%E", (Str2){u->query, "/:@"});+ if(u->fragment)
+ fmtprint(f, "#%E", (Str2){u->fragment, "/:@?+"});+ return 0;
+}
+
+char*
+Upath(Url *u)
+{+ if(u){+ if(u->path)
+ return u->path;
+ if(u->user || u->host)
+ return "/";
+ }
+ return nil;
+}
+
+static char*
+remdot(char *s)
+{+ char *b, *d, *p;
+ int dir, n;
+
+ dir = 1;
+ b = d = s;
+ if(*s == '/')
+ s++;
+ for(; s; s = p){+ if(p = strchr(s, '/'))
+ *p++ = 0;
+ if(*s == '.' && ((s[1] == 0) || (s[1] == '.' && s[2] == 0))){+ if(s[1] == '.')
+ while(d > b)
+ if(*--d == '/')
+ break;
+ dir = 1;
+ continue;
+ } else
+ dir = (p != nil);
+ if((n = strlen(s)) > 0)
+ memmove(d+1, s, n);
+ *d++ = '/';
+ d += n;
+ }
+ if(dir)
+ *d++ = '/';
+ *d = 0;
+ return b;
+}
+
+static char*
+abspath(char *s, char *b)
+{+ char *x, *a;
+
+ if(b && *b){+ if(s == nil || *s == 0)
+ return estrdup(b);
+ if(*s != '/' && (x = strrchr(b, '/'))){+ a = emalloc((x - b) + strlen(s) + 4);
+ sprint(a, "%.*s/%s", utfnlen(b, x - b), b, s);
+ return remdot(a);
+ }
+ }
+ if(s && *s){+ if(*s != '/')
+ return estrdup(s);
+ a = emalloc(strlen(s) + 4);
+ sprint(a, "%s", s);
+ return remdot(a);
+ }
+ return nil;
+}
+
+static void
+pstrdup(char **p)
+{+ if(p == nil || *p == nil)
+ return;
+ if(**p == 0){+ *p = nil;
+ return;
+ }
+ *p = estrdup(*p);
+}
+
+static char*
+mklowcase(char *s)
+{+ char *cp;
+ Rune r;
+
+ if(s == nil)
+ return s;
+ cp = s;
+ while(*cp != 0){+ chartorune(&r, cp);
+ r = tolowerrune(r);
+ cp += runetochar(cp, &r);
+ }
+ return s;
+}
+
+static Url *
+saneurl(Url *u)
+{+ if(u == nil || u->scheme == nil || u->host == nil || Upath(u) == nil){+ freeurl(u);
+ return nil;
+ }
+ if(u->port){+ /* remove default ports */
+ switch(atoi(u->port)){+ case 21: if(!strcmp(u->scheme, "ftp")) goto Defport; break;
+ case 70: if(!strcmp(u->scheme, "gopher")) goto Defport; break;
+ case 80: if(!strcmp(u->scheme, "http")) goto Defport; break;
+ case 443: if(!strcmp(u->scheme, "https")) goto Defport; break;
+ case 1965: if(!strcmp(u->scheme, "gemini")) goto Defport; break;
+ default: if(!strcmp(u->scheme, u->port)) goto Defport; break;
+ Defport:
+ free(u->port);
+ u->port = nil;
+ }
+ }
+ return u;
+}
+
+Url*
+urlparse(Url *b, char *s)
+{+ char *t, *p, *x, *y;
+ Url *u;
+
+ if(s == nil)
+ s = "";
+ t = nil;
+ s = p = estrdup(s);
+ u = emalloc(sizeof(*u));
+ for(; *p; p++){+ if(*p == ':'){+ if(p == s)
+ break;
+ *p++ = 0;
+ u->scheme = s;
+ b = nil;
+ goto Abs;
+ }
+ if(!isalpha(*p))
+ if((p == s) || ((!isdigit(*p) && strchr("+-.", *p) == nil)))+ break;
+ }
+ p = s;
+ if(b){+ switch(*p){+ case 0:
+ memmove(u, b, sizeof(*u));
+ goto Out;
+ case '#':
+ memmove(u, b, sizeof(*u));
+ u->fragment = p+1;
+ goto Out;
+ case '?':
+ memmove(u, b, sizeof(*u));
+ u->fragment = u->query = nil;
+ break;
+ case '/':
+ if(p[1] == '/'){+ u->scheme = b->scheme;
+ b = nil;
+ break;
+ }
+ default:
+ memmove(u, b, sizeof(*u));
+ u->fragment = u->query = u->path = nil;
+ break;
+ }
+ }
+Abs:
+ if(x = strchr(p, '#')){+ *x = 0;
+ u->fragment = x+1;
+ }
+ if(x = strchr(p, '?')){+ *x = 0;
+ u->query = x+1;
+ }
+ if(p[0] == '/' && p[1] == '/'){+ p += 2;
+ if(x = strchr(p, '/')){+ u->path = t = abspath(x, Upath(b));
+ *x = 0;
+ }
+ if(x = strchr(p, '@')){+ *x = 0;
+ if(y = strchr(p, ':')){+ *y = 0;
+ u->pass = y+1;
+ }
+ u->user = p;
+ p = x+1;
+ }
+ if((x = strrchr(p, ']')) == nil)
+ x = p;
+ if(x = strrchr(x, ':')){+ *x = 0;
+ u->port = x+1;
+ }
+ if(x = strchr(p, '[')){+ p = x+1;
+ if(y = strchr(p, ']'))
+ *y = 0;
+ }
+ u->host = p;
+ } else {+ u->path = t = abspath(p, Upath(b));
+ }
+Out:
+ pstrdup(&u->scheme);
+ pstrdup(&u->user);
+ pstrdup(&u->pass);
+ pstrdup(&u->host);
+ pstrdup(&u->port);
+ pstrdup(&u->path);
+ pstrdup(&u->query);
+ pstrdup(&u->fragment);
+ free(s);
+ free(t);
+
+ /* the + character encodes space only in query part */
+ if(s = u->query)
+ while(s = strchr(s, '+'))
+ *s++ = ' ';
+
+ if(s = u->host){+ t = emalloc(Domlen);
+ if(idn2utf(s, t, Domlen) >= 0){+ u->host = estrdup(t);
+ free(s);
+ }
+ free(t);
+ }
+
+ unescape(u->user, nil);
+ unescape(u->pass, nil);
+ unescape(u->path, reserved);
+ unescape(u->query, reserved);
+ unescape(u->fragment, reserved);
+ mklowcase(u->scheme);
+ mklowcase(u->host);
+ mklowcase(u->port);
+
+ if((u = saneurl(u)) != nil)
+ u->full = smprint("%U", u);+
+ return u;
+}
+
+int
+matchurl(Url *u, Url *s)
+{+ if(u){+ char *a, *b;
+
+ if(s == nil)
+ return 0;
+ if(u->scheme && (s->scheme == nil || strcmp(u->scheme, s->scheme)))
+ return 0;
+ if(u->user && (s->user == nil || strcmp(u->user, s->user)))
+ return 0;
+ if(u->host && (s->host == nil || strcmp(u->host, s->host)))
+ return 0;
+ if(u->port && (s->port == nil || strcmp(u->port, s->port)))
+ return 0;
+ if(a = Upath(u)){+ b = Upath(s);
+ if(b == nil || strncmp(a, b, strlen(a)))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void
+freeurl(Url *u)
+{+ if(u == nil)
+ return;
+ free(u->full);
+ free(u->scheme);
+ free(u->user);
+ free(u->pass);
+ free(u->host);
+ free(u->port);
+ free(u->path);
+ free(u->query);
+ free(u->fragment);
+ free(u);
+}
--- /dev/null
+++ b/util.c
@@ -1,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include "gemnine.h"
+
+void *
+emalloc(int n)
+{+ void *v;
+ if((v = malloc(n)) == nil) {+ fprint(2, "out of memory allocating %d\n", n);
+ sysfatal("mem");+ }
+ setmalloctag(v, getcallerpc(&n));
+ memset(v, 0, n);
+ return v;
+}
+
+char *
+estrdup(char *s)
+{+ char *t;
+ if((t = strdup(s)) == nil) {+ fprint(2, "out of memory in strdup(%.10s)\n", s);
+ sysfatal("mem");+ }
+ setmalloctag(t, getcallerpc(&t));
+ return t;
+}
--
⑨