shithub: riscv

Download patch

ref: aa9097b4bbf566504c80689fa45b2e82aad56c36
parent: 1a10c36b88d9acdd7edcb33a09525b74875cc1fa
author: cinap_lenrek <cinap_lenrek@centraldogma>
date: Sun Sep 4 23:29:26 EDT 2011

mothra: remove ftp,gopher,file and http code and use /mnt/web instead

--- a/sys/src/cmd/mothra/auth.c
+++ /dev/null
@@ -1,68 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-#include <bio.h>
-#include "mothra.h"
-
-static int
-basicauth(char *arg, char *str, int n)
-{
-	int i;
-	char *p;
-	char buf[1024];
-	Biobuf *b;
-
-	if(strncmp(arg, "realm=", 6) == 0)
-		arg += 6;
-	if(*arg == '"'){
-		arg++;
-		for(p = arg; *p && *p != '"'; p++);
-		*p = 0;
-	} else {
-		for(p = arg; *p && *p != ' ' && *p != '\t'; p++);
-		*p = 0;
-	}
-
-	p = getenv("home");
-	if(p == 0){
-		werrstr("$home not set");
-		return -1;
-	}
-	snprint(buf, sizeof(buf), "%s/lib/mothra/insecurity", p);
-	b = Bopen(buf, OREAD);
-	if(b == 0){
-		werrstr("www password file %s: %r", buf);
-		return -1;
-	}
-
-	i = strlen(arg);
-	while(p = Brdline(b, '\n'))
-		if(strncmp(arg, p, i) == 0 && p[i] == '\t')
-			break;
-	if(p == 0){
-		Bterm(b);
-		werrstr("no basic password for domain `%s'", arg);
-		return -1;
-	}
-
-	p[Blinelen(b)-1] = 0;
-	for(p += i; *p == '\t'; p++);
-	if (enc64(buf, sizeof buf, (uchar*)p, strlen(p)) < 0) {
-		Bterm(b);
-		werrstr("password too long: %s", p);
-		return -1;
-	}
-	snprint(str, n, "Authorization: Basic %s\r\n", buf);
-	return 0;
-}
-
-int
-auth(Url *url, char *str, int n)
-{
-	if(cistrcmp(url->authtype, "basic") == 0)
-		return basicauth(url->autharg, str, n);
-	werrstr("unknown auth method %s", url->authtype);
-	return -1;
-}
--- a/sys/src/cmd/mothra/cistr.c
+++ /dev/null
@@ -1,29 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-#include <ctype.h>
-#include "mothra.h"
-int cistrcmp(char *s1, char *s2){
-	int c1, c2;
-
-	for(; *s1; s1++, s2++){
-		c1 = isupper(*s1) ? tolower(*s1) : *s1;
-		c2 = isupper(*s2) ? tolower(*s2) : *s2;
-		if (c1 < c2) return -1;
-		if (c1 > c2) return 1;
-	}
-	return 0;
-}
-int cistrncmp(char *s1, char *s2, int n){
-	int c1, c2;
-
-	for(; *s1 && n!=0; s1++, s2++, --n){
-		c1 = isupper(*s1) ? tolower(*s1) : *s1;
-		c2 = isupper(*s2) ? tolower(*s2) : *s2;
-		if (c1 < c2) return -1;
-		if (c1 > c2) return 1;
-	}
-	return 0;
-}
--- a/sys/src/cmd/mothra/crackurl.c
+++ /dev/null
@@ -1,188 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-#include <ctype.h>
-#include "mothra.h"
-enum{
-	IP=1,		/* url can contain //ipaddress[:port] */
-	REL=2,	/* fill in ip address & root of name from current, if necessary */
-	SSL=4,	/* use SSL/TLS encryption */
-};
-Scheme scheme[]={
-	"http:",	HTTP,	IP|REL,		80,
-	"https:",	HTTP,	IP|REL|SSL,	443,
-	"ftp:",		FTP,	IP|REL,		21,
-	"file:",	FILE,	REL,	0,
-	"telnet:",	TELNET,	IP,	0,
-	"mailto:",	MAILTO,	0,	0,
-	"gopher:",	GOPHER,	IP,	70,
-	0,		HTTP,	IP|REL,	80,
-};
-int endaddr(int c){
-	return c=='/' || c==':' || c=='?' || c=='#' || c=='\0';
-}
-/*
- * Remove ., mu/.. and empty components from path names.
- * Empty last components of urls are significant, and
- * therefore preserved.
- */
-void urlcanon(char *name){
-	char *s, *t;
-	char **comp, **p, **q;
-	int rooted;
-	rooted=name[0]=='/';
-	/*
-	 * Break the name into a list of components
-	 */
-	comp=emalloc((strlen(name)+2)*sizeof(char *));
-	p=comp;
-	*p++=name;
-	for(s=name;;s++){
-		if(*s=='/'){
-			*p++=s+1;
-			*s='\0';
-		}
-		else if(*s=='\0' || *s=='?')
-			break;
-	}
-	*p=0;
-	/*
-	 * go through the component list, deleting components that are empty (except
-	 * the last component) or ., and any .. and its non-.. predecessor.
-	 */
-	p=q=comp;
-	while(*p){
-		if(strcmp(*p, "")==0 && p[1]!=0
-		|| strcmp(*p, ".")==0)
-			p++;
-		else if(strcmp(*p, "..")==0 && q!=comp && strcmp(q[-1], "..")!=0){
-			--q;
-			p++;
-		}
-		else
-			*q++=*p++;
-	}
-	*q=0;
-	/*
-	 * rebuild the path name
-	 */
-	s=name;
-	if(rooted) *s++='/';
-	for(p=comp;*p;p++){
-		t=*p;
-		while(*t) *s++=*t++;
-		if(p[1]!=0) *s++='/';
-	}
-	*s='\0';
-	free(comp);
-}
-/*
- * True url parsing is a nightmare.
- * This assumes that there are two basic syntaxes
- * for url's -- with and without an ip address.
- * If the type identifier or the ip address and port number
- * or the relative address is missing from urlname or is empty, 
- * it is copied from cur.
- */
-void crackurl(Url *url, char *urlname, Url *cur){
-	char *relp, *tagp, *httpname;
-	int len;
-	Scheme *up;
-	char buf[30];
-	/*
-	 * The following lines `fix' the most egregious urlname syntax errors
-	 */
-	while(*urlname==' ' || *urlname=='\t' || *urlname=='\n') urlname++;
-	relp=strchr(urlname, '\n');
-	if(relp) *relp='\0';
-	/*
-	 * In emulation of Netscape, attach a free "http://"
-	 * to names beginning with "www.".
-	 */
-	if(strncmp(urlname, "www.", 4)==0){
-		httpname=emalloc(strlen(urlname)+8);
-		strcpy(httpname, "http://");
-		strcat(httpname, urlname);
-		crackurl(url, httpname, cur);
-		free(httpname);
-		return;
-	}
-	url->port=cur->port;
-	strncpy(url->ipaddr, cur->ipaddr, sizeof(url->ipaddr));
-	strncpy(url->reltext, cur->reltext, sizeof(url->reltext));
-	if(strchr(urlname, ':')==0){
-		up=cur->scheme;
-		if(up==0){
-			up=&scheme[0];
-			cur->scheme=up;
-		}
-	}
-	else{
-		for(up=scheme;up->name;up++){
-			len=strlen(up->name);
-			if(strncmp(urlname, up->name, len)==0){
-				urlname+=len;
-				break;
-			}
-		}
-		if(up->name==0) up=&scheme[0];	/* default to http: */
-	}
-	url->access=up->type;
-	url->scheme=up;
-	if(up!=cur->scheme)
-		url->reltext[0]='\0';
-	if(up->flags&IP && strncmp(urlname, "//", 2)==0){
-		urlname+=2;
-		for(relp=urlname;!endaddr(*relp);relp++);
-		len=relp-urlname;
-		strncpy(url->ipaddr, urlname, len);
-		url->ipaddr[len]='\0';
-		urlname=relp;
-		if(*urlname==':'){
-			urlname++;
-			url->port=atoi(urlname);
-			while(!endaddr(*urlname)) urlname++;
-		}
-		else
-			url->port=up->port;
-		if(*urlname=='\0') urlname="/";
-	}
-	url->ssl = up->flags&SSL;
-		
-	tagp=strchr(urlname, '#');
-	if(tagp){
-		*tagp='\0';
-		strncpy(url->tag, tagp+1, sizeof(url->tag));
-	}
-	else
-		url->tag[0]='\0';	
-	if(!(up->flags&REL) || *urlname=='/')
-		strncpy(url->reltext, urlname, sizeof(url->reltext));
-	else if(urlname[0]){
-		relp=strrchr(url->reltext, '/');
-		if(relp==0)
-			strncpy(url->reltext, urlname, sizeof(url->reltext));
-		else
-			strcpy(relp+1, urlname);
-	}
-	urlcanon(url->reltext);
-	if(tagp) *tagp='#';
-	/*
-	 * The following mess of strcpys and strcats
-	 * can't be changed to a few sprints because
-	 * urls are not necessarily composed of legal utf
-	 */
-	strcpy(url->fullname, up->name);
-	if(up->flags&IP){
-		strncat(url->fullname, "//", sizeof(url->fullname));
-		strncat(url->fullname, url->ipaddr, sizeof(url->fullname));
-		if(url->port!=up->port){
-			snprint(buf, sizeof(buf), ":%d", url->port);
-			strncat(url->fullname, buf, sizeof(url->fullname));
-		}
-	}
-	strcat(url->fullname, url->reltext);
-	url->map=0;
-}
--- a/sys/src/cmd/mothra/file.c
+++ /dev/null
@@ -1,48 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-#include "mothra.h"
-/*
- * fd is the result of a successful open(name, OREAD),
- * where name is the name of a directory.  We convert
- * this into an html page containing links to the files
- * in the directory.
- */
-int dir2html(char *name, int fd){
-	int p[2], first;
-	Dir *dir;
-	int i, n;
-	if(pipe(p)==-1){
-		close(fd);
-		return -1;
-	}
-	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
-	case -1:
-		close(fd);
-		return -1;
-	case 0:
-		close(p[1]);
-		fprint(p[0], "<head>\n");
-		fprint(p[0], "<title>Directory %s</title>\n", name);
-		fprint(p[0], "</head>\n");
-		fprint(p[0], "<body>\n");
-		fprint(p[0], "<h1>%s</h1>\n", name);
-		fprint(p[0], "<ul>\n");
-		first=1;
-		while((n = dirread(fd, &dir)) > 0) {
-		  for (i = 0; i < n; i++)
-			fprint(p[0], "<li><a href=\"%s/%s\">%s%s</a>\n", name, dir[i].name, dir[i].name,
-				dir[i].mode&DMDIR?"/":"");
-		  free(dir);
-		}
-		fprint(p[0], "</ul>\n");
-		fprint(p[0], "</body>\n");
-		_exits(0);
-	default:
-		close(fd);
-		close(p[0]);
-		return p[1];
-	}
-}
--- a/sys/src/cmd/mothra/filetype.c
+++ b/sys/src/cmd/mothra/filetype.c
@@ -86,7 +86,7 @@
 	"application/pdf",		PDF,
 	"application/octet-stream",	SUFFIX,
 	"application/zip",		ZIP,
-	0,				HTML
+	0,				SUFFIX
 };
 int content2type(char *s, char *name){
 	int type;
--- a/sys/src/cmd/mothra/forms.c
+++ b/sys/src/cmd/mothra/forms.c
@@ -12,7 +12,7 @@
 typedef struct Option Option;
 struct Form{
 	int method;
-	Url *action;
+	char *action;
 	Field *fields, *efields;
 	Form *next;
 };
@@ -98,12 +98,8 @@
 			break;
 		}
 		g->form=emallocz(sizeof(Form), 1);
-		g->form->action=emalloc(sizeof(Url));
 		s=pl_getattr(g->attr, "action");
-		if(s==0)
-			*g->form->action=*g->dst->url;
-		else
-			crackurl(g->form->action, s, g->dst->base);
+		g->form->action=strdup((s && s[0]) ? s : g->dst->url->fullname);
 		s=pl_getattr(g->attr, "method");
 		if(s==0)
 			g->form->method=GET;
@@ -268,12 +264,8 @@
 		form=emalloc(sizeof(Form));
 		form->fields=0;
 		form->efields=0;
-		form->action=emalloc(sizeof(Url));
 		s=pl_getattr(g->attr, "action");
-		if(s==0)
-			*form->action=*g->dst->url;
-		else
-			crackurl(form->action, s, g->dst->base);
+		form->action=strdup((s && s[0]) ? s : g->dst->url->fullname);
 		form->method=GET;
 		form->fields=0;
 		f=newfield(form);
@@ -537,7 +529,7 @@
 	Field *f;
 	Option *o;
 	form=((Field *)p->userp)->form;
-	if(form->method==GET) size=ulen(form->action->fullname)+1;
+	if(form->method==GET) size=ulen(form->action)+1;
 	else size=1;
 	for(f=form->fields;f;f=f->next) switch(f->type){
 	case TYPEIN:
@@ -564,7 +556,7 @@
 	}
 	buf=emalloc(size);
 	if(form->method==GET){
-		strcpy(buf, form->action->fullname);
+		strcpy(buf, form->action);
 		sep='?';
 	}
 	else{
@@ -623,8 +615,8 @@
 		geturl(buf, GET, 0, 0, 0);
 	}
 	else{
-fprint(2, "POST %s: %s\n", form->action->fullname, buf);
-		geturl(form->action->fullname, POST, buf, 0, 0);
+fprint(2, "POST %s: %s\n", form->action, buf);
+		geturl(form->action, POST, buf, 0, 0);
 	}
 	free(buf);
 }
--- a/sys/src/cmd/mothra/ftp.c
+++ /dev/null
@@ -1,428 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-#include <bio.h>
-#include <ndb.h>
-#include <ctype.h>
-#include <ip.h>
-#include "mothra.h"
-
-enum
-{
-	/* return codes */
-	Extra=		1,
-	Success=	2,
-	Incomplete=	3,
-	TempFail=	4,
-	PermFail=	5,
-
-	NAMELEN=	28,
-	Nnetdir=	3*NAMELEN,	/* max length of network directory paths */
-	Ndialstr=	64,		/* max length of dial strings */
-};
-
-typedef struct Ftp Ftp;
-struct Ftp
-{
-	char	net[Nnetdir];
-	Biobuf	*ftpctl;
-	Url	*u;
-};
-
-static int ftpdebug;
-
-
-/*
- *  read from biobuf turning cr/nl into nl
- */
-char*
-getcrnl(Biobuf *b)
-{
-	char *p, *ep;
-
-	p = Brdline(b, '\n');
-	if(p == nil)
-		return nil;
-	ep = p + Blinelen(b) - 1;
-	if(*(ep-1) == '\r')
-		ep--;
-	*ep = 0;
-	return p;
-}
-
-char*
-readfile(char *file, char *buf, int len)
-{
-	int n, fd;
-
-	fd = open(file, OREAD);
-	if(fd < 0)
-		return nil;
-	n = read(fd, buf, len-1);
-	close(fd);
-	if(n <= 0)
-		return nil;
-	buf[n] = 0;
-	return buf;
-}
-
-char*
-sysname(void)
-{
-	static char sys[Ndbvlen];
-	char *p;
-
-	p = readfile("/dev/sysname", sys, sizeof(sys));
-	if(p == nil)
-		return "unknown";
-	return p;
-}
-
-char*
-domainname(void)
-{
-	static char domain[Ndbvlen];
-	Ndbtuple *t;
-
-	if(*domain)
-		return domain;
-
-	t = csgetval(0, "sys", sysname(), "dom", domain);
-	if(t){
-		ndbfree(t);
-		return domain;
-	} else
-		return sysname();
-}
-
-static int
-sendrequest(Biobuf *b, char *fmt, ...)
-{
-	char buf[2*1024], *s;
-	va_list args;
-
-	va_start(args, fmt);
-	s = buf + vsnprint(buf, (sizeof(buf)-4) / sizeof(*buf), fmt, args);
-	va_end(args);
-	*s++ = '\r';
-	*s++ = '\n';
-	if(write(Bfildes(b), buf, s - buf) != s - buf)
-		return -1;
-	if(ftpdebug)
-		write(2, buf, s - buf);
-	return 0;
-}
-
-static int
-getreply(Biobuf *b, char *msg, int len)
-{
-	char *line;
-	int rv;
-	int i, n;
-
-	while(line = getcrnl(b)){
-		/* add line to message buffer, strip off \r */
-		n = Blinelen(b);
-		if(ftpdebug)
-			write(2, line, n);
-		if(n > len - 1)
-			i = len - 1;
-		else
-			i = n;
-		if(i > 0){
-			memmove(msg, line, i);
-			msg += i;
-			len -= i;
-			*msg = 0;
-		}
-
-		/* stop if not a continuation */
-		rv = atoi(line);
-		if(rv >= 100 && rv < 600 && (n == 4 || (n > 4 && line[3] == ' ')))
-			return rv/100;
-	}
-
-	return -1;
-}
-
-int
-terminateftp(Ftp *d)
-{
-	if(d->ftpctl){
-		close(Bfildes(d->ftpctl));
-		Bterm(d->ftpctl);
-		free(d->ftpctl);
-		d->ftpctl = nil;
-	}
-	free(d);
-	return -1;
-}
-
-Biobuf*
-hello(Ftp *d)
-{
-	int fd;
-	char *p;
-	Biobuf *b;
-	char msg[1024];
-	char ndir[Nnetdir];
-
-	snprint(msg, sizeof msg, "tcp!%s!%d", d->u->ipaddr, d->u->port);
-	fd = dial(msg, 0, ndir, 0);
-	if(fd < 0){
-		d->ftpctl = nil;
-		return nil;
-	}
-	b = emalloc(sizeof(Biobuf));
-	Binit(b, fd, OREAD);
-	d->ftpctl = b;
-
-	/* remember network for the data connections */
-	p = strrchr(ndir, '/');
-	if(p == 0){
-		fprint(2, "dial is out of date\n");
-		return nil;
-	}
-	*p = 0;
-	strcpy(d->net, ndir);
-
-	/* wait for hello from other side */
-	if(getreply(b, msg, sizeof(msg)) != Success){
-		fprint(2, "instead of hello: %s\n", msg);
-		return nil;
-	}
-	return b;
-}
-
-int
-logon(Ftp *d)
-{
-	char msg[1024];
-
-	/* login anonymous */
-	sendrequest(d->ftpctl, "USER anonymous");
-	switch(getreply(d->ftpctl, msg, sizeof(msg))){
-	case Success:
-		return 0;
-	case Incomplete:
-		break;	/* need password */
-	default:
-		fprint(2, "login failed: %s\n", msg);
-		werrstr(msg);
-		return -1;
-	}
-
-	/* send user id as password */
-	sprint(msg, "%s@", getuser());
-	sendrequest(d->ftpctl, "PASS %s", msg);
-	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
-		fprint(2, "login failed: %s\n", msg);
-		werrstr(msg);
-		return -1;
-	}
-
-	return 0;
-}
-
-int
-xfertype(Ftp *d, char *t)
-{
-	char msg[1024];
-
-	sendrequest(d->ftpctl, "TYPE %s", t);
-	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
-		fprint(2, "can't set type %s: %s\n", t, msg);
-		werrstr(msg);
-		return -1;
-	}
-	return 0;
-}
-
-int
-passive(Ftp *d)
-{
-	char msg[1024];
-	char dialstr[Ndialstr];
-	char *f[6];
-	char *p;
-	int fd;
-
-	sendrequest(d->ftpctl, "PASV");
-	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success)
-		return -1;
-
-	/* get address and port number from reply, this is AI */
-	p = strchr(msg, '(');
-	if(p == nil){
-		for(p = msg+3; *p; p++)
-			if(isdigit(*p))
-				break;
-	} else
-		p++;
-	if(getfields(p, f, 6, 0, ",") < 6){
-		fprint(2, "passive mode protocol botch: %s\n", msg);
-		werrstr("ftp protocol botch");
-		return -1;
-	}
-	snprint(dialstr, sizeof(dialstr), "%s!%s.%s.%s.%s!%d", d->net,
-		f[0], f[1], f[2], f[3],
-		((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff));
-
-
-	/* open data connection */
-	fd = dial(dialstr, 0, 0, 0);
-	if(fd < 0){
-		fprint(2, "passive mode connect to %s failed: %r\n", dialstr);
-		return -1;
-	}
-
-	/* tell remote to send a file */
-	sendrequest(d->ftpctl, "RETR %s", d->u->reltext);
-	if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){
-		fprint(2, "passive mode retrieve failed: %s\n", msg);
-		werrstr(msg);
-		return -1;
-	}
-	return fd;
-}
-
-int
-active(Ftp *d)
-{
-	char msg[1024];
-	char buf[Ndialstr];
-	char netdir[Nnetdir];
-	char newdir[Nnetdir];
-	uchar ipaddr[4];
-	int dfd, cfd, listenfd;
-	char *p;
-	int port;
-
-	/* get a channel to listen on, let kernel pick the port number */
-	sprint(buf, "%s!*!0", d->net);
-	listenfd = announce(buf, netdir);
-	if(listenfd < 0){
-		fprint(2, "can't listen for ftp callback: %r\n", buf);
-		return -1;
-	}
-
-	/* get the local address and port number */
-	sprint(newdir, "%s/local", netdir);
-	readfile(newdir, buf, sizeof buf);
-	p = strchr(buf, '!')+1;
-	parseip(ipaddr, buf);
-	port = atoi(p);
-
-	/* tell remote side address and port*/
-	sendrequest(d->ftpctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2],
-		ipaddr[3], port>>8, port&0xff);
-	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
-		close(listenfd);
-		werrstr("ftp protocol botch");
-		fprint(2, "active mode connect failed %s\n", msg);
-		return -1;
-	}
-
-	/* tell remote to send a file */
-	sendrequest(d->ftpctl, "RETR %s", d->u->reltext);
-	if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){
-		close(listenfd);
-		fprint(2, "active mode connect failed: %s\n", msg);
-		werrstr(msg);
-		return -1;
-	}
-
-	/* wait for a new call */
-	cfd = listen(netdir, newdir);
-	close(listenfd);
-	if(cfd < 0){
-		fprint(2, "active mode connect failed: %r\n");
-		return -1;
-	}
-
-	/* open the data connection and close the control connection */
-	dfd = accept(cfd, newdir);
-	close(cfd);
-	if(dfd < 0){
-		fprint(2, "active mode connect failed: %r\n");
-		werrstr("ftp protocol botch");
-		return -1;
-	}
-
-	return dfd;
-}
-
-/*
- * Given a url, return a file descriptor on which caller can
- * read an ftp document.
- * The caller is responsible for processing redirection loops.
- */
-int
-ftp(Url *url)
-{
-	int n;
-	int data;
-	Ftp *d;
-	int pfd[2];
-	char buf[2048];
-
-	if(url->type == 0)
-		url->type = PLAIN;
-
-	d = (Ftp*)emalloc(sizeof(Ftp));
-	d->u = url;
-	d->ftpctl = nil;
-
-	if(hello(d) == nil)
-		return terminateftp(d);
-	if(logon(d) < 0)
-		return terminateftp(d);
-
-	switch(url->type){
-	case PLAIN:
-	case HTML:
-		if(xfertype(d, "A") < 0)
-			return terminateftp(d);
-		break;
-	default:
-		if(xfertype(d, "I") < 0)
-			return terminateftp(d);
-		break;
-	}
-
-	/* first try passive mode, then active */
-	data = passive(d);
-	if(data < 0){
-		if(d->ftpctl == nil)
-			return -1;
-		data = active(d);
-		if(data < 0)
-			return -1;
-	}
-
-	if(pipe(pfd) < 0)
-		return -1;
-
-	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
-	case -1:
-		werrstr("Can't fork");
-		close(pfd[0]);
-		close(pfd[1]);
-		return terminateftp(d);
-	case 0:
-		close(pfd[0]);
-		while((n=read(data, buf, sizeof(buf)))>0)
-			write(pfd[1], buf, n);
-		if(n<0)
-			fprint(2, "ftp: %s: %r\n", url->fullname);
-		_exits(0);
-	default:
-		close(pfd[1]);
-		close(data);
-		terminateftp(d);
-		return pfd[0];
-	}
-	return -1;
-}
--- a/sys/src/cmd/mothra/getpix.c
+++ b/sys/src/cmd/mothra/getpix.c
@@ -39,7 +39,7 @@
 	Pix *p;
 
 	ap=t->user;
-	crackurl(&url, ap->image, w->base);
+	seturl(&url, ap->image, w->url->fullname);
 	for(p=w->pix;p!=nil; p=p->next)
 		if(strcmp(ap->image, p->name)==0 && ap->width==p->width && ap->height==p->height){
 			storebitmap(t, p->b);
@@ -105,12 +105,55 @@
 }
 
 void getpix(Rtext *t, Www *w){
+	int i, pid, nworker, worker[NXPROC];
 	Action *ap;
 
+	nworker = 0;
+	for(i=0; i<nelem(worker); i++)
+		worker[i] = -1;
+
 	for(;t!=0;t=t->next){
 		ap=t->user;
-		if(ap && ap->image)
-			getimage(t, w);
+		if(ap && ap->image){
+			pid = rfork(RFFDG|RFPROC|RFMEM);
+			switch(pid){
+			case -1:
+				fprint(2, "fork: %r\n");
+				break;
+			case 0:
+				getimage(t, w);
+				exits(0);
+			default:
+				for(i=0; i<nelem(worker); i++)
+					if(worker[i] == -1){
+						worker[i] = pid;
+						nworker++;
+						break;
+					}
+
+				while(nworker == nelem(worker)){
+					if((pid = waitpid()) < 0)
+						break;
+					for(i=0; i<nelem(worker); i++)
+						if(worker[i] == pid){
+							worker[i] = -1;
+							nworker--;
+							break;
+						}
+				}
+			}
+			
+		}
+	}
+	while(nworker > 0){
+		if((pid = waitpid()) < 0)
+			break;
+		for(i=0; i<nelem(worker); i++)
+			if(worker[i] == pid){
+				worker[i] = -1;
+				nworker--;
+				break;
+			}
 	}
 }
 
--- a/sys/src/cmd/mothra/gopher.c
+++ /dev/null
@@ -1,38 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-#include "mothra.h"
-void httpheader(Url *, char *);
-/*
- * Given a url, return a file descriptor on which caller can
- * read a gopher document.
- */
-int gopher(Url *url){
-	int pfd[2];
-	char port[30];
-	if(pipe(pfd)==-1) return -1;
-	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
-	case -1:
-		close(pfd[0]);
-		close(pfd[1]);
-		return -1;
-	case 0:
-		dup(pfd[1], 1);
-		close(pfd[0]);
-		close(pfd[1]);
-		sprint(port, "%d", url->port);
-		execl("/bin/aux/gopher2html",
-			"gopher2html", url->ipaddr, port, url->reltext+1, 0);
-		fprint(2, "Can't exec aux/gopher2html!\n");
-		print("<head><title>Mothra error</title></head>\n");
-		print("<body><h1>Mothra error</h1>\n");
-		print("Can't exec aux/gopher2html!</body>\n");
-		exits("no exec");
-	default:
-		close(pfd[1]);
-		url->type=HTML;
-		return pfd[0];
-	}
-}
--- a/sys/src/cmd/mothra/gopher2html.c
+++ /dev/null
@@ -1,230 +1,0 @@
-/*
- * Reads gopher output from a TCP port, outputs
- * html on standard output.
- * Usage: gopher2html gopher-string
- *	where gopher-string is the string sent to
- *	the gopher server to get the document.
- *
- * Gopher protocol is described in rfc1436
- */
-#include <u.h>
-#include <libc.h>
-char *cmd;
-int ifd;
-void errexit(char *s, ...){
-	static char buf[1024];
-	char *out;
-	va_list args;
-	va_start(args, s);
-	out = doprint(buf, buf+sizeof(buf), s, args);
-	va_end(args);
-	*out='\0';
-	print("<head><title>%s error</title></head>\n", cmd);
-	print("<body><h1>%s error</h1>\n", cmd);
-	print("%s</body>\n", buf);
-	exits("gopher error");
-}
-void wtext(char *buf, char *ebuf){
-	char *bp;
-	for(bp=buf;bp!=ebuf;bp++){
-		if(*bp=='<' || *bp=='>' || *bp=='&' || *bp=='"'){
-			if(bp!=buf) write(1, buf, bp-buf);
-			buf=bp+1;
-			switch(*bp){
-			case '<': print("&lt;"); break;
-			case '>': print("&gt;"); break;
-			case '&': print("&amp;"); break;
-			case '"': print("&quot;"); break;
-			}
-		}
-	}
-	if(bp!=buf) write(1, buf, bp-buf);
-}
-void savefile(char *name, char *type){
-	int fd, n;
-	char save[30], buf[1024];
-	for(n=1;;n++){
-		if(n==100) errexit("can't save binary file %s: %r", name);
-		sprint(save, "gopher.save.%d", n);
-		fd=create(save, OWRITE, 0444);
-		if(fd!=-1) break;
-	}
-	print("<head><title>%s</title></head\n", name);
-	print("<body><h1>%s</h1><p>\n", name);
-	print("Saving %s file %s in <tt>%s</tt>...\n", type, name, save);
-	while((n=read(ifd, buf, sizeof buf))>0) write(fd, buf, n);
-	close(fd);
-	print("done</body>\n");
-}
-void copyfile(char *title){
-	char buf[1024];
-	int n;
-	print("<head><title>%s</title></head>\n", title);
-	print("<body><h1>%s</h1><pre>\n", title);
-	while((n=read(ifd, buf, sizeof buf))>0) wtext(buf, buf+n);
-	print("</pre></body>\n");
-}
-/*
- * A directory entry contains
- *	type name selector host port
- * all tab separated, except type and name (type is one character)
- */
-char ibuf[1024], *ibp, *eibuf;
-#define	EOF	(-1)
-int get(void){
-	int n;
-Again:
-	if(ibp==eibuf){
-		n=read(ifd, ibuf, sizeof(ibuf));
-		if(n<=0) return EOF;
-		eibuf=ibuf+n;
-		ibp=ibuf;
-	}
-	if(*ibp=='\r'){
-		ibp++;
-		goto Again;
-	}
-	return *ibp++&255;
-}
-char *escape(char *in){
-	static char out[516];
-	char *op, *eop;
-	eop=out+512;
-	op=out;
-	for(;*in;in++){
-		if(op<eop){
-			if(strchr("/$-_@.&!*'(),", *in)
-			|| 'a'<=*in && *in<='z'
-			|| 'A'<=*in && *in<='Z'
-			|| '0'<=*in && *in<='9')
-				*op++=*in;
-			else{
-				sprint(op, "%%%.2X", *in&255);
-				op+=3;
-			}
-		}
-	}
-	*op='\0';
-	return out;
-}
-void copydir(char *title){
-	int type, c;
-	char name[513], *ename;
-	char selector[513];
-	char host[513];
-	char port[513];
-	char *bp;
-	print("<head><title>%s</title></head>\n", title);
-	print("<body><h1>%s</h1><ul>\n", title);
-	for(;;){
-		type=get();
-		if(type==EOF || type=='.') break;
-		bp=name;
-		while((c=get())!=EOF && c!='\t') if(bp!=&name[512]) *bp++=c;
-		ename=bp;
-		bp=selector;
-		while((c=get())!=EOF && c!='\t') if(bp!=&selector[512]) *bp++=c;
-		*bp='\0';
-		bp=host;
-		while((c=get())!=EOF && c!='\t') if(bp!=&host[512]) *bp++=c;
-		*bp='\0';
-		bp=port;
-		while((c=get())!=EOF && c!='\t' && c!='\n') if(bp!=&port[512]) *bp++=c;
-		while(c!=EOF && c!='\n') c=get();
-		*bp='\0';
-		switch(type){
-		case '3':
-			print("<li>");
-			wtext(name, ename);
-			break;
-		case '7':
-			print("<li><isindex action=\"gopher://%s:%s/%c%s\">",
-				host, port, type, escape(selector));
-			wtext(name, ename);
-			break;
-		default:
-			print("<li><a href=\"gopher://%s:%s/%c%s\">",
-				host, port, type, escape(selector));
-			wtext(name, ename);
-			print("</a>\n");
-			break;
-		}
-	}
-	print("</ul></body>\n");
-}
-int hexdigit(int 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 -1;
-}
-void unescape(char *s){
-	char *t;
-	int hi, lo;
-	t=s;
-	while(*s){
-		if(*s=='%'
-		&& (hi=hexdigit(s[1]))>=0
-		&& (lo=hexdigit(s[2]))>=0){
-			*t++=hi*16+lo;
-			s+=3;
-		}
-		else *t++=*s++;
-	}
-	*t='\0';
-}
-void main(int argc, char *argv[]){
-	char dialstr[1024];
-	char *name;
-	cmd=argv[0];
-	if(argc!=4) errexit("Usage: %s host port selector", argv[0]);
-	sprint(dialstr, "tcp!%s!%s", argv[1], argv[2]);
-	ifd=dial(dialstr, 0, 0, 0);
-	if(ifd==-1) errexit("can't call %s:%s", argv[1], argv[2]);
-	unescape(argv[3]);
-	switch(argv[3][0]){
-	case '/':
-		fprint(ifd, "\r\n");
-		copydir(argv[3]);
-		break;
-	case '\0':
-		fprint(ifd, "\r\n");
-		copydir(argv[1]);
-		break;
-	case '7':	/* index query */
-		name=strchr(argv[3], '?');
-		if(name!=0){
-			if(name==argv[3]+1){
-				argv[3][1]=argv[3][0];
-				argv[3]++;
-			}
-			else
-				*name='\t';
-			name++;
-		}
-		else
-			name=argv[3];
-		fprint(ifd, "%s\r\n", argv[3]+1);
-		copydir(name);
-		break;
-	default:
-		fprint(ifd, "%s\r\n", argv[3]+1);
-		name=strrchr(argv[3], '/');
-		if(name==0) name=argv[3];
-		else name++;
-		switch(argv[3][0]){
-		default:	errexit("sorry, can't handle %s (type %c)",
-					argv[3]+1, argv[3][0]);
-		case '0':	copyfile(name); break;
-		case '1':	copydir(name); break;
-		case '4':	savefile(name, "Macintosh BINHEX"); break;
-		case '5':	savefile(name, "DOS binary"); break;
-		case '6':	savefile(name, "uuencoded"); break;
-		case '9':	savefile(name, "binary"); break;
-		case 'g':	savefile(name, "GIF"); break;
-		case 'I':	savefile(name, "some sort of image"); break;
-		}
-		break;
-	}
-	exits(0);
-}
--- a/sys/src/cmd/mothra/help.html
+++ /dev/null
@@ -1,78 +1,0 @@
-<html><head><title>Mothra help</title>
-</head>
-<body>
-<H1>Mothra Help</H1>
-<p>
-Mothra is a World-wide Web browser. Its display looks like this:
-<p><img src="file:display.pic" alt="[mothra display]">
-<p>The display's regions, from top to bottom, are:
-<ul>
-<li>Error messages and other information.
-<li>A text input window in which <a href="#commands">commands</a> can be typed.
-<li>A scrollable list of titles of previously visited documents, with the most recent first.
-Pointing at one of these lines with mouse button 1 revisits the document.
-<li>The title of the currently-displayed document.
-<li>The URL of the currently-displayed document.
-<li>The scrollable document display.  Underlined text and
-images surrounded by boxes may be pointed at with button 1 to
-visit the files that they refer to.  Files that are not
-HTML documents (for example images or mailto: urls) cause
-<i>9v</i> or <i>mail</i> to pop up in a new 8&#189; window.
-</ul>
-<h4>Mouse Action</H4>
-<p>Pointing with button
-2 instead of button 1 selects a url without following it;
-the url will be displayed in the selection: area and commands
-will refer to the url, but it will not be drawn in the document display.
-Button 3 pops up a command menu that contains
-<ul>
-<li><b>alt display</b><br>switches to (or from) the alternate display, which shows only
-the scrollable document display area.  This might be useful when running mothra
-in a small window.
-<li><b>snarf url</b><br>copies the selected url into the snarf buffer.
-<li><b>paste</b><br>appends the snarf buffer to the command window.
-<li><b>inline pix</b><br>turn off/on loading of inline images.  Image maps cannot be disabled.
-<li><b>fix cmap</b><br>reload the default plan 9 colormap
-<li><b>save hit</b><br>appends the selected url to file:$home/lib/hit.html
-<li><b>hit list</b><br>displays file:$home/lib/hit.html
-<li><b>exit</b>
-</ul>
-<a name="#commands"><h4>Commands</h4></a>
-<p>The commands you can type are:
-<ul>
-<li>g [url]<br>get the page with the given url (default, the selection.)
-<li>r [url]<br>refresh the display if the URL changes.
-Otherwise, you will probably see a cached version.
-<li>s file<br>save the current page in the given file.
-<li>w file<br>write a bitmap image of the document display area in the given file.
-<li>q<br>exit.
-<li>?<br>get help.
-<li>h<br>get help.
-</ul>
-<p>
-<h4>Configuration</h4>
-Mothra gets configuration information from the environment.
-<ul>
-<li>$url<br>The default <i>url</i> displayed when mothra starts.
-A <i>url</i> given on the command line overrides this.
-The default is <b>/sys/lib/mothra/start.html</b>
-<li>$httpproxy<br>The network address of an http proxy server,
-in the format expected by dial(2).  If $httpproxy is not set
-or is null, no proxy server is used.
-</ul>
-<h4>Command line</h4>
-If the mothra command has an argument, it is the name of a <i>url</i> to visit
-instead of the startup page.  Giving mothra the <b>-i</b> flag disables loading
-of inline images.  The <b>inline pix</b> menu item will reset this option.
-<h4>Files</h4>
-Mothra creates several files in $home/lib/mothra.
-<ul>
-<li>mothra.log<br>a list of all the url's visited
-<li>mothra.err<br>a log of error messages, mostly uninteresting
-<li>hit.html<br>the hit list used by the <b>save hit</b>
-and <b>hit list</b> commands.  Since <b>save hit</b> only
-adds new urls to the end of this file, it is safe to edit it
-to add annotation or sort the saved urls.
-</ul>
-</body>
-</html>
--- a/sys/src/cmd/mothra/http.c
+++ /dev/null
@@ -1,486 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <panel.h>
-
-#include <libsec.h>		/* tlsClient */
-
-#include "mothra.h"
-typedef struct Cache Cache;
-struct Cache{
-	int fd;			/* file descriptor on which to write cached data */
-	ulong hash;		/* hash of url, used to compute cache file name */
-	int modtime;		/* time at which cache entry was created */
-	int type;		/* url->type of cached entry */
-};
-void httpheader(Url *, char *);
-int httpresponse(char *);
-static char *proxyserver;	/* name of proxy server */
-void exitnow(void*, char*){
-	noted(NDFLT);
-}
-void hashname(char *name, int n, char *stem, Cache *c){
-	snprint(name, n, "/sys/lib/mothra/cache/%s.%.8lux", stem, c->hash);
-}
-// #define	CacheEnabled
-/*
- * Returns fd of cached file, if found (else -1)
- * Fills in Cache data structure for caller
- * If stale is set, caller has determined that the existing
- * cache entry for this url is stale, so we shouldn't bother re-examining it.
- */
-int cacheopen(Url *url, Cache *c, int stale){
-#ifdef CacheEnabled
-	int fd, n;
-	char name[NNAME+1], *s, *l;
-	/*
-	 * If we're using a proxy server or the url contains a ? or =,
-	 * don't even bother.
-	 */
-	if(proxyserver || strchr(url->reltext, '?')!=0 || strchr(url->reltext, '=')!=0){
-		c->fd=-1;
-		return -1;
-	}
-	c->hash=0;
-	for(s=url->fullname,n=0;*s;s++,n++) c->hash=c->hash*n+(*s&255);
-	if(stale)
-		fd=-1;
-	else{
-		hashname(name, sizeof(name), "cache", c);
-		fd=open(name, OREAD);
-	}
-	if(fd==-1){
-		hashname(name, sizeof(name), "write", c);
-		c->fd=create(name, OWRITE, 0444);
-		if(c->fd!=-1)
-			fprint(c->fd, "%s %10ld\n", url->fullname, time(0));
-		return -1;
-	}
-	c->fd=-1;
-	for(l=name;l!=&name[NNAME];l+=n){
-		n=&name[NNAME]-l;
-		n=read(fd, l, n);
-		if(n<=0) break;
-	}
-	*l='\0';
-	s=strchr(name, ' ');
-	if(s==0){
-		close(fd);
-		return -1;
-	}
-	*s='\0';
-	if(strcmp(url->fullname, name)!=0){
-		close(fd);
-		return -1;
-	}
-	c->modtime=atol(++s);
-	s=strchr(s, '\n');
-	if(s==0){
-		close(fd);
-		return -1;
-	}
-	s++;
-	if(strncmp(s, "type ", 5)!=0){
-		close(fd);
-		return -1;
-	}
-	c->type=atoi(s+5);
-	s=strchr(s+5, '\n');
-	if(s==0){
-		close(fd);
-		return -1;
-	}
-	
-	seek(fd, s-name+1, 0);
-	return fd;
-#else
-	c->fd=-1;
-	return -1;
-#endif
-}
-/*
- * Close url->fd and either rename the cache file or
- * remove it, depending on success
- */
-void cacheclose(Cache *c, int success){
-	char wname[NNAME+1], cname[NNAME+1], *celem;
-	Dir *wdir;
-	if(c->fd==-1) return;
-	close(c->fd);
-	hashname(wname, sizeof(wname), "write", c);
-	if(!success){
-		remove(wname);
-		return;
-	}
-	if((wdir = dirstat(wname)) == 0)
-		return;
-	hashname(cname, sizeof(cname), "cache", c);
-	if(access(cname, 0) == 0){
-		if(remove(cname)==-1){
-			remove(wname);
-			free(wdir);
-			return;
-		}
-		/*
-		 * This looks implausible, but it's what the mv command does
-		 */
-		do; while(remove(cname)!=-1);
-	}
-	celem=strrchr(cname, '/');
-	if(celem==0) celem=cname;
-	else celem++;
-	strcpy(wdir->name, celem);
-	if(dirwstat(wname, wdir)==-1)
-		remove(wname);
-	free(wdir);
-}
-static char *wkday[]={
-	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-static char *month[]={
-	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
-	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-/*
- * Sun, 06 Nov 1994 08:49:38 GMT
- * 123456789 123456789 123456789
- */
-char *rfc1123date(long time){
-	static char buf[50];
-	Tm *t;
-	t=gmtime(time);
-	snprint(buf, sizeof(buf), "%s, %2.2d %s %4.4d %2.2d:%2.2d:%2.2d GMT",
-		wkday[t->wday], t->mday, month[t->mon], t->year+1900,
-		t->hour, t->min, t->sec);
-	return buf;
-}
-/*
- * Given a url, return a file descriptor on which caller can
- * read an http document.  As a side effect, we parse the
- * http header and fill in some fields in the url.
- * The caller is responsible for processing redirection loops.
- * Method can be either GET or POST.  If method==post, body
- * is the text to be posted.
- */
-int http(Url *url, int method, char *body){
-	char *addr, *com;
-	int fd, n, nnl, len;
-	int ncom, m;
-	int pfd[2];
-	char buf[1024], *bp, *ebp;
-	char line[1024+1], *lp, *elp;
-	char authstr[NAUTH], *urlname;
-	int gotresponse;
-	int response;
-	Cache cache;
-	int cfd, cookiefd;
-	static int firsttime=1;
-	static int gotcookies;
-
-	if(firsttime){
-		proxyserver=getenv("httpproxy");
-		gotcookies=(access("/mnt/webcookies/http", AREAD|AWRITE)==0);
-		firsttime=0;
-	}
-	*authstr = 0;
-Authorize:
-	cfd=-1;
-	cookiefd=-1;
-	if(proxyserver && proxyserver[0]!='\0'){
-		addr=strdup(proxyserver);
-		urlname=url->fullname;
-	}
-	else{
-		addr=emalloc(strlen(url->ipaddr)+100);
-		sprint(addr, "tcp!%s!%d", url->ipaddr, url->port);
-		urlname=url->reltext;
-	}
-	fd=dial(addr, 0, 0, 0);
-	free(addr);
-	if(fd==-1) goto ErrReturn;
-	if(url->ssl){
-		int tfd;
-		TLSconn conn;
-
-		memset(&conn, 0, sizeof conn);
-		tfd = tlsClient(fd, &conn);
-		if(tfd < 0){
-			close(fd);
-			goto ErrReturn;
-		}
-		/* BUG: check cert here? */
-		if(conn.cert)
-			free(conn.cert);
-		close(fd);
-		fd = tfd;
-	}
-	ncom=strlen(urlname)+sizeof(buf);
-	com=emalloc(ncom+2);
-	cache.fd=-1;
-	switch(method){
-	case GET:
-		cfd=cacheopen(url, &cache, 0);
-		if(cfd==-1)
-			n=sprint(com,
-				"GET %s HTTP/1.0\r\n%s"
-				"Accept: */*\r\n"
-				"User-agent: mothra/%s\r\n"
-				"Host: %s\r\n",
-				urlname, authstr, version, url->ipaddr);
-		else
-			n=sprint(com,
-				"GET %s HTTP/1.0\r\n%s"
-				"If-Modified-since: %s\r\n"
-				"Accept: */*\r\n"
-				"User-agent: mothra/%s\r\n"
-				"Host: %s\r\n",
-				urlname, authstr, rfc1123date(cache.modtime), version, url->ipaddr);
-		break;
-	case POST:
-		len=strlen(body);
-		n=sprint(com,
-			"POST %s HTTP/1.0\r\n%s"
-			"Content-type: application/x-www-form-urlencoded\r\n"
-			"Content-length: %d\r\n"
-			"User-agent: mothra/%s\r\n",
-			urlname, authstr, len, version);
-		break;
-	}
-	if(gotcookies && (cookiefd=open("/mnt/webcookies/http", ORDWR)) >= 0){
-		if(fprint(cookiefd, "%s", url->fullname) > 0){
-			while((m=read(cookiefd, buf, sizeof buf)) > 0){
-				if(m+n>ncom){
-					if(write(fd, com, n)!= n){
-						free(com);
-						goto fdErrReturn;
-					}
-					n=0;
-					com[0] = '\0';
-				}
-				strncat(com, buf, m);
-				n += m;
-			}
-		}else{
-			close(cookiefd);
-			cookiefd=-1;
-		}
-	}
-	strcat(com, "\r\n");
-	n += 2;
-	switch(method){
-	case GET:
-		if(write(fd, com, n)!=n){
-			free(com);
-			goto fdErrReturn;
-		}
-		break;
-	case POST:
-		if(write(fd, com, n)!=n
-		|| write(fd, body, len)!=len){
-			free(com);
-			goto fdErrReturn;
-		}
-		break;
-	}
-	free(com);
-	if(pipe(pfd)==-1) goto fdErrReturn;
-	n=read(fd, buf, 1024);
-	if(n<=0){
-	EarlyEof:
-		if(n==0){
-			fprint(2, "%s: EOF in header\n", url->fullname);
-			werrstr("EOF in header");
-		}
-	pfdErrReturn:
-		close(pfd[0]);
-		close(pfd[1]);
-	fdErrReturn:
-		close(fd);
-	ErrReturn:
-		if(cookiefd>=0)
-			close(cookiefd);
-		cacheclose(&cache, 0);
-		return -1;
-	}
-	bp=buf;
-	ebp=buf+n;
-	url->type=0;
-	if(strncmp(buf, "HTTP/", 5)==0){	/* hack test for presence of header */
-		SET(response);
-		gotresponse=0;
-		url->redirname[0]='\0';
-		nnl=0;
-		lp=line;
-		elp=line+1024;
-		while(nnl!=2){
-			if(bp==ebp){
-				n=read(fd, buf, 1024);
-				if(n<=0) goto EarlyEof;
-				ebp=buf+n;
-				bp=buf;
-			}
-			if(*bp!='\r'){
-				if(nnl==1 && (!gotresponse || (*bp!=' ' && *bp!='\t'))){
-					*lp='\0';
-					if(gotresponse){
-						if(cookiefd>=0 && cistrncmp(line, "Set-Cookie:", 11) == 0)
-							fprint(cookiefd, "%s\n", line);
-						httpheader(url, line);
-					}else{
-						response=httpresponse(line);
-						gotresponse=1;
-					}
-					lp=line;
-				}
-				if(*bp=='\n') nnl++;
-				else{
-					nnl=0;
-					if(lp!=elp) *lp++=*bp;
-				}
-			}
-			bp++;
-		}
-		if(gotresponse) switch(response){
-		case 200:	/* OK */
-		case 201:	/* Created */
-		case 202:	/* Accepted */
-			break;
-		case 204:	/* No Content */
-			werrstr("URL has no content");
-			goto pfdErrReturn;
-		case 301:	/* Moved Permanently */
-		case 302:	/* Moved Temporarily */
-			if(url->redirname[0]){
-				url->type=FORWARD;
-				werrstr("URL forwarded");
-				goto pfdErrReturn;
-			}
-			break;
-		case 304:	/* Not Modified */
-			if(cfd!=-1){
-				url->type=cache.type;
-				close(pfd[0]);
-				close(pfd[1]);
-				close(fd);
-				if(cookiefd>=0)
-					close(cookiefd);
-				return cfd;
-			}
-			werrstr("Not modified!");
-			goto pfdErrReturn;
-		case 400:	/* Bad Request */
-			werrstr("Bad Request to server");
-			goto pfdErrReturn;
-		case 401:	/* Unauthorized */
-		case 402:	/* ??? */
-			if(*authstr == 0){
-				close(pfd[0]);
-				close(pfd[1]);
-				close(fd);
-				if(auth(url, authstr, sizeof(authstr)) == 0){
-					if(cfd!=-1)
-						close(cfd);
-					goto Authorize;
-				}
-				goto ErrReturn;
-			}
-			break;
-		case 403:	/* Forbidden */
-			werrstr("Forbidden by server");
-			goto pfdErrReturn;
-		case 404:	/* Not Found */
-			werrstr("Not found on server");
-			goto pfdErrReturn;
-		case 500:	/* Internal server error */
-			werrstr("Server choked");
-			goto pfdErrReturn;
-		case 501:	/* Not implemented */
-			werrstr("Server can't do it!");
-			goto pfdErrReturn;
-		case 502:	/* Bad gateway */
-			werrstr("Bad gateway");
-			goto pfdErrReturn;
-		case 503:	/* Service unavailable */
-			werrstr("Service unavailable");
-			goto pfdErrReturn;
-		}
-	}
-	if(cfd!=-1){
-		close(cfd);
-		cfd=cacheopen(url, &cache, 1);
-	}
-	if(cookiefd>=0){
-		close(cookiefd);
-		cookiefd=-1;
-	}
-	if(url->type==0)
-		url->type=suffix2type(url->fullname);
-	if(cache.fd!=-1) fprint(cache.fd, "type %d\n", url->type);
-	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
-	case -1:
-		werrstr("Can't fork");
-		goto pfdErrReturn;
-	case 0:
-		notify(exitnow); /* otherwise write on closed pipe below may cause havoc */
-		close(pfd[0]);
-		if(bp!=ebp){
-			write(pfd[1], bp, ebp-bp);
-			if(cache.fd!=-1) write(cache.fd, bp, ebp-bp);
-		}
-		while((n=read(fd, buf, 1024))>0){
-			write(pfd[1], buf, n);
-			if(cache.fd!=-1) write(cache.fd, buf, n);
-		}
-		cacheclose(&cache, 1);
-		_exits(0);
-	default:
-		if(cache.fd!=-1) close(cache.fd);
-		close(pfd[1]);
-		close(fd);
-		return pfd[0];
-	}
-}
-/*
- * Process a header line for this url
- */
-void httpheader(Url *url, char *line){
-	char *name, *arg, *s, *arg2;
-	name=line;
-	while(*name==' ' || *name=='\t') name++;
-	for(s=name;*s!=':';s++) if(*s=='\0') return;
-	*s++='\0';
-	while(*s==' ' || *s=='\t') s++;
-	arg=s;
-	while(*s!=' ' && *s!='\t' && *s!=';' && *s!='\0') s++;
-	while(*s == ' ' || *s == '\t' || *s == ';')
-		*s++ = '\0';
-	arg2 = s;
-	if(cistrcmp(name, "Content-Type")==0){
-		url->type|=content2type(arg, url->reltext);
-		if(cistrncmp(arg2, "charset=", 8) == 0){
-			strncpy(url->charset, arg2+8, sizeof(url->charset));
-		} else {
-			url->charset[0] = '\0';
-		}
-	}
-	else if(cistrcmp(name, "Content-Encoding")==0)
-		url->type|=encoding2type(arg);
-	else if(cistrcmp(name, "WWW-authenticate")==0){
-		strncpy(url->authtype, arg, sizeof(url->authtype));
-		strncpy(url->autharg, arg2, sizeof(url->autharg));
-	}
-	else if(cistrcmp(name, "URI")==0){
-		if(*arg!='<') return;
-		++arg;
-		for(s=arg;*s!='>';s++) if(*s=='\0') return;
-		*s='\0';
-		strncpy(url->redirname, arg, sizeof(url->redirname));
-	}
-	else if(cistrcmp(name, "Location")==0)
-		strncpy(url->redirname, arg, sizeof(url->redirname));
-}
-int httpresponse(char *line){
-	while(*line!=' ' && *line!='\t' && *line!='\0') line++;
-	return atoi(line);
-}
--- a/sys/src/cmd/mothra/libpanel/mem.c
+++ b/sys/src/cmd/mothra/libpanel/mem.c
@@ -11,6 +11,7 @@
 		fprint(2, "Can't malloc!\n");
 		exits("no mem");
 	}
+	setmalloctag(v, getcallerpc(&n));
 	return v;
 }
 void pl_unexpected(Panel *g, char *rou){
--- a/sys/src/cmd/mothra/mkfile
+++ b/sys/src/cmd/mothra/mkfile
@@ -3,19 +3,12 @@
 TARG=mothra
 LIB=libpanel/libpanel.$O.a 
 CFILES= \
-	cistr.c \
-	crackurl.c \
-	file.c \
 	filetype.c \
 	forms.c \
-	ftp.c \
 	getpix.c \
-	gopher.c \
 	html.syntax.c \
-	http.c \
 	mothra.c \
 	rdhtml.c \
-	auth.c \
 
 OFILES=${CFILES:%.c=%.$O} version.$O
 HFILES=mothra.h html.h tcs.h libpanel/panel.h libpanel/rtext.h
--- a/sys/src/cmd/mothra/mothra.c
+++ b/sys/src/cmd/mothra/mothra.c
@@ -23,29 +23,22 @@
 Panel *msg;	/* message display */
 Panel *menu3;	/* button 3 menu */
 Mouse mouse;	/* current mouse data */
-char helpfile[] = "file:/sys/lib/mothra/help.html";
 char mothra[] = "mothra!";
 Url defurl={
-	"http://plan9.bell-labs.com/",
-	0,
-	"plan9.bell-labs.com",
-	"/",
+	"http://cat-v.org/",
 	"",
-	"", "", "",
-	80,
-	HTTP,
-	HTML
+	"http://cat-v.org/",
+	"",
+	"",
+	HTML,
 };
 Url badurl={
+	"",
+	"",
 	"No file loaded",
-	0,
 	"",
-	"/dev/null",
-	"", "", "",
 	"",
-	0,
-	FILE,
-	HTML
+	HTML,
 };
 Cursor patientcurs={
 	0, 0,
@@ -83,6 +76,7 @@
 	0x0E, 0x60, 0x1C, 0x00, 0x38, 0x00, 0x71, 0xB6,
 	0x61, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 };
+char *mtpt="/mnt/web";
 Www *current=0;
 Url *selection=0;
 int logfile;
@@ -210,6 +204,7 @@
 			sprint(home, "%s/lib/mothra", henv);
 			f=create(home, OREAD, DMDIR|0777);
 			if(f!=-1) close(f);
+			free(henv);
 		}
 		else
 			strcpy(home, "/tmp");
@@ -231,6 +226,9 @@
 	ARGBEGIN{
 	case 'd': debug++; break;
 	case 'v': verbose=1; break;
+	case 'm':
+		if(mtpt = ARGF())
+			break;
 	default:  goto Usage;
 	}ARGEND
 
@@ -243,12 +241,12 @@
 	switch(argc){
 	default:
 	Usage:
-		fprint(2, "Usage: %s [-d] [url]\n", argv[0]);
+		fprint(2, "Usage: %s [-d] [-m mtpt] [url]\n", argv[0]);
 		exits("usage");
 	case 0:
 		url=getenv("url");
 		if(url==0 || url[0]=='\0')
-			url="file:/sys/lib/mothra/start.html";
+			url=defurl.fullname;
 		break;
 	case 1: url=argv[0]; break;
 	}
@@ -284,7 +282,6 @@
 	fillellipse(bullet, Pt(4,4), 3, 3, display->black, ZP);
 	new = www(-1);
 	new->url=&badurl;
-	new->base=&badurl;
 	strcpy(new->title, "See error message above");
 	plrtstr(&new->text, 0, 0, font, "See error message above", 0, 0);
 	new->alldone=1;
@@ -527,10 +524,6 @@
 	default:
 		message("Unknown command %s, type h for help", s);
 		break;
-	case '?':
-	case 'h':
-		geturl(helpfile, GET, 0, 1, 0);
-		break;
 	case 'g':
 		s=arg(s);
 		if(*s=='\0'){
@@ -543,9 +536,8 @@
 		break;
 	case 'r':
 		s = arg(s);
-		if(*s == '\0')
-			s = selection ? selection->fullname : helpfile;
-		geturl(s, GET, 0, 0, 0);
+		if(*s == '\0' && selection)
+			geturl(selection->fullname, GET, 0, 0, 0);
 		break;
 	case 'W':
 		s=arg(s);
@@ -697,57 +689,79 @@
 		_exits(0);
 	}
 }
+
+int readstr(char *buf, int nbuf, char *base, char *name)
+{
+	char path[128];
+	int n, fd;
+
+	snprint(path, sizeof path, "%s/%s", base, name);
+	if((fd = open(path, OREAD)) < 0){
+	ErrOut:
+		memset(buf, 0, nbuf);
+		return 0;
+	}
+	n = read(fd, buf, nbuf-1);
+	close(fd);
+	if(n <= 0){
+		close(fd);
+		goto ErrOut;
+	}
+	buf[n] = 0;
+	return n;
+}
+
 int urlopen(Url *url, int method, char *body){
-	int fd;
-	Url prev;
-	int nredir;
-	Dir *dir;
-	nredir=0;
-Again:
-	if(++nredir==NREDIR){
-		werrstr("redir loop");
+	int conn, ctlfd, fd, n;
+	char buf[1024+1];
+
+	snprint(buf, sizeof buf, "%s/clone", mtpt);
+	if((ctlfd = open(buf, ORDWR)) < 0)
 		return -1;
+	if((n = read(ctlfd, buf, sizeof buf-1)) <= 0){
+		close(ctlfd);
+		return -1;
 	}
-	seek(logfile, 0, 2);
-	fprint(logfile, "%s\n", url->fullname);
-	switch(url->access){
-	default:
-		werrstr("unknown access type");
+	buf[n] = 0;
+	conn = atoi(buf);
+
+	if(url->basename[0]){
+		n = snprint(buf, sizeof buf, "baseurl %s", url->basename);
+		write(ctlfd, buf, n);
+	}
+	n = snprint(buf, sizeof buf, "url %s", url->reltext);
+	if(write(ctlfd, buf, n) != n){
+	ErrOut:
+		close(ctlfd);
 		return -1;
-	case FTP:
-		url->type = suffix2type(url->reltext);
-		return ftp(url);
-	case HTTP:
-		fd=http(url, method, body);
-		if(url->type==FORWARD){
-			prev=*url;
-			crackurl(url, prev.redirname, &prev);
+	}
 
-			/*
-			 * I'm not convinced that the following two lines are right,
-			 * but once I got a redir loop because they were missing.
-			 */
-			method=GET;
-			body=0;
-			goto Again;
+	if(method == POST && body){
+		snprint(buf, sizeof buf, "%s/%d/postbody", mtpt, conn);
+		if((fd = open(buf, OWRITE)) < 0)
+			goto ErrOut;
+		n = strlen(body);
+		if(write(fd, body, n) != n){
+			close(fd);
+			goto ErrOut;
 		}
-		return fd;	
-	case FILE:
-		url->type=suffix2type(url->reltext);
-		fd=open(url->reltext, OREAD);
-		if(fd!=-1){
-			dir=dirfstat(fd);
-			if(dir->mode&DMDIR){
-				url->type=HTML;
-				free(dir);
-				return dir2html(url->reltext, fd);
-			}
-			free(dir);
-		}
-		return fd;
-	case GOPHER:
-		return gopher(url);
+		close(fd);
 	}
+
+	snprint(buf, sizeof buf, "%s/%d/body", mtpt, conn);
+	if((fd = open(buf, OREAD)) < 0)
+		goto ErrOut;
+
+	snprint(buf, sizeof buf, "%s/%d/parsed", mtpt, conn);
+	readstr(url->fullname, sizeof(url->fullname), buf, "url");
+	readstr(url->tag, sizeof(url->tag), buf, "fragment");
+
+	snprint(buf, sizeof buf, "%s/%d", mtpt, conn);
+	readstr(buf, sizeof buf, buf, "contenttype");
+	url->type = content2type(buf, url->fullname);
+
+	close(ctlfd);
+	return fd;
 }
 
 int pipeline(char *cmd, int fd)
@@ -781,27 +795,21 @@
 /*
  * select the file at the given url
  */
+void seturl(Url *url, char *urlname, char *base){
+	strncpy(url->reltext, urlname, sizeof(url->reltext));
+	strcpy(url->basename, base);
+	url->fullname[0] = 0;
+	url->charset[0] = 0;
+	url->tag[0] = 0;
+	url->type = 0;
+	url->map = 0;
+}
+
 void selurl(char *urlname){
-	Url *cur;
 	static Url url;
-	if(current){
-		cur=current->base;
-		/*
-		 * I believe that the following test should never succeed
-		 */
-		if(cur==0){
-			cur=current->url;
-			if(cur==0){
-				fprint(2, "bad base & url, getting %s\n", urlname);
-				cur=&defurl;
-			}
-			else
-				fprint(2, "bad base, current %s, getting %s\n",
-					current->url->fullname, urlname);
-		}
-	}
-	else cur=&defurl;
-	crackurl(&url, urlname, cur);
+	seturl(&url, urlname, current?
+		current->url->fullname :
+		defurl.fullname);
 	selection=&url;
 	message("selected: %s", selection->fullname);
 }
@@ -828,61 +836,15 @@
 	selurl(urlname);
 	selection->map=map;
 
-	message("getting %s", selection->fullname);
+	message("getting %s", selection->reltext);
 	esetcursor(&patientcurs);
-	switch(selection->access){
-	default:
-		message("unknown access %d", selection->access);
-		break;
-	case TELNET:
-		sprint(cmd, "telnet %s", selection->reltext);
-		popwin(cmd);
-		break;
-	case MAILTO:
-		if(body){
-			/*
-			 * Undocumented Mozilla feature
-			 */
-			pipe(pfd);
-			switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
-			case -1:
-				message("Can't fork!");
-				break;
-			case 0:
-				close(0);
-				dup(pfd[1], 0);
-				close(pfd[1]);
-				close(pfd[0]);
-				execl("/bin/upas/send",
-					"sendmail", selection->reltext, 0);
-				message("Can't exec sendmail");
-				_exits(0);
-			default:
-				close(pfd[1]);
-				fprint(pfd[0],
-				    "Content-type: application/x-www-form-urlencoded\n"
-				    "Subject: Form posted from Mothra\n"
-				    "\n"
-				    "%s\n", body);
-				close(pfd[0]);
-				break;
-			}
-		}
-		else{
-			snprint(cmd, sizeof(cmd), "mail %s", selection->reltext);
-			popwin(cmd);
-		}
-		break;
-	case FTP:
-	case HTTP:
-	case FILE:
-	case GOPHER:
-		fd=urlopen(selection, method, body);
-		if(fd==-1){
+	for(;;){
+		if((fd=urlopen(selection, method, body)) < 0){
 			message("%r");
 			setcurrent(-1, 0);
 			break;
 		}
+		message("getting %s", selection->fullname);
 		if(selection->type&COMPRESS)
 			fd=pipeline("/bin/uncompress", fd);
 		else if(selection->type&GUNZIP)
@@ -908,19 +870,11 @@
 				freetext(w->text);
 				freeform(w->form);
 				freepix(w->pix);
-				if(w->base != w->url)
-					freeurl(w->base);
 				freeurl(w->url);
 				memset(w, 0, sizeof(*w));
 			}
-			if(selection->map){
-				if(current && current->base)	/* always succeeds */
-					w->url=copyurl(current->base);
-				else{
-					fprint(2, "no base for map!\n");
-					w->url=copyurl(selection);
-				}
-			}
+			if(selection->map)
+				w->url=copyurl(current->url);
 			else
 				w->url=copyurl(selection);
 			w->finished = 0;
@@ -944,6 +898,7 @@
 			filter("fb/xbm2pic|fb/9v", fd);
 			break;
 		}
+		break;
 	}
 	donecurs();
 }
--- a/sys/src/cmd/mothra/mothra.h
+++ b/sys/src/cmd/mothra/mothra.h
@@ -1,5 +1,6 @@
 enum{
 	NWWW=64,	/* # of pages we hold in the log */
+	NXPROC=5,	/* # of parallel procs loading the pix */
 	NNAME=512,
 	NLINE=256,
 	NAUTH=128,
@@ -11,14 +12,7 @@
 typedef struct Action Action;
 typedef struct Url Url;
 typedef struct Www Www;
-typedef struct Scheme Scheme;
 typedef struct Field Field;
-struct Scheme{
-	char *name;
-	int type;
-	int flags;
-	int port;
-};
 struct Action{
 	char *image;
 	Field *field;
@@ -30,23 +24,15 @@
 };
 struct Url{
 	char fullname[NNAME];
-	Scheme *scheme;
-	char ipaddr[NNAME];
+	char basename[NNAME];
 	char reltext[NNAME];
 	char tag[NNAME];
-	char redirname[NNAME];
-	char autharg[NAUTH];
-	char authtype[NTITLE];
 	char charset[NNAME];
-	int port;
-	int access;
 	int type;
 	int map;			/* is this an image map? */
-	int ssl;
 };
 struct Www{
 	Url *url;
-	Url *base;
 	void *pix;
 	void *form;
 	char title[NTITLE];
@@ -83,18 +69,6 @@
 };
 
 /*
- * url access types
- */
-enum{
-	HTTP=1,
-	FTP,
-	FILE,
-	TELNET,
-	MAILTO,
-	GOPHER,
-};
-
-/*
  *  authentication types
  */
 enum{
@@ -119,7 +93,7 @@
 void plrdhtml(char *, int, Www *);
 void plrdplain(char *, int, Www *);
 void htmlerror(char *, int, char *, ...);	/* user-supplied routine */
-void crackurl(Url *, char *, Url *);
+void seturl(Url *, char *, char *);
 void getpix(Rtext *, Www *);
 int pipeline(char *, int);
 int urlopen(Url *, int, char *);
@@ -128,16 +102,9 @@
 void *emallocz(int, int);
 void setbitmap(Rtext *);
 void message(char *, ...);
-int ftp(Url *);
-int http(Url *, int, char *);
-int gopher(Url *);
-int cistrcmp(char *, char *);
-int cistrncmp(char *, char *, int);
 int suffix2type(char *);
 int content2type(char *, char *);
 int encoding2type(char *);
 void mkfieldpanel(Rtext *);
 void geturl(char *, int, char *, int, int);
-int dir2html(char *, int);
-int auth(Url*, char*, int);
 char version[];
--- a/sys/src/cmd/mothra/rdhtml.c
+++ b/sys/src/cmd/mothra/rdhtml.c
@@ -679,7 +679,6 @@
 	g.tp=g.text;
 	g.etext=g.text+NTITLE-1;
 	dst->title[0]='\0';
-	dst->base=dst->url;
 	g.spacc=0;
 	g.form=0;
 	g.charset[0] = '\0';
@@ -795,12 +794,6 @@
 		case Tag_b:
 		case Tag_strong:
 			g.state->font=BOLD;
-			break;
-		case Tag_base:
-			if(str=pl_getattr(g.attr, "href")){
-				dst->base=emalloc(sizeof(Url));
-				crackurl(dst->base, str, dst->url);
-			}
 			break;
 		case Tag_blockquot:
 			g.spacc=0;
--- a/sys/src/cmd/mothra/urlcanon.c
+++ /dev/null
@@ -1,70 +1,0 @@
-#include <u.h>
-#include <libc.h>
-void *emalloc(int n){
-	void *p;
-	p=malloc(n);
-	if(p==0){
-		fprint(2, "can't malloc\n");
-		exits("no mem");
-	}
-	return p;
-}
-void urlcanon(char *name){
-	char *s, *t;
-	char **comp, **p, **q;
-	int rooted;
-	rooted=name[0]=='/';
-	/*
-	 * Break the name into a list of components
-	 */
-	comp=emalloc(strlen(name)*sizeof(char *));
-	p=comp;
-	*p++=name;
-	for(s=name;;s++){
-		if(*s=='/'){
-			*p++=s+1;
-			*s='\0';
-		}
-		else if(*s=='\0')
-			break;
-	}
-	*p=0;
-	/*
-	 * go through the component list, deleting components that are empty (except
-	 * the last component) or ., and any .. and its non-.. predecessor.
-	 */
-	p=q=comp;
-	while(*p){
-		if(strcmp(*p, "")==0 && p[1]!=0
-		|| strcmp(*p, ".")==0)
-			p++;
-		else if(strcmp(*p, "..")==0 && q!=comp && strcmp(q[-1], "..")!=0){
-			--q;
-			p++;
-		}
-		else
-			*q++=*p++;
-	}
-	*q=0;
-	/*
-	 * rebuild the path name
-	 */
-	s=name;
-	if(rooted) *s++='/';
-	for(p=comp;*p;p++){
-		t=*p;
-		while(*t) *s++=*t++;
-		if(p[1]!=0) *s++='/';
-	}
-	*s='\0';
-	free(comp);
-}
-void main(int argc, char *argv[]){
-	int i;
-	for(i=1;i!=argc;i++){
-		print("%s: ", argv[i]);
-		urlcanon(argv[i]);
-		print("%s\n", argv[i]);
-	}
-	exits(0);
-}
--- a/sys/src/cmd/mothra/version.c
+++ b/sys/src/cmd/mothra/version.c
@@ -1,1 +1,1 @@
-char version[]="Sep-4-19:07:30-CET-2011";
+char version[]="Sep-5-03:25:57-CET-2011";