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