]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/webfs/http.c
webfs: change %H (hostname) format to %N to not collide with encodefmt's %H (hex)
[plan9front.git] / sys / src / cmd / webfs / http.c
index e72d4856ffed7d644405f965fd0dce01a1a1e759..93b0cfa47660525be6702b26245676298e27e81b 100644 (file)
@@ -22,8 +22,10 @@ struct Hconn
        long    time;
 
        int     fd;
+       int     ctl;
        int     keep;
        int     cancel;
+       int     tunnel;
        int     len;
        char    addr[128];
        char    buf[8192+2];
@@ -59,12 +61,43 @@ 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");
+               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);
 
@@ -82,33 +115,43 @@ hdial(Url *u)
        }
        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;
 }
@@ -136,7 +179,7 @@ hclose(Hconn *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)
@@ -197,7 +240,7 @@ hclose(Hconn *h)
                                        /* free the tail */
                                        hcloseall(x);
                                } while(i);
-                               exits(0);
+                               exits(nil);
                        }
                        return;
                }
@@ -208,11 +251,23 @@ hclose(Hconn *h)
        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)
 {
@@ -225,8 +280,10 @@ 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;
 }
 
@@ -234,7 +291,7 @@ static int
 hwrite(Hconn *h, void *data, int len)
 {
        if(write(h->fd, data, len) != len){
-               h->keep = 0;
+               hhangup(h);
                return -1;
        }
        return len;
@@ -256,9 +313,9 @@ hline(Hconn *h, char *data, int len, int cont)
                                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')
@@ -278,17 +335,20 @@ hline(Hconn *h, char *data, int len, int cont)
                                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;
@@ -299,9 +359,7 @@ authenticate(Url *u, Url *ru, char *method, char *s)
        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;
@@ -311,6 +369,7 @@ authenticate(Url *u, Url *ru, char *method, char *s)
                        return -1;
                up = nil;
                if(user == nil || pass == nil){
+                       fmtstrinit(&fmt);
                        fmtprint(&fmt, " realm=%q", realm);
                        if(user)
                                fmtprint(&fmt, " user=%q", user);
@@ -323,19 +382,20 @@ authenticate(Url *u, Url *ru, char *method, char *s)
                        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)){
@@ -351,6 +411,7 @@ authenticate(Url *u, Url *ru, char *method, char *s)
                        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);
@@ -367,7 +428,7 @@ authenticate(Url *u, Url *ru, char *method, char *s)
                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);
@@ -376,12 +437,14 @@ authenticate(Url *u, Url *ru, char *method, char *s)
                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;
@@ -393,6 +456,15 @@ authenticate(Url *u, Url *ru, char *method, char *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)
 {
@@ -420,7 +492,7 @@ Again:
 static void
 catch(void *, char *msg)
 {
-       if(strstr("alarm", msg) || strstr("die", msg))
+       if(strstr("alarm", msg) != nil)
                noted(NCONT);
        else
                noted(NDFLT);
@@ -431,8 +503,8 @@ catch(void *, char *msg)
 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;
@@ -441,7 +513,7 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
 
        incref(qbody);
        if(qpost) incref(qpost);
-       strncpy(method, m, sizeof(method));
+       nstrcpy(method, m, sizeof(method));
        switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
        default:
                return;
@@ -469,11 +541,14 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                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;
                }
 
@@ -496,32 +571,20 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                                }
                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"))
@@ -529,7 +592,7 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                        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);
@@ -537,23 +600,46 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                                                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(;;){
@@ -575,6 +661,12 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                        }
                }
 
+               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);
@@ -584,15 +676,15 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                        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)
@@ -601,34 +693,34 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                                                        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++){
@@ -643,7 +735,8 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                                        if(cistrcmp(s, "ICY"))
                                                break;
                                }
-                               strncpy(status, x, sizeof(status));
+                               if(x[0])
+                                       nstrcpy(status, x, sizeof(status));
                                continue;
                        }
                        if((k = parsehdr(s)) == nil)
@@ -674,6 +767,7 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                        cfd = -1;
                }
 
+               nobody = !cistrcmp(method, "HEAD");
                if((i = atoi(status)) < 0)
                        i = 0;
                Status:
@@ -702,7 +796,14 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                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 */
@@ -732,8 +833,11 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                                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 */
@@ -747,9 +851,7 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                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){
@@ -758,9 +860,7 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                                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 */
@@ -780,6 +880,8 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                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)
@@ -801,6 +903,19 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                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;
 
@@ -864,8 +979,7 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
                h = nil;
        }
        alarm(0);
-
-       rerrstr(buf, sizeof(buf));
+       snprint(buf, sizeof(buf), "%s %r", status);
        buclose(qbody, buf);
        bufree(qbody);
 
@@ -880,10 +994,11 @@ http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost)
 
        hclose(h);
        freeurl(u);
+       free(host);
 
        while(k = shdr){
                shdr = k->next;
                free(k);
        }
-       exits(0);
+       exits(nil);
 }