plan9fox/sys/src/cmd/webfs/http.c
cinap_lenrek fc0eee2980 webfs: do not reuse digest Authorization headers
We must use the digest authorization header only
once for a single request.
2022-04-02 20:29:20 +00:00

1079 lines
22 KiB
C

#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "dat.h"
#include "fns.h"
#include <auth.h>
#include <mp.h>
#include <libsec.h>
typedef struct Hconn Hconn;
typedef struct Hpool Hpool;
typedef struct Hauth Hauth;
struct Hconn
{
Hconn *next;
long time;
int fd;
int ctl;
int keep;
int cancel;
int tunnel;
int len;
char addr[128];
char buf[8192+2];
};
struct Hpool
{
QLock;
Hconn *head;
int active;
int limit;
int peer;
int idle;
};
struct Hauth
{
Hauth *next;
Url *url;
char *auth;
};
static Hpool hpool = {
.limit = 16,
.peer = 4,
.idle = 5, /* seconds */
};
static QLock authlk;
static Hauth *hauth;
static void hclose(Hconn *h);
static int
tlstrace(char *fmt, ...)
{
int r;
va_list a;
va_start(a, fmt);
r = vfprint(2, fmt, a);
va_end(a);
return r;
}
static int
tlswrap(int fd, char *servername)
{
TLSconn conn;
memset(&conn, 0, sizeof(conn));
if(debug)
conn.trace = tlstrace;
if(servername != nil)
conn.serverName = smprint("%N", servername);
if((fd = tlsClient(fd, &conn)) < 0){
if(debug) fprint(2, "tlsClient: %r\n");
}
free(conn.cert);
free(conn.sessionID);
free(conn.serverName);
return fd;
}
static Hconn*
hdial(Url *u, int cached)
{
char addr[128];
Hconn *h, *p;
int fd, ctl;
snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme);
qlock(&hpool);
if(cached){
for(p = nil, h = hpool.head; h; p = h, h = h->next){
if(strcmp(h->addr, addr) == 0){
if(p)
p->next = h->next;
else
hpool.head = h->next;
h->next = nil;
qunlock(&hpool);
return h;
}
}
}
hpool.active++;
qunlock(&hpool);
if(debug)
fprint(2, "hdial [%d] %s\n", hpool.active, addr);
if(proxy)
snprint(addr, sizeof(addr), "tcp!%s!%s",
proxy->host, proxy->port ? proxy->port : proxy->scheme);
if((fd = dial(addr, 0, 0, &ctl)) < 0)
return nil;
if(proxy){
if(strcmp(proxy->scheme, "https") == 0)
fd = tlswrap(fd, proxy->host);
} else {
if(strcmp(u->scheme, "https") == 0)
fd = tlswrap(fd, u->host);
}
if(fd < 0){
close(ctl);
return nil;
}
h = emalloc(sizeof(*h));
h->next = nil;
h->time = 0;
h->cancel = 0;
h->tunnel = 0;
h->keep = cached;
h->len = 0;
h->fd = fd;
h->ctl = ctl;
if(proxy){
h->tunnel = strcmp(u->scheme, "https") == 0;
snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme);
}
nstrcpy(h->addr, addr, sizeof(h->addr));
return h;
}
static void
hcloseall(Hconn *x)
{
Hconn *h;
while(h = x){
x = h->next;
h->next = nil;
h->keep = 0;
hclose(h);
}
}
static void
hclose(Hconn *h)
{
Hconn *x, *t;
int i, n;
if(h == nil)
return;
qlock(&hpool);
if(!h->tunnel && h->keep && h->fd >= 0){
for(n = 0, i = 0, t = nil, x = hpool.head; x; x = x->next){
if(strcmp(x->addr, h->addr) == 0)
if(++n > hpool.peer)
break;
if(++i < hpool.limit)
t = x;
}
if(x == nil){
/* return connection to pool */
h->time = time(0);
h->next = hpool.head;
hpool.head = h;
/* cut off tail */
if(t){
x = t->next;
t->next = nil;
}
i = h->next != nil;
qunlock(&hpool);
/* free the tail */
hcloseall(x);
/*
* if h is first one in pool, spawn proc to close
* idle connections.
*/
if(i == 0)
if(rfork(RFMEM|RFPROC|RFNOWAIT) == 0){
do {
Hconn **xx;
long now;
sleep(1000);
qlock(&hpool);
now = time(0);
x = nil;
xx = &hpool.head;
while(h = *xx){
if((now - h->time) > hpool.idle){
*xx = h->next;
/* link to tail */
h->next = x;
x = h;
continue;
}
xx = &h->next;
}
i = hpool.head != nil;
qunlock(&hpool);
/* free the tail */
hcloseall(x);
} while(i);
exits(nil);
}
return;
}
}
hpool.active--;
qunlock(&hpool);
if(debug)
fprint(2, "hclose [%d] %s\n", hpool.active, h->addr);
if(h->ctl >= 0)
close(h->ctl);
if(h->fd >= 0)
close(h->fd);
free(h);
}
static void
hhangup(Hconn *h)
{
if(debug)
fprint(2, "hangup pc=%p: %r\n", getcallerpc(&h));
h->keep = 0;
if(h->ctl >= 0)
hangup(h->ctl);
}
static int
hread(Hconn *h, void *data, int len)
{
if(h->len > 0){
if(len > h->len)
len = h->len;
memmove(data, h->buf, len);
h->len -= len;
if(h->len > 0)
memmove(h->buf, h->buf + len, h->len);
return len;
}
if((len = read(h->fd, data, len)) == 0)
h->keep = 0;
if(len < 0)
hhangup(h);
return len;
}
static int
hwrite(Hconn *h, void *data, int len)
{
if(write(h->fd, data, len) != len){
hhangup(h);
return -1;
}
return len;
}
static int
hline(Hconn *h, char *data, int len, int cont)
{
char *x, *y, *e;
int n;
data[0] = 0;
for(;;){
if(h->len > 0){
while(x = memchr(h->buf, '\n', h->len)){
n = x - h->buf;
if(n > 0 && x[-1] == '\r')
n--;
if(n > 0 && cont){
e = h->buf + h->len;
for(y = x+1; y < e; y++)
if(*y != ' ' && *y != '\t')
break;
if(y >= e || *y == 0)
break;
if(y > x+1){
if(x > h->buf && x[-1] == '\r')
x--;
memmove(x, y, e - y);
h->len -= y - x;
continue;
}
}
if(n < len)
len = n;
memmove(data, h->buf, len);
data[len] = 0;
h->len -= (++x - h->buf);
if(h->len > 0)
memmove(h->buf, x, h->len);
return len;
}
}
n = sizeof(h->buf) - h->len;
if(n <= 0)
return 0;
if(h->tunnel)
n = 1; /* do not read beyond header */
if((n = read(h->fd, h->buf + h->len, n)) <= 0){
hhangup(h);
return -1;
}
h->len += n;
}
}
static int
hauthgetkey(char *params)
{
if(debug)
fprint(2, "hauthgetkey %s\n", params);
werrstr("needkey %s", params);
return -1;
}
int
authenticate(Url *u, Url *ru, char *method, char *s)
{
char oerr[ERRMAX], *user, *pass, *realm, *nonce, *qop, *opaque, *x;
Hauth *a;
Fmt fmt;
int n;
snprint(oerr, sizeof(oerr), "authentification failed");
errstr(oerr, sizeof(oerr));
user = u->user;
pass = u->pass;
realm = nonce = opaque = nil;
if(!cistrncmp(s, "Basic ", 6)){
UserPasswd *up;
s += 6;
if(x = cistrstr(s, "realm="))
realm = unquote(x+6, &s);
if(realm == nil)
return -1;
up = nil;
if(user == nil || pass == nil){
fmtstrinit(&fmt);
fmtprint(&fmt, " realm=%q", realm);
if(user)
fmtprint(&fmt, " user=%q", user);
if((s = fmtstrflush(&fmt)) == nil)
return -1;
up = auth_getuserpasswd(hauthgetkey,
"proto=pass service=http server=%q%s", u->host, s);
free(s);
if(up == nil)
return -1;
user = up->user;
pass = up->passwd;
}
fmtstrinit(&fmt);
fmtprint(&fmt, "%s:%s", user ? user : "", pass ? pass : "");
if(up){
memset(up->user, 0, strlen(up->user));
memset(up->passwd, 0, strlen(up->passwd));
free(up);
}
if((s = fmtstrflush(&fmt)) == nil)
return -1;
n = strlen(s);
fmtstrinit(&fmt);
fmtprint(&fmt, "Basic %.*[", n, s);
memset(s, 0, n);
free(s);
u = saneurl(url(".", u)); /* all uris below the requested one */
}else
if(!cistrncmp(s, "Digest ", 7)){
char chal[1024], ouser[128], resp[2*MD5LEN+1];
uchar cnonce[16];
uint nc;
int nchal;
s += 7;
if(x = cistrstr(s, "realm="))
realm = unquote(x+6, &s);
if(x = cistrstr(s, "nonce="))
nonce = unquote(x+6, &s);
if(x = cistrstr(s, "qop="))
qop = unquote(x+4, &s);
else
qop = "";
if(x = cistrstr(s, "opaque="))
opaque = unquote(x+7, &s);
if(realm == nil || nonce == nil)
return -1;
fmtstrinit(&fmt);
fmtprint(&fmt, " realm=%q", realm);
if(user)
fmtprint(&fmt, " user=%q", user);
if((s = fmtstrflush(&fmt)) == nil)
return -1;
/* RFC2617 "quality of protection" (qop) extension */
if(cistrcmp(qop, "auth") == 0){
static uint counter = 0;
nc = ++counter;
genrandom(cnonce, sizeof(cnonce));
nchal = snprint(chal, sizeof(chal), "%s:%8.8ud:%.*H:%s %s %U",
nonce, nc, sizeof(cnonce), cnonce, qop, method, ru);
} else {
nc = 0;
nchal = snprint(chal, sizeof(chal), "%s %s %U", nonce, method, ru);
}
n = auth_respond(chal, nchal, ouser, sizeof ouser, resp, sizeof resp, hauthgetkey,
"proto=httpdigest role=client server=%q%s", u->host, s);
memset(chal, 0, sizeof(chal));
free(s);
if(n < 0)
return -1;
fmtstrinit(&fmt);
fmtprint(&fmt, "Digest ");
fmtprint(&fmt, "username=\"%s\", ", ouser);
fmtprint(&fmt, "realm=\"%s\", ", realm);
fmtprint(&fmt, "host=\"%N\", ", u->host);
fmtprint(&fmt, "uri=\"%U\", ", ru);
fmtprint(&fmt, "nonce=\"%s\", ", nonce);
if(cistrcmp(qop, "auth") == 0){
fmtprint(&fmt, "qop=%s, nc=%8.8ud, cnonce=\"%.*H\", ", qop, nc, sizeof(cnonce), cnonce);
memset(cnonce, 0, sizeof(cnonce));
}
fmtprint(&fmt, "response=\"%.*s\"", n, resp);
if(opaque)
fmtprint(&fmt, ", opaque=\"%s\"", opaque);
u = saneurl(url("/", u)); /* BUG: should be the ones in domain= only */
} else
return -1;
if((s = fmtstrflush(&fmt)) == nil){
freeurl(u);
return -1;
}
if(u == nil){
free(s);
return -1;
}
a = emalloc(sizeof(*a));
a->url = u;
a->auth = s;
qlock(&authlk);
a->next = hauth;
hauth = a;
qunlock(&authlk);
errstr(oerr, sizeof(oerr));
return 0;
}
int
hauthenticate(Url *u, Url *ru, char *method, char *key, Key *hdr)
{
for(hdr = getkey(hdr, key); hdr != nil; hdr = getkey(hdr->next, key))
if(authenticate(u, ru, method, hdr->val) == 0)
return 0;
return -1;
}
void
freeauth(Hauth **a)
{
Hauth *x = *a;
if(x == nil)
return;
*a = x->next;
if(debug)
fprint(2, "freeauth for %U\n", x->url);
freeurl(x->url);
memset(x->auth, 0, strlen(x->auth));
free(x->auth);
free(x);
}
void
flushauth(Url *u, char *t)
{
Hauth **a;
for(a = &hauth; *a != nil; ){
if(matchurl(u, (*a)->url) && (t == nil || !strcmp(t, (*a)->auth))){
freeauth(a);
continue;
}
a = &(*a)->next;
}
}
static void
catch(void *, char *msg)
{
if(strstr("alarm", msg) != nil)
noted(NCONT);
else
noted(NDFLT);
}
#define NOLENGTH 0x7fffffffffffffffLL
void
http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
{
int i, l, n, try, pid, fd, cfd, needlength, chunked, retry, nobody, badauth;
char *s, *x, buf[8192+2], status[256], method[16], *host;
vlong length, offset;
Url ru, tu, *nu;
Key *k, *rhdr;
Hconn *h;
Hauth **a;
incref(qbody);
if(qpost) incref(qpost);
nstrcpy(method, m, sizeof(method));
switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
default:
return;
case -1:
buclose(qbody, "can't fork");
bufree(qbody);
buclose(qpost, "can't fork");
bufree(qpost);
while(k = shdr){
shdr = k->next;
free(k);
}
freeurl(u);
return;
case 0:
break;
}
notify(catch);
if(qpost){
/* file for spooling the postbody if we need to restart the request */
snprint(buf, sizeof(buf), "/tmp/http.%d.%d.post", getppid(), getpid());
fd = create(buf, OEXCL|ORDWR|ORCLOSE, 0600);
} else
fd = -1;
h = nil;
cfd = -1;
pid = 0;
host = nil;
needlength = 0;
badauth = 0;
for(try = 0; try < 12; try++){
strcpy(status, "0 No status");
if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https"))){
werrstr("bad url scheme");
break;
}
if(debug)
fprint(2, "http(%d): %s %U\n", try, method, u);
/* preemptive authentication from hauth cache */
qlock(&authlk);
if(proxy && !lookkey(shdr, "Proxy-Authorization"))
for(a = &hauth; *a != nil; a = &(*a)->next)
if(matchurl((*a)->url, proxy)){
shdr = addkey(shdr, "Proxy-Authorization", (*a)->auth);
if(strncmp((*a)->auth, "Digest ", 7) == 0) freeauth(a);
break;
}
if(!lookkey(shdr, "Authorization"))
for(a = &hauth; *a != nil; a = &(*a)->next)
if(matchurl((*a)->url, u)){
shdr = addkey(shdr, "Authorization", (*a)->auth);
if(strncmp((*a)->auth, "Digest ", 7) == 0) freeauth(a);
break;
}
qunlock(&authlk);
length = 0;
chunked = 0;
if(qpost){
/* have to read it to temp file to figure out the length */
if(fd >= 0 && needlength && lookkey(shdr, "Content-Length") == nil){
seek(fd, 0, 2);
while((n = buread(qpost, buf, sizeof(buf))) > 0)
write(fd, buf, n);
shdr = delkey(shdr, "Transfer-Encoding");
}
qlock(qpost);
/* wait until buffer is full, most posts are small */
while(!qpost->closed && qpost->size < qpost->limit && qpost->nwq == 0)
rsleep(&qpost->rz);
if(lookkey(shdr, "Content-Length"))
chunked = 0;
else if(x = lookkey(shdr, "Transfer-Encoding"))
chunked = cistrstr(x, "chunked") != nil;
else if(chunked = !qpost->closed)
shdr = addkey(shdr, "Transfer-Encoding", "chunked");
else if(qpost->closed){
if(fd >= 0){
length = seek(fd, 0, 2);
if(length < 0)
length = 0;
}
length += qpost->size;
snprint(buf, sizeof(buf), "%lld", length);
shdr = addkey(shdr, "Content-Length", buf);
}
qunlock(qpost);
}
/* http requires ascii encoding of host */
free(host);
host = smprint("%N", u->host);
if(proxy && strcmp(u->scheme, "https") != 0){
ru = *u;
ru.host = host;
ru.fragment = nil;
} else {
memset(&ru, 0, sizeof(ru));
ru.path = Upath(u);
ru.query = u->query;
}
n = snprint(buf, sizeof(buf), "%s %U HTTP/1.1\r\nHost: %]%s%s\r\n",
method, &ru, host, u->port ? ":" : "", u->port ? u->port : "");
if(n >= sizeof(buf)-64){
werrstr("request too large");
break;
}
if(h == nil){
alarm(timeout);
if((h = hdial(u, qpost==nil)) == nil)
break;
}
if(h->tunnel){
n = snprint(buf, sizeof(buf), "CONNECT %]:%s HTTP/1.1\r\nHost: %]:%s\r\n",
host, u->port ? u->port : "443",
host, u->port ? u->port : "443");
}
else if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){
/* only scheme, host and path are relevant for cookies */
memset(&tu, 0, sizeof(tu));
tu.scheme = u->scheme;
tu.host = host;
tu.path = Upath(u);
fprint(cfd, "%U", &tu);
for(;;){
if(n >= sizeof(buf)-2){
if(debug)
fprint(2, "-> %.*s", utfnlen(buf, n), buf);
if(hwrite(h, buf, n) != n)
goto Badflush;
n = 0;
}
if((l = read(cfd, buf+n, sizeof(buf)-2 - n)) == 0)
break;
if(l < 0){
close(cfd);
cfd = -1;
break;
}
n += l;
}
}
for(k = shdr; k; k = k->next){
/* only send proxy headers when establishing tunnel */
if(h->tunnel && cistrncmp(k->key, "Proxy-", 6) != 0)
continue;
if(n > 0){
if(debug)
fprint(2, "-> %.*s", utfnlen(buf, n), buf);
if(hwrite(h, buf, n) != n)
goto Badflush;
}
n = snprint(buf, sizeof(buf)-2, "%s: %s\r\n", k->key, k->val);
}
n += snprint(buf+n, sizeof(buf)-n, "\r\n");
if(debug)
fprint(2, "-> %.*s", utfnlen(buf, n), buf);
if(hwrite(h, buf, n) != n){
Badflush:
alarm(0);
goto Retry;
}
if(qpost && !h->tunnel){
h->cancel = 0;
if((pid = rfork(RFMEM|RFPROC)) <= 0){
int ifd;
if((ifd = fd) >= 0)
seek(ifd, 0, 0);
while(!h->cancel){
alarm(0);
if((ifd < 0) || ((n = read(ifd, buf, sizeof(buf)-2)) <= 0)){
ifd = -1;
if((n = buread(qpost, buf, sizeof(buf)-2)) <= 0)
break;
if(fd >= 0)
if(write(fd, buf, n) != n)
break;
}
alarm(timeout);
if(chunked){
char tmp[32];
if(hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n)) < 0)
break;
buf[n++] = '\r';
buf[n++] = '\n';
}
if(hwrite(h, buf, n) != n)
break;
}
if(chunked){
alarm(timeout);
hwrite(h, "0\r\n\r\n", 5);
}else
h->keep = 0;
if(pid == 0)
exits(nil);
}
/* no timeout when posting */
alarm(0);
} else
alarm(timeout);
Cont:
rhdr = 0;
retry = 0;
chunked = 0;
offset = 0;
length = NOLENGTH;
for(l = 0; hline(h, s = buf, sizeof(buf)-1, 1) > 0; l++){
if(debug)
fprint(2, "<- %s\n", s);
if(l == 0){
if(x = strchr(s, ' '))
while(*x == ' ')
*x++ = 0;
if(cistrncmp(s, "HTTP", 4)){
h->keep = 0;
if(cistrcmp(s, "ICY"))
break;
}
if(x[0])
nstrcpy(status, x, sizeof(status));
continue;
}
if((k = parsehdr(s)) == nil)
continue;
if(!cistrcmp(k->key, "Connection")){
if(cistrstr(k->val, "close"))
h->keep = 0;
}
else if(!cistrcmp(k->key, "Content-Length"))
length = atoll(k->val);
else if(!cistrcmp(k->key, "Transfer-Encoding")){
if(cistrstr(k->val, "chunked"))
chunked = 1;
}
else if(!cistrcmp(k->key, "Set-Cookie") ||
!cistrcmp(k->key, "Set-Cookie2")){
if(cfd >= 0)
fprint(cfd, "Set-Cookie: %s\n", k->val);
free(k);
continue;
}
k->next = rhdr;
rhdr = k;
}
alarm(0);
if(cfd >= 0){
close(cfd);
cfd = -1;
}
nobody = !cistrcmp(method, "HEAD");
if((i = atoi(status)) < 0)
i = 0;
Status:
switch(i){
default:
if(i % 100){
i -= (i % 100);
goto Status;
}
goto Error;
case 100: /* Continue */
case 101: /* Switching Protocols */
case 102: /* Processing */
case 103: /* Early Hints */
while(k = rhdr){
rhdr = k->next;
free(k);
}
strcpy(status, "0 No status");
goto Cont;
case 304: /* Not Modified */
nobody = 1;
case 305: /* Use Proxy */
case 400: /* Bad Request */
case 402: /* Payment Required */
case 403: /* Forbidden */
case 404: /* Not Found */
case 405: /* Method Not Allowed */
case 406: /* Not Acceptable */
case 408: /* Request Timeout */
case 409: /* Conflict */
case 410: /* Gone */
goto Error;
case 411: /* Length Required */
if(qpost){
needlength = 1;
h->cancel = 1;
retry = 1;
break;
}
case 412: /* Precondition Failed */
case 413: /* Request Entity Too Large */
case 414: /* Request URI Too Large */
case 415: /* Unsupported Media Type */
case 416: /* Requested Range Not Satisfiable */
case 417: /* Expectation Failed */
case 500: /* Internal server error */
case 501: /* Not implemented */
case 502: /* Bad gateway */
case 503: /* Service unavailable */
case 504: /* Gateway Timeout */
case 505: /* HTTP Version not Supported */
Error:
h->cancel = 1;
buclose(qbody, status);
buclose(qpost, status);
break;
case 300: /* Multiple choices */
case 302: /* Found */
case 303: /* See Other */
if(qpost){
if(pid > 0){
waitpid();
pid = 0;
}
buclose(qpost, 0);
bufree(qpost);
qpost = nil;
}
shdr = delkey(shdr, "Content-Length");
shdr = delkey(shdr, "Content-Type");
shdr = delkey(shdr, "Transfer-Encoding");
if(cistrcmp(method, "HEAD"))
nstrcpy(method, "GET", sizeof(method));
case 301: /* Moved Permanently */
case 307: /* Temporary Redirect */
case 308: /* Resume Incomplete */
if((x = lookkey(rhdr, "Location")) == nil)
goto Error;
if((nu = saneurl(url(x, u))) == nil)
goto Error;
freeurl(u);
u = nu;
if(0){
case 401: /* Unauthorized */
if(x = lookkey(shdr, "Authorization")){
qlock(&authlk);
flushauth(nil, x);
qunlock(&authlk);
if(badauth++)
goto Error;
}
if(hauthenticate(u, &ru, method, "WWW-Authenticate", rhdr) < 0){
Autherror:
h->cancel = 1;
rerrstr(buf, sizeof(buf));
buclose(qbody, buf);
buclose(qpost, buf);
break;
}
}
if(0){
case 407: /* Proxy Auth */
if(proxy == nil)
goto Error;
if(x = lookkey(shdr, "Proxy-Authorization")){
qlock(&authlk);
flushauth(proxy, x);
qunlock(&authlk);
if(badauth++)
goto Error;
}
if(hauthenticate(proxy, proxy, method, "Proxy-Authenticate", rhdr) < 0)
goto Autherror;
}
case 0: /* No status */
if(qpost && fd < 0){
if(i > 0)
goto Error;
break;
}
h->cancel = 1;
retry = 1;
break;
case 204: /* No Content */
case 205: /* Reset Content */
nobody = 1;
case 200: /* OK */
case 201: /* Created */
case 202: /* Accepted */
case 203: /* Non-Authoritative Information */
case 206: /* Partial Content */
if(h->tunnel)
break;
qbody->url = u; u = nil;
qbody->hdr = rhdr; rhdr = nil;
if(nobody)
buclose(qbody, 0);
break;
}
while(k = rhdr){
rhdr = k->next;
free(k);
}
/*
* remove authorization headers so on the next round, we use
* the hauth cache (wich checks the scope url). this makes
* sure we wont send credentials to the wrong url after
* a redirect.
*/
shdr = delkey(shdr, "Proxy-Authorization");
shdr = delkey(shdr, "Authorization");
/*
* when 2xx response is given for the CONNECT request
* then the proxy server has established the connection.
*/
if(h->tunnel && !retry && (i/100) == 2){
if((h->fd = tlswrap(h->fd, host)) < 0)
break;
/* proceed to the original request */
h->tunnel = 0;
continue;
}
if(!chunked && length == NOLENGTH)
h->keep = 0;
/*
* read the response body (if any). retry means we'r just
* skipping the error page so we wont touch qbody.
*/
while(!nobody){
if((qbody->closed || retry) && !h->keep)
break;
if(chunked){
if(hline(h, buf, sizeof(buf)-1, 0) <= 0)
break;
length = strtoll(buf, nil, 16);
offset = 0;
}
while(offset < length){
l = sizeof(buf);
if(l > (length - offset))
l = (length - offset);
if((n = hread(h, buf, l)) <= 0)
break;
offset += n;
if(!retry)
if(buwrite(qbody, buf, n) != n)
break;
}
if(offset != length){
h->keep = 0;
if(length != NOLENGTH)
break;
}
if(chunked){
while(hline(h, buf, sizeof(buf)-1, 1) > 0){
if(debug)
fprint(2, "<= %s\n", buf);
if(!retry)
if(k = parsehdr(buf)){
k->next = qbody->hdr;
qbody->hdr = k;
}
}
if(length > 0)
continue;
}
if(!retry)
buclose(qbody, 0);
break;
}
if(!retry)
break;
Retry:
if(cfd >= 0)
close(cfd);
if(pid > 0){
waitpid();
pid = 0;
}
hclose(h);
h = nil;
}
alarm(0);
snprint(buf, sizeof(buf), "%s %r", status);
buclose(qbody, buf);
bufree(qbody);
if(qpost){
if(pid > 0)
waitpid();
buclose(qpost, buf);
bufree(qpost);
}
if(fd >= 0)
close(fd);
hclose(h);
freeurl(u);
free(host);
while(k = shdr){
shdr = k->next;
free(k);
}
exits(nil);
}