tadd libhttpd - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 9df487d720a59bf8cb0dc4ccffc30ad8eb48256a
 (DIR) parent b6afd33e2f23953f00c6fac6b5d45946a9113654
 (HTM) Author: rsc <devnull@localhost>
       Date:   Sun, 23 Nov 2003 18:19:35 +0000
       
       add libhttpd
       
       Diffstat:
         A src/libhttpd/alloc.c                |      35 +++++++++++++++++++++++++++++++
         A src/libhttpd/checkcontent.c         |      33 +++++++++++++++++++++++++++++++
         A src/libhttpd/date.c                 |     212 ++++++++++++++++++++++++++++++
         A src/libhttpd/escape.h               |     123 +++++++++++++++++++++++++++++++
         A src/libhttpd/fail.c                 |      81 ++++++++++++++++++++++++++++++
         A src/libhttpd/gethead.c              |      40 +++++++++++++++++++++++++++++++
         A src/libhttpd/hio.c                  |     473 ++++++++++++++++++++++++++++++
         A src/libhttpd/httpfmt.c              |      30 ++++++++++++++++++++++++++++++
         A src/libhttpd/httpunesc.c            |      49 +++++++++++++++++++++++++++++++
         A src/libhttpd/lower.c                |      19 +++++++++++++++++++
         A src/libhttpd/mkfile                 |      30 ++++++++++++++++++++++++++++++
         A src/libhttpd/okheaders.c            |      22 ++++++++++++++++++++++
         A src/libhttpd/parse.c                |    1057 +++++++++++++++++++++++++++++++
         A src/libhttpd/parsereq.c             |     296 +++++++++++++++++++++++++++++++
         A src/libhttpd/query.c                |      39 +++++++++++++++++++++++++++++++
         A src/libhttpd/redirected.c           |      64 +++++++++++++++++++++++++++++++
         A src/libhttpd/unallowed.c            |      35 +++++++++++++++++++++++++++++++
         A src/libhttpd/urlfmt.c               |      26 ++++++++++++++++++++++++++
         A src/libhttpd/urlunesc.c             |      58 ++++++++++++++++++++++++++++++
       
       19 files changed, 2722 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/libhttpd/alloc.c b/src/libhttpd/alloc.c
       t@@ -0,0 +1,35 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +/*
       + * memory allocators:
       + * h routines call canalloc; they should be used by everything else
       + * note this memory is wiped out at the start of each new request
       + * note: these routines probably shouldn't fatal.
       + */
       +char*
       +hstrdup(HConnect *c, char *s)
       +{
       +        char *t;
       +        int n;
       +
       +        n = strlen(s) + 1;
       +        t = binalloc(&c->bin, n, 0);
       +        if(t == nil)
       +                sysfatal("out of memory");
       +        memmove(t, s, n);
       +        return t;
       +}
       +
       +void*
       +halloc(HConnect *c, ulong n)
       +{
       +        void *p;
       +
       +        p = binalloc(&c->bin, n, 1);
       +        if(p == nil)
       +                sysfatal("out of memory");
       +        return p;
       +}
 (DIR) diff --git a/src/libhttpd/checkcontent.c b/src/libhttpd/checkcontent.c
       t@@ -0,0 +1,33 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +int
       +hcheckcontent(HContent *me, HContent *oks, char *list, int size)
       +{
       +        HContent *ok;
       +
       +        if(oks == nil || me == nil)
       +                return 1;
       +        for(ok = oks; ok != nil; ok = ok->next){
       +                if((cistrcmp(ok->generic, me->generic) == 0 || strcmp(ok->generic, "*") == 0)
       +                && (me->specific == nil || cistrcmp(ok->specific, me->specific) == 0 || strcmp(ok->specific, "*") == 0)){
       +                        if(ok->mxb > 0 && size > ok->mxb)
       +                                return 0;
       +                        return 1;
       +                }
       +        }
       +
       +        USED(list);
       +        if(0){
       +                fprint(2, "list: %s/%s not found\n", me->generic, me->specific);
       +                for(; oks != nil; oks = oks->next){
       +                        if(oks->specific)
       +                                fprint(2, "\t%s/%s\n", oks->generic, oks->specific);
       +                        else
       +                                fprint(2, "\t%s\n", oks->generic);
       +                }
       +        }
       +        return 0;
       +}
 (DIR) diff --git a/src/libhttpd/date.c b/src/libhttpd/date.c
       t@@ -0,0 +1,212 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <httpd.h>
       +
       +/*
       + * print dates in the format
       + * Wkd, DD Mon YYYY HH:MM:SS GMT
       + * parse dates of formats
       + * Wkd, DD Mon YYYY HH:MM:SS GMT
       + * Weekday, DD-Mon-YY HH:MM:SS GMT
       + * Wkd Mon ( D|DD) HH:MM:SS YYYY
       + * plus anything similar
       + */
       +static char *
       +weekdayname[7] =
       +{
       +        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
       +};
       +static char *
       +wdayname[7] =
       +{
       +        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
       +};
       +
       +static char *
       +monname[12] =
       +{
       +        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
       +};
       +
       +static        int        dateindex(char*, char**, int);
       +
       +static int
       +dtolower(int c)
       +{
       +        if(c >= 'A' && c <= 'Z')
       +                return c - 'A' + 'a';
       +        return c;
       +}
       +
       +static int
       +disalpha(int c)
       +{
       +        return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
       +}
       +
       +static int
       +disdig(int c)
       +{
       +        return c >= '0' && c <= '9';
       +}
       +
       +int
       +hdatefmt(Fmt *f)
       +{
       +        Tm *tm;
       +        ulong t;
       +
       +        t = va_arg(f->args, ulong);
       +        tm = gmtime(t);
       +        return fmtprint(f, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
       +                wdayname[tm->wday], tm->mday, monname[tm->mon], tm->year+1900,
       +                tm->hour, tm->min, tm->sec);
       +}
       +
       +static char*
       +dateword(char *date, char *buf)
       +{
       +        char *p;
       +        int c;
       +
       +        p = buf;
       +        while(!disalpha(c = *date) && !disdig(c) && c)
       +                date++;
       +        while(disalpha(c = *date)){
       +                if(p - buf < 30)
       +                        *p++ = dtolower(c);
       +                date++;
       +        }
       +        *p = 0;
       +        return date;
       +}
       +
       +static int
       +datenum(char **d)
       +{
       +        char *date;
       +        int c, n;
       +
       +        date = *d;
       +        while(!disdig(c = *date) && c)
       +                date++;
       +        if(c == 0){
       +                *d = date;
       +                return -1;
       +        }
       +        n = 0;
       +        while(disdig(c = *date)){
       +                n = n * 10 + c - '0';
       +                date++;
       +        }
       +        *d = date;
       +        return n;
       +}
       +
       +/*
       + * parse a date and return the seconds since the epoch
       + * return 0 for a failure
       + */
       +ulong
       +hdate2sec(char *date)
       +{
       +        Tm tm;
       +        char buf[32];
       +
       +        /*
       +         * Weekday|Wday
       +         */
       +        date = dateword(date, buf);
       +        tm.wday = dateindex(buf, wdayname, 7);
       +        if(tm.wday < 0)
       +                tm.wday = dateindex(buf, weekdayname, 7);
       +        if(tm.wday < 0)
       +                return 0;
       +
       +        /*
       +         * check for the two major formats
       +         */
       +        date = dateword(date, buf);
       +        tm.mon = dateindex(buf, monname, 12);
       +        if(tm.mon >= 0){
       +                /*
       +                 * MM
       +                 */
       +                tm.mday = datenum(&date);
       +                if(tm.mday < 1 || tm.mday > 31)
       +                        return 0;
       +
       +                /*
       +                 * HH:MM:SS
       +                 */
       +                tm.hour = datenum(&date);
       +                if(tm.hour < 0 || tm.hour >= 24)
       +                        return 0;
       +                tm.min = datenum(&date);
       +                if(tm.min < 0 || tm.min >= 60)
       +                        return 0;
       +                tm.sec = datenum(&date);
       +                if(tm.sec < 0 || tm.sec >= 60)
       +                        return 0;
       +
       +                /*
       +                 * YYYY
       +                 */
       +                tm.year = datenum(&date);
       +                if(tm.year < 70 || tm.year > 99 && tm.year < 1970)
       +                        return 0;
       +                if(tm.year >= 1970)
       +                        tm.year -= 1900;
       +        }else{
       +                /*
       +                 * MM-Mon-(YY|YYYY)
       +                 */
       +                tm.mday = datenum(&date);
       +                if(tm.mday < 1 || tm.mday > 31)
       +                        return 0;
       +                date = dateword(date, buf);
       +                tm.mon = dateindex(buf, monname, 12);
       +                if(tm.mon < 0 || tm.mon >= 12)
       +                        return 0;
       +                tm.year = datenum(&date);
       +                if(tm.year < 70 || tm.year > 99 && tm.year < 1970)
       +                        return 0;
       +                if(tm.year >= 1970)
       +                        tm.year -= 1900;
       +
       +                /*
       +                 * HH:MM:SS
       +                 */
       +                tm.hour = datenum(&date);
       +                if(tm.hour < 0 || tm.hour >= 24)
       +                        return 0;
       +                tm.min = datenum(&date);
       +                if(tm.min < 0 || tm.min >= 60)
       +                        return 0;
       +                tm.sec = datenum(&date);
       +                if(tm.sec < 0 || tm.sec >= 60)
       +                        return 0;
       +
       +                /*
       +                 * timezone
       +                 */
       +                dateword(date, buf);
       +                if(strncmp(buf, "gmt", 3) != 0)
       +                        return 0;
       +        }
       +
       +        strcpy(tm.zone, "GMT");
       +        tm.tzoff = 0;
       +        return tm2sec(&tm);
       +}
       +
       +static int
       +dateindex(char *d, char **tab, int n)
       +{
       +        int i;
       +
       +        for(i = 0; i < n; i++)
       +                if(cistrcmp(d, tab[i]) == 0)
       +                        return i;
       +        return -1;
       +}
 (DIR) diff --git a/src/libhttpd/escape.h b/src/libhttpd/escape.h
       t@@ -0,0 +1,123 @@
       +
       +Htmlesc htmlesc[] =
       +{
       +        { "&#161;",        0x00a1, },
       +        { "&#162;",        0x00a2, },
       +        { "&#163;",        0x00a3, },
       +        { "&#164;",        0x00a4, },
       +        { "&#165;",        0x00a5, },
       +        { "&#166;",        0x00a6, },
       +        { "&#167;",        0x00a7, },
       +        { "&#168;",        0x00a8, },
       +        { "&#169;",        0x00a9, },
       +        { "&#170;",        0x00aa, },
       +        { "&#171;",        0x00ab, },
       +        { "&#172;",        0x00ac, },
       +        { "&#173;",        0x00ad, },
       +        { "&#174;",        0x00ae, },
       +        { "&#175;",        0x00af, },
       +        { "&#176;",        0x00b0, },
       +        { "&#177;",        0x00b1, },
       +        { "&#178;",        0x00b2, },
       +        { "&#179;",        0x00b3, },
       +        { "&#180;",        0x00b4, },
       +        { "&#181;",        0x00b5, },
       +        { "&#182;",        0x00b6, },
       +        { "&#183;",        0x00b7, },
       +        { "&#184;",        0x00b8, },
       +        { "&#185;",        0x00b9, },
       +        { "&#186;",        0x00ba, },
       +        { "&#187;",        0x00bb, },
       +        { "&#188;",        0x00bc, },
       +        { "&#189;",        0x00bd, },
       +        { "&#190;",        0x00be, },
       +        { "&#191;",        0x00bf, },
       +        { "&Agrave;",        0x00c0, },
       +        { "&Aacute;",        0x00c1, },
       +        { "&Acirc;",        0x00c2, },
       +        { "&Atilde;",        0x00c3, },
       +        { "&Auml;",        0x00c4, },
       +        { "&Aring;",        0x00c5, },
       +        { "&AElig;",        0x00c6, },
       +        { "&Ccedil;",        0x00c7, },
       +        { "&Egrave;",        0x00c8, },
       +        { "&Eacute;",        0x00c9, },
       +        { "&Ecirc;",        0x00ca, },
       +        { "&Euml;",        0x00cb, },
       +        { "&Igrave;",        0x00cc, },
       +        { "&Iacute;",        0x00cd, },
       +        { "&Icirc;",        0x00ce, },
       +        { "&Iuml;",        0x00cf, },
       +        { "&ETH;",        0x00d0, },
       +        { "&Ntilde;",        0x00d1, },
       +        { "&Ograve;",        0x00d2, },
       +        { "&Oacute;",        0x00d3, },
       +        { "&Ocirc;",        0x00d4, },
       +        { "&Otilde;",        0x00d5, },
       +        { "&Ouml;",        0x00d6, },
       +        { "&215;",        0x00d7, },
       +        { "&Oslash;",        0x00d8, },
       +        { "&Ugrave;",        0x00d9, },
       +        { "&Uacute;",        0x00da, },
       +        { "&Ucirc;",        0x00db, },
       +        { "&Uuml;",        0x00dc, },
       +        { "&Yacute;",        0x00dd, },
       +        { "&THORN;",        0x00de, },
       +        { "&szlig;",        0x00df, },
       +        { "&agrave;",        0x00e0, },
       +        { "&aacute;",        0x00e1, },
       +        { "&acirc;",        0x00e2, },
       +        { "&atilde;",        0x00e3, },
       +        { "&auml;",        0x00e4, },
       +        { "&aring;",        0x00e5, },
       +        { "&aelig;",        0x00e6, },
       +        { "&ccedil;",        0x00e7, },
       +        { "&egrave;",        0x00e8, },
       +        { "&eacute;",        0x00e9, },
       +        { "&ecirc;",        0x00ea, },
       +        { "&euml;",        0x00eb, },
       +        { "&igrave;",        0x00ec, },
       +        { "&iacute;",        0x00ed, },
       +        { "&icirc;",        0x00ee, },
       +        { "&iuml;",        0x00ef, },
       +        { "&eth;",        0x00f0, },
       +        { "&ntilde;",        0x00f1, },
       +        { "&ograve;",        0x00f2, },
       +        { "&oacute;",        0x00f3, },
       +        { "&ocirc;",        0x00f4, },
       +        { "&otilde;",        0x00f5, },
       +        { "&ouml;",        0x00f6, },
       +        { "&247;",        0x00f7, },
       +        { "&oslash;",        0x00f8, },
       +        { "&ugrave;",        0x00f9, },
       +        { "&uacute;",        0x00fa, },
       +        { "&ucirc;",        0x00fb, },
       +        { "&uuml;",        0x00fc, },
       +        { "&yacute;",        0x00fd, },
       +        { "&thorn;",        0x00fe, },
       +        { "&yuml;",        0x00ff, },
       +
       +        { "&quot;",        0x0022, },
       +        { "&amp;",        0x0026, },
       +        { "&lt;",        0x003c, },
       +        { "&gt;",        0x003e, },
       +
       +        { "CAP-DELTA",        0x0394, },
       +        { "ALPHA",        0x03b1, },
       +        { "BETA",        0x03b2, },
       +        { "DELTA",        0x03b4, },
       +        { "EPSILON",        0x03b5, },
       +        { "THETA",        0x03b8, },
       +        { "MU",                0x03bc, },
       +        { "PI",                0x03c0, },
       +        { "TAU",        0x03c4, },
       +        { "CHI",        0x03c7, },
       +
       +        { "<-",                0x2190, },
       +        { "^",                0x2191, },
       +        { "->",                0x2192, },
       +        { "v",                0x2193, },
       +        { "!=",                0x2260, },
       +        { "<=",                0x2264, },
       +        { nil, 0 },
       +};
 (DIR) diff --git a/src/libhttpd/fail.c b/src/libhttpd/fail.c
       t@@ -0,0 +1,81 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +typedef struct Error        Error;
       +
       +struct Error
       +{
       +        char        *num;
       +        char        *concise;
       +        char        *verbose;
       +};
       +
       +Error errormsg[] =
       +{
       +        [HInternal]        {"500 Internal Error", "Internal Error",
       +                "This server could not process your request due to an internal error."},
       +        [HTempFail]        {"500 Internal Error", "Temporary Failure",
       +                "The object %s is currently inaccessible.<p>Please try again later."},
       +        [HUnimp]        {"501 Not implemented", "Command not implemented",
       +                "This server does not implement the %s command."},
       +        [HUnkVers]        {"501 Not Implemented", "Unknown http version",
       +                "This server does not know how to respond to http version %s."},
       +        [HBadCont]        {"501 Not Implemented", "Impossible format",
       +                "This server cannot produce %s in any of the formats your client accepts."},
       +        [HBadReq]        {"400 Bad Request", "Strange Request",
       +                "Your client sent a query that this server could not understand."},
       +        [HSyntax]        {"400 Bad Request", "Garbled Syntax",
       +                "Your client sent a query with incoherent syntax."},
       +        [HBadSearch]        {"400 Bad Request", "Inapplicable Search",
       +                "Your client sent a search that cannot be applied to %s."},
       +        [HNotFound]        {"404 Not Found", "Object not found",
       +                "The object %s does not exist on this server."},
       +        [HNoSearch]        {"403 Forbidden", "Search not supported",
       +                "The object %s does not support the search command."},
       +        [HNoData]        {"403 Forbidden", "No data supplied",
       +                "Search or forms data must be supplied to %s."},
       +        [HExpectFail]        {"403 Expectation Failed", "Expectation Failed",
       +                "This server does not support some of your request's expectations."},
       +        [HUnauth]        {"403 Forbidden", "Forbidden",
       +                "You are not allowed to see the object %s."},
       +        [HOK]                {"200 OK", "everything is fine"},
       +};
       +
       +/*
       + * write a failure message to the net and exit
       + */
       +int
       +hfail(HConnect *c, int reason, ...)
       +{
       +        Hio *hout;
       +        char makeup[HBufSize];
       +        va_list arg;
       +        int n;
       +
       +        hout = &c->hout;
       +        va_start(arg, reason);
       +        vseprint(makeup, makeup+HBufSize, errormsg[reason].verbose, arg);
       +        va_end(arg);
       +        n = snprint(c->xferbuf, HBufSize, "<head><title>%s</title></head>\n<body><h1>%s</h1>\n%s</body>\n",
       +                errormsg[reason].concise, errormsg[reason].concise, makeup);
       +
       +        hprint(hout, "%s %s\r\n", hversion, errormsg[reason].num);
       +        hprint(hout, "Date: %D\r\n", time(nil));
       +        hprint(hout, "Server: Plan9\r\n");
       +        hprint(hout, "Content-Type: text/html\r\n");
       +        hprint(hout, "Content-Length: %d\r\n", n);
       +        if(c->head.closeit)
       +                hprint(hout, "Connection: close\r\n");
       +        else if(!http11(c))
       +                hprint(hout, "Connection: Keep-Alive\r\n");
       +        hprint(hout, "\r\n");
       +
       +        if(c->req.meth == nil || strcmp(c->req.meth, "HEAD") != 0)
       +                hwrite(hout, c->xferbuf, n);
       +
       +        if(c->replog)
       +                c->replog(c, "Reply: %s\nReason: %s\n", errormsg[reason].num, errormsg[reason].concise);
       +        return hflush(hout);
       +}
 (DIR) diff --git a/src/libhttpd/gethead.c b/src/libhttpd/gethead.c
       t@@ -0,0 +1,40 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +/*
       + * read in some header lines, either one or all of them.
       + * copy results into header log buffer.
       + */
       +int
       +hgethead(HConnect *c, int many)
       +{
       +        Hio *hin;
       +        char *s, *p, *pp;
       +        int n;
       +
       +        hin = &c->hin;
       +        for(;;){
       +                s = (char*)hin->pos;
       +                pp = s;
       +                while(p = memchr(pp, '\n', (char*)hin->stop - pp)){
       +                        if(!many || p == pp || p == pp + 1 && *pp == '\r'){
       +                                pp = p + 1;
       +                                break;
       +                        }
       +                        pp = p + 1;
       +                }
       +                hin->pos = (uchar*)pp;
       +                n = pp - s;
       +                if(c->hstop + n > &c->header[HBufSize])
       +                        return 0;
       +                memmove(c->hstop, s, n);
       +                c->hstop += n;
       +                *c->hstop = '\0';
       +                if(p != nil)
       +                        return 1;
       +                if(hreadbuf(hin, hin->pos) == nil || hin->state == Hend)
       +                        return 0;
       +        }
       +}
 (DIR) diff --git a/src/libhttpd/hio.c b/src/libhttpd/hio.c
       t@@ -0,0 +1,473 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <httpd.h>
       +
       +static        char        hstates[] = "nrewE";
       +static        char        hxfers[] = " x";
       +
       +int
       +hinit(Hio *h, int fd, int mode)
       +{
       +        if(fd == -1 || mode != Hread && mode != Hwrite)
       +                return -1;
       +        h->hh = nil;
       +        h->fd = fd;
       +        h->seek = 0;
       +        h->state = mode;
       +        h->start = h->buf + 16;                /* leave space for chunk length */
       +        h->stop = h->pos = h->start;
       +        if(mode == Hread){
       +                h->bodylen = ~0UL;
       +                *h->pos = '\0';
       +        }else
       +                h->stop = h->start + Hsize;
       +        return 0;
       +}
       +
       +int
       +hiserror(Hio *h)
       +{
       +        return h->state == Herr;
       +}
       +
       +int
       +hgetc(Hio *h)
       +{
       +        uchar *p;
       +
       +        p = h->pos;
       +        if(p < h->stop){
       +                h->pos = p + 1;
       +                return *p;
       +        }
       +        p -= UTFmax;
       +        if(p < h->start)
       +                p = h->start;
       +        if(!hreadbuf(h, p) || h->pos == h->stop)
       +                return -1;
       +        return *h->pos++;
       +}
       +
       +int
       +hungetc(Hio *h)
       +{
       +        if(h->state == Hend)
       +                h->state = Hread;
       +        else if(h->state == Hread)
       +                h->pos--;
       +        if(h->pos < h->start || h->state != Hread){
       +                h->state = Herr;
       +                h->pos = h->stop;
       +                return -1;
       +        }
       +        return 0;
       +}
       +
       +/*
       + * fill the buffer, saving contents from vsave onwards.
       + * nothing is saved if vsave is nil.
       + * returns the beginning of the buffer.
       + *
       + * understands message body sizes and chunked transfer encoding
       + */
       +void *
       +hreadbuf(Hio *h, void *vsave)
       +{
       +        Hio *hh;
       +        uchar *save;
       +        int c, in, cpy, dpos;
       +
       +        save = vsave;
       +        if(save && (save < h->start || save > h->stop)
       +        || h->state != Hread && h->state != Hend){
       +                h->state = Herr;
       +                h->pos = h->stop;
       +                return nil;
       +        }
       +
       +        dpos = 0;
       +        if(save && h->pos > save)
       +                dpos = h->pos - save;
       +        cpy = 0;
       +        if(save){
       +                cpy = h->stop - save;
       +                memmove(h->start, save, cpy);
       +        }
       +        h->seek += h->stop - h->start - cpy;
       +        h->pos = h->start + dpos;
       +
       +        in = Hsize - cpy;
       +        if(h->state == Hend)
       +                in = 0;
       +        else if(in > h->bodylen)
       +                in = h->bodylen;
       +
       +        /*
       +         * for chunked encoding, fill buffer,
       +         * then read in new chunk length and wipe out that line
       +         */
       +        hh = h->hh;
       +        if(hh != nil){
       +                if(!in && h->xferenc && h->state != Hend){
       +                        if(h->xferenc == 2){
       +                                c = hgetc(hh);
       +                                if(c == '\r')
       +                                        c = hgetc(hh);
       +                                if(c != '\n'){
       +                                        h->pos = h->stop;
       +                                        h->state = Herr;
       +                                        return nil;
       +                                }
       +                        }
       +                        h->xferenc = 2;
       +                        in = 0;
       +                        while((c = hgetc(hh)) != '\n'){
       +                                if(c >= '0' && c <= '9')
       +                                        c -= '0';
       +                                else if(c >= 'a' && c <= 'f')
       +                                        c -= 'a' - 10;
       +                                else if(c >= 'A' && c <= 'F')
       +                                        c -= 'A' - 10;
       +                                else
       +                                        break;
       +                                in = in * 16 + c;
       +                        }
       +                        while(c != '\n'){
       +                                if(c < 0){
       +                                        h->pos = h->stop;
       +                                        h->state = Herr;
       +                                        return nil;
       +                                }
       +                                c = hgetc(hh);
       +                        }
       +                        h->bodylen = in;
       +
       +                        in = Hsize - cpy;
       +                        if(in > h->bodylen)
       +                                in = h->bodylen;
       +                }
       +                if(in){
       +                        while(hh->pos + in > hh->stop){
       +                                if(hreadbuf(hh, hh->pos) == nil){
       +                                        h->pos = h->stop;
       +                                        h->state = Herr;
       +                                        return nil;
       +                                }
       +                        }
       +                        memmove(h->start + cpy, hh->pos, in);
       +                        hh->pos += in;
       +                }
       +        }else if(in && (in = read(h->fd, h->start + cpy, in)) < 0){
       +                h->state = Herr;
       +                h->pos = h->stop;
       +                return nil;
       +        }
       +        if(in == 0)
       +                h->state = Hend;
       +
       +        h->bodylen -= in;
       +
       +        h->stop = h->start + cpy + in;
       +        *h->stop = '\0';
       +        if(h->pos == h->stop)
       +                return nil;
       +        return h->start;
       +}
       +
       +int
       +hbuflen(Hio *h, void *p)
       +{
       +        return h->stop - (uchar*)p;
       +}
       +
       +/*
       + * prepare to receive a message body
       + * len is the content length (~0 => unspecified)
       + * te is the transfer encoding
       + * returns < 0 if setup failed
       + */
       +Hio*
       +hbodypush(Hio *hh, ulong len, HFields *te)
       +{
       +        Hio *h;
       +        int xe;
       +
       +        if(hh->state != Hread)
       +                return nil;
       +        xe = 0;
       +        if(te != nil){
       +                if(te->params != nil || te->next != nil)
       +                        return nil;
       +                if(cistrcmp(te->s, "chunked") == 0){
       +                        xe = 1;
       +                        len = 0;
       +                }else if(cistrcmp(te->s, "identity") == 0){
       +                        ;
       +                }else
       +                        return nil;
       +        }
       +
       +        h = malloc(sizeof *h);
       +        if(h == nil)
       +                return nil;
       +
       +        h->hh = hh;
       +        h->fd = -1;
       +        h->seek = 0;
       +        h->state = Hread;
       +        h->xferenc = xe;
       +        h->start = h->buf + 16;                /* leave space for chunk length */
       +        h->stop = h->pos = h->start;
       +        *h->pos = '\0';
       +        h->bodylen = len;
       +        return h;
       +}
       +
       +/*
       + * dump the state of the io buffer into a string
       + */
       +char *
       +hunload(Hio *h)
       +{
       +        uchar *p, *t, *stop, *buf;
       +        int ne, n, c;
       +
       +        stop = h->stop;
       +        ne = 0;
       +        for(p = h->pos; p < stop; p++){
       +                c = *p;
       +                if(c == 0x80)
       +                        ne++;
       +        }
       +        p = h->pos;
       +
       +        n = (stop - p) + ne + 3;
       +        buf = mallocz(n, 1);
       +        if(buf == nil)
       +                return nil;
       +        buf[0] = hstates[h->state];
       +        buf[1] = hxfers[h->xferenc];
       +
       +        t = &buf[2];
       +        for(; p < stop; p++){
       +                c = *p;
       +                if(c == 0 || c == 0x80){
       +                        *t++ = 0x80;
       +                        if(c == 0x80)
       +                                *t++ = 0x80;
       +                }else
       +                        *t++ = c;
       +        }
       +        *t++ = '\0';
       +        if(t != buf + n)
       +                return nil;
       +        return (char*)buf;
       +}
       +
       +/*
       + * read the io buffer state from a string
       + */
       +int
       +hload(Hio *h, char *buf)
       +{
       +        uchar *p, *t, *stop;
       +        char *s;
       +        int c;
       +
       +        s = strchr(hstates, buf[0]);
       +        if(s == nil)
       +                return 0;
       +        h->state = s - hstates;
       +
       +        s = strchr(hxfers, buf[1]);
       +        if(s == nil)
       +                return 0;
       +        h->xferenc = s - hxfers;
       +
       +        t = h->start;
       +        stop = t + Hsize;
       +        for(p = (uchar*)&buf[2]; c = *p; p++){
       +                if(c == 0x80){
       +                        if(p[1] != 0x80)
       +                                c = 0;
       +                        else
       +                                p++;
       +                }
       +                *t++ = c;
       +                if(t >= stop)
       +                        return 0;
       +        }
       +        *t = '\0';
       +        h->pos = h->start;
       +        h->stop = t;
       +        h->seek = 0;
       +        return 1;
       +}
       +
       +void
       +hclose(Hio *h)
       +{
       +        if(h->fd >= 0){
       +                if(h->state == Hwrite)
       +                        hxferenc(h, 0);
       +                close(h->fd);
       +        }
       +        h->stop = h->pos = nil;
       +        h->fd = -1;
       +}
       +
       +/*
       + * flush the buffer and possibly change encoding modes
       + */
       +int
       +hxferenc(Hio *h, int on)
       +{
       +        if(h->xferenc && !on && h->pos != h->start)
       +                hflush(h);
       +        if(hflush(h) < 0)
       +                return -1;
       +        h->xferenc = !!on;
       +        return 0;
       +}
       +
       +int
       +hputc(Hio *h, int c)
       +{
       +        uchar *p;
       +
       +        p = h->pos;
       +        if(p < h->stop){
       +                h->pos = p + 1;
       +                return *p = c;
       +        }
       +        if(hflush(h) < 0)
       +                return -1;
       +        return *h->pos++ = c;
       +}
       +
       +static int
       +fmthflush(Fmt *f)
       +{
       +        Hio *h;
       +
       +        h = f->farg;
       +        h->pos = f->to;
       +        if(hflush(h) < 0)
       +                return 0;
       +        f->stop = h->stop;
       +        f->to = h->pos;
       +        f->start = h->pos;
       +        return 1;
       +}
       +
       +int
       +hvprint(Hio *h, char *fmt, va_list args)
       +{
       +        int n;
       +        Fmt f;
       +
       +        f.runes = 0;
       +        f.stop = h->stop;
       +        f.to = h->pos;
       +        f.start = h->pos;
       +        f.flush = fmthflush;
       +        f.farg = h;
       +        f.nfmt = 0;
       +        f.args = args;
       +        n = dofmt(&f, fmt);
       +        h->pos = f.to;
       +        return n;
       +}
       +
       +int
       +hprint(Hio *h, char *fmt, ...)
       +{
       +        int n;
       +        va_list arg;
       +
       +        va_start(arg, fmt);
       +        n = hvprint(h, fmt, arg);
       +        va_end(arg);
       +        return n;
       +}
       +
       +int
       +hflush(Hio *h)
       +{
       +        uchar *s;
       +        int w;
       +
       +        if(h->state != Hwrite){
       +                h->state = Herr;
       +                h->stop = h->pos;
       +                return -1;
       +        }
       +        s = h->start;
       +        w = h->pos - s;
       +        if(h->xferenc){
       +                *--s = '\n';
       +                *--s = '\r';
       +                do{
       +                        *--s = "0123456789abcdef"[w & 0xf];
       +                        w >>= 4;
       +                }while(w);
       +                h->pos[0] = '\r';
       +                h->pos[1] = '\n';
       +                w = &h->pos[2] - s;
       +        }
       +        if(write(h->fd, s, w) != w){
       +                h->state = Herr;
       +                h->stop = h->pos;
       +                return -1;
       +        }
       +        h->seek += w;
       +        h->pos = h->start;
       +        return 0;
       +}
       +
       +int
       +hwrite(Hio *h, void *vbuf, int len)
       +{
       +        uchar *pos, *buf;
       +        int n, m;
       +
       +        buf = vbuf;
       +        n = len;
       +        if(n < 0 || h->state != Hwrite){
       +                h->state = Herr;
       +                h->stop = h->pos;
       +                return -1;
       +        }
       +        pos = h->pos;
       +        if(pos + n >= h->stop){
       +                m = pos - h->start;
       +                if(m){
       +                        m = Hsize - m;
       +                        if(m){
       +                                memmove(pos, buf, m);
       +                                buf += m;
       +                                n -= m;
       +                        }
       +                        if(write(h->fd, h->start, Hsize) != Hsize){
       +                                h->state = Herr;
       +                                h->stop = h->pos;
       +                                return -1;
       +                        }
       +                        h->seek += Hsize;
       +                }
       +                m = n % Hsize;
       +                n -= m;
       +                if(n != 0 && write(h->fd, buf, n) != n){
       +                        h->state = Herr;
       +                        h->stop = h->pos;
       +                        return -1;
       +                }
       +                h->seek += n;
       +                buf += n;
       +                pos = h->pos = h->start;
       +                n = m;
       +        }
       +        memmove(pos, buf, n);
       +        h->pos = pos + n;
       +        return len;
       +}
 (DIR) diff --git a/src/libhttpd/httpfmt.c b/src/libhttpd/httpfmt.c
       t@@ -0,0 +1,30 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +int
       +httpfmt(Fmt *f)
       +{
       +        char buf[HMaxWord*2];
       +        Rune r;
       +        char *t, *s;
       +        Htmlesc *l;
       +
       +        s = va_arg(f->args, char*);
       +        for(t = buf; t < buf + sizeof(buf) - 8; ){
       +                s += chartorune(&r, s);
       +                if(r == 0)
       +                        break;
       +                for(l = htmlesc; l->name != nil; l++)
       +                        if(l->value == r)
       +                                break;
       +                if(l->name != nil){
       +                        strcpy(t, l->name);
       +                        t += strlen(t);
       +                }else
       +                        *t++ = r;
       +        }
       +        *t = 0;
       +        return fmtstrcpy(f, buf);
       +}
 (DIR) diff --git a/src/libhttpd/httpunesc.c b/src/libhttpd/httpunesc.c
       t@@ -0,0 +1,49 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +/*
       + *  go from http with latin1 escapes to utf,
       + *  we assume that anything >= Runeself is already in utf
       + */
       +char *
       +httpunesc(HConnect *cc, char *s)
       +{
       +        char *t, *v;
       +        int c;
       +        Htmlesc *e;
       +
       +        v = halloc(cc, UTFmax*strlen(s) + 1);
       +        for(t = v; c = *s;){
       +                if(c == '&'){
       +                        if(s[1] == '#' && s[2] && s[3] && s[4] && s[5] == ';'){
       +                                c = atoi(s+2);
       +                                if(c < Runeself){
       +                                        *t++ = c;
       +                                        s += 6;
       +                                        continue;
       +                                }
       +                                if(c < 256 && c >= 161){
       +                                        e = &htmlesc[c-161];
       +                                        t += runetochar(t, &e->value);
       +                                        s += 6;
       +                                        continue;
       +                                }
       +                        } else {
       +                                for(e = htmlesc; e->name != nil; e++)
       +                                        if(strncmp(e->name, s, strlen(e->name)) == 0)
       +                                                break;
       +                                if(e->name != nil){
       +                                        t += runetochar(t, &e->value);
       +                                        s += strlen(e->name);
       +                                        continue;
       +                                }
       +                        }
       +                }
       +                *t++ = c;
       +                s++;
       +        }
       +        *t = 0;
       +        return v;
       +}
 (DIR) diff --git a/src/libhttpd/lower.c b/src/libhttpd/lower.c
       t@@ -0,0 +1,19 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +char*
       +hlower(char *p)
       +{
       +        char c;
       +        char *x;
       +
       +        if(p == nil)
       +                return p;
       +
       +        for(x = p; c = *x; x++)
       +                if(c >= 'A' && c <= 'Z')
       +                        *x -= 'A' - 'a';
       +        return p;
       +}
 (DIR) diff --git a/src/libhttpd/mkfile b/src/libhttpd/mkfile
       t@@ -0,0 +1,30 @@
       +PLAN9=../..
       +<$PLAN9/src/mkhdr
       +
       +LIB=libhttpd.a
       +
       +OFILES=\
       +        alloc.$O\
       +        checkcontent.$O\
       +        date.$O\
       +        fail.$O\
       +        gethead.$O\
       +        hio.$O\
       +        httpfmt.$O\
       +        httpunesc.$O\
       +        lower.$O\
       +        okheaders.$O\
       +        parse.$O\
       +        parsereq.$O\
       +        query.$O\
       +        redirected.$O\
       +        unallowed.$O\
       +        urlfmt.$O\
       +        urlunesc.$O\
       +
       +HFILES=\
       +        $PLAN9/include/httpd.h\
       +        escape.h\
       +
       +<$PLAN9/src/mksyslib
       +
 (DIR) diff --git a/src/libhttpd/okheaders.c b/src/libhttpd/okheaders.c
       t@@ -0,0 +1,22 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +/*
       + * write initial part of successful header
       + */
       +void
       +hokheaders(HConnect *c)
       +{
       +        Hio *hout;
       +
       +        hout = &c->hout;
       +        hprint(hout, "%s 200 OK\r\n", hversion);
       +        hprint(hout, "Server: Plan9\r\n");
       +        hprint(hout, "Date: %D\r\n", time(nil));
       +        if(c->head.closeit)
       +                hprint(hout, "Connection: close\r\n");
       +        else if(!http11(c))
       +                hprint(hout, "Connection: Keep-Alive\r\n");
       +}
 (DIR) diff --git a/src/libhttpd/parse.c b/src/libhttpd/parse.c
       t@@ -0,0 +1,1057 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +#include "escape.h"
       +
       +typedef struct Hlex        Hlex;
       +typedef struct MimeHead        MimeHead;
       +
       +enum
       +{
       +        /*
       +         * tokens
       +         */
       +        Word        = 1,
       +        QString,
       +};
       +
       +#define UlongMax        4294967295UL
       +
       +struct Hlex
       +{
       +        int        tok;
       +        int        eoh;
       +        int        eol;                        /* end of header line encountered? */
       +        uchar        *hstart;                /* start of header */
       +        jmp_buf        jmp;                        /* jmp here to parse header */
       +        char        wordval[HMaxWord];
       +        HConnect *c;
       +};
       +
       +struct MimeHead
       +{
       +        char        *name;
       +        void        (*parse)(Hlex*, char*);
       +        uchar        seen;
       +        uchar        ignore;
       +};
       +
       +static void        mimeaccept(Hlex*, char*);
       +static void        mimeacceptchar(Hlex*, char*);
       +static void        mimeacceptenc(Hlex*, char*);
       +static void        mimeacceptlang(Hlex*, char*);
       +static void        mimeagent(Hlex*, char*);
       +static void        mimeauthorization(Hlex*, char*);
       +static void        mimeconnection(Hlex*, char*);
       +static void        mimecontlen(Hlex*, char*);
       +static void        mimeexpect(Hlex*, char*);
       +static void        mimefresh(Hlex*, char*);
       +static void        mimefrom(Hlex*, char*);
       +static void        mimehost(Hlex*, char*);
       +static void        mimeifrange(Hlex*, char*);
       +static void        mimeignore(Hlex*, char*);
       +static void        mimematch(Hlex*, char*);
       +static void        mimemodified(Hlex*, char*);
       +static void        mimenomatch(Hlex*, char*);
       +static void        mimerange(Hlex*, char*);
       +static void        mimetransenc(Hlex*, char*);
       +static void        mimeunmodified(Hlex*, char*);
       +
       +/*
       + * headers seen also include
       + * allow  cache-control chargeto
       + * content-encoding content-language content-location content-md5 content-range content-type
       + * date etag expires forwarded last-modified max-forwards pragma
       + * proxy-agent proxy-authorization proxy-connection
       + * ua-color ua-cpu ua-os ua-pixels
       + * upgrade via x-afs-tokens x-serial-number
       + */
       +static MimeHead        mimehead[] =
       +{
       +        {"accept",                mimeaccept},
       +        {"accept-charset",        mimeacceptchar},
       +        {"accept-encoding",        mimeacceptenc},
       +        {"accept-language",        mimeacceptlang},
       +        {"authorization",        mimeauthorization},
       +        {"connection",                mimeconnection},
       +        {"content-length",        mimecontlen},
       +        {"expect",                mimeexpect},
       +        {"fresh",                mimefresh},
       +        {"from",                mimefrom},
       +        {"host",                mimehost},
       +        {"if-match",                mimematch},
       +        {"if-modified-since",        mimemodified},
       +        {"if-none-match",        mimenomatch},
       +        {"if-range",                mimeifrange},
       +        {"if-unmodified-since",        mimeunmodified},
       +        {"range",                mimerange},
       +        {"transfer-encoding",        mimetransenc},
       +        {"user-agent",                mimeagent},
       +};
       +
       +char*                hmydomain;
       +char*                hversion = "HTTP/1.1";
       +
       +static        void        lexhead(Hlex*);
       +static        void        parsejump(Hlex*, char*);
       +static        int        getc(Hlex*);
       +static        void        ungetc(Hlex*);
       +static        int        wordcr(Hlex*);
       +static        int        wordnl(Hlex*);
       +static        void        word(Hlex*, char*);
       +static        int        lex1(Hlex*, int);
       +static        int        lex(Hlex*);
       +static        int        lexbase64(Hlex*);
       +static        ulong        digtoul(char *s, char **e);
       +
       +/*
       + * flush an clean up junk from a request
       + */
       +void
       +hreqcleanup(HConnect *c)
       +{
       +        int i;
       +
       +        hxferenc(&c->hout, 0);
       +        memset(&c->req, 0, sizeof(c->req));
       +        memset(&c->head, 0, sizeof(c->head));
       +        c->hpos = c->header;
       +        c->hstop = c->header;
       +        binfree(&c->bin);
       +        for(i = 0; i < nelem(mimehead); i++){
       +                mimehead[i].seen = 0;
       +                mimehead[i].ignore = 0;
       +        }
       +}
       +
       +/*
       + * list of tokens
       + * if the client is HTTP/1.0,
       + * ignore headers which match one of the tokens.
       + * restarts parsing if necessary.
       + */
       +static void
       +mimeconnection(Hlex *h, char *unused)
       +{
       +        char *u, *p;
       +        int reparse, i;
       +
       +        reparse = 0;
       +        for(;;){
       +                while(lex(h) != Word)
       +                        if(h->tok != ',')
       +                                goto breakout;
       +
       +                if(cistrcmp(h->wordval, "keep-alive") == 0)
       +                        h->c->head.persist = 1;
       +                else if(cistrcmp(h->wordval, "close") == 0)
       +                        h->c->head.closeit = 1;
       +                else if(!http11(h->c)){
       +                        for(i = 0; i < nelem(mimehead); i++){
       +                                if(cistrcmp(mimehead[i].name, h->wordval) == 0){
       +                                        reparse = mimehead[i].seen && !mimehead[i].ignore;
       +                                        mimehead[i].ignore = 1;
       +                                        if(cistrcmp(mimehead[i].name, "authorization") == 0){
       +                                                h->c->head.authuser = nil;
       +                                                h->c->head.authpass = nil;
       +                                        }
       +                                }
       +                        }
       +                }
       +
       +                if(lex(h) != ',')
       +                        break;
       +        }
       +
       +breakout:;
       +        /*
       +         * if need to ignore headers we've already parsed,
       +         * reset & start over.  need to save authorization
       +         * info because it's written over when parsed.
       +         */
       +        if(reparse){
       +                u = h->c->head.authuser;
       +                p = h->c->head.authpass;
       +                memset(&h->c->head, 0, sizeof(h->c->head));
       +                h->c->head.authuser = u;
       +                h->c->head.authpass = p;
       +
       +                h->c->hpos = h->hstart;
       +                longjmp(h->jmp, 1);
       +        }
       +}
       +
       +int
       +hparseheaders(HConnect *c, int timeout)
       +{
       +        Hlex h;
       +
       +        c->head.fresh_thresh = 0;
       +        c->head.fresh_have = 0;
       +        c->head.persist = 0;
       +        if(c->req.vermaj == 0){
       +                c->head.host = hmydomain;
       +                return 1;
       +        }
       +
       +        memset(&h, 0, sizeof(h));
       +        h.c = c;
       +        alarm(timeout);
       +        if(!hgethead(c, 1))
       +                return -1;
       +        alarm(0);
       +        h.hstart = c->hpos;
       +
       +        if(setjmp(h.jmp) == -1)
       +                return -1;
       +
       +        h.eol = 0;
       +        h.eoh = 0;
       +        h.tok = '\n';
       +        while(lex(&h) != '\n'){
       +                if(h.tok == Word && lex(&h) == ':')
       +                        parsejump(&h, hstrdup(c, h.wordval));
       +                while(h.tok != '\n')
       +                        lex(&h);
       +                h.eol = h.eoh;
       +        }
       +
       +        if(http11(c)){
       +                /*
       +                 * according to the http/1.1 spec,
       +                 * these rules must be followed
       +                 */
       +                if(c->head.host == nil){
       +                        hfail(c, HBadReq, nil);
       +                        return -1;
       +                }
       +                if(c->req.urihost != nil)
       +                        c->head.host = c->req.urihost;
       +                /*
       +                 * also need to check host is actually this one
       +                 */
       +        }else if(c->head.host == nil)
       +                c->head.host = hmydomain;
       +        return 1;
       +}
       +
       +/*
       + * mimeparams        : | mimeparams ";" mimepara
       + * mimeparam        : token "=" token | token "=" qstring
       + */
       +static HSPairs*
       +mimeparams(Hlex *h)
       +{
       +        HSPairs *p;
       +        char *s;
       +
       +        p = nil;
       +        for(;;){
       +                if(lex(h) != Word)
       +                        break;
       +                s = hstrdup(h->c, h->wordval);
       +                if(lex(h) != Word && h->tok != QString)
       +                        break;
       +                p = hmkspairs(h->c, s, hstrdup(h->c, h->wordval), p);
       +        }
       +        return hrevspairs(p);
       +}
       +
       +/*
       + * mimehfields        : mimehfield | mimehfields commas mimehfield
       + * mimehfield        : token mimeparams
       + * commas        : "," | commas ","
       + */
       +static HFields*
       +mimehfields(Hlex *h)
       +{
       +        HFields *f;
       +
       +        f = nil;
       +        for(;;){
       +                while(lex(h) != Word)
       +                        if(h->tok != ',')
       +                                goto breakout;
       +
       +                f = hmkhfields(h->c, hstrdup(h->c, h->wordval), nil, f);
       +
       +                if(lex(h) == ';')
       +                        f->params = mimeparams(h);
       +                if(h->tok != ',')
       +                        break;
       +        }
       +breakout:;
       +        return hrevhfields(f);
       +}
       +
       +/*
       + * parse a list of acceptable types, encodings, languages, etc.
       + */
       +static HContent*
       +mimeok(Hlex *h, char *name, int multipart, HContent *head)
       +{
       +        char *generic, *specific, *s;
       +        float v;
       +
       +        /*
       +         * each type is separated by one or more commas
       +         */
       +        while(lex(h) != Word)
       +                if(h->tok != ',')
       +                        return head;
       +
       +        generic = hstrdup(h->c, h->wordval);
       +        lex(h);
       +        if(h->tok == '/' || multipart){
       +                /*
       +                 * at one time, IE5 improperly said '*' for single types
       +                 */
       +                if(h->tok != '/')
       +                        return nil;
       +                if(lex(h) != Word)
       +                        return head;
       +                specific = hstrdup(h->c, h->wordval);
       +                if(!multipart && strcmp(specific, "*") != 0)
       +                        return head;
       +                lex(h);
       +        }else
       +                specific = nil;
       +        head = hmkcontent(h->c, generic, specific, head);
       +
       +        for(;;){
       +                switch(h->tok){
       +                case ';':
       +                        /*
       +                         * should make a list of these params
       +                         * for accept, they fall into two classes:
       +                         *        up to a q=..., they modify the media type.
       +                         *        afterwards, they acceptance criteria
       +                         */
       +                        if(lex(h) == Word){
       +                                s = hstrdup(h->c, h->wordval);
       +                                if(lex(h) != '=' || lex(h) != Word && h->tok != QString)
       +                                        return head;
       +                                v = strtod(h->wordval, nil);
       +                                if(strcmp(s, "q") == 0)
       +                                        head->q = v;
       +                                else if(strcmp(s, "mxb") == 0)
       +                                        head->mxb = v;
       +                        }
       +                        break;
       +                case ',':
       +                        return  mimeok(h, name, multipart, head);
       +                default:
       +                        return head;
       +                }
       +                lex(h);
       +        }
       +        return head;
       +}
       +
       +/*
       + * parse a list of entity tags
       + * 1#entity-tag
       + * entity-tag = [weak] opaque-tag
       + * weak = "W/"
       + * opaque-tag = quoted-string
       + */
       +static HETag*
       +mimeetag(Hlex *h, HETag *head)
       +{
       +        HETag *e;
       +        int weak;
       +
       +        for(;;){
       +                while(lex(h) != Word && h->tok != QString)
       +                        if(h->tok != ',')
       +                                return head;
       +
       +                weak = 0;
       +                if(h->tok == Word && strcmp(h->wordval, "*") != 0){
       +                        if(strcmp(h->wordval, "W") != 0)
       +                                return head;
       +                        if(lex(h) != '/' || lex(h) != QString)
       +                                return head;
       +                        weak = 1;
       +                }
       +
       +                e = halloc(h->c, sizeof(HETag));
       +                e->etag = hstrdup(h->c, h->wordval);
       +                e->weak = weak;
       +                e->next = head;
       +                head = e;
       +
       +                if(lex(h) != ',')
       +                        return head;
       +        }
       +        return head;
       +}
       +
       +/*
       + * ranges-specifier = byte-ranges-specifier
       + * byte-ranges-specifier = "bytes" "=" byte-range-set
       + * byte-range-set = 1#(byte-range-spec|suffix-byte-range-spec)
       + * byte-range-spec = byte-pos "-" [byte-pos]
       + * byte-pos = 1*DIGIT
       + * suffix-byte-range-spec = "-" suffix-length
       + * suffix-length = 1*DIGIT
       + *
       + * syntactically invalid range specifiers cause the
       + * entire header field to be ignored.
       + * it is syntactically incorrect for the second byte pos
       + * to be smaller than the first byte pos
       + */
       +static HRange*
       +mimeranges(Hlex *h, HRange *head)
       +{
       +        HRange *r, *rh, *tail;
       +        char *w;
       +        ulong start, stop;
       +        int suf;
       +
       +        if(lex(h) != Word || strcmp(h->wordval, "bytes") != 0 || lex(h) != '=')
       +                return head;
       +
       +        rh = nil;
       +        tail = nil;
       +        for(;;){
       +                while(lex(h) != Word){
       +                        if(h->tok != ','){
       +                                if(h->tok == '\n')
       +                                        goto breakout;
       +                                return head;
       +                        }
       +                }
       +
       +                w = h->wordval;
       +                start = 0;
       +                suf = 1;
       +                if(w[0] != '-'){
       +                        suf = 0;
       +                        start = digtoul(w, &w);
       +                        if(w[0] != '-')
       +                                return head;
       +                }
       +                w++;
       +                stop = ~0UL;
       +                if(w[0] != '\0'){
       +                        stop = digtoul(w, &w);
       +                        if(w[0] != '\0')
       +                                return head;
       +                        if(!suf && stop < start)
       +                                return head;
       +                }
       +
       +                r = halloc(h->c, sizeof(HRange));
       +                r->suffix = suf;
       +                r->start = start;
       +                r->stop = stop;
       +                r->next = nil;
       +                if(rh == nil)
       +                        rh = r;
       +                else
       +                        tail->next = r;
       +                tail = r;
       +
       +                if(lex(h) != ','){
       +                        if(h->tok == '\n')
       +                                break;
       +                        return head;
       +                }
       +        }
       +breakout:;
       +
       +        if(head == nil)
       +                return rh;
       +
       +        for(tail = head; tail->next != nil; tail = tail->next)
       +                ;
       +        tail->next = rh;
       +        return head;
       +}
       +
       +static void
       +mimeaccept(Hlex *h, char *name)
       +{
       +        h->c->head.oktype = mimeok(h, name, 1, h->c->head.oktype);
       +}
       +
       +static void
       +mimeacceptchar(Hlex *h, char *name)
       +{
       +        h->c->head.okchar = mimeok(h, name, 0, h->c->head.okchar);
       +}
       +
       +static void
       +mimeacceptenc(Hlex *h, char *name)
       +{
       +        h->c->head.okencode = mimeok(h, name, 0, h->c->head.okencode);
       +}
       +
       +static void
       +mimeacceptlang(Hlex *h, char *name)
       +{
       +        h->c->head.oklang = mimeok(h, name, 0, h->c->head.oklang);
       +}
       +
       +static void
       +mimemodified(Hlex *h, char *unused)
       +{
       +        lexhead(h);
       +        h->c->head.ifmodsince = hdate2sec(h->wordval);
       +}
       +
       +static void
       +mimeunmodified(Hlex *h, char *unused)
       +{
       +        lexhead(h);
       +        h->c->head.ifunmodsince = hdate2sec(h->wordval);
       +}
       +
       +static void
       +mimematch(Hlex *h, char *unused)
       +{
       +        h->c->head.ifmatch = mimeetag(h, h->c->head.ifmatch);
       +}
       +
       +static void
       +mimenomatch(Hlex *h, char *unused)
       +{
       +        h->c->head.ifnomatch = mimeetag(h, h->c->head.ifnomatch);
       +}
       +
       +/*
       + * argument is either etag or date
       + */
       +static void
       +mimeifrange(Hlex *h, char *unused)
       +{
       +        int c, d, et;
       +
       +        et = 0;
       +        c = getc(h);
       +        while(c == ' ' || c == '\t')
       +                c = getc(h);
       +        if(c == '"')
       +                et = 1;
       +        else if(c == 'W'){
       +                d = getc(h);
       +                if(d == '/')
       +                        et = 1;
       +                ungetc(h);
       +        }
       +        ungetc(h);
       +        if(et){
       +                h->c->head.ifrangeetag = mimeetag(h, h->c->head.ifrangeetag);
       +        }else{
       +                lexhead(h);
       +                h->c->head.ifrangedate = hdate2sec(h->wordval);
       +        }
       +}
       +
       +static void
       +mimerange(Hlex *h, char *unused)
       +{
       +        h->c->head.range = mimeranges(h, h->c->head.range);
       +}
       +
       +/*
       + * note: netscape and ie through versions 4.7 and 4
       + * support only basic authorization, so that is all that is supported here
       + *
       + * "Authorization" ":" "Basic" base64-user-pass
       + * where base64-user-pass is the base64 encoding of
       + * username ":" password
       + */
       +static void
       +mimeauthorization(Hlex *h, char *unused)
       +{
       +        char *up, *p;
       +        int n;
       +
       +        if(lex(h) != Word || cistrcmp(h->wordval, "basic") != 0)
       +                return;
       +
       +        n = lexbase64(h);
       +        if(!n)
       +                return;
       +
       +        /*
       +         * wipe out source for password, so it won't be logged.
       +         * it is replaced by a single =,
       +         * which is valid base64, but not ok for an auth reponse.
       +         * therefore future parses of the header field will not overwrite
       +         * authuser and authpass.
       +         */
       +        memmove(h->c->hpos - (n - 1), h->c->hpos, h->c->hstop - h->c->hpos);
       +        h->c->hstop -= n - 1;
       +        *h->c->hstop = '\0';
       +        h->c->hpos -= n - 1;
       +        h->c->hpos[-1] = '=';
       +
       +        up = halloc(h->c, n + 1);
       +        n = dec64((uchar*)up, n, h->wordval, n);
       +        up[n] = '\0';
       +        p = strchr(up, ':');
       +        if(p != nil){
       +                *p++ = '\0';
       +                h->c->head.authuser = hstrdup(h->c, up);
       +                h->c->head.authpass = hstrdup(h->c, p);
       +        }
       +}
       +
       +static void
       +mimeagent(Hlex *h, char *unused)
       +{
       +        lexhead(h);
       +        h->c->head.client = hstrdup(h->c, h->wordval);
       +}
       +
       +static void
       +mimefrom(Hlex *h, char *unused)
       +{
       +        lexhead(h);
       +}
       +
       +static void
       +mimehost(Hlex *h, char *unused)
       +{
       +        char *hd;
       +
       +        lexhead(h);
       +        for(hd = h->wordval; *hd == ' ' || *hd == '\t'; hd++)
       +                ;
       +        h->c->head.host = hlower(hstrdup(h->c, hd));
       +}
       +
       +/*
       + * if present, implies that a message body follows the headers
       + * "content-length" ":" digits
       + */
       +static void
       +mimecontlen(Hlex *h, char *unused)
       +{
       +        char *e;
       +        ulong v;
       +
       +        if(lex(h) != Word)
       +                return;
       +        e = h->wordval;
       +        v = digtoul(e, &e);
       +        if(v == ~0UL || *e != '\0')
       +                return;
       +        h->c->head.contlen = v;
       +}
       +
       +/*
       + * mimexpect        : "expect" ":" expects
       + * expects        : | expects "," expect
       + * expect        : "100-continue" | token | token "=" token expectparams | token "=" qstring expectparams
       + * expectparams        : ";" token | ";" token "=" token | token "=" qstring
       + * for now, we merely parse "100-continue" or anything else.
       + */
       +static void
       +mimeexpect(Hlex *h, char *unused)
       +{
       +        if(lex(h) != Word || cistrcmp(h->wordval, "100-continue") != 0 || lex(h) != '\n')
       +                h->c->head.expectother = 1;
       +        h->c->head.expectcont = 1;
       +}
       +
       +static void
       +mimetransenc(Hlex *h, char *unused)
       +{
       +        h->c->head.transenc = mimehfields(h);
       +}
       +
       +static void
       +mimefresh(Hlex *h, char *unused)
       +{
       +        char *s;
       +
       +        lexhead(h);
       +        for(s = h->wordval; *s && (*s==' ' || *s=='\t'); s++)
       +                ;
       +        if(strncmp(s, "pathstat/", 9) == 0)
       +                h->c->head.fresh_thresh = atoi(s+9);
       +        else if(strncmp(s, "have/", 5) == 0)
       +                h->c->head.fresh_have = atoi(s+5);
       +}
       +
       +static void
       +mimeignore(Hlex *h, char *unused)
       +{
       +        lexhead(h);
       +}
       +
       +static void
       +parsejump(Hlex *h, char *k)
       +{
       +        int l, r, m;
       +
       +        l = 1;
       +        r = nelem(mimehead) - 1;
       +        while(l <= r){
       +                m = (r + l) >> 1;
       +                if(cistrcmp(mimehead[m].name, k) <= 0)
       +                        l = m + 1;
       +                else
       +                        r = m - 1;
       +        }
       +        m = l - 1;
       +        if(cistrcmp(mimehead[m].name, k) == 0 && !mimehead[m].ignore){
       +                mimehead[m].seen = 1;
       +                (*mimehead[m].parse)(h, k);
       +        }else
       +                mimeignore(h, k);
       +}
       +
       +static int
       +lex(Hlex *h)
       +{
       +        return h->tok = lex1(h, 0);
       +}
       +
       +static int
       +lexbase64(Hlex *h)
       +{
       +        int c, n;
       +
       +        n = 0;
       +        lex1(h, 1);
       +
       +        while((c = getc(h)) >= 0){
       +                if(!(c >= 'A' && c <= 'Z'
       +                || c >= 'a' && c <= 'z'
       +                || c >= '0' && c <= '9'
       +                || c == '+' || c == '/')){
       +                        ungetc(h);
       +                        break;
       +                }
       +
       +                if(n < HMaxWord-1)
       +                        h->wordval[n++] = c;
       +        }
       +        h->wordval[n] = '\0';
       +        return n;
       +}
       +
       +/*
       + * rfc 822/rfc 1521 lexical analyzer
       + */
       +static int
       +lex1(Hlex *h, int skipwhite)
       +{
       +        int level, c;
       +
       +        if(h->eol)
       +                return '\n';
       +
       +top:
       +        c = getc(h);
       +        switch(c){
       +        case '(':
       +                level = 1;
       +                while((c = getc(h)) >= 0){
       +                        if(c == '\\'){
       +                                c = getc(h);
       +                                if(c < 0)
       +                                        return '\n';
       +                                continue;
       +                        }
       +                        if(c == '(')
       +                                level++;
       +                        else if(c == ')' && --level == 0)
       +                                break;
       +                        else if(c == '\n'){
       +                                c = getc(h);
       +                                if(c < 0)
       +                                        return '\n';
       +                                if(c == ')' && --level == 0)
       +                                        break;
       +                                if(c != ' ' && c != '\t'){
       +                                        ungetc(h);
       +                                        return '\n';
       +                                }
       +                        }
       +                }
       +                goto top;
       +
       +        case ' ': case '\t':
       +                goto top;
       +
       +        case '\r':
       +                c = getc(h);
       +                if(c != '\n'){
       +                        ungetc(h);
       +                        goto top;
       +                }
       +
       +        case '\n':
       +                if(h->tok == '\n'){
       +                        h->eol = 1;
       +                        h->eoh = 1;
       +                        return '\n';
       +                }
       +                c = getc(h);
       +                if(c < 0){
       +                        h->eol = 1;
       +                        return '\n';
       +                }
       +                if(c != ' ' && c != '\t'){
       +                        ungetc(h);
       +                        h->eol = 1;
       +                        return '\n';
       +                }
       +                goto top;
       +
       +        case ')':
       +        case '<': case '>':
       +        case '[': case ']':
       +        case '@': case '/':
       +        case ',': case ';': case ':': case '?': case '=':
       +                if(skipwhite){
       +                        ungetc(h);
       +                        return c;
       +                }
       +                return c;
       +
       +        case '"':
       +                if(skipwhite){
       +                        ungetc(h);
       +                        return c;
       +                }
       +                word(h, "\"");
       +                getc(h);                /* skip the closing quote */
       +                return QString;
       +
       +        default:
       +                ungetc(h);
       +                if(skipwhite)
       +                        return c;
       +                word(h, "\"(){}<>@,;:/[]?=\r\n \t");
       +                if(h->wordval[0] == '\0'){
       +                        h->c->head.closeit = 1;
       +                        hfail(h->c, HSyntax);
       +                        longjmp(h->jmp, -1);
       +                }
       +                return Word;
       +        }
       +        goto top;
       +        return 0;
       +}
       +
       +/*
       + * return the rest of an rfc 822, including \n
       + * do not map to lower case
       + */
       +static void
       +lexhead(Hlex *h)
       +{
       +        int c, n;
       +
       +        n = 0;
       +        while((c = getc(h)) >= 0){
       +                if(c == '\r')
       +                        c = wordcr(h);
       +                else if(c == '\n')
       +                        c = wordnl(h);
       +                if(c == '\n')
       +                        break;
       +                if(c == '\\'){
       +                        c = getc(h);
       +                        if(c < 0)
       +                                break;
       +                }
       +
       +                if(n < HMaxWord-1)
       +                        h->wordval[n++] = c;
       +        }
       +        h->tok = '\n';
       +        h->eol = 1;
       +        h->wordval[n] = '\0';
       +}
       +
       +static void
       +word(Hlex *h, char *stop)
       +{
       +        int c, n;
       +
       +        n = 0;
       +        while((c = getc(h)) >= 0){
       +                if(c == '\r')
       +                        c = wordcr(h);
       +                else if(c == '\n')
       +                        c = wordnl(h);
       +                if(c == '\\'){
       +                        c = getc(h);
       +                        if(c < 0)
       +                                break;
       +                }else if(c < 32 || strchr(stop, c) != nil){
       +                        ungetc(h);
       +                        break;
       +                }
       +
       +                if(n < HMaxWord-1)
       +                        h->wordval[n++] = c;
       +        }
       +        h->wordval[n] = '\0';
       +}
       +
       +static int
       +wordcr(Hlex *h)
       +{
       +        int c;
       +
       +        c = getc(h);
       +        if(c == '\n')
       +                return wordnl(h);
       +        ungetc(h);
       +        return ' ';
       +}
       +
       +static int
       +wordnl(Hlex *h)
       +{
       +        int c;
       +
       +        c = getc(h);
       +        if(c == ' ' || c == '\t')
       +                return c;
       +        ungetc(h);
       +
       +        return '\n';
       +}
       +
       +static int
       +getc(Hlex *h)
       +{
       +        if(h->eoh)
       +                return -1;
       +        if(h->c->hpos < h->c->hstop)
       +                return *h->c->hpos++;
       +        h->eoh = 1;
       +        h->eol = 1;
       +        return -1;
       +}
       +
       +static void
       +ungetc(Hlex *h)
       +{
       +        if(h->eoh)
       +                return;
       +        h->c->hpos--;
       +}
       +
       +static ulong
       +digtoul(char *s, char **e)
       +{
       +        ulong v;
       +        int c, ovfl;
       +
       +        v = 0;
       +        ovfl = 0;
       +        for(;;){
       +                c = *s;
       +                if(c < '0' || c > '9')
       +                        break;
       +                s++;
       +                c -= '0';
       +                if(v > UlongMax/10 || v == UlongMax/10 && c >= UlongMax%10)
       +                        ovfl = 1;
       +                v = v * 10 + c;
       +        }
       +
       +        if(e)
       +                *e = s;
       +        if(ovfl)
       +                return UlongMax;
       +        return v;
       +}
       +
       +int
       +http11(HConnect *c)
       +{
       +        return c->req.vermaj > 1 || c->req.vermaj == 1 && c->req.vermin > 0;
       +}
       +
       +char*
       +hmkmimeboundary(HConnect *c)
       +{
       +        char buf[32];
       +        int i;
       +
       +        srand((time(0)<<16)|getpid());
       +        strcpy(buf, "upas-");
       +        for(i = 5; i < sizeof(buf)-1; i++)
       +                buf[i] = 'a' + nrand(26);
       +        buf[i] = 0;
       +        return hstrdup(c, buf);
       +}
       +
       +HSPairs*
       +hmkspairs(HConnect *c, char *s, char *t, HSPairs *next)
       +{
       +        HSPairs *sp;
       +
       +        sp = halloc(c, sizeof *sp);
       +        sp->s = s;
       +        sp->t = t;
       +        sp->next = next;
       +        return sp;
       +}
       +
       +HSPairs*
       +hrevspairs(HSPairs *sp)
       +{
       +        HSPairs *last, *next;
       +
       +        last = nil;
       +        for(; sp != nil; sp = next){
       +                next = sp->next;
       +                sp->next = last;
       +                last = sp;
       +        }
       +        return last;
       +}
       +
       +HFields*
       +hmkhfields(HConnect *c, char *s, HSPairs *p, HFields *next)
       +{
       +        HFields *hf;
       +
       +        hf = halloc(c, sizeof *hf);
       +        hf->s = s;
       +        hf->params = p;
       +        hf->next = next;
       +        return hf;
       +}
       +
       +HFields*
       +hrevhfields(HFields *hf)
       +{
       +        HFields *last, *next;
       +
       +        last = nil;
       +        for(; hf != nil; hf = next){
       +                next = hf->next;
       +                hf->next = last;
       +                last = hf;
       +        }
       +        return last;
       +}
       +
       +HContent*
       +hmkcontent(HConnect *c, char *generic, char *specific, HContent *next)
       +{
       +        HContent *ct;
       +
       +        ct = halloc(c, sizeof(HContent));
       +        ct->generic = generic;
       +        ct->specific = specific;
       +        ct->next = next;
       +        ct->q = 1;
       +        ct->mxb = 0;
       +        return ct;
       +}
 (DIR) diff --git a/src/libhttpd/parsereq.c b/src/libhttpd/parsereq.c
       t@@ -0,0 +1,296 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +typedef struct Strings                Strings;
       +
       +struct Strings
       +{
       +        char        *s1;
       +        char        *s2;
       +};
       +
       +static        char*                abspath(HConnect *cc, char *origpath, char *curdir);
       +static        int                getc(HConnect*);
       +static        char*                getword(HConnect*);
       +static        Strings                parseuri(HConnect *c, char*);
       +static        Strings                stripmagic(char*);
       +static        Strings                stripsearch(char*);
       +
       +/*
       + * parse the next request line
       + * returns:
       + *        1 ok
       + *        0 eof
       + *        -1 error
       + */
       +int
       +hparsereq(HConnect *c, int timeout)
       +{
       +        Strings ss;
       +        char *vs, *v, *search, *uri, *origuri, *extra;
       +
       +        if(c->bin != nil){
       +                hfail(c, HInternal);
       +                return -1;
       +        }
       +
       +        /*
       +         * serve requests until a magic request.
       +         * later requests have to come quickly.
       +         * only works for http/1.1 or later.
       +         */
       +        alarm(timeout);
       +        if(!hgethead(c, 0))
       +                return 0;
       +        alarm(0);
       +        c->reqtime = time(nil);
       +        c->req.meth = getword(c);
       +        if(c->req.meth == nil){
       +                hfail(c, HSyntax);
       +                return -1;
       +        }
       +        uri = getword(c);
       +        if(uri == nil || strlen(uri) == 0){
       +                hfail(c, HSyntax);
       +                return -1;
       +        }
       +        v = getword(c);
       +        if(v == nil){
       +                if(strcmp(c->req.meth, "GET") != 0){
       +                        hfail(c, HUnimp, c->req.meth);
       +                        return -1;
       +                }
       +                c->req.vermaj = 0;
       +                c->req.vermin = 9;
       +        }else{
       +                vs = v;
       +                if(strncmp(vs, "HTTP/", 5) != 0){
       +                        hfail(c, HUnkVers, vs);
       +                        return -1;
       +                }
       +                vs += 5;
       +                c->req.vermaj = strtoul(vs, &vs, 10);
       +                if(*vs != '.' || c->req.vermaj != 1){
       +                        hfail(c, HUnkVers, vs);
       +                        return -1;
       +                }
       +                vs++;
       +                c->req.vermin = strtoul(vs, &vs, 10);
       +                if(*vs != '\0'){
       +                        hfail(c, HUnkVers, vs);
       +                        return -1;
       +                }
       +
       +                extra = getword(c);
       +                if(extra != nil){
       +                        hfail(c, HSyntax);
       +                        return -1;
       +                }
       +        }
       +
       +        /*
       +         * the fragment is not supposed to be sent
       +         * strip it 'cause some clients send it
       +         */
       +        origuri = uri;
       +        uri = strchr(origuri, '#');
       +        if(uri != nil)
       +                *uri = 0;
       +
       +        /*
       +         * http/1.1 requires the server to accept absolute
       +         * or relative uri's.  convert to relative with an absolute path
       +         */
       +        if(http11(c)){
       +                ss = parseuri(c, origuri);
       +                uri = ss.s1;
       +                c->req.urihost = ss.s2;
       +                if(uri == nil){
       +                        hfail(c, HBadReq, uri);
       +                        return -1;
       +                }
       +                origuri = uri;
       +        }
       +
       +        /*
       +         * munge uri for search, protection, and magic
       +         */
       +        ss = stripsearch(origuri);
       +        origuri = ss.s1;
       +        search = ss.s2;
       +        uri = hurlunesc(c, origuri);
       +        uri = abspath(c, uri, "/");
       +        if(uri == nil || uri[0] == '\0'){
       +                hfail(c, HNotFound, "no object specified");
       +                return -1;
       +        }
       +
       +        c->req.uri = uri;
       +        c->req.search = search;
       +
       +        return 1;
       +}
       +
       +static Strings
       +parseuri(HConnect *c, char *uri)
       +{
       +        Strings ss;
       +        char *urihost, *p;
       +
       +        urihost = nil;
       +        if(uri[0] != '/'){
       +                if(cistrncmp(uri, "http://", 7) != 0){
       +                        ss.s1 = nil;
       +                        ss.s2 = nil;
       +                        return ss;
       +                }
       +                uri += 5;        /* skip http: */
       +        }
       +
       +        /*
       +         * anything starting with // is a host name or number
       +         * hostnames constists of letters, digits, - and .
       +         * for now, just ignore any port given
       +         */
       +        if(uri[0] == '/' && uri[1] == '/'){
       +                urihost = uri + 2;
       +                p = strchr(urihost, '/');
       +                if(p == nil)
       +                        uri = hstrdup(c, "/");
       +                else{
       +                        uri = hstrdup(c, p);
       +                        *p = '\0';
       +                }
       +                p = strchr(urihost, ':');
       +                if(p != nil)
       +                        *p = '\0';
       +        }
       +
       +        if(uri[0] != '/' || uri[1] == '/'){
       +                ss.s1 = nil;
       +                ss.s2 = nil;
       +                return ss;
       +        }
       +
       +        ss.s1 = uri;
       +        ss.s2 = hlower(urihost);
       +        return ss;
       +}
       +static Strings
       +stripsearch(char *uri)
       +{
       +        Strings ss;
       +        char *search;
       +
       +        search = strchr(uri, '?');
       +        if(search != nil)
       +                *search++ = 0;
       +        ss.s1 = uri;
       +        ss.s2 = search;
       +        return ss;
       +}
       +
       +/*
       + *  to circumscribe the accessible files we have to eliminate ..'s
       + *  and resolve all names from the root.
       + */
       +static char*
       +abspath(HConnect *cc, char *origpath, char *curdir)
       +{
       +        char *p, *sp, *path, *work, *rpath;
       +        int len, n, c;
       +
       +        if(curdir == nil)
       +                curdir = "/";
       +        if(origpath == nil)
       +                origpath = "";
       +        work = hstrdup(cc, origpath);
       +        path = work;
       +
       +        /*
       +         * remove any really special characters
       +         */
       +        for(sp = "`;| "; *sp; sp++){
       +                p = strchr(path, *sp);
       +                if(p)
       +                        *p = 0;
       +        }
       +
       +        len = strlen(curdir) + strlen(path) + 2 + UTFmax;
       +        if(len < 10)
       +                len = 10;
       +        rpath = halloc(cc, len);
       +        if(*path == '/')
       +                rpath[0] = 0;
       +        else
       +                strcpy(rpath, curdir);
       +        n = strlen(rpath);
       +
       +        while(path){
       +                p = strchr(path, '/');
       +                if(p)
       +                        *p++ = 0;
       +                if(strcmp(path, "..") == 0){
       +                        while(n > 1){
       +                                n--;
       +                                c = rpath[n];
       +                                rpath[n] = 0;
       +                                if(c == '/')
       +                                        break;
       +                        }
       +                }else if(strcmp(path, ".") == 0){
       +                        ;
       +                }else if(n == 1)
       +                        n += snprint(rpath+n, len-n, "%s", path);
       +                else
       +                        n += snprint(rpath+n, len-n, "/%s", path);
       +                path = p;
       +        }
       +
       +        if(strncmp(rpath, "/bin/", 5) == 0)
       +                strcpy(rpath, "/");
       +        return rpath;
       +}
       +
       +static char*
       +getword(HConnect *c)
       +{
       +        char *buf;
       +        int ch, n;
       +
       +        while((ch = getc(c)) == ' ' || ch == '\t' || ch == '\r')
       +                ;
       +        if(ch == '\n')
       +                return nil;
       +        n = 0;
       +        buf = halloc(c, 1);
       +        for(;;){
       +                switch(ch){
       +                case ' ':
       +                case '\t':
       +                case '\r':
       +                case '\n':
       +                        buf[n] = '\0';
       +                        return hstrdup(c, buf);
       +                }
       +
       +                if(n < HMaxWord-1){
       +                        buf = bingrow(&c->bin, buf, n, n + 1, 0);
       +                        if(buf == nil)
       +                                return nil;
       +                        buf[n++] = ch;
       +                }
       +                ch = getc(c);
       +        }
       +        return nil;
       +}
       +
       +static int
       +getc(HConnect *c)
       +{
       +        if(c->hpos < c->hstop)
       +                return *c->hpos++;
       +        return '\n';
       +}
 (DIR) diff --git a/src/libhttpd/query.c b/src/libhttpd/query.c
       t@@ -0,0 +1,39 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <httpd.h>
       +
       +/*
       + * parse a search string of the form
       + * tag=val&tag1=val1...
       + */
       +HSPairs*
       +hparsequery(HConnect *c, char *search)
       +{
       +        HSPairs *q;
       +        char *tag, *val, *s;
       +
       +        while((s = strchr(search, '?')) != nil)
       +                search = s + 1;
       +        s = search;
       +        while((s = strchr(s, '+')) != nil)
       +                *s++ = ' ';
       +        q = nil;
       +        while(*search){
       +                tag = search;
       +                while(*search != '='){
       +                        if(*search == '\0')
       +                                return q;
       +                        search++;
       +                }
       +                *search++ = 0;
       +                val = search;
       +                while(*search != '&'){
       +                        if(*search == '\0')
       +                                return hmkspairs(c, hurlunesc(c, tag), hurlunesc(c, val), q);
       +                        search++;
       +                }
       +                *search++ = '\0';
       +                q = hmkspairs(c, hurlunesc(c, tag), hurlunesc(c, val), q);
       +        }
       +        return q;
       +}
 (DIR) diff --git a/src/libhttpd/redirected.c b/src/libhttpd/redirected.c
       t@@ -0,0 +1,64 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +int
       +hredirected(HConnect *c, char *how, char *uri)
       +{
       +        Hio *hout;
       +        char *s, *ss, *host;
       +        int n;
       +
       +        host = c->head.host;
       +        if(strchr(uri, ':')){
       +                host = "";
       +        }else if(uri[0] != '/'){
       +                s = strrchr(c->req.uri, '/');
       +                if(s != nil)
       +                        *s = '\0';
       +                ss = halloc(c, strlen(c->req.uri) + strlen(uri) + 2 + UTFmax);
       +                sprint(ss, "%s/%s", c->req.uri, uri);
       +                uri = ss;
       +                if(s != nil)
       +                        *s = '/';
       +        }
       +
       +        n = snprint(c->xferbuf, HBufSize, 
       +                        "<head><title>Redirection</title></head>\r\n"
       +                        "<body><h1>Redirection</h1>\r\n"
       +                        "Your selection can be found <a href=\"%U\"> here</a>.<p></body>\r\n", uri);
       +
       +        hout = &c->hout;
       +        hprint(hout, "%s %s\r\n", hversion, how);
       +        hprint(hout, "Date: %D\r\n", time(nil));
       +        hprint(hout, "Server: Plan9\r\n");
       +        hprint(hout, "Content-type: text/html\r\n");
       +        hprint(hout, "Content-Length: %d\r\n", n);
       +        if(host == nil || host[0] == 0)
       +                hprint(hout, "Location: %U\r\n", uri);
       +        else
       +                hprint(hout, "Location: http://%U%U\r\n", host, uri);
       +        if(c->head.closeit)
       +                hprint(hout, "Connection: close\r\n");
       +        else if(!http11(c))
       +                hprint(hout, "Connection: Keep-Alive\r\n");
       +        hprint(hout, "\r\n");
       +
       +        if(strcmp(c->req.meth, "HEAD") != 0)
       +                hwrite(hout, c->xferbuf, n);
       +
       +        if(c->replog){
       +                if(host == nil || host[0] == 0)
       +                        c->replog(c, "Reply: %s\nRedirect: %U\n", how, uri);
       +                else
       +                        c->replog(c, "Reply: %s\nRedirect: http://%U%U\n", how, host, uri);
       +        }
       +        return hflush(hout);
       +}
       +
       +int
       +hmoved(HConnect *c, char *uri)
       +{
       +        return hredirected(c, "301 Moved Permanently", uri);
       +}
 (DIR) diff --git a/src/libhttpd/unallowed.c b/src/libhttpd/unallowed.c
       t@@ -0,0 +1,35 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +int
       +hunallowed(HConnect *c, char *allowed)
       +{
       +        Hio *hout;
       +        int n;
       +
       +        n = snprint(c->xferbuf, HBufSize, "<head><title>Method Not Allowed</title></head>\r\n"
       +                "<body><h1>Method Not Allowed</h1>\r\n"
       +                "You can't %s on <a href=\"%U\"> here</a>.<p></body>\r\n", c->req.meth, c->req.uri);
       +
       +        hout = &c->hout;
       +        hprint(hout, "%s 405 Method Not Allowed\r\n", hversion);
       +        hprint(hout, "Date: %D\r\n", time(nil));
       +        hprint(hout, "Server: Plan9\r\n");
       +        hprint(hout, "Content-Type: text/html\r\n");
       +        hprint(hout, "Allow: %s\r\n", allowed);
       +        hprint(hout, "Content-Length: %d\r\n", n);
       +        if(c->head.closeit)
       +                hprint(hout, "Connection: close\r\n");
       +        else if(!http11(c))
       +                hprint(hout, "Connection: Keep-Alive\r\n");
       +        hprint(hout, "\r\n");
       +
       +        if(strcmp(c->req.meth, "HEAD") != 0)
       +                hwrite(hout, c->xferbuf, n);
       +
       +        if(c->replog)
       +                c->replog(c, "Reply: 405 Method Not Allowed\nReason: Method Not Allowed\n");
       +        return hflush(hout);
       +}
 (DIR) diff --git a/src/libhttpd/urlfmt.c b/src/libhttpd/urlfmt.c
       t@@ -0,0 +1,26 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +int
       +hurlfmt(Fmt *f)
       +{
       +        char buf[HMaxWord*2];
       +        Rune r;
       +        char *s;
       +        int t;
       +
       +        s = va_arg(f->args, char*);
       +        for(t = 0; t < sizeof(buf) - 8; ){
       +                s += chartorune(&r, s);
       +                if(r == 0)
       +                        break;
       +                if(r <= ' ' || r == '%' || r >= Runeself)
       +                        t += snprint(&buf[t], sizeof(buf)-t, "%%%2.2x", r);
       +                else
       +                        buf[t++] = r;
       +        }
       +        buf[t] = 0;
       +        return fmtstrcpy(f, buf);
       +}
 (DIR) diff --git a/src/libhttpd/urlunesc.c b/src/libhttpd/urlunesc.c
       t@@ -0,0 +1,58 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bin.h>
       +#include <httpd.h>
       +
       +/* go from url with escaped utf to utf */
       +char *
       +hurlunesc(HConnect *cc, char *s)
       +{
       +        char *t, *v, *u;
       +        Rune r;
       +        int c, n;
       +
       +        /* unescape */
       +        u = halloc(cc, strlen(s)+1);
       +        for(t = u; c = *s; s++){
       +                if(c == '%'){
       +                        n = s[1];
       +                        if(n >= '0' && n <= '9')
       +                                n = n - '0';
       +                        else if(n >= 'A' && n <= 'F')
       +                                n = n - 'A' + 10;
       +                        else if(n >= 'a' && n <= 'f')
       +                                n = n - 'a' + 10;
       +                        else
       +                                break;
       +                        r = n;
       +                        n = s[2];
       +                        if(n >= '0' && n <= '9')
       +                                n = n - '0';
       +                        else if(n >= 'A' && n <= 'F')
       +                                n = n - 'A' + 10;
       +                        else if(n >= 'a' && n <= 'f')
       +                                n = n - 'a' + 10;
       +                        else
       +                                break;
       +                        s += 2;
       +                        c = (r<<4)+n;
       +                }
       +                *t++ = c;
       +        }
       +        *t = '\0';
       +
       +        /* convert to valid utf */
       +        v = halloc(cc, UTFmax*strlen(u) + 1);
       +        s = u;
       +        t = v;
       +        while(*s){
       +                /* in decoding error, assume latin1 */
       +                if((n=chartorune(&r, s)) == 1 && r == Runeerror)
       +                        r = (uchar)*s;
       +                s += n;
       +                t += runetochar(t, &r);
       +        }
       +        *t = '\0';
       +
       +        return v;
       +}