summaryrefslogtreecommitdiff
path: root/sys/src/cmd
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@gmx.de>2012-06-18 21:26:28 +0200
committercinap_lenrek <cinap_lenrek@gmx.de>2012-06-18 21:26:28 +0200
commitae311094ad8f95c1ff5d06a8edd865579175922d (patch)
treed7054a1bed19d471b37a798ef48fd12397d875ae /sys/src/cmd
parent9428062b551f78a6a0bf0625568604d79d2ba632 (diff)
mothra/webfs: multipart/form-data and file upload support
Diffstat (limited to 'sys/src/cmd')
-rw-r--r--sys/src/cmd/mothra/forms.c157
-rw-r--r--sys/src/cmd/mothra/libpanel/edit.c1
-rw-r--r--sys/src/cmd/mothra/mothra.c1
-rw-r--r--sys/src/cmd/mothra/mothra.h5
-rw-r--r--sys/src/cmd/webfs/buq.c54
-rw-r--r--sys/src/cmd/webfs/dat.h1
-rw-r--r--sys/src/cmd/webfs/http.c114
7 files changed, 212 insertions, 121 deletions
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
@@ -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 @@ 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,32 +515,20 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
}
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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
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 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
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 @@ 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);