long time;
int fd;
+ int ctl;
int keep;
int cancel;
+ int tunnel;
int len;
char addr[128];
char buf[8192+2];
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");
+ return -1;
+ }
+ free(conn.cert);
+ free(conn.sessionID);
+ free(conn.serverName);
+ return fd;
+}
+
static Hconn*
hdial(Url *u)
{
char addr[128];
Hconn *h, *p;
- int fd, ofd;
+ int fd, ctl;
snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme);
}
hpool.active++;
qunlock(&hpool);
+
if(debug)
fprint(2, "hdial [%d] %s\n", hpool.active, addr);
- if((fd = dial(addr, 0, 0, 0)) < 0)
+ 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){
+ 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;
- 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->time = 0;
h->cancel = 0;
+ h->tunnel = 0;
h->keep = 1;
h->len = 0;
h->fd = fd;
- strncpy(h->addr, addr, sizeof(h->addr));
+ 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;
}
return;
qlock(&hpool);
- if(h->keep && h->fd >= 0){
+ 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)
/* free the tail */
hcloseall(x);
} while(i);
- exits(0);
+ exits(nil);
}
return;
}
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)
{
memmove(h->buf, h->buf + len, h->len);
return len;
}
- if((len = read(h->fd, data, len)) <= 0)
+ if((len = read(h->fd, data, len)) == 0)
h->keep = 0;
+ if(len < 0)
+ hhangup(h);
return len;
}
hwrite(Hconn *h, void *data, int len)
{
if(write(h->fd, data, len) != len){
- h->keep = 0;
+ hhangup(h);
return -1;
}
return len;
if(n > 0 && cont){
e = h->buf + h->len;
for(y = x+1; y < e; y++)
- if(!strchr("\t ", *y))
+ if(*y != ' ' && *y != '\t')
break;
- if(y >= e || strchr("\t ", *y))
+ if(y >= e || *y == 0)
break;
if(y > x+1){
if(x > h->buf && x[-1] == '\r')
return len;
}
}
- if(h->len >= sizeof(h->buf))
+ n = sizeof(h->buf) - h->len;
+ if(n <= 0)
return 0;
- if((n = read(h->fd, h->buf + h->len, sizeof(h->buf) - h->len)) <= 0){
- h->keep = 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
+int
authenticate(Url *u, Url *ru, char *method, char *s)
{
char *user, *pass, *realm, *nonce, *opaque, *x;
user = u->user;
pass = u->pass;
realm = nonce = opaque = nil;
- fmtstrinit(&fmt);
if(!cistrncmp(s, "Basic ", 6)){
- char cred[128], plain[128];
UserPasswd *up;
s += 6;
return -1;
up = nil;
if(user == nil || pass == nil){
+ fmtstrinit(&fmt);
fmtprint(&fmt, " realm=%q", realm);
if(user)
fmtprint(&fmt, " user=%q", user);
user = up->user;
pass = up->passwd;
}
- n = snprint(plain, sizeof(plain), "%s:%s", user ? user : "", pass ? pass : "");
+ 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);
}
- n = enc64(cred, sizeof(cred), (uchar*)plain, n);
- memset(plain, 0, sizeof(plain));
- if(n == -1)
+ if((s = fmtstrflush(&fmt)) == nil)
return -1;
+ n = strlen(s);
fmtstrinit(&fmt);
- fmtprint(&fmt, "Basic %s", cred);
- memset(cred, 0, sizeof(cred));
+ 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)){
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);
fmtprint(&fmt, "Digest ");
fmtprint(&fmt, "username=\"%s\", ", ouser);
fmtprint(&fmt, "realm=\"%s\", ", realm);
- fmtprint(&fmt, "host=\"%s\", ", u->host);
+ fmtprint(&fmt, "host=\"%N\", ", u->host);
fmtprint(&fmt, "uri=\"%U\", ", ru);
fmtprint(&fmt, "nonce=\"%s\", ", nonce);
fmtprint(&fmt, "response=\"%s\"", resp);
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;
}
+ if(u == nil){
+ free(s);
+ return -1;
+ }
a = emalloc(sizeof(*a));
a->url = u;
a->auth = s;
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
flushauth(Url *u, char *t)
{
static void
catch(void *, char *msg)
{
- if(strstr("alarm", msg) || strstr("die", msg))
+ if(strstr("alarm", msg) != nil)
noted(NCONT);
else
noted(NDFLT);
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];
+ int i, l, n, try, pid, fd, cfd, needlength, chunked, retry, nobody;
+ char *s, *x, buf[8192+2], status[256], method[16], *host;
vlong length, offset;
Url ru, tu, *nu;
Key *k, *rhdr;
incref(qbody);
if(qpost) incref(qpost);
- strncpy(method, m, sizeof(method));
+ nstrcpy(method, m, sizeof(method));
switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
default:
return;
fd = -1;
h = nil;
+ cfd = -1;
pid = 0;
- werrstr("too many errors");
- for(try = 0; try < 6; try++){
+ host = nil;
+ needlength = 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");
+ werrstr("bad url scheme");
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 : "");
-
- for(k = shdr; k; k = k->next)
- n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val);
-
- if(n >= sizeof(buf)-64){
- werrstr("request too large");
- break;
- }
-
- nobody = !cistrcmp(method, "HEAD");
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)
+ while(!qpost->closed && qpost->size < qpost->limit && qpost->nwq == 0)
rsleep(&qpost->rz);
if(lookkey(shdr, "Content-Length"))
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");
+ shdr = addkey(shdr, "Transfer-Encoding", "chunked");
else if(qpost->closed){
if(fd >= 0){
length = seek(fd, 0, 2);
length = 0;
}
length += qpost->size;
- n += snprint(buf+n, sizeof(buf)-n, "Content-Length: %lld\r\n", length);
+ snprint(buf, sizeof(buf), "%lld", length);
+ shdr = addkey(shdr, "Content-Length", buf);
}
qunlock(qpost);
}
- /* give 5 seconds to dial */
+ /* 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%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(5000);
- if((h = hdial(proxy ? proxy : u)) == nil)
+ alarm(timeout);
+ if((h = hdial(u)) == nil)
break;
}
-
- if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){
+ if(h->tunnel){
+ n = snprint(buf, sizeof(buf), "CONNECT %s:%s HTTP/1.1\r\nHost: %s:%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 = u->host;
+ tu.host = host;
tu.path = Upath(u);
fprint(cfd, "%U", &tu);
for(;;){
}
}
+ for(k = shdr; k; k = k->next){
+ /* only send proxy headers when establishing tunnel */
+ if(h->tunnel && cistrncmp(k->key, "Proxy-", 6) != 0)
+ continue;
+ n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val);
+ }
n += snprint(buf+n, sizeof(buf)-n, "\r\n");
if(debug)
fprint(2, "-> %.*s", n, buf);
goto Retry;
}
- if(qpost){
+ if(qpost && !h->tunnel){
h->cancel = 0;
if((pid = rfork(RFMEM|RFPROC)) <= 0){
int ifd;
- alarm(0);
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)
if(write(fd, buf, n) != n)
break;
}
+ alarm(timeout);
if(chunked){
char tmp[32];
- hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n));
+ 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)
+ if(chunked){
+ alarm(timeout);
hwrite(h, "0\r\n\r\n", 5);
- else
+ }else
h->keep = 0;
if(pid == 0)
- exits(0);
+ exits(nil);
}
/* no timeout when posting */
alarm(0);
- } else {
- /* wait 10 seconds for the response */
- alarm(10000);
- }
+ } else
+ alarm(timeout);
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(cistrcmp(s, "ICY"))
break;
}
- strncpy(status, x, sizeof(status));
+ if(x[0])
+ nstrcpy(status, x, sizeof(status));
continue;
}
if((k = parsehdr(s)) == nil)
cfd = -1;
}
+ nobody = !cistrcmp(method, "HEAD");
if((i = atoi(status)) < 0)
i = 0;
Status:
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 */
bufree(qpost);
qpost = nil;
}
+ shdr = delkey(shdr, "Content-Length");
+ shdr = delkey(shdr, "Content-Type");
+ shdr = delkey(shdr, "Transfer-Encoding");
if(cistrcmp(method, "HEAD"))
- strncpy(method, "GET", sizeof(method));
+ nstrcpy(method, "GET", sizeof(method));
case 301: /* Moved Permanently */
case 307: /* Temporary Redirect */
case 308: /* Resume Incomplete */
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)
+ if(hauthenticate(u, &ru, method, "WWW-Authenticate", rhdr) < 0)
goto Error;
}
if(0){
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)
+ if(hauthenticate(proxy, proxy, method, "Proxy-Authenticate", rhdr) < 0)
goto Error;
}
case 0: /* No status */
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)
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;
h = nil;
}
alarm(0);
-
- rerrstr(buf, sizeof(buf));
+ snprint(buf, sizeof(buf), "%s %r", status);
buclose(qbody, buf);
bufree(qbody);
hclose(h);
freeurl(u);
+ free(host);
while(k = shdr){
shdr = k->next;
free(k);
}
- exits(0);
+ exits(nil);
}