commit 9df487d720a59bf8cb0dc4ccffc30ad8eb48256a from: rsc date: Sun Nov 23 18:19:35 2003 UTC add libhttpd commit - b6afd33e2f23953f00c6fac6b5d45946a9113654 commit + 9df487d720a59bf8cb0dc4ccffc30ad8eb48256a blob - /dev/null blob + 1fdd46c6694508803452c7d9d2e3a58b6d5f50d7 (mode 644) --- /dev/null +++ src/libhttpd/alloc.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +/* + * 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; +} blob - /dev/null blob + 1b535ec917373f8a754f89230aa9a9be2bf1af3c (mode 644) --- /dev/null +++ src/libhttpd/checkcontent.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +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; +} blob - /dev/null blob + 450f60f7e9474bf28abda30f351c9a18f48f34ff (mode 644) --- /dev/null +++ src/libhttpd/date.c @@ -0,0 +1,212 @@ +#include +#include +#include + +/* + * 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; +} blob - /dev/null blob + 37d35baa0ce989459a4fb7e21452394bf1c5b96e (mode 644) --- /dev/null +++ src/libhttpd/escape.h @@ -0,0 +1,123 @@ + +Htmlesc htmlesc[] = +{ + { "¡", 0x00a1, }, + { "¢", 0x00a2, }, + { "£", 0x00a3, }, + { "¤", 0x00a4, }, + { "¥", 0x00a5, }, + { "¦", 0x00a6, }, + { "§", 0x00a7, }, + { "¨", 0x00a8, }, + { "©", 0x00a9, }, + { "ª", 0x00aa, }, + { "«", 0x00ab, }, + { "¬", 0x00ac, }, + { "­", 0x00ad, }, + { "®", 0x00ae, }, + { "¯", 0x00af, }, + { "°", 0x00b0, }, + { "±", 0x00b1, }, + { "²", 0x00b2, }, + { "³", 0x00b3, }, + { "´", 0x00b4, }, + { "µ", 0x00b5, }, + { "¶", 0x00b6, }, + { "·", 0x00b7, }, + { "¸", 0x00b8, }, + { "¹", 0x00b9, }, + { "º", 0x00ba, }, + { "»", 0x00bb, }, + { "¼", 0x00bc, }, + { "½", 0x00bd, }, + { "¾", 0x00be, }, + { "¿", 0x00bf, }, + { "À", 0x00c0, }, + { "Á", 0x00c1, }, + { "Â", 0x00c2, }, + { "Ã", 0x00c3, }, + { "Ä", 0x00c4, }, + { "Å", 0x00c5, }, + { "Æ", 0x00c6, }, + { "Ç", 0x00c7, }, + { "È", 0x00c8, }, + { "É", 0x00c9, }, + { "Ê", 0x00ca, }, + { "Ë", 0x00cb, }, + { "Ì", 0x00cc, }, + { "Í", 0x00cd, }, + { "Î", 0x00ce, }, + { "Ï", 0x00cf, }, + { "Ð", 0x00d0, }, + { "Ñ", 0x00d1, }, + { "Ò", 0x00d2, }, + { "Ó", 0x00d3, }, + { "Ô", 0x00d4, }, + { "Õ", 0x00d5, }, + { "Ö", 0x00d6, }, + { "&215;", 0x00d7, }, + { "Ø", 0x00d8, }, + { "Ù", 0x00d9, }, + { "Ú", 0x00da, }, + { "Û", 0x00db, }, + { "Ü", 0x00dc, }, + { "Ý", 0x00dd, }, + { "Þ", 0x00de, }, + { "ß", 0x00df, }, + { "à", 0x00e0, }, + { "á", 0x00e1, }, + { "â", 0x00e2, }, + { "ã", 0x00e3, }, + { "ä", 0x00e4, }, + { "å", 0x00e5, }, + { "æ", 0x00e6, }, + { "ç", 0x00e7, }, + { "è", 0x00e8, }, + { "é", 0x00e9, }, + { "ê", 0x00ea, }, + { "ë", 0x00eb, }, + { "ì", 0x00ec, }, + { "í", 0x00ed, }, + { "î", 0x00ee, }, + { "ï", 0x00ef, }, + { "ð", 0x00f0, }, + { "ñ", 0x00f1, }, + { "ò", 0x00f2, }, + { "ó", 0x00f3, }, + { "ô", 0x00f4, }, + { "õ", 0x00f5, }, + { "ö", 0x00f6, }, + { "&247;", 0x00f7, }, + { "ø", 0x00f8, }, + { "ù", 0x00f9, }, + { "ú", 0x00fa, }, + { "û", 0x00fb, }, + { "ü", 0x00fc, }, + { "ý", 0x00fd, }, + { "þ", 0x00fe, }, + { "ÿ", 0x00ff, }, + + { """, 0x0022, }, + { "&", 0x0026, }, + { "<", 0x003c, }, + { ">", 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 }, +}; blob - /dev/null blob + 92da30c87df5b6f3c6a464d38c51d149748d37de (mode 644) --- /dev/null +++ src/libhttpd/fail.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +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.

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, "%s\n

%s

\n%s\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); +} blob - /dev/null blob + 91abc7c9bc3a778d30675dcf3f90fd33594046a4 (mode 644) --- /dev/null +++ src/libhttpd/gethead.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +/* + * 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; + } +} blob - /dev/null blob + 7cfb555721bd2701c4224a02238d8482492422e5 (mode 644) --- /dev/null +++ src/libhttpd/hio.c @@ -0,0 +1,473 @@ +#include +#include +#include + +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; +} blob - /dev/null blob + feedbd5e7842207ba1b7251f6072064b0897f41c (mode 644) --- /dev/null +++ src/libhttpd/httpfmt.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +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); +} blob - /dev/null blob + 85a8df639a45d8b3a30bb5aadd371017c3ef7ed9 (mode 644) --- /dev/null +++ src/libhttpd/httpunesc.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +/* + * 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; +} blob - /dev/null blob + 40f49d514c8059fd99fba72f51594a8aeb6ae752 (mode 644) --- /dev/null +++ src/libhttpd/lower.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +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; +} blob - /dev/null blob + bd4db82c241d704d93a39b9ce5d7d0b8f018027e (mode 644) --- /dev/null +++ src/libhttpd/mkfile @@ -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 + blob - /dev/null blob + cba39a91c0173ad5614c0f00b277ae3497af1dd8 (mode 644) --- /dev/null +++ src/libhttpd/okheaders.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +/* + * 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"); +} blob - /dev/null blob + 5fd4092a388fb914bb8d2987581216ece1ee715c (mode 644) --- /dev/null +++ src/libhttpd/parse.c @@ -0,0 +1,1057 @@ +#include +#include +#include +#include +#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; +} blob - /dev/null blob + 19f031724912a90b1502699d8b2373b1bba12911 (mode 644) --- /dev/null +++ src/libhttpd/parsereq.c @@ -0,0 +1,296 @@ +#include +#include +#include +#include + +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'; +} blob - /dev/null blob + 47768da2fe57f2d31b43ba2ddbdd2d17896d2cfb (mode 644) --- /dev/null +++ src/libhttpd/query.c @@ -0,0 +1,39 @@ +#include +#include +#include + +/* + * 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; +} blob - /dev/null blob + c2419457c18090fdae4860691b522691087bfb91 (mode 644) --- /dev/null +++ src/libhttpd/redirected.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +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, + "Redirection\r\n" + "

Redirection

\r\n" + "Your selection can be found here.

\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); +} blob - /dev/null blob + ff50856f1cae894e4937d9ae0efadd7e5cfe23d1 (mode 644) --- /dev/null +++ src/libhttpd/unallowed.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +int +hunallowed(HConnect *c, char *allowed) +{ + Hio *hout; + int n; + + n = snprint(c->xferbuf, HBufSize, "Method Not Allowed\r\n" + "

Method Not Allowed

\r\n" + "You can't %s on here.

\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); +} blob - /dev/null blob + 2ce28acec6b58d4fce9ec2155385b21b2dfd9bd9 (mode 644) --- /dev/null +++ src/libhttpd/urlfmt.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +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); +} blob - /dev/null blob + ae8ca81f5442b3475992aa881447815150f7479c (mode 644) --- /dev/null +++ src/libhttpd/urlunesc.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +/* 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; +}