git: 9front

Download patch

ref: f04e113279274526a8dae34de373027b68921fbf
parent: 6cfc30f3063d7fdf4c1e7c042bfe038382712140
author: cinap_lenrek <cinap_lenrek@gmx.de>
date: Mon Jun 18 17:26:28 EDT 2012

mothra/webfs: multipart/form-data and file upload support

--- a/sys/src/cmd/mothra/forms.c
+++ b/sys/src/cmd/mothra/forms.c
@@ -50,6 +50,7 @@
 	TEXTWIN,
 	HIDDEN,
 	INDEX,
+	FILE,
 };
 struct Option{
 	int selected;
@@ -59,12 +60,13 @@
 	Option *next;
 };
 
-#define BOUNDARY "hjdicksHjDiCkSHJDICKS"
+#define BOUNDARY "nAboJ9uN6ZXsqoVGzLAdjKq97TWDTGjo"
 
 void h_checkinput(Panel *, int, int);
 void h_radioinput(Panel *, int, int);
 void h_submitinput(Panel *, int);
 void h_buttoninput(Panel *, int);
+void h_fileinput(Panel *, int);
 void h_submittype(Panel *, char *);
 void h_submitindex(Panel *, char *);
 void h_resetinput(Panel *, int);
@@ -167,14 +169,11 @@
 			f->type=SUBMIT;
 		else if(cistrcmp(s, "button")==0)
 			f->type=BUTTON;
-		else if(cistrcmp(s, "image")==0){
-			/* presotto's egregious hack to make image submits do something */
-			if(f->name){
-				free(f->name);
-				f->name=0;
-			}
-			f->type=SUBMIT;
-		} else if(cistrcmp(s, "reset")==0)
+		else if(cistrcmp(s, "image")==0)
+			f->type=FILE;
+		else if(cistrcmp(s, "file")==0)
+			f->type=FILE;
+		else if(cistrcmp(s, "reset")==0)
 			f->type=RESET;
 		else if(cistrcmp(s, "hidden")==0)
 			f->type=HIDDEN;
@@ -243,6 +242,8 @@
 		*g->tp++=' ';
 		o->def=pl_hasattr(g->attr, "selected");
 		o->selected=o->def;
+		if(pl_hasattr(g->attr, "disabled"))
+			o->selected=0;
 		s=pl_getattr(g->attr, "value");
 		if(s==0)
 			o->value=o->label+1;
@@ -369,6 +370,9 @@
 	case BUTTON:
 		f->p=plbutton(0, 0, f->value[0]?f->value:"button", h_buttoninput);
 		break;
+	case FILE:
+		f->p=plbutton(0, 0, f->value[0]?f->value:"file", h_fileinput);
+		break;
 	case SELECT:
 		f->pulldown=plgroup(0,0);
 		scrl=plscrollbar(f->pulldown, PACKW|FILLY);
@@ -453,7 +457,26 @@
 }
 void h_buttoninput(Panel *p, int){
 }
+void h_fileinput(Panel *p, int){
+	char name[NNAME];
+	Field *f;
 
+	f = p->userp;
+	nstrcpy(name, f->value, sizeof(name));
+	free(f->value);
+	f->state=0;
+	for(;;){
+		if(eenter("Upload file", name, sizeof(name), &mouse) <= 0)
+			break;
+		if(access(name, AREAD) == 0){
+			f->state=1;
+			break;
+		}
+	}
+	f->value = strdup(name);
+	pldraw(f->p, screen);
+}
+
 /*
  * If there's exactly one button with type=text, then
  * a CR in the button is supposed to submit the form.
@@ -478,57 +501,76 @@
 	Rune *rp;
 	int n;
 
-#define SEPS	"-----------------------------" BOUNDARY
-#define NEXT	"\r\n" SEPS
-
-	sep = SEPS;
-	for(f=form->fields;f;f=f->next){
-		switch(f->type){
-		case TYPEIN:
-		case PASSWD:
-			fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
-				sep, f->name, plentryval(f->p));
-			sep = NEXT;
-			break;
-		case CHECK:
-		case RADIO:
-			if(!f->state) break;
-		case HIDDEN:
-			fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
-				sep, f->name, f->value);
-			sep = NEXT;
-			break;
-		case SELECT:
-			if(f->name==0)
-				continue;
-			for(o=f->options;o;o=o->next)
-				if(o->selected && o->value){
-					fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
-						sep, f->name, o->value);
-					sep = NEXT;
-				}
-			break;
-		case TEXTWIN:
-			if(f->name==0)
-				continue;
-			n=plelen(f->textwin);
-			rp=pleget(f->textwin);
-			p=b=malloc(UTFmax*n+1);
-			if(b == nil)
-				continue;
-			while(n > 0){
-				p += runetochar(p, rp);
-				rp++;
-				n--;
+	sep = "--" BOUNDARY;
+	for(f=form->fields;f;f=f->next)switch(f->type){
+	case TYPEIN:
+	case PASSWD:
+		fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+			sep, f->name, plentryval(f->p));
+		sep = "\r\n--" BOUNDARY;
+		break;
+	case CHECK:
+	case RADIO:
+		if(!f->state) break;
+	case SUBMIT:
+	case HIDDEN:
+		if(f->name==0 || f->value==0)
+			continue;
+		fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+			sep, f->name, f->value);
+		sep = "\r\n--" BOUNDARY;
+		break;
+	case SELECT:
+		if(f->name==0)
+			continue;
+		for(o=f->options;o;o=o->next)
+			if(o->selected && o->value){
+				fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+					sep, f->name, o->value);
+				sep = "\r\n--" BOUNDARY;
 			}
-			*p = 0;
-			fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
-				sep, f->name, b);
-			sep = NEXT;
-			free(b);
-			break;
+		break;
+	case TEXTWIN:
+		if(f->name==0)
+			continue;
+		n=plelen(f->textwin);
+		rp=pleget(f->textwin);
+		p=b=malloc(UTFmax*n+1);
+		if(b == nil)
+			continue;
+		while(n > 0){
+			p += runetochar(p, rp);
+			rp++;
+			n--;
 		}
+		*p = 0;
+		fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+			sep, f->name, b);
+		sep = "\r\n--" BOUNDARY;
+		free(b);
+		break;
 	}
+	for(f=form->fields;f;f=f->next)if(f->type == FILE){
+		char buf[1024];
+		int ifd;
+
+		if(f->name==0 || f->value[0]==0)
+			continue;
+		if(p = strrchr(f->value, '/'))
+			p++;
+		if(p == 0 || *p == 0)
+			p = f->value;
+		if((ifd = open(f->value, OREAD)) < 0)
+			continue;
+		if(filetype(ifd, buf, sizeof(buf)) < 0)
+			strcpy(buf, "application/octet-stream");
+		fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\""
+			"\r\nContent-Type: %s\r\n\r\n", sep, f->name, p, buf);
+		while((n = read(ifd, buf, sizeof(buf))) > 0)
+			write(fd, buf, n);
+		close(ifd);
+		sep = "\r\n--" BOUNDARY;
+	}
 	fprint(fd, "%s--\r\n", sep);
 }
 
@@ -553,6 +595,7 @@
 	case CHECK:
 	case RADIO:
 		if(!f->state) break;
+	case SUBMIT:
 	case HIDDEN:
 		if(f->name==0 || f->value==0)
 			continue;
--- a/sys/src/cmd/mothra/libpanel/edit.c
+++ b/sys/src/cmd/mothra/libpanel/edit.c
@@ -258,6 +258,7 @@
 int plelen(Panel *p){
 	Textwin *t;
 	t=((Edit *)p->data)->t;
+	if(t==0) return 0;
 	return t->etext-t->text;
 }
 Rune *pleget(Panel *p){
--- a/sys/src/cmd/mothra/mothra.c
+++ b/sys/src/cmd/mothra/mothra.c
@@ -23,7 +23,6 @@
 Panel *list;	/* list of previously acquired www pages */
 Panel *msg;	/* message display */
 Panel *menu3;	/* button 3 menu */
-Mouse mouse;	/* current mouse data */
 char mothra[] = "mothra!";
 Cursor patientcurs={
 	0, 0,
--- a/sys/src/cmd/mothra/mothra.h
+++ b/sys/src/cmd/mothra/mothra.h
@@ -96,10 +96,11 @@
 int Ufmt(Fmt *f);
 #pragma	varargck type "U" char*
 void message(char *, ...);
-int snooptype(int fd);
+int filetype(int, char *, int);
+int snooptype(int);
 void mkfieldpanel(Rtext *);
 void geturl(char *, int, int, int);
 int urlpost(Url*, char*);
 int urlget(Url*, int);
 char version[];
-
+Mouse mouse;
--- a/sys/src/cmd/webfs/buq.c
+++ b/sys/src/cmd/webfs/buq.c
@@ -9,6 +9,32 @@
 #include "fns.h"
 
 static void
+kickwqr(Buq *q, Req *r)
+{
+	Buf **bb, *b;
+	int l;
+
+	for(bb = &q->bh; q->nwq > 0; bb = &b->next){
+		if((b = *bb) == nil)
+			break;
+		if(b->wreq == nil || (b->wreq != r && r != nil))
+			continue;
+		l = b->ep - b->rp;
+		b = realloc(b, sizeof(*b) + l);
+		*bb = b;
+		if(b->next == nil)
+			q->bt = &b->next;
+		memmove(b->end, b->rp, l);
+		b->rp = b->end;
+		b->ep = b->rp + l;
+		b->wreq->ofcall.count = b->wreq->ifcall.count;
+		respond(b->wreq, q->error);
+		b->wreq = nil;
+		q->nwq--;
+	}
+}
+
+static void
 matchreq(Buq *q)
 {
 	Req *r;
@@ -47,10 +73,13 @@
 			if(r = b->wreq){
 				r->ofcall.count = r->ifcall.count;
 				respond(r, nil);
+				q->nwq--;
 			}
 			free(b);
 		}
 	}
+	if(q->closed && q->nwq > 0)
+		kickwqr(q, nil);
 	rwakeupall(&q->rz);
 }
 
@@ -136,8 +165,8 @@
 		if(error)
 			q->error = estrdup9p(error);
 		q->closed = 1;
-		matchreq(q);
 	}
+	matchreq(q);
 	qunlock(q);
 }
 
@@ -188,7 +217,7 @@
 		return;
 	case Twrite:
 		l = r->ifcall.count;
-		if((q->size + l) < q->limit){
+		if(!q->closed && (q->size + l) < q->limit){
 			r->ofcall.count = buwrite(q, r->ifcall.data, r->ifcall.count);
 			respond(r, nil);
 			return;
@@ -202,6 +231,7 @@
 		*q->bt = b;
 		q->bt = &b->next;
 		q->size += l;
+		q->nwq++;
 		break;
 	case Tread:
 	case Topen:
@@ -218,9 +248,7 @@
 void
 buflushreq(Buq *q, Req *r)
 {
-	Buf **bb, *b;
 	Req **rr;
-	int l;
 
 	switch(r->ifcall.type){
 	default:
@@ -228,23 +256,7 @@
 		return;
 	case Twrite:
 		qlock(q);
-		for(bb = &q->bh; b = *bb; bb = &b->next){
-			if(b->wreq != r)
-				continue;
-			/* fake successfull write */
-			l = b->ep - b->rp;
-			b = realloc(b, sizeof(*b) + l);
-			memmove(b->end, b->rp, l);
-			b->rp = b->end;
-			b->ep = b->rp + l;
-			b->wreq = nil;
-			*bb = b;
-			if(b->next == nil)
-				q->bt = &b->next;
-			r->ofcall.count = r->ifcall.count;
-			respond(r, nil);
-			break;
-		}
+		kickwqr(q, r);
 		break;
 	case Topen:
 	case Tread:
--- a/sys/src/cmd/webfs/dat.h
+++ b/sys/src/cmd/webfs/dat.h
@@ -51,6 +51,7 @@
 	int	closed;
 	int	limit;
 	int	size;
+	int	nwq;
 
 	/* write buffers */
 	Buf	*bh;
--- a/sys/src/cmd/webfs/http.c
+++ b/sys/src/cmd/webfs/http.c
@@ -22,6 +22,7 @@
 	long	time;
 
 	int	fd;
+	int	ctl;
 	int	keep;
 	int	cancel;
 	int	len;
@@ -64,7 +65,7 @@
 {
 	char addr[128];
 	Hconn *h, *p;
-	int fd, ofd;
+	int fd, ctl, ofd;
 
 	snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme);
 
@@ -85,7 +86,7 @@
 	if(debug)
 		fprint(2, "hdial [%d] %s\n", hpool.active, addr);
 
-	if((fd = dial(addr, 0, 0, 0)) < 0)
+	if((fd = dial(addr, 0, 0, &ctl)) < 0)
 		return nil;
 	if(strcmp(u->scheme, "https") == 0){
 		TLSconn *tc;
@@ -97,8 +98,10 @@
 		free(tc->cert);
 		free(tc->sessionID);
 		free(tc);
-		if(fd < 0)
+		if(fd < 0){
+			close(ctl);
 			return nil;
+		}
 	}
 
 	h = emalloc(sizeof(*h));
@@ -108,6 +111,7 @@
 	h->keep = 1;
 	h->len = 0;
 	h->fd = fd;
+	h->ctl = ctl;
 	nstrcpy(h->addr, addr, sizeof(h->addr));
 
 	return h;
@@ -208,11 +212,23 @@
 	if(debug)
 		fprint(2, "hclose [%d] %s\n", hpool.active, h->addr);
 
+	if(h->ctl >= 0)
+		close(h->ctl);
 	if(h->fd >= 0)
 		close(h->fd);
 	free(h);
 }
 
+static void
+hhangup(Hconn *h)
+{
+	if(debug)
+		fprint(2, "hangup pc=%p: %r\n", getcallerpc(&h));
+	h->keep = 0;
+	if(h->ctl >= 0)
+		hangup(h->ctl);
+}
+
 static int
 hread(Hconn *h, void *data, int len)
 {
@@ -225,8 +241,10 @@
 			memmove(h->buf, h->buf + len, h->len);
 		return len;
 	}
-	if((len = read(h->fd, data, len)) <= 0)
+	if((len = read(h->fd, data, len)) == 0)
 		h->keep = 0;
+	if(len < 0)
+		hhangup(h);
 	return len;
 }
 
@@ -234,7 +252,7 @@
 hwrite(Hconn *h, void *data, int len)
 {
 	if(write(h->fd, data, len) != len){
-		h->keep = 0;
+		hhangup(h);
 		return -1;
 	}
 	return len;
@@ -281,7 +299,7 @@
 		if(h->len >= sizeof(h->buf))
 			return 0;
 		if((n = read(h->fd, h->buf + h->len, sizeof(h->buf) - h->len)) <= 0){
-			h->keep = 0;
+			hhangup(h);
 			return -1;
 		}
 		h->len += n;
@@ -431,7 +449,7 @@
 void
 http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
 {
-	int i, l, n, try, pid, fd, cfd, chunked, retry, nobody;
+	int i, l, n, try, pid, fd, cfd, needlength, chunked, retry, nobody;
 	char *s, *x, buf[8192+2], status[256], method[16];
 	vlong length, offset;
 	Url ru, tu, *nu;
@@ -470,10 +488,11 @@
 
 	h = nil;
 	pid = 0;
-	werrstr("too many errors");
+	needlength = 0;
 	for(try = 0; try < 6; try++){
+		strcpy(status, "0 No status");
 		if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https"))){
-			werrstr("bad url");
+			werrstr("bad url scheme");
 			break;
 		}
 
@@ -496,32 +515,20 @@
 				}
 		qunlock(&authlk);
 
-		if(proxy){
-			ru = *u;
-			ru.fragment = nil;
-		} else {
-			memset(&ru, 0, sizeof(tu));
-			ru.path = Upath(u);
-			ru.query = u->query;
-		}
-		n = snprint(buf, sizeof(buf), "%s %U HTTP/1.1\r\nHost: %s%s%s\r\n",
-			method, &ru, u->host, u->port ? ":" : "", u->port ? u->port : "");
-
-		for(k = shdr; k; k = k->next)
-			n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val);
-
-		if(n >= sizeof(buf)-64){
-			werrstr("request too large");
-			break;
-		}
-
-		nobody = !cistrcmp(method, "HEAD");
 		length = 0;
 		chunked = 0;
 		if(qpost){
+			/* have to read it to temp file to figure out the length */
+			if(fd >= 0 && needlength && lookkey(shdr, "Content-Length") == nil){
+				seek(fd, 0, 2);
+				while((n = buread(qpost, buf, sizeof(buf))) > 0)
+					write(fd, buf, n);
+				shdr = delkey(shdr, "Transfer-Encoding");
+			}
+
 			qlock(qpost);
 			/* wait until buffer is full, most posts are small */
-			while(!qpost->closed && qpost->size < qpost->limit)
+			while(!qpost->closed && qpost->size < qpost->limit && qpost->nwq == 0)
 				rsleep(&qpost->rz);
 
 			if(lookkey(shdr, "Content-Length"))
@@ -529,7 +536,7 @@
 			else if(x = lookkey(shdr, "Transfer-Encoding"))
 				chunked = cistrstr(x, "chunked") != nil;
 			else if(chunked = !qpost->closed)
-				n += snprint(buf+n, sizeof(buf)-n, "Transfer-Encoding: chunked\r\n");
+				shdr = addkey(shdr, "Transfer-Encoding", "chunked");
 			else if(qpost->closed){
 				if(fd >= 0){
 					length = seek(fd, 0, 2);
@@ -537,11 +544,26 @@
 						length = 0;
 				}
 				length += qpost->size;
-				n += snprint(buf+n, sizeof(buf)-n, "Content-Length: %lld\r\n", length);
+				snprint(buf, sizeof(buf), "%lld", length);
+				shdr = addkey(shdr, "Content-Length", buf);
 			}
 			qunlock(qpost);
 		}
 
+		if(proxy){
+			ru = *u;
+			ru.fragment = nil;
+		} else {
+			memset(&ru, 0, sizeof(tu));
+			ru.path = Upath(u);
+			ru.query = u->query;
+		}
+		n = snprint(buf, sizeof(buf), "%s %U HTTP/1.1\r\nHost: %s%s%s\r\n",
+			method, &ru, u->host, u->port ? ":" : "", u->port ? u->port : "");
+		if(n >= sizeof(buf)-64){
+			werrstr("request too large");
+			break;
+		}
 		if(h == nil){
 			alarm(timeout);
 			if((h = hdial(proxy ? proxy : u)) == nil)
@@ -574,6 +596,8 @@
 			}
 		}
 
+		for(k = shdr; k; k = k->next)
+			n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val);
 		n += snprint(buf+n, sizeof(buf)-n, "\r\n");
 		if(debug)
 			fprint(2, "-> %.*s", n, buf);
@@ -588,10 +612,10 @@
 			if((pid = rfork(RFMEM|RFPROC)) <= 0){
 				int ifd;
 
-				alarm(0);
 				if((ifd = fd) >= 0)
 					seek(ifd, 0, 0);
 				while(!h->cancel){
+					alarm(0);
 					if((ifd < 0) || ((n = read(ifd, buf, sizeof(buf)-2)) <= 0)){
 						ifd = -1;
 						if((n = buread(qpost, buf, sizeof(buf)-2)) <= 0)
@@ -600,9 +624,11 @@
 							if(write(fd, buf, n) != n)
 								break;
 					}
+					alarm(timeout);
 					if(chunked){
 						char tmp[32];
-						hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n));
+						if(hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n)) < 0)
+							break;
 						buf[n++] = '\r';
 						buf[n++] = '\n';
 					}
@@ -609,9 +635,10 @@
 					if(hwrite(h, buf, n) != n)
 						break;
 				}
-				if(chunked)
+				if(chunked){
+					alarm(timeout);
 					hwrite(h, "0\r\n\r\n", 5);
-				else
+				}else
 					h->keep = 0;
 				if(pid == 0)
 					exits(0);
@@ -625,7 +652,6 @@
 		rhdr = 0;
 		retry = 0;
 		chunked = 0;
-		status[0] = 0;
 		offset = 0;
 		length = NOLENGTH;
 		for(l = 0; hline(h, s = buf, sizeof(buf)-1, 1) > 0; l++){
@@ -640,7 +666,8 @@
 					if(cistrcmp(s, "ICY"))
 						break;
 				}
-				nstrcpy(status, x, sizeof(status));
+				if(x[0])
+					nstrcpy(status, x, sizeof(status));
 				continue;
 			}
 			if((k = parsehdr(s)) == nil)
@@ -671,6 +698,7 @@
 			cfd = -1;
 		}
 
+		nobody = !cistrcmp(method, "HEAD");
 		if((i = atoi(status)) < 0)
 			i = 0;
 		Status:
@@ -699,7 +727,14 @@
 		case 408:	/* Request Timeout */
 		case 409:	/* Conflict */
 		case 410:	/* Gone */
+			goto Error;
 		case 411:	/* Length Required */
+			if(qpost){
+				needlength = 1;
+				h->cancel = 1;
+				retry = 1;
+				break;
+			}
 		case 412:	/* Precondition Failed */
 		case 413:	/* Request Entity Too Large */
 		case 414:	/* Request URI Too Large */
@@ -861,8 +896,7 @@
 		h = nil;
 	}
 	alarm(0);
-
-	rerrstr(buf, sizeof(buf));
+	snprint(buf, sizeof(buf), "%s %r", status);
 	buclose(qbody, buf);
 	bufree(qbody);
 
--