Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <plumb.h>
#include <thread.h>
#include <fcall.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;

typedef struct HttpState HttpState;
struct HttpState
{
        int fd;
        Client *c;
        char *location;
        char *setcookie;
        char *netaddr;
        char *credentials;
        char autherror[ERRMAX];
        Ibuf    b;
};

static void
location(HttpState *hs, char *value)
{
        if(hs->location == nil)
                hs->location = estrdup(value);
}

static void
contenttype(HttpState *hs, char *value)
{
        if(hs->c->contenttype != nil)
                free(hs->c->contenttype);
        hs->c->contenttype = estrdup(value);
}

static void
setcookie(HttpState *hs, char *value)
{
        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;
        }
}

static char*
unquote(char *s, char **ps)
{
        char *p;

        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(*p == '\\' && *(p+1)){
                        p++;
                        continue;
                }
        }
        memmove(s, s+1, p-(s+1));
        s[p-(s+1)] = 0;
        *ps = p;
        return s;
}

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;
}

void
wwwauthenticate(HttpState *hs, char *line)
{
        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;
        }
        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);
}

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)
{
        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");
                return -1;
        }
        buf[n] = 0;
        return atoi(p+1);
}
 
/*
 *  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)
{
        char *p, *e;
        int i;

        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 */
                        }
                }
        }

        if(httpdebug)
                fprint(2, "-> %s\n", buf);
        return p-buf;
}

static int
httpheaders(HttpState *hs)
{
        char buf[2048];
        char *p;
        int i, n;

        for(;;){
                n = getheader(hs, buf, sizeof(buf));
                if(n < 0)
                        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;
                        }
                }
        }
}

int
httpopen(Client *c, Url *url)
{
        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;
        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);
                }
        }
        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
                break;

        case 206:       /* Partial Content */
                werrstr("Partial Content (206)");
                goto Error;

        case 301:       /* Moved Permanently */
        case 302:       /* Moved Temporarily */
        case 303:       /* See Other */
        case 307: /* Temporary Redirect  */
                redirect = 1;
                break;

        case 304:       /* Not Modified */
                break;

        case 400:       /* Bad Request */
                werrstr("Bad Request (400)");
                goto Error;

        case 401:       /* Unauthorized */
                if(c->authenticate){
                        werrstr("Authentication failed (401)");
                        goto Error;
                }
                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;
                }
                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;
                }
                c->redirect = hs->location;
                hs->location = nil;
        }
        return 0;
}

int
httpread(Client *c, Req *r)
{
        HttpState *hs;
        long n;

        hs = c->aux;
        n = readibuf(&hs->b, r->ofcall.data, r->ifcall.count);
        if(n < 0)
                return -1;

        r->ofcall.count = n;
        return 0;
}

void
httpclose(Client *c)
{
        HttpState *hs;

        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;
}