ref: 6690137466cd4acf7fede316e821fef9422a66ab
parent: 0946587d6c95b365174c88abe47b6f4fcb465741
author: Julien Blanchard <[email protected]>
date: Tue Dec 8 12:33:13 EST 2020
Replace manual URL parsing with url.c Don't wrap preformatted text
--- a/castor.c
+++ b/castor.c
@@ -10,28 +10,22 @@
#include <bio.h>
#include <stdio.h>
#include <ctype.h>
-//#include <plumb.h>
-#include "dat.h"
-#include "icons.h"
+#include <plumb.h>
+#include "castor.h"
-void texthit(Panel *p, int b, Rtext *t);
-void message(char *s, ...);
-Image *backi;
-Image *fwdi;
-Image *reloadi;
Panel *root;
Panel *backp;
Panel *fwdp;
-Panel *reloadp;
Panel *entryp;
Panel *urlp;
Panel *textp;
Panel *statusp;
Panel *popup;
-Url *current_url;
+Url *current_base_url;
Mouse *mouse;
Hist *hist = nil;
+int preformatted = 0;
enum
{
@@ -62,8 +56,11 @@
char *
cleanup(char *line)
{
+ if(line=="" || line==NULL)
+ return line;
+
char *src, *dst;
- for (src = dst = line; *src != '\0'; src++) {
+ for (src=dst=line; *src != '\0'; src++) {
*dst = *src;
if (*dst != '\r' && *dst != '\n') dst++;
}
@@ -74,58 +71,32 @@
}
void
-set_current_url(Url *url)
+set_current_base_url(Url *url)
{
- free(current_url);
- current_url = url;
+ freeurl(current_base_url);
+ current_base_url = url;
}
void
-show(Gmenu *m)
+show(Ctx *c)
{
- plinittextview(textp, PACKE|EXPAND, ZP, m->text, texthit);
+ plinittextview(textp, PACKE|EXPAND, ZP, c->text, texthit);
pldraw(textp, screen);
- plinitlabel(urlp, PACKN|FILLX, m->url->url);
+ plinitlabel(urlp, PACKN|FILLX, c->url->raw);
pldraw(urlp, screen);
message("Castor9");
}
-Url *
-parseurl(char *url)
+void
+plumburl(char *u)
{
- char *server, *port, *s, *e;
- Url *u;
+ int fd;
- 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;
+ fd = plumbopen("send", OWRITE|OCEXEC);
+ if(fd<0)
+ return;
+ plumbsendtext(fd, "gopher", nil, nil, u);
+ close(fd);
}
char *
@@ -139,42 +110,180 @@
return " [GOPHER]";
} else if(strstr(link, "finger://") != nil) {
return " [FINGER]";
+ } else if(strstr(link, "mailto:") != nil) {
+ return " [MAIL]";
} else {
return "";
}
}
+char *
+symbol(char *link)
+{
+ if(strstr(link, "http://") != nil) {
+ return "⇄";
+ } else if(strstr(link, "https://") != nil) {
+ return "⇄";
+ } else if(strstr(link, "gopher://") != nil) {
+ return "⇒";
+ } else if(strstr(link, "finger://") != nil) {
+ return "⇒";
+ } else {
+ return "→";
+ }
+}
+
void
+handle_status(char *status, Response *r)
+{
+ int code;
+ char *meta;
+ code = atoi(strtok(status, " "));
+ if(code == 0)
+ message("STATUS: %s\n", status);
+ meta = strtok(NULL, "\n");
+ r->status = code;
+ r->prompt = cleanup(meta);
+}
+
+void
+render_text(Ctx *c, char *line)
+{
+ char *base, *right_margin;
+ int length, width;
+
+ length = strlen(strdup(line));
+ base = strdup(line);
+ width = 80;
+
+ char *preformatted_marker = "```";
+ if(strncmp(line, preformatted_marker, strlen(preformatted_marker)) == 0){
+ if(preformatted==0){
+ preformatted=1;
+ }else{
+ preformatted=0;
+ }
+ return;
+ }
+
+ while(*base)
+ {
+ if(preformatted==1)
+ {
+ plrtstr(&c->text, 1000000, 8, 0, font, strdup(cleanup(base)), PL_HEAD, 0);
+ break;
+ }
+ if((length <= width))
+ {
+ plrtstr(&c->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0);
+ break;
+ }
+
+
+ right_margin = base + width;
+ while(!isspace(*right_margin))
+ {
+ right_margin--;
+ if(right_margin == base)
+ {
+ right_margin += width;
+ while(!isspace(*right_margin))
+ {
+ if(*right_margin == '\0')
+ break;
+ right_margin++;
+ }
+ }
+ }
+ *right_margin = '\0';
+ plrtstr(&c->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0);
+ length -= right_margin - base + 1; /* +1 for the space */
+ base = right_margin + 1;
+ }
+}
+
+void
+render_link(Ctx *c, char *line)
+{
+ char *copy = strdup(cleanup(line + 2)); /* bypass => */
+ char *link = strtok(copy, " ");
+ char *rest = strtok(NULL, "\0");
+ char *label;
+
+ if(rest != NULL)
+ {
+ while(isspace(*rest))
+ rest++;
+
+ label = smprint("%s %s%s", symbol(link), rest, protocol(link));
+ }else{
+ label = smprint("%s %s%s", symbol(link), link, protocol(link));
+ }
+
+ plrtstr(&c->text, 1000000, 8, 0, font, strdup(label), PL_HOT, estrdup(link));
+}
+
+Url *
+base_url(Url *url)
+{
+ char *base_url, *path, *ptr;
+
+ if(url->path == "/" || url->path == NULL){
+ path = "/";
+ }else{
+ path = estrdup(url->path);
+ ptr = strrchr(path, '/');
+ if(path[strlen(path)-1] != '/')
+ strcpy(ptr, "/");
+ }
+ base_url = smprint("gemini://%s%s", url->host, path);
+
+ return urlparse(nil, base_url);
+}
+
+void
gemini_get(Url *url)
{
Thumbprint *th;
TLSconn conn;
int fd;
- char *line;
+ char *line, *port;
Biobuf body;
- Gmenu *m;
- m = malloc(sizeof *m);
- if(m==nil)
+ Ctx *c;
+ c = malloc(sizeof *c);
+ if(c==nil)
sysfatal("malloc: %r");
- m->text = nil;
+ c->text = nil;
+ Response *r;
+ r = malloc(sizeof *r);
+ if(r == nil)
+ sysfatal("malloc: %r");
+ r->url = url;
+
Hist *h;
h = malloc(sizeof *h);
if(h == nil)
sysfatal("malloc: %r");
- plrtstr(&m->text, 1000000, 0, 0, font, strdup(" "), 0, 0);
+ plrtstr(&c->text, 1000000, 0, 0, font, strdup(" "), 0, 0);
- message("loading %s...", url->url);
- char *naddr = netmkaddr(url->server, "tcp", url->port);
+ message("loading %s...", url->raw);
+
+ if(url->port == NULL){
+ port = "1965";
+ }else{
+ port = url->port;
+ }
+ char *naddr = netmkaddr(url->host, "tcp", port);
fd = dial(naddr, 0, 0, 0);
if(fd < 0){
- message("unable to connect to %s: %r", url->server);
+ message("unable to connect to %s:%s: %r", url->host, url->port);
return;
}
- conn.serverName = url->server;
+ conn.serverName = url->host;
memset(&conn, 0, sizeof(conn));
fd = tlsClient(fd, &conn);
@@ -186,89 +295,41 @@
free(conn.cert);
}
- fprint(fd, "%s\r\n", url->url);
+ fprint(fd, "%s\r\n", url->raw);
Binit(&body, fd, OREAD);
- m->url = url;
-
- char c;
- do {
- c = Bgetc(&body);
- } while (c != '\n');
+ char *status = Brdstr(&body, '\n', '0');
+ handle_status(status, r);
- while((line = Brdstr(&body, '\n', 0)) != nil)
- {
- if (strstr(line, "=>") == nil) {
- /* Not a link so wrapping text */
- char *base, *right_margin;
- int length, width;
-
- length = strlen(strdup(line));
- base = strdup(line);
- width = 80;
-
- while(*base)
- {
- if(length <= width)
- {
- plrtstr(&m->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0);
- break;
- }
- right_margin = base+width;
- while(!isspace(*right_margin))
- {
- right_margin--;
- if(right_margin == base)
- {
- right_margin += width;
- while(!isspace(*right_margin))
- {
- if(*right_margin == '\0')
- break;
- right_margin++;
- }
- }
- }
- *right_margin = '\0';
- plrtstr(&m->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0);
- length -= right_margin - base + 1; /* +1 for the space */
- base = right_margin + 1;
+ if(r->status == 20){
+ c->url = url;
+ set_current_base_url(base_url(url));
+
+ while((line = Brdstr(&body, '\n', 0)) != nil)
+ {
+ if (strstr(line, "=>") == NULL) {
+ /* Not a link so wrapping text */
+ render_text(c, line);
+ } else {
+ /* a link */
+ render_link(c, line);
}
- } else {
- /* a link */
- char *copy = strdup(cleanup(line));
- strtok(copy, " ");
- char *link = strtok(NULL, " ");
- char *rest = strtok(NULL, "\0");
- char *label;
-
- if(rest != NULL)
- {
- while(isspace(*rest))
- {
- rest++;
- }
- label = smprint("→ %s%s", rest, protocol(link));
- }else{
- label = smprint("→ %s%s", link, protocol(link));
- }
-
- plrtstr(&m->text, 1000000, 8, 0, font, strdup(label), PL_HOT, strdup(link));
+ free(line);
}
- free(line);
- }
- h->p = hist;
- h->n = nil;
- h->m = m;
- hist = h;
+ Bflush(&body);
+ close(fd);
- Bflush(&body);
- close(fd);
+ h->p = hist;
+ h->n = nil;
+ h->c = c;
+ hist = h;
-
-
- show(m);
+ show(c);
+ } else if(r->status == 31) {
+ Url *redirect = urlparse(nil, r->prompt);
+ gemini_get(redirect);
+ }
}
void
@@ -281,8 +342,8 @@
return;
hist->p->n = hist;
hist = hist->p;
- //set_current_url(hist->m->url);
- show(hist->m);
+ set_current_base_url(base_url(hist->c->url));
+ show(hist->c);
}
void
@@ -294,8 +355,8 @@
if(hist==nil || hist->n==nil)
return;
hist = hist->n;
- //set_current_url(hist->m->url);
- show(hist->m);
+ set_current_base_url(base_url(hist->c->url));
+ show(hist->c);
}
void
@@ -343,7 +404,10 @@
}
break;
default:
- gemini_get(parseurl(t));
+ if(strstr(t, "gemini://") == NULL)
+ t = smprint("gemini://%s", t);
+
+ gemini_get(urlparse(nil, t));
}
plinitentry(entryp, PACKN|FILLX, 0, "", entryhit);
pldraw(root, screen);
@@ -352,30 +416,34 @@
void
texthit(Panel *p, int b, Rtext *rt)
{
- //message("text hit %s", rt->user);
- char *copy, *next_url;
+ char *n;
+ Url *next_url;
char *link = rt->user;
- copy = link;
- int len = strlen(copy);
- if ((strpbrk(link, " :/") == nil) || link[0] == '/' || link[len-1] == '/') {
+ USED(p);
+ if(b!=1)
+ return;
+ if(link==nil)
+ return;
+
+ if (strstr(link, "gemini://") != nil || strstr(link, "://") != nil){
+ next_url = urlparse(nil, link);
+ } else {
/* assuming relative URL */
- message(copy);
- if (*copy == '/') {
- next_url = smprint("gemini://%s:%s/%s", current_url->server, current_url->port, copy+1);
- } else {
- next_url = smprint("gemini://%s:%s/%s", current_url->server, current_url->port, copy);
+ if(*link == '/'){
+ n = smprint("%s%s", urlparse(current_base_url, link)->raw, estrdup(link)+1);
+ }else{
+ n = smprint("%s%s", urlparse(current_base_url, link)->raw, estrdup(link));
}
- } else {
- /* absolute URL */
- next_url = strdup(link);
+ next_url = urlparse(nil, n);
}
- free(link);
-
- if(strstr(next_url, "gemini://") != nil) {
- gemini_get(parseurl(next_url));
+
+ if(strcmp(next_url->scheme, "gemini") == 0){
+ free(link);
+ gemini_get(next_url);
} else {
- message("%s protocol not supported yet!", next_url);
+ message("%s protocol not supported yet!", link);
+ free(link);
}
}
@@ -405,9 +473,6 @@
p = plgroup(root, PACKN|FILLX);
statusp = pllabel(p, PACKN|FILLX, "Castor!");
plplacelabel(statusp, PLACEW);
- //plbutton(p, PACKW|BITMAP|NOBORDER, backi, backhit);
- //plbutton(p, PACKW|BITMAP|NOBORDER, fwdi, nexthit);
- //plbutton(p, PACKW|BITMAP|NOBORDER, reloadi, reloadhit);
pllabel(p, PACKW, "Go: ");
entryp = plentry(p, PACKN|FILLX, 0, "", entryhit);
p = plgroup(root, PACKN|FILLX);
@@ -444,15 +509,6 @@
sysfatal("loadimage: %r");
return i;
}
-void
-loadicons(void)
-{
- Rectangle r = Rect(0,0,16,16);
-
- backi = loadicon(r, ibackdata, sizeof ibackdata);
- fwdi = loadicon(r, ifwddata, sizeof ifwddata);
- reloadi = loadicon(r, ireloaddata, sizeof ireloaddata);
-}
void scrolltext(int dy, int whence)
{
@@ -484,19 +540,18 @@
main(int argc, char *argv[])
{
Event e;
- Response *r;
Url *url;
-
+
if(argc == 2)
- url = parseurl(argv[1]);
+ url = urlparse(nil, argv[1]);
else
- url = parseurl("gemini.circumlunar.space/capcom/");
+ url = urlparse(nil, "gemini://gemini.circumlunar.space/capcom/");
+
quotefmtinstall();
if(initdraw(nil, nil, "gemini")<0)
sysfatal("initdraw: %r");
einit(Emouse|Ekeyboard);
plinit(screen->depth);
- loadicons();
mkpanels();
gemini_get(url);
eresized(0);
@@ -549,4 +604,447 @@
}
}
+// //////////////////////////
+enum {
+ Domlen = 256,
+};
+
+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));
+
+ u->raw = estrdup(s);
+
+ 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->raw);
+ free(u);
+}
+
+// ///////////////
+
+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;
+}
\ No newline at end of file