shithub: castor9

Download patch

ref: d50835ed8e9c630099318326135a7202465eb708
parent: dfc5c880042c1c61b88a2f3f2eaf344e8fe88684
author: Julien Blanchard <[email protected]>
date: Thu Dec 10 06:08:38 EST 2020

Extract url and util stuff

--- a/castor.c
+++ b/castor.c
@@ -14,6 +14,36 @@
 #include "castor.h"
 
 
+typedef struct Ctx Ctx;
+typedef struct Hist Hist;
+typedef struct Response Response;
+
+struct Response
+{
+	Url *url;
+	char *meta;
+	int status;
+	int fd;
+};
+
+struct Ctx
+{
+	Url *url;
+	Rtext *text;
+};
+
+struct Hist
+{
+	Hist *p;
+	Hist *n;
+	Ctx *c;
+};
+
+int request(Url *u);
+void gemini_get(Url *u);
+void texthit(Panel *p, int b, Rtext *t);
+void message(char *s, ...);
+
 Panel *root;
 Panel *backp;
 Panel *fwdp;
@@ -44,9 +74,10 @@
 };
 
 
-char* replace_char(char* str, char find, char replace){
+char* 
+replace_char(char* str, char find, char replace){
     char *current_pos = strchr(str,find);
-    while (current_pos){
+    while(current_pos){
         *current_pos = replace;
         current_pos = strchr(current_pos,find);
     }
@@ -53,7 +84,7 @@
     return str;
 }
 
-char *
+char*
 cleanup(char *line)
 {
 	if(line=="" || line==NULL)
@@ -60,9 +91,10 @@
 		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++;
+        if(*dst != '\r' && *dst != '\n') 
+			dst++;
     }
     *dst = '\0';
 	
@@ -125,34 +157,34 @@
 	}
 }
 
-char *
+char*
 protocol(char *link)
 {
-	if(strstr(link, "http://") != nil) {
+	if(strstr(link, "http://") != nil){
 		return " [WWW]";
-	} else if(strstr(link, "https://") != nil) {
+	}else if(strstr(link, "https://") != nil){
 		return " [WWW]";
-	} else if(strstr(link, "gopher://") != nil) {
+	}else if(strstr(link, "gopher://") != nil){
 		return " [GOPHER]";
-	} else if(strstr(link, "finger://") != nil) {
+	}else if(strstr(link, "finger://") != nil){
 		return " [FINGER]";
-	} else {
+	}else{
 		return "";
 	}
 }
 
-char *
+char*
 symbol(char *link)
 {
-	if(strstr(link, "http://") != nil) {
+	if(strstr(link, "http://") != nil){
 		return "⇄";
-	} else if(strstr(link, "https://") != nil) {
+	}else if(strstr(link, "https://") != nil){
 		return "⇄";
-	} else if(strstr(link, "gopher://") != nil) {
+	}else if(strstr(link, "gopher://") != nil){
 		return "⇒";
-	} else if(strstr(link, "finger://") != nil) {
+	}else if(strstr(link, "finger://") != nil){
 		return "⇒";
-	} else {
+	}else{
 		return "→";
 	}
 }
@@ -181,7 +213,7 @@
     width = 80;
 
 	char *preformatted_marker = "```";
-	if(strncmp(line, preformatted_marker, strlen(preformatted_marker)) == 0){
+	if(strbeg(line, preformatted_marker) == 0){
 		if(preformatted==0){
 			preformatted=1;
 		}else{
@@ -190,34 +222,29 @@
 		return;
 	}
 
-	while(*base)
-	{
-		if(preformatted==1)
-		{
+	while(*base){
+		/* Preformatted text */
+		if(preformatted==1){
 			plrtstr(&c->text, 1000000, 8, 0, font, strdup(cleanup(base)), PL_HEAD, 0);
 			break;
 		}
-		if(strncmp(line, "#", strlen("#")) == 0)
-		{
+		/* Headers */
+		if(strbeg(line, "#") == 0){
 			plrtstr(&c->text, 1000000, 8, 0, font, strdup(cleanup(base)), PL_SEL, 0);
 			break;
-        }		
-		if((length <= width))
-		{
+        } /* Small lines */		
+		if((length <= width)){
 			plrtstr(&c->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0);
 			break;
         }
 		
-
+		/* Wrapping the rest */
 		right_margin = base + width;
-		while(!isspace(*right_margin))
-		{
+		while(!isspace(*right_margin)){
 			right_margin--;
-			if(right_margin == base)
-			{
+			if(right_margin == base){
 				right_margin += width;
-				while(!isspace(*right_margin))
-				{
+				while(!isspace(*right_margin)){
 					if(*right_margin == '\0')
 						break;
 					right_margin++;
@@ -239,8 +266,7 @@
 	char *rest = strtok(NULL, "\0");
 	char *label;
 
-	if(rest != NULL)
-	{
+	if(rest != NULL){
 		while(isspace(*rest))
  			rest++;
 
@@ -252,7 +278,7 @@
 	plrtstr(&c->text, 1000000, 8, 0, font, strdup(label), PL_HOT, estrdup(link));
 }
 
-Url *
+Url*
 base_url(Url *url)
 {
 	char *base_url, *path, *ptr;
@@ -344,8 +370,7 @@
 		c->url = url;
 		set_current_base_url(base_url(url));
 
-		if(r->meta != NULL && strncmp(r->meta, "text/", strlen("text/")) != 0)
-		{
+		if(r->meta != NULL && strbeg(r->meta, "text/") != 0){
 			Bflush(&body);
 			close(fd);
 
@@ -352,10 +377,8 @@
 			page(url);
 			message("Castor9");
 		}else{
-			while((line = Brdstr(&body, '\n', 0)) != nil)
-			{
-				if(strncmp(line, "=>", strlen("=>")) == 0)
-				{
+			while((line = Brdstr(&body, '\n', 0)) != nil){
+				if(strbeg(line, "=>") == 0){
 					render_link(c, line);					
 				}else{
 					render_text(c, line);
@@ -373,7 +396,7 @@
 
 			show(c);
 		}
-	} else if(r->status == 31) {
+	}else if(r->status == 31){
 		Url *redirect = urlparse(nil, r->meta);
 		gemini_get(redirect);
 	}
@@ -431,31 +454,14 @@
 entryhit(Panel *p, char *t)
 {
 	USED(p);
-	switch(strlen(t)){
-	case 0:
+	if(strlen(t) == 0)
 		return;
-	case 1:
-		switch(*t){
-		case 'b':
-			//backhit(backp, 1);
-			break;
-		case 'n':
-			//nexthit(fwdp, 1);
-			break;
-		case 'q':
-			exits(nil);
-			break;
-		default:
-			message("unknown command %s", t);
-			break;
-		}
-		break;
-	default:
-		if(strstr(t, "gemini://") == NULL)
-			t = smprint("gemini://%s", t);
+
+	if(strstr(t, "gemini://") == NULL)
+		t = smprint("gemini://%s", t);
 			
-		gemini_get(urlparse(nil, t));
-	}
+	gemini_get(urlparse(nil, t));
+	
 	plinitentry(entryp, PACKN|FILLX, 0, "", entryhit);
 	pldraw(root, screen);
 }
@@ -473,9 +479,9 @@
 	if(link==nil)
 		return;
 
-	if (strstr(link, "gemini://") != nil || strstr(link, "://") != nil){
+	if(strstr(link, "gemini://") != nil || strstr(link, "://") != nil){
 		next_url = urlparse(nil, link);
-	} else {
+	}else{
 		/* assuming relative URL */
 		if(*link == '/'){
 			n = smprint("%s%s", urlparse(current_base_url, link)->raw, estrdup(link)+1);
@@ -487,7 +493,7 @@
 	
 	if(strcmp(next_url->scheme, "gemini") == 0){
 		gemini_get(next_url);
-	} else {
+	}else{
 		plumburl(next_url);
 	}
 }
@@ -638,447 +644,3 @@
 	}
 }
 
-// //////////////////////////
-
-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
--- a/castor.h
+++ b/castor.h
@@ -1,9 +1,7 @@
-typedef struct Ctx Ctx;
-typedef struct Hist Hist;
 typedef struct Url Url;
-typedef struct Response Response;
 
-struct Url {
+struct Url
+{
 	char *raw;
 	char *full;
 	char *scheme;
@@ -20,28 +18,8 @@
 typedef struct {
 	char *s1;
 	char *s2;
-}Str2;
+} Str2;
 
-struct Response {
-	Url *url;
-	char *meta;
-	int status;
-	int fd;
-};
-
-struct Ctx
-{
-	Url		*url;
-	Rtext	*text;
-};
-
-struct Hist
-{
-	Hist *p;
-	Hist *n;
-	Ctx *c;
-};
-
 #pragma varargck type "U" Url*
 #pragma varargck type "E" Str2
 #pragma varargck type "N" char*
@@ -50,6 +28,7 @@
 /* util.c */
 void *emalloc(int n);
 char *estrdup(char *s);
+int strbeg(char* str, char *prefix);
 
 /* url.c */
 int	Efmt(Fmt*);
@@ -59,9 +38,4 @@
 Url *urlparse(Url *from, char *s);
 int matchurl(Url *u, Url *s);
 void freeurl(Url *u);
-char *Upath(Url *u);
-
-int request(Url *u);
-void gemini_get(Url *u);
-void texthit(Panel *p, int b, Rtext *t);
-void message(char *s, ...);
\ No newline at end of file
+char *Upath(Url *u);
\ No newline at end of file
--- a/mkfile
+++ b/mkfile
@@ -2,8 +2,8 @@
 
 TARG=castor
 LIB=libpanel/libpanel.$O.a
-OFILES=castor.$O
-HFILES=libpanel/panel.h libpanel/rtext.h
+OFILES=url.$O util.$O castor.$O
+HFILES=castor.h libpanel/panel.h libpanel/rtext.h
 BIN=/$objtype/bin/
 
 </sys/src/cmd/mkone
--- /dev/null
+++ b/url.c
@@ -1,0 +1,421 @@
+/* this is a copy from webfs */
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "castor.h"
+
+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 || strbeg(a, b))
+				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);
+}
--- /dev/null
+++ b/util.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include "castor.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;
+}
+
+int
+strbeg(char *str, char *prefix)
+{
+	return strncmp(str, prefix, strlen(prefix)) == 0 ? 0 : -1;
+}
\ No newline at end of file