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