diff --git a/sys/src/cmd/mothra/forms.c b/sys/src/cmd/mothra/forms.c index 9b9126e99..4a3d0e76c 100644 --- a/sys/src/cmd/mothra/forms.c +++ b/sys/src/cmd/mothra/forms.c @@ -50,6 +50,7 @@ enum{ TEXTWIN, HIDDEN, INDEX, + FILE, }; struct Option{ int selected; @@ -59,12 +60,13 @@ struct Option{ 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 @@ void rdform(Hglob *g){ 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 @@ void rdform(Hglob *g){ *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 @@ void mkfieldpanel(Rtext *t){ 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,6 +457,25 @@ void h_resetinput(Panel *p, int){ } 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 @@ -478,56 +501,75 @@ void mencodeform(Form *form, int fd){ 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 @@ void uencodeform(Form *form, int fd){ case CHECK: case RADIO: if(!f->state) break; + case SUBMIT: case HIDDEN: if(f->name==0 || f->value==0) continue; diff --git a/sys/src/cmd/mothra/libpanel/edit.c b/sys/src/cmd/mothra/libpanel/edit.c index afb52f776..e6d46c459 100644 --- a/sys/src/cmd/mothra/libpanel/edit.c +++ b/sys/src/cmd/mothra/libpanel/edit.c @@ -258,6 +258,7 @@ void plegetsel(Panel *p, int *sel0, int *sel1){ 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){ diff --git a/sys/src/cmd/mothra/mothra.c b/sys/src/cmd/mothra/mothra.c index 805bd7744..aec45f062 100644 --- a/sys/src/cmd/mothra/mothra.c +++ b/sys/src/cmd/mothra/mothra.c @@ -23,7 +23,6 @@ Panel *cururl; /* label giving the url of the visible text */ 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, diff --git a/sys/src/cmd/mothra/mothra.h b/sys/src/cmd/mothra/mothra.h index 532d4de6b..ecae06972 100644 --- a/sys/src/cmd/mothra/mothra.h +++ b/sys/src/cmd/mothra/mothra.h @@ -96,10 +96,11 @@ void freeform(void *p); 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; diff --git a/sys/src/cmd/webfs/buq.c b/sys/src/cmd/webfs/buq.c index cbf5c9672..cf45936bd 100644 --- a/sys/src/cmd/webfs/buq.c +++ b/sys/src/cmd/webfs/buq.c @@ -8,6 +8,32 @@ #include "dat.h" #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) { @@ -47,10 +73,13 @@ matchreq(Buq *q) 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 @@ buclose(Buq *q, char *error) if(error) q->error = estrdup9p(error); q->closed = 1; - matchreq(q); } + matchreq(q); qunlock(q); } @@ -188,7 +217,7 @@ bureq(Buq *q, Req *r) 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 @@ bureq(Buq *q, Req *r) *q->bt = b; q->bt = &b->next; q->size += l; + q->nwq++; break; case Tread: case Topen: @@ -218,9 +248,7 @@ bureq(Buq *q, Req *r) void buflushreq(Buq *q, Req *r) { - Buf **bb, *b; Req **rr; - int l; switch(r->ifcall.type){ default: @@ -228,23 +256,7 @@ buflushreq(Buq *q, Req *r) 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: diff --git a/sys/src/cmd/webfs/dat.h b/sys/src/cmd/webfs/dat.h index a7fe76281..60dd0870c 100644 --- a/sys/src/cmd/webfs/dat.h +++ b/sys/src/cmd/webfs/dat.h @@ -51,6 +51,7 @@ struct Buq int closed; int limit; int size; + int nwq; /* write buffers */ Buf *bh; diff --git a/sys/src/cmd/webfs/http.c b/sys/src/cmd/webfs/http.c index 9b951e4fe..c0c4e435f 100644 --- a/sys/src/cmd/webfs/http.c +++ b/sys/src/cmd/webfs/http.c @@ -22,6 +22,7 @@ struct Hconn long time; int fd; + int ctl; int keep; int cancel; int len; @@ -64,7 +65,7 @@ hdial(Url *u) { 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 @@ hdial(Url *u) 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 @@ hdial(Url *u) 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 @@ hdial(Url *u) 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 @@ hclose(Hconn *h) 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 @@ hread(Hconn *h, void *data, int len) 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 @@ static int 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 @@ hline(Hconn *h, char *data, int len, int cont) 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 @@ catch(void *, char *msg) 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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) 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,6 +515,41 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) } qunlock(&authlk); + 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 && qpost->nwq == 0) + rsleep(&qpost->rz); + + if(lookkey(shdr, "Content-Length")) + chunked = 0; + else if(x = lookkey(shdr, "Transfer-Encoding")) + chunked = cistrstr(x, "chunked") != nil; + else if(chunked = !qpost->closed) + shdr = addkey(shdr, "Transfer-Encoding", "chunked"); + else if(qpost->closed){ + if(fd >= 0){ + length = seek(fd, 0, 2); + if(length < 0) + length = 0; + } + length += qpost->size; + snprint(buf, sizeof(buf), "%lld", length); + shdr = addkey(shdr, "Content-Length", buf); + } + qunlock(qpost); + } + if(proxy){ ru = *u; ru.fragment = nil; @@ -506,42 +560,10 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) } 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){ - qlock(qpost); - /* wait until buffer is full, most posts are small */ - while(!qpost->closed && qpost->size < qpost->limit) - rsleep(&qpost->rz); - - if(lookkey(shdr, "Content-Length")) - chunked = 0; - 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"); - else if(qpost->closed){ - if(fd >= 0){ - length = seek(fd, 0, 2); - if(length < 0) - length = 0; - } - length += qpost->size; - n += snprint(buf+n, sizeof(buf)-n, "Content-Length: %lld\r\n", length); - } - qunlock(qpost); - } - if(h == nil){ alarm(timeout); if((h = hdial(proxy ? proxy : u)) == nil) @@ -574,6 +596,8 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) } } + 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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) 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,18 +624,21 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) 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'; } 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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) 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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) 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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) cfd = -1; } + nobody = !cistrcmp(method, "HEAD"); if((i = atoi(status)) < 0) i = 0; Status: @@ -699,7 +727,14 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) 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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) h = nil; } alarm(0); - - rerrstr(buf, sizeof(buf)); + snprint(buf, sizeof(buf), "%s %r", status); buclose(qbody, buf); bufree(qbody);