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