diff --git a/eris.c b/eris.c index 7567fa3..5409ff2 100644 --- a/eris.c +++ b/eris.c @@ -34,11 +34,12 @@ #endif #define DUMP() DUMPf("") #define DUMP_d(v) DUMPf("%s = %d", #v, v) +#define DUMP_u(v) DUMPf("%s = %u", #v, v) #define DUMP_x(v) DUMPf("%s = 0x%x", #v, v) #define DUMP_s(v) DUMPf("%s = %s", #v, v) #define DUMP_c(v) DUMPf("%s = %c", #v, v) #define DUMP_p(v) DUMPf("%s = %p", #v, v) -#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, l, v) +#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v) /* * the following is the time in seconds that fnord should wait for a valid @@ -123,7 +124,7 @@ char *remote_port; char *remote_ident; #define BUFFER_OUTSIZE 8192 -char stdout_buf[BUFFER_OUTSIZE]; +char stdout_buf[BUFFER_OUTSIZE]; static void sanitize(char *ua) @@ -158,10 +159,10 @@ badrequest(long code, const char *httpcomment, const char *message) retcode = code; dolog(0); printf("HTTP/1.0 %ld %s\r\nConnection: close\r\n", code, httpcomment); - if (message && message[0]) { + if (message) { printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n", - (unsigned long) strlen(message)); - fputs(message, stdout); + (unsigned long) (strlen(message) * 2) + 15); + printf("%s%s", message, message); } else { fputs("\r\n", stdout); } @@ -216,47 +217,64 @@ elen(register const char *const *e) return i; } -static ssize_t -read_header(int fd, char *buf, size_t buflen, size_t *headerlen) +/* + * Read header block. Try to read from stdin until \r?\n\r?\n is + * encountered. Read no more than *buflen bytes. Convert bare \n to + * \r\n. Preserve state across calls, provided buf is the same. Returns: + * 1 found 0 not found, call again -1 EOF or other error + */ +static int +read_header(FILE * f, char *buf, size_t * buflen) { - size_t len = 0; - int found = 0; - size_t p = 0; + static char *lastbuf = NULL; + static int found = 0; + static int bare = 1; /* LF here would be bare */ + int bufsize = *buflen; - while (found < 2) { - int tmp; + if (lastbuf != buf) { + lastbuf = buf; + bare = 1; + found = 0; + } + *buflen = 0; - tmp = read(fd, buf + len, buflen - len); - if (tmp < 0) { - return -1; + while (*buflen + bare < bufsize) { + int c = getchar(); + + switch (c) { + case EOF: + if (errno == EWOULDBLOCK) { + return 0; + } else { + return -1; + } + break; + case '\r': + bare = 0; + break; + case '\n': + if (bare) { + buf[(*buflen)++] = '\r'; + bare = 1; + } + found += 1; + break; + default: + found = 0; + bare = 1; + break; } - if (tmp == 0) { - break; - } - len += tmp; + buf[(*buflen)++] = c; - for (; (found < 2) && (p < len); p += 1) { - switch (buf[p]) { - case '\n': - found += 1; - break; - case '\r': - break; - default: - found = 0; - break; - } + if (found == 2) { + return 1; } } - if (headerlen) { - *headerlen = p; - } - - return len; + return 0; } -char * +char * env_append(const char *key, const char *val) { static char buf[MAXHEADERLEN * 2 + PATH_MAX + 200]; @@ -407,20 +425,21 @@ cgi_child(int sig) signal(SIGCHLD, cgi_child); } -/* Convert bare \n to \r\n in header. Return 0 if - * header is over. */ +/* + * Convert bare \n to \r\n in header. Return 0 if header is over. + */ static int cgi_send_correct_http(const char *s, unsigned int sl) { unsigned int i; - int newline = 0; + int newline = 0; for (i = 0; i < sl; i += 1) { switch (s[i]) { case '\r': if (s[i + 1] == '\n') { i += 1; - case '\n': + case '\n': printf("\r\n"); if (newline) { fwrite(s + i + 1, sl - i - 1, 1, stdout); @@ -486,16 +505,20 @@ start_cgi(int nph, const char *pathinfo, const char *const *envp) * read from cgi */ if (pfd[0].revents & POLLIN) { - size_t len; + size_t len; if (startup) { - /* XXX: could block :< */ - len = read_header(fd[0], ibuf, sizeof ibuf, NULL); + /* + * XXX: could block :< + */ + // len = read_header(fd[0], ibuf, sizeof ibuf, + // NULL); + len = 0; } else { len = read(fd[0], ibuf, sizeof ibuf); } - DUMP_d(len); - + DUMP_u((unsigned int) len); + if (0 == len) { break; } @@ -509,7 +532,7 @@ start_cgi(int nph, const char *pathinfo, const char *const *envp) if (startup) { if (nph) { /* NPH-CGI */ startup = 0; - printf("%.*s", len, ibuf); + printf("%.*s", (int) len, ibuf); /* * skip HTTP/x.x */ @@ -741,15 +764,18 @@ matchcommalist(const char *needle, const char *haystack) * return nonzero if match was found */ int len = strlen(needle); + + DUMP_s(needle); + DUMP_s(haystack); if (strncmp(needle, haystack, len)) return 0; switch (haystack[len]) { - case ';': - case ',': - case '\r': - case '\n': - case 0: - return 1; + case ';': + case ',': + case '\r': + case '\n': + case 0: + return 1; } return 0; } @@ -758,6 +784,9 @@ static int findincommalist(const char *needle, const char *haystack) { const char *accept; + + DUMP_s(needle); + DUMP_s(haystack); for (accept = haystack; accept;) { /* * format: foo/bar, @@ -772,6 +801,7 @@ findincommalist(const char *needle, const char *haystack) ++tmp; } final = (*tmp == 0 || *tmp == ';'); + DUMP_s(accept); if (matchcommalist("*/*", accept)) break; if (matchcommalist(haystack, accept)) @@ -851,100 +881,100 @@ timerfc(const char *s) do { c = *s++; switch (state) { - case D_START: - if (c == ' ') { - state = D_MON; - isctime = 1; - } else if (c == ',') - state = D_DAY; - break; - case D_MON: - if (isalpha(c)) { - if (n < 3) - month[n++] = c; - } else { - if (n < 3) - return -1; - n = 0; - state = isctime ? D_DAY : D_YEAR; - } - break; - case D_DAY: - if (c == ' ' && flag); - else if (isdigit(c)) { - flag = 0; - n = 10 * n + (c - '0'); - } else { - day = n; - n = 0; - state = isctime ? D_HOUR : D_MON; - } - break; - case D_YEAR: - if (isdigit(c)) - n = 10 * n + (c - '0'); - else { - year = n; - n = 0; - state = isctime ? D_END : D_HOUR; - } - break; - case D_HOUR: - if (isdigit(c)) - n = 10 * n + (c - '0'); - else { - hour = n; - n = 0; - state = D_MIN; - } - break; - case D_MIN: - if (isdigit(c)) - n = 10 * n + (c - '0'); - else { - min = n; - n = 0; - state = D_SEC; - } - break; - case D_SEC: - if (isdigit(c)) - n = 10 * n + (c - '0'); - else { - sec = n; - n = 0; - state = isctime ? D_YEAR : D_END; - } - break; + case D_START: + if (c == ' ') { + state = D_MON; + isctime = 1; + } else if (c == ',') + state = D_DAY; + break; + case D_MON: + if (isalpha(c)) { + if (n < 3) + month[n++] = c; + } else { + if (n < 3) + return -1; + n = 0; + state = isctime ? D_DAY : D_YEAR; + } + break; + case D_DAY: + if (c == ' ' && flag); + else if (isdigit(c)) { + flag = 0; + n = 10 * n + (c - '0'); + } else { + day = n; + n = 0; + state = isctime ? D_HOUR : D_MON; + } + break; + case D_YEAR: + if (isdigit(c)) + n = 10 * n + (c - '0'); + else { + year = n; + n = 0; + state = isctime ? D_END : D_HOUR; + } + break; + case D_HOUR: + if (isdigit(c)) + n = 10 * n + (c - '0'); + else { + hour = n; + n = 0; + state = D_MIN; + } + break; + case D_MIN: + if (isdigit(c)) + n = 10 * n + (c - '0'); + else { + min = n; + n = 0; + state = D_SEC; + } + break; + case D_SEC: + if (isdigit(c)) + n = 10 * n + (c - '0'); + else { + sec = n; + n = 0; + state = isctime ? D_YEAR : D_END; + } + break; } } while (state != D_END && c); switch (month[0]) { - case 'A': - mon = (month[1] == 'p') ? 4 : 8; - break; - case 'D': - mon = 12; - break; - case 'F': - mon = 2; - break; - case 'J': - mon = (month[1] == 'a') ? 1 : ((month[2] == 'l') ? 7 : 6); - break; - case 'M': - mon = (month[2] == 'r') ? 3 : 5; - break; - case 'N': - mon = 11; - break; - case 'O': - mon = 10; - break; - case 'S': - mon = 9; - break; - default: - return -1; + case 'A': + mon = (month[1] == 'p') ? 4 : 8; + break; + case 'D': + mon = 12; + break; + case 'F': + mon = 2; + break; + case 'J': + mon = (month[1] == 'a') ? 1 : ((month[2] == 'l') ? 7 : 6); + break; + case 'M': + mon = (month[2] == 'r') ? 3 : 5; + break; + case 'N': + mon = 11; + break; + case 'O': + mon = 10; + break; + case 'S': + mon = 9; + break; + default: + return -1; } if (year <= 100) year += (year < 70) ? 2000 : 1900; @@ -971,7 +1001,7 @@ static struct stat st; * try to return a file */ static int -doit(char *buf, int buflen, char *url, int explicit) +doit(char *headerbuf, size_t headerlen, char *url, int explicit) { int fd = -1; char *accept; @@ -979,23 +1009,32 @@ doit(char *buf, int buflen, char *url, int explicit) ++url; getmimetype(url, explicit); { - char *b = buf; - int l = buflen; + char *b = headerbuf; + int l = headerlen; for (;;) { char *h = header(b, l, "Accept"); + + DUMP_p(h); + DUMP_s(h); + DUMP_s(mimetype); if (!h) goto ok; - if (findincommalist(mimetype, h)) + if (findincommalist(mimetype, h)) { + DUMP(); goto ok; + } l -= (h - b) + 1; b = h + 1; } + DUMP(); retcode = 406; goto bad; } ok: + DUMP(); if (encoding) { /* see if client accepts the encoding */ - char *tmp = header(buf, buflen, "Accept-Encoding"); + char *tmp = + header(headerbuf, headerlen, "Accept-Encoding"); if (!tmp || !strstr(tmp, "gzip")) { retcode = 406; goto bad; @@ -1017,7 +1056,7 @@ doit(char *buf, int buflen, char *url, int explicit) */ { char *field = - header(buf, buflen, "If-Modified-Since"); + header(headerbuf, headerlen, "If-Modified-Since"); if (field) { time_t ims; @@ -1031,7 +1070,7 @@ doit(char *buf, int buflen, char *url, int explicit) } rangestart = 0; rangeend = st.st_size; - if ((accept = header(buf, buflen, "Range"))) { + if ((accept = header(headerbuf, headerlen, "Range"))) { /* * format: "bytes=17-23", "bytes=23-" */ @@ -1298,11 +1337,11 @@ serve_read_write(int fd) while (len > 0) { int written; switch (poll(&duh, 1, (fini - now) * 1000)) { - case 0: - if (now < fini) - continue; /* fall through */ - case -1: - return 1; /* timeout or error */ + case 0: + if (now < fini) + continue; /* fall through */ + case -1: + return 1; /* timeout or error */ } if ((written = write(1, tmp2, len)) < 0) return -1; @@ -1359,11 +1398,11 @@ serve_mmap(int fd) while (len > 0) { int written; switch (poll(&duh, 1, (fini - now) * 1000)) { - case 0: - if (now < fini) - continue; /* fall through */ - case -1: - return 1; /* timeout or error */ + case 0: + if (now < fini) + continue; /* fall through */ + case -1: + return 1; /* timeout or error */ } if ((written = write(1, tmp2, len)) < 0) return -1; @@ -1460,36 +1499,36 @@ serve_static_data(int fd) int main(int argc, char *argv[], const char *const *envp) { - char buf[MAXHEADERLEN]; + char headerbuf[MAXHEADERLEN]; char *nurl, *origurl; int docgi = 0; int dirlist = 0; int redirect = 0; int portappend = 0; - size_t len; - ssize_t in; + size_t headerlen; { int opt; while (-1 != (opt = getopt(argc, argv, "cdrp"))) { switch (opt) { - case 'c': - docgi = 1; - break; - case 'd': - dirlist = 1; - break; - case 'r': - redirect = 1; - break; - case 'p': - portappend = 1; - break; - default: - fprintf(stderr, "Usage: %s [-c] [-d] [-r] [-p]\n", argv[0]); - return 69; + case 'c': + docgi = 1; + break; + case 'd': + dirlist = 1; + break; + case 'r': + redirect = 1; + break; + case 'p': + portappend = 1; + break; + default: + fprintf(stderr, "Usage: %s [-c] [-d] [-r] [-p]\n", + argv[0]); + return 69; } } } @@ -1501,40 +1540,56 @@ main(int argc, char *argv[], const char *const *envp) handlenext: encoding = 0; + alarm(READTIMEOUT); - in = read_header(0, buf, sizeof buf, &len); - if (len < 10) - badrequest(400, "Bad Request", - "Bad RequestThat does not look like HTTP to me..."); - buf[len] = 0; + headerlen = sizeof headerbuf; + switch (read_header(stdin, headerbuf, &headerlen)) { + case -1: + return 0; + break; + case 0: + badrequest(400, "Bad Request", "Header too long"); + break; + } - if (!strncasecmp(buf, "GET /", 5)) { + alarm(0); + + if (headerlen < 10) + badrequest(400, "Bad Request", "That does not look like HTTP"); + + if (!memcmp(headerbuf, "GET /", 5)) { method = GET; - url = buf + 4; - } else if (!strncasecmp(buf, "POST /", 6)) { + url = headerbuf + 4; + } else if (!memcmp(headerbuf, "POST /", 6)) { method = POST; - url = buf + 5; - } else if (!strncasecmp(buf, "HEAD /", 6)) { + url = headerbuf + 5; + } else if (!memcmp(headerbuf, "HEAD /", 6)) { method = HEAD; - url = buf + 5; + url = headerbuf + 5; } else - badrequest(400, "Bad Request", - "Bad RequestUnsupported HTTP method."); + badrequest(405, "Method Not Allowed", "Unsupported HTTP method."); origurl = url; { - char *nl = strchr(buf, '\r'); - char *space = strchr(url, ' '); - if (space >= nl) - badrequest(400, "Bad Request", - "Bad RequestHTTP/0.9 not supported"); - if (strncmp(space + 1, "HTTP/1.", 7)) - badrequest(400, "Bad Request", - "Bad RequestOnly HTTP 1.x supported"); + /* + * If we got here we are *guaranteed* (by read_header) to have at + * least one \r + */ + char *nl = memchr(headerbuf, '\r', headerlen); + char *space = memchr(url, ' ', nl - url); + + if (!space) { + badrequest(400, "Bad Request", "HTTP/0.9 not supported"); + } + if (memcmp(space + 1, "HTTP/1.", 7)) + badrequest(400, "Bad Request", "Only HTTP/1.x supported"); *space = 0; httpversion = space[8] - '0'; + if (httpversion > 1) { + badrequest(400, "Bad Request", "HTTP Version not supported"); + } keepalive = 0; /* @@ -1572,39 +1627,42 @@ main(int argc, char *argv[], const char *const *envp) ++d; } *d = 0; - /* - * not good enough, we need a second pass - */ } + /* + * XXX: check use of uri to see if it needs to be duplicated + */ uri = strdup(url); } { char *tmp; - ua = header(buf, len, "User-Agent"); - refer = header(buf, len, "Referer"); - accept_enc = header(buf, len, "Accept-Encoding"); - if ((tmp = header(buf, len, "Connection"))) { /* see if it's - * "keep-alive" - * or * "close" */ + ua = header(headerbuf, headerlen, "User-Agent"); + refer = header(headerbuf, headerlen, "Referer"); + accept_enc = header(headerbuf, headerlen, "Accept-Encoding"); + if ((tmp = header(headerbuf, headerlen, "Connection"))) { /* see + * if + * it's + * * + * "keep-alive" + * * or "close" */ if (!strcasecmp(tmp, "keep-alive")) keepalive = 1; else if (!strcasecmp(tmp, "close")) keepalive = -1; } - cookie = header(buf, len, "Cookie"); - auth_type = header(buf, len, "Authorization"); + cookie = header(headerbuf, headerlen, "Cookie"); + auth_type = header(headerbuf, headerlen, "Authorization"); if (method == POST) { - content_type = header(buf, len, "Content-Type"); - content_len = header(buf, len, "Content-Length"); - if (content_len) { - post_len = strtoul(content_len, NULL, 10); - post_miss = buf + len + 1; - post_mlen = in - len - 1; - if (post_len <= post_mlen) - post_mlen = post_len; + content_type = header(headerbuf, headerlen, "Content-Type"); + content_len = header(headerbuf, headerlen, "Content-Length"); + + if ((!content_type) || (!content_len)) { + badrequest(411, "Length Required", + "POST missing Content-Type or Content-Length"); } + + post_len = strtoul(content_len, NULL, 10); } } @@ -1614,7 +1672,7 @@ main(int argc, char *argv[], const char *const *envp) { char *Buf; int i; - host = header(buf, len, "Host"); + host = header(headerbuf, headerlen, "Host"); if (!host) i = 100; else @@ -1645,8 +1703,7 @@ main(int argc, char *argv[], const char *const *envp) for (i = strlen(host); i >= 0; --i) if ((host[i] = tolower(host[i])) == '/') hostb0rken: - badrequest(400, "Bad Request", - "Bad RequestBullshit Host header"); + badrequest(400, "Bad Request", "Invalid host header"); if (host[0] == '.') goto hostb0rken; if (keepalive > 0) { @@ -1680,7 +1737,7 @@ main(int argc, char *argv[], const char *const *envp) } if (chdir("default") && argc < 2) { badrequest(404, "Not Found", - "Not FoundThis host is not served here."); + "This host is not served here."); } } } @@ -1693,7 +1750,7 @@ main(int argc, char *argv[], const char *const *envp) pid_t child; const char *authorization; - authorization = header(buf, len, "Authorization"); + authorization = header(headerbuf, headerlen, "Authorization"); child = fork(); if (child < 0) { badrequest(500, "Internal Server Error", @@ -1745,16 +1802,19 @@ main(int argc, char *argv[], const char *const *envp) if (docgi) { char *tmp, *pathinfo; + pathinfo = 0; - for (tmp = url; tmp < nurl; ++tmp) + for (tmp = url; tmp < nurl; ++tmp) { if (findcgi(tmp)) { nurl = tmp; if (tmp[4] == '/') pathinfo = tmp + 4; break; } + } if (pathinfo) { int len = strlen(pathinfo) + 1; + tmp = alloca(len); memcpy(tmp, pathinfo, len); *pathinfo = 0; @@ -1762,6 +1822,7 @@ main(int argc, char *argv[], const char *const *envp) } if (findcgi(nurl)) { int i; + if ((method == HEAD)) badrequest(400, "Bad Request", "Illegal HTTP method for Gateway call."); @@ -1786,7 +1847,9 @@ main(int argc, char *argv[], const char *const *envp) { int fd; - if ((fd = doit(buf, len, url, 1)) >= 0) { /* file was there */ + if ((fd = doit(headerbuf, headerlen, url, 1)) >= 0) { /* file + * was + * there */ /* * look if file.gz is also there and acceptable */ @@ -1797,7 +1860,7 @@ main(int argc, char *argv[], const char *const *envp) strcpy(fnord, url); strcpy(fnord + ul, ".gz"); - fd2 = doit(buf, len, fnord, 0); + fd2 = doit(headerbuf, headerlen, fnord, 0); if (fd2 >= 0) { /* yeah! */ url = fnord; close(fd); @@ -1815,12 +1878,12 @@ main(int argc, char *argv[], const char *const *envp) fputs(mimetype, stdout); fputs("\r\n", stdout); switch (keepalive) { - case -1: - fputs("Connection: close\r\n", stdout); - break; - case 1: - fputs("Connection: Keep-Alive\r\n", stdout); - break; + case -1: + fputs("Connection: close\r\n", stdout); + break; + case 1: + fputs("Connection: Keep-Alive\r\n", stdout); + break; } if (encoding) { printf("Content-Encoding: %s\r\n", encoding); @@ -1837,12 +1900,11 @@ main(int argc, char *argv[], const char *const *envp) /* * "Sun, 06 Nov 1994 08:49:37 GMT" */ - printf("Last-Modified: %.3s, %02d %.3s %d %02d:%02d:%02d GMT\r\n", - days + (3 * x->tm_wday), - x->tm_mday, - months + (3 * x->tm_mon), - x->tm_year + 1900, - x->tm_hour, x->tm_min, x->tm_sec); + printf + ("Last-Modified: %.3s, %02d %.3s %d %02d:%02d:%02d GMT\r\n", + days + (3 * x->tm_wday), x->tm_mday, + months + (3 * x->tm_mon), x->tm_year + 1900, + x->tm_hour, x->tm_min, x->tm_sec); } if (rangestart || rangeend != st.st_size) { printf @@ -1854,12 +1916,12 @@ main(int argc, char *argv[], const char *const *envp) fputs("\r\n", stdout); if (method == GET || method == POST) { switch (serve_static_data(fd)) { - case 0: - break; - case -1: - goto error500; - case 1: - return 1; + case 0: + break; + case -1: + goto error500; + case 1: + return 1; } #ifdef TCP_CORK if (corked) { @@ -1882,28 +1944,26 @@ main(int argc, char *argv[], const char *const *envp) } } switch (retcode) { - case 404: - { - char *space = alloca(strlen(url) + 2); + case 404: + { + char *space = alloca(strlen(url) + 2); - if (handleindexcgi(url, origurl, space)) - goto indexcgi; - handleredirect(url, origurl); - if (dirlist) { - handledirlist(origurl); + if (handleindexcgi(url, origurl, space)) + goto indexcgi; + handleredirect(url, origurl); + if (dirlist) { + handledirlist(origurl); + } + badrequest(404, "Not Found", "No such file or directory."); } - badrequest(404, "Not Found", - "Not FoundNo such file or directory."); - } - case 406: - badrequest(406, "Not Acceptable", - "Not AcceptableNothing acceptable found."); - case 416: - badrequest(416, "Requested Range Not Satisfiable", ""); - case 304: - badrequest(304, "Not Changed", ""); - case 500: - badrequest(500, "Internal Server Error", ""); + case 406: + badrequest(406, "Not Acceptable", "Nothing acceptable found."); + case 416: + badrequest(416, "Requested Range Not Satisfiable", ""); + case 304: + badrequest(304, "Not Changed", ""); + case 500: + badrequest(500, "Internal Server Error", ""); } return 1; } diff --git a/tests/test.py b/tests/test.py index 99e5592..5e93231 100755 --- a/tests/test.py +++ b/tests/test.py @@ -15,6 +15,15 @@ class LinesTests(unittest.TestCase): def assertLinesEqual(self, a, b): self.assertSequenceEqual(a.split(b'\n'), b.split(b'\n')) +class NewlineTests(LinesTests): + def testBareNL(self): + + p = eris() + so, se = p.communicate(b'GET / HTTP/1.0\n\n') + self.assertRegexpMatches(so, b'HTTP/1.0 200 OK\r\nServer: eris/2\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 6\r\nLast-Modified: (Mon|Tue|Wed|Thu|Fri|Sat|Sun), .. (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 2... ..:..:.. GMT\r\n\r\njames\n') + self.assertLinesEqual(se, b'10.1.2.3 200 6 127.0.0.1 (null) (null) /index.html\n') + + class ArgTests(LinesTests): def check_index(self, *args): p = eris(*args)