]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/webfs/http.c
webfs: nstrcpy paranoia
[plan9front.git] / sys / src / cmd / webfs / http.c
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <fcall.h>
5 #include <thread.h>
6 #include <9p.h>
7
8 #include "dat.h"
9 #include "fns.h"
10
11 #include <auth.h>
12 #include <mp.h>
13 #include <libsec.h>
14
15 typedef struct Hconn Hconn;
16 typedef struct Hpool Hpool;
17 typedef struct Hauth Hauth;
18
19 struct Hconn
20 {
21         Hconn   *next;
22         long    time;
23
24         int     fd;
25         int     keep;
26         int     cancel;
27         int     len;
28         char    addr[128];
29         char    buf[8192+2];
30 };
31
32 struct Hpool
33 {
34         QLock;
35
36         Hconn   *head;
37         int     active;
38
39         int     limit;
40         int     peer;
41         int     idle;
42 };
43
44 struct Hauth
45 {
46         Hauth   *next;
47         Url     *url;
48         char    *auth;
49 };
50
51 static Hpool hpool = {
52         .limit  = 16,
53         .peer   = 4,
54         .idle   = 5,    /* seconds */
55 };
56
57 static QLock authlk;
58 static Hauth *hauth;
59
60 static void hclose(Hconn *h);
61
62 static Hconn*
63 hdial(Url *u)
64 {
65         char addr[128];
66         Hconn *h, *p;
67         int fd, ofd;
68
69         snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme);
70
71         qlock(&hpool);
72         for(p = nil, h = hpool.head; h; p = h, h = h->next){
73                 if(strcmp(h->addr, addr) == 0){
74                         if(p)
75                                 p->next = h->next;
76                         else
77                                 hpool.head = h->next;
78                         h->next = nil;
79                         qunlock(&hpool);
80                         return h;
81                 }
82         }
83         hpool.active++;
84         qunlock(&hpool);
85         if(debug)
86                 fprint(2, "hdial [%d] %s\n", hpool.active, addr);
87
88         if((fd = dial(addr, 0, 0, 0)) < 0)
89                 return nil;
90         if(strcmp(u->scheme, "https") == 0){
91                 TLSconn *tc;
92
93                 tc = emalloc(sizeof(*tc));
94                 fd = tlsClient(ofd = fd, tc);
95                 close(ofd);
96                 /* BUG: should validate but how? */
97                 free(tc->cert);
98                 free(tc->sessionID);
99                 free(tc);
100                 if(fd < 0)
101                         return nil;
102         }
103
104         h = emalloc(sizeof(*h));
105         h->next = nil;
106         h->time = 0;
107         h->cancel = 0;
108         h->keep = 1;
109         h->len = 0;
110         h->fd = fd;
111         nstrcpy(h->addr, addr, sizeof(h->addr));
112
113         return h;
114 }
115
116 static void
117 hcloseall(Hconn *x)
118 {
119         Hconn *h;
120
121         while(h = x){
122                 x = h->next;
123                 h->next = nil;
124                 h->keep = 0;
125                 hclose(h);
126         }
127 }
128
129 static void
130 hclose(Hconn *h)
131 {
132         Hconn *x, *t;
133         int i, n;
134
135         if(h == nil)
136                 return;
137
138         qlock(&hpool);
139         if(h->keep && h->fd >= 0){
140                 for(n = 0, i = 0, t = nil, x = hpool.head; x; x = x->next){
141                         if(strcmp(x->addr, h->addr) == 0)
142                                 if(++n > hpool.peer)
143                                         break;
144                         if(++i < hpool.limit)
145                                 t = x;
146                 }
147                 if(x == nil){
148                         /* return connection to pool */
149                         h->time = time(0);
150                         h->next = hpool.head;
151                         hpool.head = h;
152
153                         /* cut off tail */
154                         if(t){
155                                 x = t->next;
156                                 t->next = nil;
157                         }
158
159                         i = h->next != nil;
160                         qunlock(&hpool);
161
162                         /* free the tail */
163                         hcloseall(x);
164
165                         /*
166                          * if h is first one in pool, spawn proc to close
167                          * idle connections.
168                          */
169                         if(i == 0)
170                         if(rfork(RFMEM|RFPROC|RFNOWAIT) == 0){
171                                 do {
172                                         Hconn **xx;
173                                         long now;
174
175                                         sleep(1000);
176
177                                         qlock(&hpool);
178                                         now = time(0);
179
180                                         x = nil;
181                                         xx = &hpool.head;
182                                         while(h = *xx){
183                                                 if((now - h->time) > hpool.idle){
184                                                         *xx = h->next;
185
186                                                         /* link to tail */
187                                                         h->next = x;
188                                                         x = h;
189                                                         continue;
190                                                 }
191                                                 xx = &h->next;
192                                         }
193
194                                         i = hpool.head != nil;
195                                         qunlock(&hpool);
196
197                                         /* free the tail */
198                                         hcloseall(x);
199                                 } while(i);
200                                 exits(0);
201                         }
202                         return;
203                 }
204         }
205         hpool.active--;
206         qunlock(&hpool);
207
208         if(debug)
209                 fprint(2, "hclose [%d] %s\n", hpool.active, h->addr);
210
211         if(h->fd >= 0)
212                 close(h->fd);
213         free(h);
214 }
215
216 static int
217 hread(Hconn *h, void *data, int len)
218 {
219         if(h->len > 0){
220                 if(len > h->len)
221                         len = h->len;
222                 memmove(data, h->buf, len);
223                 h->len -= len;
224                 if(h->len > 0)
225                         memmove(h->buf, h->buf + len, h->len);
226                 return len;
227         }
228         if((len = read(h->fd, data, len)) <= 0)
229                 h->keep = 0;
230         return len;
231 }
232
233 static int
234 hwrite(Hconn *h, void *data, int len)
235 {
236         if(write(h->fd, data, len) != len){
237                 h->keep = 0;
238                 return -1;
239         }
240         return len;
241 }
242
243 static int
244 hline(Hconn *h, char *data, int len, int cont)
245 {
246         char *x, *y, *e;
247         int n;
248
249         data[0] = 0;
250         for(;;){
251                 if(h->len > 0){
252                         while(x = memchr(h->buf, '\n', h->len)){
253                                 n = x - h->buf;
254                                 if(n > 0 && x[-1] == '\r')
255                                         n--;
256                                 if(n > 0 && cont){
257                                         e = h->buf + h->len;
258                                         for(y = x+1; y < e; y++)
259                                                 if(!strchr("\t ", *y))
260                                                         break;
261                                         if(y >= e || strchr("\t ", *y))
262                                                 break;
263                                         if(y > x+1){
264                                                 if(x > h->buf && x[-1] == '\r')
265                                                         x--;
266                                                 memmove(x, y, e - y);
267                                                 h->len -= y - x;
268                                                 continue;
269                                         }
270                                 }                       
271                                 if(n < len)
272                                         len = n;
273                                 memmove(data, h->buf, len);
274                                 data[len] = 0;
275                                 h->len -= (++x - h->buf);
276                                 if(h->len > 0)
277                                         memmove(h->buf, x, h->len);
278                                 return len;
279                         }
280                 }
281                 if(h->len >= sizeof(h->buf))
282                         return 0;
283                 if((n = read(h->fd, h->buf + h->len, sizeof(h->buf) - h->len)) <= 0){
284                         h->keep = 0;
285                         return -1;
286                 }
287                 h->len += n;
288         }
289 }
290
291 static int
292 authenticate(Url *u, Url *ru, char *method, char *s)
293 {
294         char *user, *pass, *realm, *nonce, *opaque, *x;
295         Hauth *a;
296         Fmt fmt;
297         int n;
298
299         user = u->user;
300         pass = u->pass;
301         realm = nonce = opaque = nil;
302         fmtstrinit(&fmt);
303         if(!cistrncmp(s, "Basic ", 6)){
304                 char cred[128], plain[128];
305                 UserPasswd *up;
306
307                 s += 6;
308                 if(x = cistrstr(s, "realm="))
309                         realm = unquote(x+6, &s);
310                 if(realm == nil)
311                         return -1;
312                 up = nil;
313                 if(user == nil || pass == nil){
314                         fmtprint(&fmt, " realm=%q", realm);
315                         if(user)
316                                 fmtprint(&fmt, " user=%q", user);
317                         if((s = fmtstrflush(&fmt)) == nil)
318                                 return -1;
319                         up = auth_getuserpasswd(nil, "proto=pass service=http server=%q%s", u->host, s);
320                         free(s);
321                         if(up == nil)
322                                 return -1;
323                         user = up->user;
324                         pass = up->passwd;
325                 }
326                 n = snprint(plain, sizeof(plain), "%s:%s", user ? user : "", pass ? pass : "");
327                 if(up){
328                         memset(up->user, 0, strlen(up->user));
329                         memset(up->passwd, 0, strlen(up->passwd));
330                         free(up);
331                 }
332                 n = enc64(cred, sizeof(cred), (uchar*)plain, n);
333                 memset(plain, 0, sizeof(plain));
334                 if(n == -1)
335                         return -1;
336                 fmtstrinit(&fmt);
337                 fmtprint(&fmt, "Basic %s", cred);
338                 memset(cred, 0, sizeof(cred));
339                 u = saneurl(url(".", u));       /* all uris below the requested one */
340         }else
341         if(!cistrncmp(s, "Digest ", 7)){
342                 char chal[1024], ouser[128], resp[2*MD5LEN+1];
343                 int nchal;
344
345                 s += 7;
346                 if(x = cistrstr(s, "realm="))
347                         realm = unquote(x+6, &s);
348                 if(x = cistrstr(s, "nonce="))
349                         nonce = unquote(x+6, &s);
350                 if(x = cistrstr(s, "opaque="))
351                         opaque = unquote(x+7, &s);
352                 if(realm == nil || nonce == nil)
353                         return -1;
354                 fmtprint(&fmt, " realm=%q", realm);
355                 if(user)
356                         fmtprint(&fmt, " user=%q", user);
357                 if((s = fmtstrflush(&fmt)) == nil)
358                         return -1;
359                 nchal = snprint(chal, sizeof(chal), "%s %s %U", nonce, method, ru);
360                 n = auth_respond(chal, nchal, ouser, sizeof ouser, resp, sizeof resp, nil,
361                         "proto=httpdigest role=client server=%q%s", u->host, s);
362                 memset(chal, 0, sizeof(chal));
363                 free(s);
364                 if(n < 0)
365                         return -1;
366                 fmtstrinit(&fmt);
367                 fmtprint(&fmt, "Digest ");
368                 fmtprint(&fmt, "username=\"%s\", ", ouser);
369                 fmtprint(&fmt, "realm=\"%s\", ", realm);
370                 fmtprint(&fmt, "host=\"%s\", ", u->host);
371                 fmtprint(&fmt, "uri=\"%U\", ", ru);
372                 fmtprint(&fmt, "nonce=\"%s\", ", nonce);
373                 fmtprint(&fmt, "response=\"%s\"", resp);
374                 if(opaque)
375                         fmtprint(&fmt, ", opaque=\"%s\"", opaque);
376                 u = saneurl(url("/", u));       /* BUG: should be the ones in domain= only */
377         } else
378                 return -1;
379         if(u == nil)
380                 return -1;
381         if((s = fmtstrflush(&fmt)) == nil){
382                 freeurl(u);
383                 return -1;
384         }
385         a = emalloc(sizeof(*a));
386         a->url = u;
387         a->auth = s;
388         qlock(&authlk);
389         a->next = hauth;
390         hauth = a;
391         qunlock(&authlk);
392
393         return 0;
394 }
395
396 void
397 flushauth(Url *u, char *t)
398 {
399         Hauth *a, *p;
400
401         qlock(&authlk);
402 Again:
403         for(p = nil, a = hauth; a; p = a, a = a->next)
404                 if(matchurl(u, a->url) && (t == nil || !strcmp(t, a->auth))){
405                         if(p)
406                                 p->next = a->next;
407                         else
408                                 hauth = a->next;
409                         if(debug)
410                                 fprint(2, "flushauth for %U\n", a->url);
411                         freeurl(a->url);
412                         memset(a->auth, 0, strlen(a->auth));
413                         free(a->auth);
414                         free(a);
415                         goto Again;
416                 }
417         qunlock(&authlk);
418 }
419
420 static void
421 catch(void *, char *msg)
422 {
423         if(strstr("alarm", msg) || strstr("die", msg))
424                 noted(NCONT);
425         else
426                 noted(NDFLT);
427 }
428
429 #define NOLENGTH 0x7fffffffffffffffLL
430
431 void
432 http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
433 {
434         int i, l, n, try, pid, fd, cfd, chunked, retry, nobody;
435         char *s, *x, buf[8192+2], status[256], method[16];
436         vlong length, offset;
437         Url ru, tu, *nu;
438         Key *k, *rhdr;
439         Hconn *h;
440         Hauth *a;
441
442         incref(qbody);
443         if(qpost) incref(qpost);
444         nstrcpy(method, m, sizeof(method));
445         switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
446         default:
447                 return;
448         case -1:
449                 buclose(qbody, "can't fork");
450                 bufree(qbody);
451                 buclose(qpost, "can't fork");
452                 bufree(qpost);
453                 while(k = shdr){
454                         shdr = k->next;
455                         free(k);
456                 }
457                 freeurl(u);
458                 return;
459         case 0:
460                 break;
461         }
462
463         notify(catch);
464         if(qpost){
465                 /* file for spooling the postbody if we need to restart the request */
466                 snprint(buf, sizeof(buf), "/tmp/http.%d.%d.post", getppid(), getpid());
467                 fd = create(buf, OEXCL|ORDWR|ORCLOSE, 0600);
468         } else
469                 fd = -1;
470
471         h = nil;
472         pid = 0;
473         werrstr("too many errors");
474         for(try = 0; try < 6; try++){
475                 if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https"))){
476                         werrstr("bad url");
477                         break;
478                 }
479
480                 if(debug)
481                         fprint(2, "http(%d): %s %U\n", try, method, u);
482
483                 /* preemptive authentication from hauth cache */
484                 qlock(&authlk);
485                 if(proxy && !lookkey(shdr, "Proxy-Authorization"))
486                         for(a = hauth; a; a = a->next)
487                                 if(matchurl(a->url, proxy)){
488                                         shdr = addkey(shdr, "Proxy-Authorization", a->auth);
489                                         break;
490                                 }
491                 if(!lookkey(shdr, "Authorization"))
492                         for(a = hauth; a; a = a->next)
493                                 if(matchurl(a->url, u)){
494                                         shdr = addkey(shdr, "Authorization", a->auth);
495                                         break;
496                                 }
497                 qunlock(&authlk);
498
499                 if(proxy){
500                         ru = *u;
501                         ru.fragment = nil;
502                 } else {
503                         memset(&ru, 0, sizeof(tu));
504                         ru.path = Upath(u);
505                         ru.query = u->query;
506                 }
507                 n = snprint(buf, sizeof(buf), "%s %U HTTP/1.1\r\nHost: %s%s%s\r\n",
508                         method, &ru, u->host, u->port ? ":" : "", u->port ? u->port : "");
509
510                 for(k = shdr; k; k = k->next)
511                         n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val);
512
513                 if(n >= sizeof(buf)-64){
514                         werrstr("request too large");
515                         break;
516                 }
517
518                 nobody = !cistrcmp(method, "HEAD");
519                 length = 0;
520                 chunked = 0;
521                 if(qpost){
522                         qlock(qpost);
523                         /* wait until buffer is full, most posts are small */
524                         while(!qpost->closed && qpost->size < qpost->limit)
525                                 rsleep(&qpost->rz);
526
527                         if(lookkey(shdr, "Content-Length"))
528                                 chunked = 0;
529                         else if(x = lookkey(shdr, "Transfer-Encoding"))
530                                 chunked = cistrstr(x, "chunked") != nil;
531                         else if(chunked = !qpost->closed)
532                                 n += snprint(buf+n, sizeof(buf)-n, "Transfer-Encoding: chunked\r\n");
533                         else if(qpost->closed){
534                                 if(fd >= 0){
535                                         length = seek(fd, 0, 2);
536                                         if(length < 0)
537                                                 length = 0;
538                                 }
539                                 length += qpost->size;
540                                 n += snprint(buf+n, sizeof(buf)-n, "Content-Length: %lld\r\n", length);
541                         }
542                         qunlock(qpost);
543                 }
544
545                 if(h == nil){
546                         alarm(timeout);
547                         if((h = hdial(proxy ? proxy : u)) == nil)
548                                 break;
549                 }
550
551                 if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){
552                         /* only scheme, host and path are relevant for cookies */
553                         memset(&tu, 0, sizeof(tu));
554                         tu.scheme = u->scheme;
555                         tu.host = u->host;
556                         tu.path = Upath(u);
557                         fprint(cfd, "%U", &tu);
558                         for(;;){
559                                 if(n >= sizeof(buf)-2){
560                                         if(debug)
561                                                 fprint(2, "-> %.*s", n, buf);
562                                         if(hwrite(h, buf, n) != n)
563                                                 goto Badflush;
564                                         n = 0;
565                                 }
566                                 if((l = read(cfd, buf+n, sizeof(buf)-2 - n)) == 0)
567                                         break;
568                                 if(l < 0){
569                                         close(cfd);
570                                         cfd = -1;
571                                         break;
572                                 }
573                                 n += l;
574                         }
575                 }
576
577                 n += snprint(buf+n, sizeof(buf)-n, "\r\n");
578                 if(debug)
579                         fprint(2, "-> %.*s", n, buf);
580                 if(hwrite(h, buf, n) != n){
581                 Badflush:
582                         alarm(0);
583                         goto Retry;
584                 }
585
586                 if(qpost){
587                         h->cancel = 0;
588                         if((pid = rfork(RFMEM|RFPROC)) <= 0){
589                                 int ifd;
590
591                                 alarm(0);
592                                 if((ifd = fd) >= 0)
593                                         seek(ifd, 0, 0);
594                                 while(!h->cancel){
595                                         if((ifd < 0) || ((n = read(ifd, buf, sizeof(buf)-2)) <= 0)){
596                                                 ifd = -1;
597                                                 if((n = buread(qpost, buf, sizeof(buf)-2)) <= 0)
598                                                         break;
599                                                 if(fd >= 0)
600                                                         if(write(fd, buf, n) != n)
601                                                                 break;
602                                         }
603                                         if(chunked){
604                                                 char tmp[32];
605                                                 hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n));
606                                                 buf[n++] = '\r';
607                                                 buf[n++] = '\n';
608                                         }
609                                         if(hwrite(h, buf, n) != n)
610                                                 break;
611                                 }
612                                 if(chunked)
613                                         hwrite(h, "0\r\n\r\n", 5);
614                                 else
615                                         h->keep = 0;
616                                 if(pid == 0)
617                                         exits(0);
618                         }
619                         /* no timeout when posting */
620                         alarm(0);
621                 } else
622                         alarm(timeout);
623
624                 Cont:
625                 rhdr = 0;
626                 retry = 0;
627                 chunked = 0;
628                 status[0] = 0;
629                 offset = 0;
630                 length = NOLENGTH;
631                 for(l = 0; hline(h, s = buf, sizeof(buf)-1, 1) > 0; l++){
632                         if(debug)
633                                 fprint(2, "<- %s\n", s);
634                         if(l == 0){
635                                 if(x = strchr(s, ' '))
636                                         while(*x == ' ')
637                                                 *x++ = 0;
638                                 if(cistrncmp(s, "HTTP", 4)){
639                                         h->keep = 0;
640                                         if(cistrcmp(s, "ICY"))
641                                                 break;
642                                 }
643                                 nstrcpy(status, x, sizeof(status));
644                                 continue;
645                         }
646                         if((k = parsehdr(s)) == nil)
647                                 continue;
648                         if(!cistrcmp(k->key, "Connection")){
649                                 if(cistrstr(k->val, "close"))
650                                         h->keep = 0;
651                         }
652                         else if(!cistrcmp(k->key, "Content-Length"))
653                                 length = atoll(k->val);
654                         else if(!cistrcmp(k->key, "Transfer-Encoding")){
655                                 if(cistrstr(k->val, "chunked"))
656                                         chunked = 1;
657                         }
658                         else if(!cistrcmp(k->key, "Set-Cookie") || 
659                                 !cistrcmp(k->key, "Set-Cookie2")){
660                                 if(cfd >= 0)
661                                         fprint(cfd, "Set-Cookie: %s\n", k->val);
662                                 free(k);
663                                 continue;
664                         }
665                         k->next = rhdr;
666                         rhdr = k;
667                 }
668                 alarm(0);
669                 if(cfd >= 0){
670                         close(cfd);
671                         cfd = -1;
672                 }
673
674                 if((i = atoi(status)) < 0)
675                         i = 0;
676                 Status:
677                 switch(i){
678                 default:
679                         if(i % 100){
680                                 i -= (i % 100);
681                                 goto Status;
682                         }
683                 case 100:       /* Continue */
684                 case 101:       /* Switching Protocols */
685                         while(k = rhdr){
686                                 rhdr = k->next;
687                                 free(k);
688                         }
689                         goto Cont;
690                 case 304:       /* Not Modified */
691                         nobody = 1;
692                 case 305:       /* Use Proxy */
693                 case 400:       /* Bad Request */
694                 case 402:       /* Payment Required */
695                 case 403:       /* Forbidden */
696                 case 404:       /* Not Found */
697                 case 405:       /* Method Not Allowed */
698                 case 406:       /* Not Acceptable */
699                 case 408:       /* Request Timeout */
700                 case 409:       /* Conflict */
701                 case 410:       /* Gone */
702                 case 411:       /* Length Required */
703                 case 412:       /* Precondition Failed */
704                 case 413:       /* Request Entity Too Large */
705                 case 414:       /* Request URI Too Large */
706                 case 415:       /* Unsupported Media Type */
707                 case 416:       /* Requested Range Not Satisfiable */
708                 case 417:       /* Expectation Failed */
709                 case 500:       /* Internal server error */
710                 case 501:       /* Not implemented */
711                 case 502:       /* Bad gateway */
712                 case 503:       /* Service unavailable */
713                 case 504:       /* Gateway Timeout */
714                 case 505:       /* HTTP Version not Supported */
715                 Error:
716                         h->cancel = 1;
717                         buclose(qbody, status);
718                         buclose(qpost, status);
719                         break;
720                 case 300:       /* Multiple choices */
721                 case 302:       /* Found */
722                 case 303:       /* See Other */
723                         if(qpost){
724                                 if(pid > 0){
725                                         waitpid();
726                                         pid = 0;
727                                 }
728                                 buclose(qpost, 0);
729                                 bufree(qpost);
730                                 qpost = nil;
731                         }
732                         if(cistrcmp(method, "HEAD"))
733                                 nstrcpy(method, "GET", sizeof(method));
734                 case 301:       /* Moved Permanently */
735                 case 307:       /* Temporary Redirect */
736                 case 308:       /* Resume Incomplete */
737                         if((x = lookkey(rhdr, "Location")) == nil)
738                                 goto Error;
739                         if((nu = saneurl(url(x, u))) == nil)
740                                 goto Error;
741                         freeurl(u);
742                         u = nu;
743                         if(0){
744                 case 401:       /* Unauthorized */
745                         if(x = lookkey(shdr, "Authorization"))
746                                 flushauth(nil, x);
747                         if((x = lookkey(rhdr, "WWW-Authenticate")) == nil)
748                                 goto Error;
749                         if(authenticate(u, &ru, method, x) < 0)
750                                 goto Error;
751                         }
752                         if(0){
753                 case 407:       /* Proxy Auth */
754                         if(proxy == nil)
755                                 goto Error;
756                         if(x = lookkey(shdr, "Proxy-Authorization"))
757                                 flushauth(proxy, x);
758                         if((x = lookkey(rhdr, "Proxy-Authenticate")) == nil)
759                                 goto Error;
760                         if(authenticate(proxy, proxy, method, x) < 0)
761                                 goto Error;
762                         }
763                 case 0:         /* No status */
764                         if(qpost && fd < 0){
765                                 if(i > 0)
766                                         goto Error;
767                                 break;
768                         }
769                         h->cancel = 1;
770                         retry = 1;
771                         break;
772                 case 204:       /* No Content */
773                 case 205:       /* Reset Content */
774                         nobody = 1;
775                 case 200:       /* OK */
776                 case 201:       /* Created */
777                 case 202:       /* Accepted */
778                 case 203:       /* Non-Authoritative Information */
779                 case 206:       /* Partial Content */
780                         qbody->url = u; u = nil;
781                         qbody->hdr = rhdr; rhdr = nil;
782                         if(nobody)
783                                 buclose(qbody, 0);
784                         break;
785                 }
786
787                 while(k = rhdr){
788                         rhdr = k->next;
789                         free(k);
790                 }
791
792                 /*
793                  * remove authorization headers so on the next round, we use
794                  * the hauth cache (wich checks the scope url). this makes
795                  * sure we wont send credentials to the wrong url after
796                  * a redirect.
797                  */
798                 shdr = delkey(shdr, "Proxy-Authorization");
799                 shdr = delkey(shdr, "Authorization");
800
801                 if(!chunked && length == NOLENGTH)
802                         h->keep = 0;
803
804                 /*
805                  * read the response body (if any). retry means we'r just
806                  * skipping the error page so we wont touch qbody.
807                  */
808                 while(!nobody){
809                         if((qbody->closed || retry) && !h->keep)
810                                 break;
811                         if(chunked){
812                                 if(hline(h, buf, sizeof(buf)-1, 0) <= 0)
813                                         break;
814                                 length = strtoll(buf, nil, 16);
815                                 offset = 0;
816                         }
817                         while(offset < length){
818                                 l = sizeof(buf);
819                                 if(l > (length - offset))
820                                         l = (length - offset);
821                                 if((n = hread(h, buf, l)) <= 0)
822                                         break;
823                                 offset += n;
824                                 if(!retry)
825                                         if(buwrite(qbody, buf, n) != n)
826                                                 break;
827                         }
828                         if(offset != length){
829                                 h->keep = 0;
830                                 if(length != NOLENGTH)
831                                         break;
832                         }
833                         if(chunked){
834                                 while(hline(h, buf, sizeof(buf)-1, 1) > 0){
835                                         if(debug)
836                                                 fprint(2, "<= %s\n", buf);
837                                         if(!retry)
838                                                 if(k = parsehdr(buf)){
839                                                         k->next = qbody->hdr;
840                                                         qbody->hdr = k;
841                                                 }
842                                 }
843                                 if(length > 0)
844                                         continue;
845                         }
846                         if(!retry)
847                                 buclose(qbody, 0);
848                         break;
849                 }
850
851                 if(!retry)
852                         break;
853                 Retry:
854                 if(cfd >= 0)
855                         close(cfd);
856                 if(pid > 0){
857                         waitpid();
858                         pid = 0;
859                 }
860                 hclose(h);
861                 h = nil;
862         }
863         alarm(0);
864
865         rerrstr(buf, sizeof(buf));
866         buclose(qbody, buf);
867         bufree(qbody);
868
869         if(qpost){
870                 if(pid > 0)
871                         waitpid();
872                 buclose(qpost, buf);
873                 bufree(qpost);
874         }
875         if(fd >= 0)
876                 close(fd);
877
878         hclose(h);
879         freeurl(u);
880
881         while(k = shdr){
882                 shdr = k->next;
883                 free(k);
884         }
885         exits(0);
886 }