summaryrefslogtreecommitdiff
path: root/sys/src/cmd/webfs/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/src/cmd/webfs/http.c')
-rw-r--r--sys/src/cmd/webfs/http.c1223
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);
}