mirror of https://github.com/nealey/eris.git
Static files + directory indexing
This commit is contained in:
parent
b12721e22a
commit
ae8112f5c5
4
CHANGES
4
CHANGES
|
@ -1,3 +1,7 @@
|
|||
3.0:
|
||||
More or less a ground-up rewrite. A few fnord parts remain
|
||||
here and there.
|
||||
|
||||
2.0:
|
||||
Replace poll with select, which is more portable and may be
|
||||
slightly faster; however, it's only called for CGI and by
|
||||
|
|
6
Makefile
6
Makefile
|
@ -1,10 +1,12 @@
|
|||
VERSION := $(shell head -n 1 CHANGES | tr -d :)
|
||||
|
||||
CFLAGS = -DFNORD='"eris/$(VERSION)"' -Wall
|
||||
#-Werror
|
||||
CFLAGS = -DFNORD='"eris/$(VERSION)"' -Wall -Werror
|
||||
|
||||
all: eris
|
||||
|
||||
eris: eris.c strings.c mime.c time.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
test: eris
|
||||
sh ./test.sh
|
||||
|
||||
|
|
694
eris.c
694
eris.c
|
@ -5,6 +5,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
|
@ -44,6 +45,10 @@
|
|||
#define DUMP_p(v) DUMPf("%s = %p", #v, v)
|
||||
#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v)
|
||||
|
||||
#include "strings.c"
|
||||
#include "mime.c"
|
||||
#include "time.c"
|
||||
|
||||
/* Wait this long (seconds) for a valid HTTP request */
|
||||
#define READTIMEOUT 2
|
||||
|
||||
|
@ -67,7 +72,7 @@
|
|||
*/
|
||||
int doauth = 0;
|
||||
int docgi = 0;
|
||||
int dirlist = 0;
|
||||
int doidx = 0;
|
||||
int redirect = 0;
|
||||
int portappend = 0;
|
||||
|
||||
|
@ -77,15 +82,22 @@ char *remote_ip = NULL;
|
|||
char *remote_port = NULL;
|
||||
char *remote_ident = NULL;
|
||||
|
||||
/* Things that are really super convenient to have globally */
|
||||
/*
|
||||
* Things that are really super convenient to have globally.
|
||||
* These could be put into a struct for a threading version
|
||||
* of eris.
|
||||
*/
|
||||
enum { GET, POST, HEAD } method;
|
||||
char *host;
|
||||
char *user_agent;
|
||||
char *refer;
|
||||
char *path;
|
||||
int http_version;
|
||||
size_t content_length;
|
||||
char *query_string = NULL;
|
||||
off_t range_start, range_end;
|
||||
time_t ims = 0;
|
||||
|
||||
static const char days[] = "SunMonTueWedThuFriSat";
|
||||
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
||||
|
||||
#define BUFFER_SIZE 8192
|
||||
char stdout_buf[BUFFER_SIZE];
|
||||
|
@ -109,21 +121,6 @@ cork(int enable)
|
|||
#endif
|
||||
}
|
||||
|
||||
|
||||
/** Replace whitespace with underscores for logging */
|
||||
static void
|
||||
sanitize(char *s)
|
||||
{
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
for (; *s; s += 1) {
|
||||
if (isspace(*s)) {
|
||||
*s = '_';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Log a request */
|
||||
static void
|
||||
dolog(int code, off_t len)
|
||||
|
@ -136,6 +133,20 @@ dolog(int code, off_t len)
|
|||
remote_ip, code, (unsigned long) len, host, user_agent, refer, path);
|
||||
}
|
||||
|
||||
void
|
||||
header(unsigned int code, const char *httpcomment)
|
||||
{
|
||||
printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment);
|
||||
printf("Server: " FNORD "\r\n");
|
||||
printf("Connection: %s\r\n", keepalive?"keep-alive":"close");
|
||||
}
|
||||
|
||||
void
|
||||
eoh()
|
||||
{
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* output an error message and exit
|
||||
*/
|
||||
|
@ -144,358 +155,37 @@ badrequest(long code, const char *httpcomment, const char *message)
|
|||
{
|
||||
size_t msglen = 0;
|
||||
|
||||
printf("HTTP/1.0 %ld %s\r\nConnection: close\r\n", code, httpcomment);
|
||||
keepalive = 0;
|
||||
header(code, httpcomment);
|
||||
if (message) {
|
||||
msglen = (strlen(message) * 2) + 15;
|
||||
|
||||
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n",
|
||||
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n",
|
||||
(unsigned long) msglen);
|
||||
printf("<title>%s</title>%s", message, message);
|
||||
} else {
|
||||
fputs("\r\n", stdout);
|
||||
}
|
||||
printf("\r\n");
|
||||
fflush(stdout);
|
||||
dolog(code, msglen);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
/** Parse a header out of a line.
|
||||
*
|
||||
* This capitalizes the header name, and strips trailing [\r\n]
|
||||
* Returns the length of the line (after stripping)
|
||||
*/
|
||||
size_t
|
||||
extract_header_field(char *buf, char **val, int cgi)
|
||||
void
|
||||
not_found()
|
||||
{
|
||||
size_t len;
|
||||
char msg[] = "The requested URL does not exist here.";
|
||||
|
||||
*val = NULL;
|
||||
|
||||
for (len = 0; buf[len]; len += 1) {
|
||||
if (! *val) {
|
||||
if (buf[len] == ':') {
|
||||
buf[len] = 0;
|
||||
for (*val = &(buf[len+1]); **val == ' '; *val += 1);
|
||||
} else if (cgi) {
|
||||
switch (buf[len]) {
|
||||
case 'a'...'z':
|
||||
buf[len] ^= ' ';
|
||||
break;
|
||||
case 'A'...'Z':
|
||||
case '0'...'9':
|
||||
case '\r':
|
||||
case '\n':
|
||||
break;
|
||||
default:
|
||||
buf[len] = '_';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; (buf[len-1] == '\n') || (buf[len-1] == '\r'); len -= 1);
|
||||
buf[len] = 0;
|
||||
|
||||
return len;
|
||||
header(404, "Not Found");
|
||||
printf("Content-Type: text/html\r\n");
|
||||
printf("Content-Length: %d\r\n", sizeof msg);
|
||||
printf("\r\n");
|
||||
printf("%s\n", msg); /* sizeof msg includes the NULL */
|
||||
dolog(404, sizeof msg);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
fromhex(int c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
else {
|
||||
c |= ' ';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* header(buf,buflen,"User-Agent")="Mozilla"
|
||||
*/
|
||||
static char *
|
||||
header(char *buf, int buflen, const char *hname)
|
||||
{
|
||||
int slen = strlen(hname);
|
||||
int i;
|
||||
char *c;
|
||||
|
||||
for (i = 0; i < buflen - slen - 2; ++i) {
|
||||
if (!strncasecmp(buf + i, hname, slen)) {
|
||||
if (i && (buf[i - 1] && buf[i - 1] != '\n'))
|
||||
continue;
|
||||
if (buf[i + slen] != ':' || buf[i + slen + 1] != ' ')
|
||||
continue;
|
||||
c = buf + i + slen + 2;
|
||||
i += slen + 2;
|
||||
for (; i < buflen; ++i) {
|
||||
if (buf[i] == 0 || buf[i] == '\n' || buf[i] == '\r') {
|
||||
buf[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (*c == ' ' || *c == '\t')
|
||||
++c;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct mimeentry {
|
||||
const char *name,
|
||||
*type;
|
||||
} mimetab[] = {
|
||||
{
|
||||
"html", "text/html; charset=UTF-8"}, {
|
||||
"htm", "text/html; charset=UTF-8"}, {
|
||||
"txt", "text/plain; charset=UTF-8"}, {
|
||||
"css", "text/css"}, {
|
||||
"ps", "application/postscript"}, {
|
||||
"pdf", "application/pdf"}, {
|
||||
"js", "application/javascript"}, {
|
||||
"gif", "image/gif"}, {
|
||||
"png", "image/png"}, {
|
||||
"jpeg", "image/jpeg"}, {
|
||||
"jpg", "image/jpeg"}, {
|
||||
"svg", "image/svg+xml"}, {
|
||||
"mpeg", "video/mpeg"}, {
|
||||
"mpg", "video/mpeg"}, {
|
||||
"avi", "video/x-msvideo"}, {
|
||||
"mov", "video/quicktime"}, {
|
||||
"qt", "video/quicktime"}, {
|
||||
"mp3", "audio/mpeg"}, {
|
||||
"ogg", "audio/ogg"}, {
|
||||
"wav", "audio/x-wav"}, {
|
||||
"epub", "application/epub+zip"}, {
|
||||
"dvi", "application/x-dvi"}, {
|
||||
"pac", "application/x-ns-proxy-autoconfig"}, {
|
||||
"sig", "application/pgp-signature"}, {
|
||||
"swf", "application/x-shockwave-flash"}, {
|
||||
"torrent", "application/x-bittorrent"}, {
|
||||
"tar", "application/x-tar"}, {
|
||||
"zip", "application/zip"}, {
|
||||
"dtd", "text/xml"}, {
|
||||
"xml", "text/xml"}, {
|
||||
"xbm", "image/x-xbitmap"}, {
|
||||
"xpm", "image/x-xpixmap"}, {
|
||||
"xwd", "image/x-xwindowdump"}, {
|
||||
"ico", "image/x-icon"}, {
|
||||
0, 0}};
|
||||
|
||||
static const char *default_mimetype = "application/octet-stream";
|
||||
|
||||
/*
|
||||
* Determine MIME type from file extension
|
||||
*/
|
||||
static const char *
|
||||
getmimetype(char *url)
|
||||
{
|
||||
char *ext = strrchr(url, '.');
|
||||
|
||||
|
||||
if (ext) {
|
||||
int i;
|
||||
|
||||
ext++;
|
||||
for (i = 0; mimetab[i].name; ++i) {
|
||||
if (!strcmp(mimetab[i].name, ext)) {
|
||||
return mimetab[i].type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return default_mimetype;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* timerfc function Copyright 1996, Michiel Boland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or
|
||||
* without modification, are permitted provided that the following
|
||||
* conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* 3. The name of the author may not be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
static time_t
|
||||
timerfc(const char *s)
|
||||
{
|
||||
static const int daytab[2][12] = {
|
||||
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
|
||||
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
|
||||
};
|
||||
unsigned sec,
|
||||
min,
|
||||
hour,
|
||||
day,
|
||||
mon,
|
||||
year;
|
||||
char month[3];
|
||||
int c;
|
||||
unsigned n;
|
||||
char flag;
|
||||
char state;
|
||||
char isctime;
|
||||
enum { D_START, D_END, D_MON, D_DAY, D_YEAR, D_HOUR, D_MIN, D_SEC };
|
||||
|
||||
sec = 60;
|
||||
min = 60;
|
||||
hour = 24;
|
||||
day = 32;
|
||||
year = 1969;
|
||||
isctime = 0;
|
||||
month[0] = 0;
|
||||
state = D_START;
|
||||
n = 0;
|
||||
flag = 1;
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
if (year <= 100)
|
||||
year += (year < 70) ? 2000 : 1900;
|
||||
--mon;
|
||||
--day;
|
||||
if (sec >= 60 || min >= 60 || hour >= 60 || day >= 31)
|
||||
return -1;
|
||||
if (year < 1970)
|
||||
return 0;
|
||||
return sec + 60L * (min + 60L * (hour + 24L * (day +
|
||||
daytab[year % 4 == 0
|
||||
&& (year % 100
|
||||
|| year %
|
||||
400 ==
|
||||
0)][mon] +
|
||||
365L * (year - 1970L) +
|
||||
((year -
|
||||
1969L) >> 2))));
|
||||
}
|
||||
|
||||
void
|
||||
get_ucspi_env()
|
||||
{
|
||||
|
@ -535,7 +225,7 @@ parse_options(int argc, char *argv[])
|
|||
docgi = 1;
|
||||
break;
|
||||
case 'd':
|
||||
dirlist = 1;
|
||||
doidx = 1;
|
||||
break;
|
||||
case 'p':
|
||||
portappend = 1;
|
||||
|
@ -561,29 +251,261 @@ parse_options(int argc, char *argv[])
|
|||
}
|
||||
}
|
||||
|
||||
enum { GET, POST, HEAD };
|
||||
|
||||
void
|
||||
fake_sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
|
||||
{
|
||||
char buf[BUFFER_SIZE];
|
||||
ssize_t l, m;
|
||||
|
||||
/* is mmap quicker? does it matter? */
|
||||
if (-1 == lseek(in_fd, *offset, SEEK_SET)) {
|
||||
/* We're screwed. The most helpful thing we can do now is die. */
|
||||
fprintf(stderr, "Unable to seek. Dying.\n");
|
||||
exit(0);
|
||||
}
|
||||
l = read(in_fd, buf, min(count, sizeof buf));
|
||||
if (-1 == l) {
|
||||
/* Also screwed. */
|
||||
fprintf(stderr, "Unable to read an open file. Dying.\n");
|
||||
exit(0);
|
||||
}
|
||||
*offset += l;
|
||||
|
||||
while (l) {
|
||||
m = write(out_fd, buf, l);
|
||||
if (-1 == m) {
|
||||
/* ALSO screwed. */
|
||||
fprintf(stderr, "Unable to write to client. Dying.\n");
|
||||
exit(0);
|
||||
}
|
||||
l -= m;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
serve_file(int fd, char *filename, struct stat *st)
|
||||
{
|
||||
off_t len, remain;
|
||||
|
||||
if (method == POST) {
|
||||
badrequest(405, "Method Not Supported", "POST is not supported by this URL");
|
||||
}
|
||||
|
||||
if (st->st_mtime < ims) {
|
||||
header(304, "Not Changed");
|
||||
eoh();
|
||||
return;
|
||||
}
|
||||
|
||||
header(200, "OK");
|
||||
printf("Content-Type: %s\r\n", getmimetype(filename));
|
||||
|
||||
if ((range_end == 0) || (range_end > st->st_size)) {
|
||||
range_end = st->st_size;
|
||||
}
|
||||
len = range_end - range_start;
|
||||
printf("Content-Length: %llu\r\n", (unsigned long long) len);
|
||||
|
||||
{
|
||||
struct tm *tp;
|
||||
char buf[40];
|
||||
|
||||
tp = gmtime(&(st->st_mtime));
|
||||
|
||||
strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S GMT", tp);
|
||||
printf("Last-Modified: %s\r\n", buf);
|
||||
}
|
||||
|
||||
eoh();
|
||||
fflush(stdout);
|
||||
|
||||
if (method == HEAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (remain = len; remain; ) {
|
||||
size_t count = min(remain, SIZE_MAX);
|
||||
ssize_t sent;
|
||||
|
||||
sent = sendfile(1, fd, &range_start, count);
|
||||
if (-1 == sent) {
|
||||
fake_sendfile(1, fd, &range_start, count);
|
||||
}
|
||||
remain -= sent;
|
||||
}
|
||||
|
||||
dolog(200, len);
|
||||
}
|
||||
|
||||
void
|
||||
serve_idx(int fd, char *path)
|
||||
{
|
||||
DIR *d = fdopendir(fd);
|
||||
struct dirent *de;
|
||||
|
||||
if (method == POST) {
|
||||
badrequest(405, "Method Not Supported", "POST is not supported by this URL");
|
||||
}
|
||||
|
||||
keepalive = 0;
|
||||
header(200, "OK");
|
||||
printf("Content-Type: text/html\r\n");
|
||||
eoh();
|
||||
|
||||
printf("<!DOCTYPE html>\r<html><head><title>");
|
||||
html_esc(stdout, path);
|
||||
printf("</title></head><body><h1>Directory Listing: ");
|
||||
html_esc(stdout, path);
|
||||
printf("</h1><pre>");
|
||||
if (path[1]) {
|
||||
printf("<a href=\"../\">Parent Directory</a>\n");
|
||||
}
|
||||
|
||||
while ((de = readdir(d))) {
|
||||
char *name = de->d_name;
|
||||
char symlink[PATH_MAX];
|
||||
struct stat st;
|
||||
|
||||
if (name[0] == '.') {
|
||||
continue; /* hidden files -> skip */
|
||||
}
|
||||
if (lstat(name, &st)) {
|
||||
continue; /* can't stat -> skip */
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
printf("[DIR] ");
|
||||
} else if (S_ISLNK(st.st_mode)) {
|
||||
ssize_t len = readlink(de->d_name, symlink, (sizeof symlink) - 1);
|
||||
|
||||
if (len < 1) {
|
||||
continue;
|
||||
}
|
||||
name = symlink;
|
||||
printf("[LNK] "); /* symlink */
|
||||
} else if (S_ISREG(st.st_mode)) {
|
||||
printf("[ ] %10llu", (unsigned long long)st.st_size);
|
||||
} else {
|
||||
continue; /* not a file we can provide -> skip */
|
||||
}
|
||||
|
||||
/*
|
||||
* write a href
|
||||
*/
|
||||
printf(" <a href=\"");
|
||||
url_esc(stdout, name);
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
printf("/");
|
||||
}
|
||||
printf("\">");
|
||||
url_esc(stdout, name);
|
||||
printf("</a>\n");
|
||||
}
|
||||
printf("</pre></body></html>");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void
|
||||
serve_cgi(char *path)
|
||||
{
|
||||
DUMP_s(path);
|
||||
}
|
||||
|
||||
void
|
||||
find_serve_file(char *relpath)
|
||||
{
|
||||
int fd;
|
||||
struct stat st;
|
||||
|
||||
/* Open fspath. If that worked, */
|
||||
if ((fd = open(relpath, O_RDONLY)) > -1) {
|
||||
fstat(fd, &st);
|
||||
/* If it is a directory, */
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
char path2[PATH_MAX];
|
||||
int fd2;
|
||||
|
||||
/* Redirect if it doesn't end with / */
|
||||
if (! endswith(path, "/")) {
|
||||
header(301, "Redirect");
|
||||
printf("Location: %s/", path);
|
||||
eoh();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Open relpath + "index.html". If that worked,*/
|
||||
snprintf(path2, sizeof path2, "%sindex.html", relpath);
|
||||
if ((fd2 = open(path2, O_RDONLY)) > -1) {
|
||||
/* serve that file and return. */
|
||||
fstat(fd2, &st);
|
||||
serve_file(fd2, path2, &st);
|
||||
close(fd2);
|
||||
close(fd);
|
||||
return;
|
||||
} else {
|
||||
if (docgi) {
|
||||
snprintf(path2, sizeof path2, "%sindex.cgi", relpath);
|
||||
if (! stat(path2, &st)) {
|
||||
return serve_cgi(path2);
|
||||
}
|
||||
}
|
||||
if (doidx) {
|
||||
serve_idx(fd, relpath + 1);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
return not_found();
|
||||
}
|
||||
} else {
|
||||
if (docgi && endswith(relpath, ".cgi")) {
|
||||
close(fd);
|
||||
return serve_cgi(relpath);
|
||||
}
|
||||
serve_file(fd, relpath, &st);
|
||||
}
|
||||
} else {
|
||||
if (docgi && (errno == ENOTDIR)) {
|
||||
char *p;
|
||||
|
||||
if ((p = strstr(relpath, ".cgi"))) {
|
||||
p += 4;
|
||||
setenv("PATH_INFO", p, 1);
|
||||
*p = 0;
|
||||
if (! stat(relpath, &st)) {
|
||||
close(fd);
|
||||
return serve_cgi(relpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return not_found();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_request()
|
||||
{
|
||||
char request[MAXREQUESTLEN];
|
||||
char fspath[MAXREQUESTLEN];
|
||||
char fspath[PATH_MAX];
|
||||
char buf[MAXHEADERLEN];
|
||||
char *p;
|
||||
char *query_string = NULL;
|
||||
int method;
|
||||
time_t ims = 0;
|
||||
|
||||
host = NULL;
|
||||
user_agent = NULL;
|
||||
refer = NULL;
|
||||
path = NULL;
|
||||
range_start = 0;
|
||||
range_end = 0;
|
||||
content_length = 0;
|
||||
|
||||
alarm(READTIMEOUT);
|
||||
|
||||
/* Read request line first */
|
||||
request[0] = 0;
|
||||
fgets(request, sizeof request, stdin);
|
||||
if (NULL == fgets(request, sizeof request, stdin)) {
|
||||
/* They must have hung up! */
|
||||
exit(0);
|
||||
}
|
||||
if (!strncmp(request, "GET /", 5)) {
|
||||
method = GET;
|
||||
p = request + 5;
|
||||
|
@ -598,12 +520,12 @@ handle_request()
|
|||
badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
|
||||
}
|
||||
|
||||
/* Interpret path into fspath */
|
||||
/* Interpret path into fspath. */
|
||||
path = p - 1;
|
||||
{
|
||||
char *fsp = fspath;
|
||||
FILE *f = fmemopen(fspath, sizeof fspath, "w");
|
||||
|
||||
*(fsp++) = '/';
|
||||
fprintf(f, "./");
|
||||
for (; *p != ' '; p += 1) {
|
||||
if (! query_string) {
|
||||
char c = *p;
|
||||
|
@ -634,20 +556,20 @@ handle_request()
|
|||
break;
|
||||
}
|
||||
|
||||
*(fsp++) = c;
|
||||
fputc(c, f);
|
||||
}
|
||||
}
|
||||
*fsp = 0;
|
||||
DUMP_s(fspath);
|
||||
fputc(0, f);
|
||||
fclose(f);
|
||||
}
|
||||
*(p++) = 0; /* NULL-terminate path */
|
||||
DUMP_s(path);
|
||||
|
||||
http_version = -1;
|
||||
if (! strncmp(p, "HTTP/1.", 7) && (p[8] || (p[8] == '\r') || (p[8] == '\n'))) {
|
||||
if (! strncmp(p, "HTTP/1.", 7) && p[8] && ((p[8] == '\r') || (p[8] == '\n'))) {
|
||||
http_version = p[7] - '0';
|
||||
}
|
||||
if (! ((http_version == 0) || (http_version == 1))) {
|
||||
http_version = 0;
|
||||
badrequest(505, "Version Not Supported", "HTTP version not supported");
|
||||
}
|
||||
if (http_version == 1) {
|
||||
|
@ -697,14 +619,19 @@ handle_request()
|
|||
/* Set up CGI environment variables */
|
||||
setenv(cgi_name, val, 1);
|
||||
|
||||
/* By default, re-use buffer space */
|
||||
base = cgi_name;
|
||||
|
||||
/* Handle special header fields */
|
||||
base = name + len + 1;
|
||||
if (! strcmp(name, "HOST")) {
|
||||
host = val;
|
||||
base = name + len + 1;
|
||||
} else if (! strcmp(name, "USER_AGENT")) {
|
||||
user_agent = val;
|
||||
base = name + len + 1;
|
||||
} else if (! strcmp(name, "REFERER")) {
|
||||
refer = val;
|
||||
base = name + len + 1;
|
||||
} else if (! strcmp(name, "CONNECTION")) {
|
||||
if (! strcasecmp(val, "keep-alive")) {
|
||||
keepalive = 1;
|
||||
|
@ -713,11 +640,21 @@ handle_request()
|
|||
}
|
||||
} else if (! strcmp(name, "IF_MODIFIED_SINCE")) {
|
||||
ims = timerfc(val);
|
||||
} else if (! strcmp(name, "CONTENT_LENGTH")) {
|
||||
content_length = (size_t) strtoull(val, NULL, 10);
|
||||
} else if (! strcmp(name, "RANGE")) {
|
||||
/* Range: bytes=17-23 */
|
||||
/* Range: bytes=23- */
|
||||
if (! strncmp(val, "bytes=", 6)) {
|
||||
p = val + 6;
|
||||
range_start = (off_t) strtoull(p, &p, 10);
|
||||
if (*p == '-') {
|
||||
range_end = (off_t) strtoull(p+1, NULL, 10);
|
||||
} else {
|
||||
/* We can re-use this buffer space */
|
||||
base = cgi_name;
|
||||
range_end = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -725,7 +662,11 @@ handle_request()
|
|||
{
|
||||
char fn[PATH_MAX];
|
||||
|
||||
if (host) {
|
||||
strncpy(fn, host, sizeof fn);
|
||||
} else {
|
||||
fn[0] = 0;
|
||||
}
|
||||
if (fn[0] == '.') {
|
||||
fn[0] = ':';
|
||||
}
|
||||
|
@ -737,6 +678,9 @@ handle_request()
|
|||
case ':':
|
||||
*p = 0;
|
||||
break;
|
||||
case 'A'...'Z':
|
||||
*p ^= ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -746,7 +690,9 @@ handle_request()
|
|||
}
|
||||
|
||||
/* Serve the file */
|
||||
execl("/bin/sh", "sh", "-c", "set", NULL);
|
||||
cork(1);
|
||||
find_serve_file(fspath);
|
||||
cork(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
47
test.sh
47
test.sh
|
@ -56,17 +56,26 @@ echo "IDX: $HTTPD_IDX "
|
|||
|
||||
H "Basic tests"
|
||||
|
||||
title "GET"
|
||||
printf 'GET / HTTP/1.0\r\n\r\n' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.0 200 OK#%Server: [a-z]*/[0-9.]*#%Content-Type: text/html; charset=UTF-8#%Content-Length: 6#%Last-Modified: ..., .. ... 20.. ..:..:.. GMT#%#%james%' && pass || fail
|
||||
title "GET /index.html"
|
||||
printf 'GET /index.html HTTP/1.0\r\n\r\n' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.0 200 OK#%Server: [a-z]*/[0-9.]*#%Connection: close#%Content-Type: text/html; charset=UTF-8#%Content-Length: 6#%Last-Modified: ..., .. ... 20.. ..:..:.. GMT#%#%james%' && pass || fail
|
||||
|
||||
title "GET /"
|
||||
printf 'GET / HTTP/1.0\r\n\r\n' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.0 200 OK#%Server: [a-z]*/[0-9.]*#%Connection: close#%Content-Type: text/html; charset=UTF-8#%Content-Length: 6#%Last-Modified: ..., .. ... 20.. ..:..:.. GMT#%#%james%' && pass || fail
|
||||
|
||||
title "Keepalive"
|
||||
printf 'GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\n\r\n' | $HTTPD 2>/dev/null | grep -c 'james' | grep -q 2 && pass || fail
|
||||
|
||||
title "POST"
|
||||
printf 'POST / HTTP/1.0\r\nContent-Type: a\r\nContent-Length: 5\r\n\r\njames' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.0 200 OK#%Server: [a-z]*/[0-9.]*#%Content-Type: text/html; charset=UTF-8#%Content-Length: 6#%Last-Modified: ..., .. ... 20.. ..:..:.. GMT#%#%james%' && pass || fail
|
||||
printf 'POST / HTTP/1.0\r\nContent-Type: a\r\nContent-Length: 5\r\n\r\njames' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.0 405 ' && pass || fail
|
||||
|
||||
title "HTTP/1.2"
|
||||
printf 'GET / HTTP/1.2\r\n\r\n' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.. 505 .*ction: close' && pass || fail
|
||||
|
||||
title "HTTP/1.12"
|
||||
printf 'GET / HTTP/1.12\r\n\r\n' | $HTTPD 2>/dev/null | grep -q 'HTTP/1.0 505' && pass || fail
|
||||
printf 'GET / HTTP/1.12\r\n\r\n' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.. 505 .*ction: close' && pass || fail
|
||||
|
||||
title "Bare newline"
|
||||
printf 'GET / HTTP/1.0\n\n' | $HTTPD 2>/dev/null | d | grep -q 'HTTP/1.0 200 OK#%Server: [a-z]*/[0-9.]*#%Content-Type: text/html; charset=UTF-8#%Content-Length: 6#%Last-Modified: ..., .. ... 20.. ..:..:.. GMT#%#%james%' && pass || fail
|
||||
printf 'GET / HTTP/1.0\n\n' | $HTTPD 2>/dev/null | grep -q 'james' && pass || fail
|
||||
|
||||
title "Logging /"
|
||||
(printf 'GET / HTTP/1.1\r\nHost: host\r\n\r\n' |
|
||||
|
@ -77,7 +86,15 @@ title "Logging /index.html"
|
|||
PROTO=TCP TCPREMOTEPORT=1234 TCPREMOTEIP=10.0.0.2 $HTTPD >/dev/null) 2>&1 | grep -q '^10.0.0.2 200 6 host (null) (null) /index.html$' && pass || fail
|
||||
|
||||
|
||||
H "High weirdness"
|
||||
|
||||
# "Huge header"
|
||||
# "Huge header across MAXHEADERLEN"
|
||||
# "Too many headers"
|
||||
|
||||
|
||||
H "If-Modified-Since"
|
||||
|
||||
title "Has been modified"
|
||||
printf 'GET / HTTP/1.0\r\nIf-Modified-Since: Sun, 27 Feb 1980 12:12:12 GMT\r\n\r\n' | $HTTPD 2>/dev/null | grep -q '200 OK' && pass || fail
|
||||
|
||||
|
@ -93,15 +110,15 @@ printf 'GET / HTTP/1.0\r\nIf-Modified-Since: Thursday, 27-Feb-30 12:12:12 GMT\r\
|
|||
title "ANSI C Date"
|
||||
printf 'GET / HTTP/1.0\r\nIf-Modified-Since: Sun Feb 27 12:12:12 2030\r\n\r\n' | $HTTPD 2>/dev/null | grep -q '304 Not Changed' && pass || fail
|
||||
|
||||
title "No trailing slash"
|
||||
printf 'GET /empty HTTP/1.0\r\n\r\n' | $HTTPD 2>/dev/null | d | grep -q '301 Redirect#%.*Location: /empty/#%' && pass || fail
|
||||
|
||||
|
||||
|
||||
H "Directory indexing"
|
||||
|
||||
title "Basic index"
|
||||
printf 'GET /empty/ HTTP/1.0\r\n\r\n' | $HTTPD_IDX 2>/dev/null | d | grep -Fq '<h3>Directory Listing: /empty/</h3>%<pre>%<a href="/">Parent directory</a>%</pre>%' && pass || fail
|
||||
|
||||
title "No trailing slash"
|
||||
printf 'GET /empty HTTP/1.0\r\n\r\n' | $HTTPD_IDX 2>/dev/null | d | grep -Fq '404 Not Found' && pass || fail
|
||||
printf 'GET /empty/ HTTP/1.0\r\n\r\n' | $HTTPD_IDX 2>/dev/null | d | grep -Fq '<h1>Directory Listing: /empty/</h1><pre><a href="../">Parent Directory</a>%</pre>' && pass || fail
|
||||
|
||||
|
||||
H "CGI"
|
||||
|
@ -134,8 +151,8 @@ title "Multiple requests in one packet"
|
|||
printf 'GET / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\nGET / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\n' | $HTTPD 2>/dev/null | grep -c '^HTTP/1.' | grep -q 2 && pass || fail
|
||||
|
||||
# 4. Should return 406 Not Acceptable; instead ignores Accept header
|
||||
title "Accept header"
|
||||
printf 'GET / HTTP/1.0\r\nAccept: nothing\r\n\r\n' | $HTTPD 2>/dev/null | grep 406 && pass || fail
|
||||
#title "Accept header"
|
||||
#printf 'GET / HTTP/1.0\r\nAccept: nothing\r\n\r\n' | $HTTPD 2>/dev/null | grep 406 && pass || fail
|
||||
|
||||
# 5. Should serve second request as default MIME-Type (text/plain); instead uses previous mime type
|
||||
title "Second MIME-Type"
|
||||
|
@ -144,10 +161,10 @@ title "Second MIME-Type"
|
|||
printf 'GET /a HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\n\r\n') | $HTTPD 2>/dev/null | grep -q 'text/plain\|application/octet-stream' && pass || fail
|
||||
|
||||
# 6. Should consume POST data; instead tries to read POST data as second request
|
||||
title "POST to static HTML"
|
||||
(printf 'POST / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n';
|
||||
ls / > /dev/null
|
||||
printf 'aPOST / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\na') | $HTTPD 2>/dev/null | grep -c '200 OK' | grep -q 2 && pass || fail
|
||||
#title "POST to static HTML"
|
||||
#(printf 'POST / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n';
|
||||
# ls / > /dev/null
|
||||
# printf 'aPOST / HTTP/1.1\r\nHost: a\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\na') | $HTTPD 2>/dev/null | grep -c '200 OK' | grep -q 2 && pass || fail
|
||||
|
||||
# 7. HTTP/1.1 should default to keepalive; instead connection is closed
|
||||
title "HTTP/1.1 default keepalive"
|
||||
|
|
Loading…
Reference in New Issue