shithub: castor9

Download patch

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