diff options
Diffstat (limited to 'sys/src/cmd/webfs/http.c')
-rw-r--r-- | sys/src/cmd/webfs/http.c | 1223 |
1 files changed, 754 insertions, 469 deletions
diff --git a/sys/src/cmd/webfs/http.c b/sys/src/cmd/webfs/http.c index 5acbcb4d2..04724268d 100644 --- a/sys/src/cmd/webfs/http.c +++ b/sys/src/cmd/webfs/http.c @@ -1,540 +1,825 @@ #include <u.h> #include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> +#include <ctype.h> #include <fcall.h> +#include <thread.h> #include <9p.h> -#include <libsec.h> -#include <auth.h> + #include "dat.h" #include "fns.h" -char PostContentType[] = "application/x-www-form-urlencoded"; -int httpdebug; +#include <auth.h> +#include <mp.h> +#include <libsec.h> + +typedef struct Hconn Hconn; +typedef struct Hpool Hpool; +typedef struct Hauth Hauth; -typedef struct HttpState HttpState; -struct HttpState +struct Hconn { - int fd; - Client *c; - char *location; - char *setcookie; - char *netaddr; - char *credentials; - char autherror[ERRMAX]; - Ibuf b; + Hconn *next; + + int fd; + int keep; + int cancel; + int len; + char addr[128]; + char buf[8192+2]; }; -static void -location(HttpState *hs, char *value) +struct Hpool { - if(hs->location == nil) - hs->location = estrdup(value); -} + QLock; -static void -contenttype(HttpState *hs, char *value) + Hconn *head; + int active; + int limit; +}; + +struct Hauth { - if(hs->c->contenttype != nil) - free(hs->c->contenttype); - hs->c->contenttype = estrdup(value); -} + Hauth *next; + Url *url; + char *auth; +}; -static void -setcookie(HttpState *hs, char *value) +static Hpool hpool = { + .limit = 16, +}; + +static QLock authlk; +static Hauth *hauth; + +static Hconn* +hdial(Url *u) { - char *s, *t; - Fmt f; - - s = hs->setcookie; - fmtstrinit(&f); - if(s) - fmtprint(&f, "%s", s); - fmtprint(&f, "set-cookie: "); - fmtprint(&f, "%s", value); - fmtprint(&f, "\n"); - t = fmtstrflush(&f); - if(t){ - free(s); - hs->setcookie = t; + char addr[128]; + Hconn *h, *p; + int fd, ofd; + + snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme); + + qlock(&hpool); + 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((fd = dial(addr, 0, 0, 0)) < 0) + return nil; + if(strcmp(u->scheme, "https") == 0){ + TLSconn *tc; + + tc = emalloc(sizeof(*tc)); + fd = tlsClient(ofd = fd, tc); + close(ofd); + /* BUG: should validate but how? */ + free(tc->cert); + free(tc->sessionID); + free(tc); + if(fd < 0) + return nil; } + + h = emalloc(sizeof(*h)); + h->next = nil; + h->cancel = 0; + h->keep = 1; + h->len = 0; + h->fd = fd; + strncpy(h->addr, addr, sizeof(h->addr)); + + return h; } -static char* -unquote(char *s, char **ps) +static void +hclose(Hconn *h) { - char *p; + Hconn *x, *t; + int i; - if(*s != '"'){ - p = strpbrk(s, " \t\r\n"); - *p++ = 0; - *ps = p; - return s; - } - for(p=s+1; *p; p++){ - if(*p == '\"'){ - *p++ = 0; - break; + if(h == nil) + return; + + qlock(&hpool); + if(h->keep && h->fd >= 0){ + for(i = 0, t = nil, x = hpool.head; x; x = x->next){ + if(strcmp(x->addr, h->addr) == 0) + break; + if(++i < hpool.limit) + t = x; } - if(*p == '\\' && *(p+1)){ - p++; - continue; + if(x == nil){ + /* return connection to pool */ + h->next = hpool.head; + hpool.head = h; + + /* cut off tail */ + if(t){ + x = t->next; + t->next = nil; + } + qunlock(&hpool); + + /* free the tail */ + while(h = x){ + x = h->next; + h->next = nil; + h->keep = 0; + hclose(h); + } + return; } } - memmove(s, s+1, p-(s+1)); - s[p-(s+1)] = 0; - *ps = p; - return s; -} + hpool.active--; + qunlock(&hpool); -static char* -servername(char *addr) -{ - char *p; - - if(strncmp(addr, "tcp!", 4) == 0 - || strncmp(addr, "net!", 4) == 0) - addr += 4; - addr = estrdup(addr); - p = addr+strlen(addr); - if(p>addr && *(p-1) == 's') - p--; - if(p>addr+5 && strcmp(p-5, "!http") == 0) - p[-5] = 0; - return addr; + if(debug) + fprint(2, "hclose [%d] %s\n", hpool.active, h->addr); + + if(h->fd >= 0) + close(h->fd); + free(h); } -void -wwwauthenticate(HttpState *hs, char *line) +static int +hread(Hconn *h, void *data, int len) { - char cred[64], *user, *pass, *realm, *s, *spec, *name; - Fmt fmt; - UserPasswd *up; - - spec = nil; - up = nil; - cred[0] = 0; - hs->autherror[0] = 0; - if(cistrncmp(line, "basic ", 6) != 0){ - werrstr("unknown auth: %s", line); - goto error; - } - line += 6; - if(cistrncmp(line, "realm=", 6) != 0){ - werrstr("missing realm: %s", line); - goto error; + 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; } - line += 6; - user = hs->c->url->user; - pass = hs->c->url->passwd; - if(user==nil || pass==nil){ - realm = unquote(line, &line); - fmtstrinit(&fmt); - name = servername(hs->netaddr); - fmtprint(&fmt, "proto=pass service=http server=%q realm=%q", name, realm); - free(name); - if(hs->c->url->user) - fmtprint(&fmt, " user=%q", hs->c->url->user); - spec = fmtstrflush(&fmt); - if(spec == nil) - goto error; - if((up = auth_getuserpasswd(nil, "%s", spec)) == nil) - goto error; - user = up->user; - pass = up->passwd; - } - if((s = smprint("%s:%s", user, pass)) == nil) - goto error; - free(up); - enc64(cred, sizeof(cred), (uchar*)s, strlen(s)); - memset(s, 0, strlen(s)); - free(s); - hs->credentials = smprint("Basic %s", cred); - if(hs->credentials == nil) - goto error; - return; - -error: - free(up); - free(spec); - snprint(hs->autherror, sizeof hs->autherror, "%r"); - fprint(2, "%s: Authentication failed: %r\n", argv0); + if((len = read(h->fd, data, len)) <= 0) + h->keep = 0; + return len; } -struct { - char *name; /* Case-insensitive */ - void (*fn)(HttpState *hs, char *value); -} hdrtab[] = { - { "location:", location }, - { "content-type:", contenttype }, - { "set-cookie:", setcookie }, - { "www-authenticate:", wwwauthenticate }, -}; - static int -httprcode(HttpState *hs) +hwrite(Hconn *h, void *data, int len) { - int n; - char *p; - char buf[256]; - - n = readline(&hs->b, buf, sizeof(buf)-1); - if(n <= 0) - return n; - if(httpdebug) - fprint(2, "-> %s\n", buf); - p = strchr(buf, ' '); - if(memcmp(buf, "HTTP/", 5) != 0 || p == nil){ - werrstr("bad response from server"); + if(write(h->fd, data, len) != len){ + h->keep = 0; return -1; } - buf[n] = 0; - return atoi(p+1); + return len; } - -/* - * read a single mime header, collect continuations. - * - * this routine assumes that there is a blank line twixt - * the header and the message body, otherwise bytes will - * be lost. - */ + static int -getheader(HttpState *hs, char *buf, int n) +hline(Hconn *h, char *data, int len, int cont) { - char *p, *e; - int i; + char *x, *y, *e; + int n; - n--; - p = buf; - for(e = p + n; ; p += i){ - i = readline(&hs->b, p, e-p); - if(i < 0) - return i; - - if(p == buf){ - /* first line */ - if(strchr(buf, ':') == nil) - break; /* end of headers */ - } else { - /* continuation line */ - if(*p != ' ' && *p != '\t'){ - unreadline(&hs->b, p); - *p = 0; - break; /* end of this header */ + 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(!strchr("\t ", *y)) + break; + if(y >= e || strchr("\t ", *y)) + 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; } } + if(h->len >= sizeof(h->buf)) + return 0; + if((n = read(h->fd, h->buf + h->len, sizeof(h->buf) - h->len)) <= 0){ + h->keep = 0; + return -1; + } + h->len += n; } - - if(httpdebug) - fprint(2, "-> %s\n", buf); - return p-buf; } static int -httpheaders(HttpState *hs) +authenticate(Url *u, Url *ru, char *method, char *s) { - char buf[2048]; - char *p; - int i, n; + char *user, *pass, *realm, *nonce, *opaque, *x; + UserPasswd *up; + Hauth *a; + Fmt fmt; + int n; - for(;;){ - n = getheader(hs, buf, sizeof(buf)); - if(n < 0) + up = nil; + user = u->user; + pass = u->pass; + realm = nonce = opaque = nil; + fmtstrinit(&fmt); + if(!cistrncmp(s, "Basic ", 6)){ + char cred[64]; + + s += 6; + if(x = cistrstr(s, "realm=")) + realm = unquote(x+6, &s); + if(realm == nil) return -1; - if(n == 0) - return 0; - // print("http header: '%.*s'\n", n, buf); - for(i = 0; i < nelem(hdrtab); i++){ - n = strlen(hdrtab[i].name); - if(cistrncmp(buf, hdrtab[i].name, n) == 0){ - /* skip field name and leading white */ - p = buf + n; - while(*p == ' ' || *p == '\t') - p++; - (*hdrtab[i].fn)(hs, p); - break; - } + if(user == nil || pass == nil){ + fmtprint(&fmt, " realm=%q", realm); + if(user) + fmtprint(&fmt, " user=%q", user); + if((s = fmtstrflush(&fmt)) == nil) + return -1; + if((up = auth_getuserpasswd(nil, "proto=pass service=http server=%q%s", + u->host, s)) == nil) + return -1; + user = up->user; + pass = up->passwd; } + fmtstrinit(&fmt); + fmtprint(&fmt, "%s:%s", user ? user : "", pass ? pass : ""); + free(up); + if((s = fmtstrflush(&fmt)) == nil) + return -1; + n = enc64(cred, sizeof(cred), (uchar*)s, strlen(s)); + memset(s, 0, strlen(s)); + free(s); + if(n == -1) + return -1; + fmtstrinit(&fmt); + fmtprint(&fmt, "Basic %s", cred); + 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]; + 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, "opaque=")) + opaque = unquote(x+7, &s); + if(realm == nil || nonce == nil) + return -1; + nchal = snprint(chal, sizeof(chal), "%s %s %U", nonce, method, ru); + fmtprint(&fmt, " realm=%q", realm); + if(user) + fmtprint(&fmt, " user=%q", user); + if((s = fmtstrflush(&fmt)) == nil) + return -1; + if(auth_respond(chal, nchal, ouser, sizeof ouser, resp, sizeof resp, nil, + "proto=httpdigest role=client server=%q%s", u->host, s) < 0) + return -1; + fmtstrinit(&fmt); + fmtprint(&fmt, "Digest "); + fmtprint(&fmt, "username=\"%s\", ", ouser); + fmtprint(&fmt, "realm=\"%s\", ", realm); + fmtprint(&fmt, "host=\"%s\", ", u->host); + fmtprint(&fmt, "uri=\"%U\", ", ru); + fmtprint(&fmt, "nonce=\"%s\", ", nonce); + fmtprint(&fmt, "response=\"%s\"", resp); + if(opaque) + fmtprint(&fmt, ", opaque=\"%s\"", opaque); + u = saneurl(url("/", u)); /* BUG: should be the ones in domain= only */ + } else + return -1; + if(u == nil) + return -1; + if((s = fmtstrflush(&fmt)) == nil){ + freeurl(u); + return -1; } + a = emalloc(sizeof(*a)); + a->url = u; + a->auth = s; + qlock(&authlk); + a->next = hauth; + hauth = a; + qunlock(&authlk); + + return 0; +} + +void +flushauth(Url *u, char *t) +{ + Hauth *a, *p; + + qlock(&authlk); +Again: + for(p = nil, a = hauth; a; p = a, a = a->next) + if(matchurl(u, a->url) && (t == nil || !strcmp(t, a->auth))){ + if(p) + p->next = a->next; + else + hauth = a->next; + if(debug) + fprint(2, "flushauth for %U\n", a->url); + freeurl(a->url); + memset(a->auth, 0, strlen(a->auth)); + free(a->auth); + free(a); + goto Again; + } + qunlock(&authlk); } -int -httpopen(Client *c, Url *url) +static void +catch(void *, char *msg) { - int fd, code, redirect, authenticate; - char *cookies; - Ioproc *io; - HttpState *hs; - char *service; - - if(httpdebug) - fprint(2, "httpopen\n"); - io = c->io; - hs = emalloc(sizeof(*hs)); - hs->c = c; - - if(url->port) - service = url->port; + if(strstr("alarm", msg) || strstr("die", msg)) + noted(NCONT); else - service = url->scheme; - hs->netaddr = estrdup(netmkaddr(url->host, 0, service)); - c->aux = hs; - if(httpdebug){ - fprint(2, "dial %s\n", hs->netaddr); - fprint(2, "dial port: %s\n", url->port); - } - fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps); - if(fd < 0){ - Error: - if(httpdebug) - fprint(2, "iodial: %r\n"); - free(hs->location); - free(hs->setcookie); - free(hs->netaddr); - free(hs->credentials); - if(fd >= 0) - ioclose(io, hs->fd); - hs->fd = -1; - free(hs); - c->aux = nil; - return -1; - } - hs->fd = fd; - if(httpdebug) - fprint(2, "<- %s %s HTTP/1.0\n<- Host: %s\n", - c->havepostbody? "POST": "GET", url->http.page_spec, url->host); - ioprint(io, fd, "%s %s HTTP/1.0\r\nHost: %s\r\n", - c->havepostbody? "POST" : "GET", url->http.page_spec, url->host); - if(httpdebug) - fprint(2, "<- User-Agent: %s\n", c->ctl.useragent); - if(c->ctl.useragent) - ioprint(io, fd, "User-Agent: %s\r\n", c->ctl.useragent); - if(c->ctl.sendcookies){ - /* should we use url->page here? sometimes it is nil. */ - cookies = httpcookies(url->host, url->http.page_spec, - url->ischeme == UShttps); - if(cookies && cookies[0]) - ioprint(io, fd, "%s", cookies); - if(httpdebug) - fprint(2, "<- %s", cookies); - free(cookies); - } - if(c->havepostbody){ - ioprint(io, fd, "Content-type: %s\r\n", PostContentType); - ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody); - if(httpdebug){ - fprint(2, "<- Content-type: %s\n", PostContentType); - fprint(2, "<- Content-length: %ud\n", c->npostbody); + 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, chunked, retry, nobody; + char *s, *x, buf[8192+2], status[256], method[16]; + vlong length, offset; + Url ru, tu, *nu; + Key *k, *rhdr; + Hconn *h; + Hauth *a; + + incref(qbody); + if(qpost) incref(qpost); + strncpy(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); } - } - if(c->authenticate){ - ioprint(io, fd, "Authorization: %s\r\n", c->authenticate); - if(httpdebug) - fprint(2, "<- Authorization: %s\n", c->authenticate); - } - ioprint(io, fd, "\r\n"); - if(c->havepostbody) - if(iowrite(io, fd, c->postbody, c->npostbody) != c->npostbody) - goto Error; - - redirect = 0; - authenticate = 0; - initibuf(&hs->b, io, fd); - code = httprcode(hs); - - switch(code){ - case -1: /* connection timed out */ - goto Error; - -/* - case Eof: - werrstr("EOF from HTTP server"); - goto Error; -*/ - - case 200: /* OK */ - case 201: /* Created */ - case 202: /* Accepted */ - case 204: /* No Content */ - case 205: /* Reset Content */ -#ifdef NOT_DEFINED - if(ofile == nil && r->start != 0) - sysfatal("page changed underfoot"); -#endif + freeurl(u); + return; + case 0: break; + } - case 206: /* Partial Content */ - werrstr("Partial Content (206)"); - goto Error; + 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; + pid = 0; + werrstr("too many errors"); + for(try = 0; try < 6; try++){ + if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https"))){ + werrstr("bad url"); + break; + } - case 303: /* See Other */ - c->havepostbody = 0; - case 301: /* Moved Permanently */ - case 302: /* Moved Temporarily */ - case 307: /* Temporary Redirect */ - redirect = 1; - 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; a = a->next) + if(matchurl(a->url, proxy)){ + shdr = addkey(shdr, "Proxy-Authorization", a->auth); + break; + } + if(!lookkey(shdr, "Authorization")) + for(a = hauth; a; a = a->next) + if(matchurl(a->url, u)){ + shdr = addkey(shdr, "Authorization", a->auth); + break; + } + qunlock(&authlk); + + if(proxy){ + ru = *u; + ru.fragment = nil; + } else { + memset(&ru, 0, sizeof(tu)); + ru.path = Upath(u); + ru.query = u->query; + } + n = snprint(buf, sizeof(buf), "%s %U HTTP/1.1\r\nHost: %s%s%s\r\n", + method, &ru, u->host, u->port ? ":" : "", u->port ? u->port : ""); - case 304: /* Not Modified */ - break; + for(k = shdr; k; k = k->next) + n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val); - case 400: /* Bad Request */ - werrstr("Bad Request (400)"); - goto Error; + if(n >= sizeof(buf)-64){ + werrstr("request too large"); + break; + } - case 401: /* Unauthorized */ - if(c->authenticate){ - werrstr("Authentication failed (401)"); - goto Error; + nobody = !cistrcmp(method, "HEAD"); + length = 0; + chunked = 0; + if(qpost){ + qlock(qpost); + /* wait until buffer is full, most posts are small */ + while(!qpost->closed && qpost->size < qpost->limit) + 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) + n += snprint(buf+n, sizeof(buf)-n, "Transfer-Encoding: chunked\r\n"); + else if(qpost->closed){ + if(fd >= 0){ + length = seek(fd, 0, 2); + if(length < 0) + length = 0; + } + length += qpost->size; + n += snprint(buf+n, sizeof(buf)-n, "Content-Length: %lld\r\n", length); + } + qunlock(qpost); } - authenticate = 1; - break; - case 402: /* Payment Required */ - werrstr("Payment Required (402)"); - goto Error; - - case 403: /* Forbidden */ - werrstr("Forbidden by server (403)"); - goto Error; - - case 404: /* Not Found */ - werrstr("Not found on server (404)"); - goto Error; - - case 405: /* Method Not Allowed */ - werrstr("Method not allowed (405)"); - goto Error; - - case 406: /* Not Acceptable */ - werrstr("Not Acceptable (406)"); - goto Error; - - case 407: /* Proxy auth */ - werrstr("Proxy authentication required (407)"); - goto Error; - - case 408: /* Request Timeout */ - werrstr("Request Timeout (408)"); - goto Error; - - case 409: /* Conflict */ - werrstr("Conflict (409)"); - goto Error; - - case 410: /* Gone */ - werrstr("Gone (410)"); - goto Error; - - case 411: /* Length Required */ - werrstr("Length Required (411)"); - goto Error; - - case 412: /* Precondition Failed */ - werrstr("Precondition Failed (412)"); - goto Error; - - case 413: /* Request Entity Too Large */ - werrstr("Request Entity Too Large (413)"); - goto Error; - - case 414: /* Request-URI Too Long */ - werrstr("Request-URI Too Long (414)"); - goto Error; - - case 415: /* Unsupported Media Type */ - werrstr("Unsupported Media Type (415)"); - goto Error; - - case 416: /* Requested Range Not Satisfiable */ - werrstr("Requested Range Not Satisfiable (416)"); - goto Error; - - case 417: /* Expectation Failed */ - werrstr("Expectation Failed (417)"); - goto Error; - - case 500: /* Internal server error */ - werrstr("Server choked (500)"); - goto Error; - - case 501: /* Not implemented */ - werrstr("Server can't do it (501)"); - goto Error; - - case 502: /* Bad gateway */ - werrstr("Bad gateway (502)"); - goto Error; - - case 503: /* Service unavailable */ - werrstr("Service unavailable (503)"); - goto Error; - - default: - /* Bogus: we should treat unknown code XYZ as code X00 */ - werrstr("Unknown response code %d", code); - goto Error; - } - if(httpheaders(hs) < 0) - goto Error; - if(c->ctl.acceptcookies && hs->setcookie) - httpsetcookie(hs->setcookie, url->host, url->path); - if(authenticate){ - if(!hs->credentials){ - if(hs->autherror[0]) - werrstr("%s", hs->autherror); - else - werrstr("unauthorized; no www-authenticate: header"); - goto Error; + /* give 5 seconds to dial */ + if(h == nil){ + alarm(5000); + if((h = hdial(proxy ? proxy : u)) == nil) + break; } - c->authenticate = hs->credentials; - hs->credentials = nil; - }else if(c->authenticate) - c->authenticate = 0; - if(redirect){ - if(!hs->location){ - werrstr("redirection without Location: header"); - goto Error; + + 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 = u->host; + tu.path = Upath(u); + fprint(cfd, "%U", &tu); + for(;;){ + if(n >= sizeof(buf)-2){ + if(debug) + fprint(2, "-> %.*s", 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; + } } - c->redirect = hs->location; - hs->location = nil; - } - return 0; -} -int -httpread(Client *c, Req *r) -{ - HttpState *hs; - long n; + n += snprint(buf+n, sizeof(buf)-n, "\r\n"); + if(debug) + fprint(2, "-> %.*s", n, buf); + if(hwrite(h, buf, n) != n){ + Badflush: + alarm(0); + goto Retry; + } - hs = c->aux; - n = readibuf(&hs->b, r->ofcall.data, r->ifcall.count); - if(n < 0) - return -1; + if(qpost){ + h->cancel = 0; + if((pid = rfork(RFMEM|RFPROC)) <= 0){ + int ifd; + + alarm(0); + if((ifd = fd) >= 0) + seek(ifd, 0, 0); + while(!h->cancel){ + 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; + } + if(chunked){ + char tmp[32]; + hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n)); + buf[n++] = '\r'; + buf[n++] = '\n'; + } + if(hwrite(h, buf, n) != n) + break; + } + if(chunked) + hwrite(h, "0\r\n\r\n", 5); + else + h->keep = 0; + if(pid == 0) + exits(0); + } + /* no timeout when posting */ + alarm(0); + } else { + /* wait 10 seconds for the response */ + alarm(10000); + } - r->ofcall.count = n; - return 0; -} + Cont: + rhdr = 0; + retry = 0; + chunked = 0; + status[0] = 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; + } + strncpy(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; + } -void -httpclose(Client *c) -{ - HttpState *hs; + if((i = atoi(status)) < 0) + i = 0; + Status: + switch(i){ + default: + if(i % 100){ + i -= (i % 100); + goto Status; + } + case 100: /* Continue */ + case 101: /* Switching Protocols */ + while(k = rhdr){ + rhdr = k->next; + free(k); + } + 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 */ + case 411: /* Length Required */ + 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; + } + if(cistrcmp(method, "HEAD")) + strncpy(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")) + flushauth(nil, x); + if((x = lookkey(rhdr, "WWW-Authenticate")) == nil) + goto Error; + if(authenticate(u, &ru, method, x) < 0) + goto Error; + } + if(0){ + case 407: /* Proxy Auth */ + if(proxy == nil) + goto Error; + if(x = lookkey(shdr, "Proxy-Authorization")) + flushauth(proxy, x); + if((x = lookkey(rhdr, "Proxy-Authenticate")) == nil) + goto Error; + if(authenticate(proxy, proxy, method, x) < 0) + goto Error; + } + 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 */ + qbody->url = u; u = nil; + qbody->hdr = rhdr; rhdr = nil; + if(nobody) + buclose(qbody, 0); + break; + } - hs = c->aux; - if(hs == nil) - return; - if(hs->fd >= 0) - ioclose(c->io, hs->fd); - hs->fd = -1; - free(hs->location); - free(hs->setcookie); - free(hs->netaddr); - free(hs->credentials); - free(hs); - c->aux = nil; + 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"); + + 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); + + rerrstr(buf, sizeof(buf)); + 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); + + while(k = shdr){ + shdr = k->next; + free(k); + } + exits(0); } |