mirror of https://github.com/nealey/eris.git
1805 lines
52 KiB
C
1805 lines
52 KiB
C
/*
|
|
* simple httpd to be started from tcpserver
|
|
*/
|
|
#define _FILE_OFFSET_BITS 64
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <grp.h>
|
|
#include <errno.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <dirent.h>
|
|
#include <sys/mman.h>
|
|
#include <limits.h>
|
|
|
|
#ifndef min
|
|
#define min(a,b) ((a)<(b)?(a):(b))
|
|
#endif
|
|
|
|
/*
|
|
* Some things I use for debugging
|
|
*/
|
|
#define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
|
|
#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, (int)(l), v)
|
|
|
|
/*
|
|
* the following is the time in seconds that fnord should wait for a valid
|
|
* HTTP request
|
|
*/
|
|
#define READTIMEOUT 2
|
|
|
|
/*
|
|
* the following is the time in seconds that fnord should wait before
|
|
* aborting a request when trying to write the answer
|
|
*/
|
|
#define WRITETIMEOUT 20
|
|
|
|
#define CGI_TIMEOUT (5*60) /* 5 minutes time-out for CGI to complete */
|
|
|
|
#ifdef __linux__
|
|
/*
|
|
* defining USE_SENDFILE enables zero-copy TCP on Linux for static files.
|
|
* I (Fefe) measured over 320 meg per second with apache bench over localhost
|
|
* with sendfile and keep-alive.
|
|
*/
|
|
#define USE_SENDFILE
|
|
|
|
#include <sys/sendfile.h>
|
|
#endif
|
|
|
|
/*
|
|
* Memory-mapping may result in a performance boost. thttpd does it,
|
|
* but for a different reason (to cache frequently-accessed files).
|
|
* XXX: Some performance testing is in order here.
|
|
*/
|
|
#ifdef _POSIX_MAPPED_FILES
|
|
#define USE_MMAP
|
|
#endif
|
|
|
|
enum { UNKNOWN, GET, HEAD, POST } method;
|
|
|
|
static long retcode = 404; /* used for logging code */
|
|
char *host = "?"; /* Host: header */
|
|
char *port; /* also Host: header, :80 part */
|
|
char *args; /* URL behind ? (for CGIs) */
|
|
char *path; /* string between GET and HTTP/1.0, *
|
|
* demangled */
|
|
char rpath[PATH_MAX];
|
|
char *ua = "?"; /* user-agent */
|
|
char *refer; /* Referrer: header */
|
|
char *accept_enc; /* Accept-Encoding */
|
|
int httpversion; /* 0 == 1.0, 1 == 1.1 */
|
|
int keepalive = 0; /* should we keep the connection alive? */
|
|
int rootdir; /* fd of root directory, so we can fchdir
|
|
* * back for keep-alive */
|
|
char *cookie; /* Referrer: header */
|
|
char *uri; /* copy of url before demangling */
|
|
char *content_type;
|
|
char *content_len;
|
|
char *auth_type;
|
|
unsigned long post_len = 0;
|
|
|
|
#if _FILE_OFFSET_BITS == 64
|
|
static unsigned long long rangestart,
|
|
rangeend; /* for ranged queries */
|
|
#define strtorange strtoull
|
|
#else
|
|
static unsigned long rangestart,
|
|
rangeend; /* for ranged queries */
|
|
#define strtorange strtoul
|
|
#endif
|
|
|
|
static const char days[] = "SunMonTueWedThuFriSat";
|
|
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
|
|
#define MAXHEADERLEN 8192
|
|
|
|
char *remote_ip;
|
|
char *remote_port;
|
|
char *remote_ident;
|
|
|
|
#define BUFFER_SIZE 8192
|
|
char stdout_buf[BUFFER_SIZE];
|
|
|
|
/*
|
|
* TCP_CORK is a Linux extension to work around a TCP problem.
|
|
* http://www.baus.net/on-tcp_cork has a good description.
|
|
* XXX: Since we do our own buffering, TCP_CORK may not be helping
|
|
* with anything. This needs testing.
|
|
*/
|
|
void
|
|
cork(int enable)
|
|
{
|
|
#ifdef TCP_CORK
|
|
static int corked = 0;
|
|
|
|
if (enable != corked) {
|
|
setsockopt(1, IPPROTO_TCP, TCP_CORK, &enable, sizeof(enable));
|
|
corked = enable;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
sanitize(char *ua)
|
|
{ /* replace strings with underscores for *
|
|
* logging */
|
|
int j;
|
|
if (!ua)
|
|
return;
|
|
for (j = 0; ua[j]; ++j)
|
|
if (isspace(ua[j]))
|
|
ua[j] = '_';
|
|
}
|
|
|
|
static void
|
|
dolog(off_t len)
|
|
{ /* write a log line to stderr */
|
|
sanitize(host);
|
|
sanitize(ua);
|
|
sanitize(refer);
|
|
|
|
fprintf(stderr, "%s %ld %lu %s %s %s %s\n",
|
|
remote_ip ? remote_ip : "0.0.0.0",
|
|
retcode, (unsigned long) len, host, ua, refer, path);
|
|
}
|
|
|
|
/*
|
|
* output an error message and exit
|
|
*/
|
|
static void
|
|
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) {
|
|
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n",
|
|
(unsigned long) (strlen(message) * 2) + 15);
|
|
printf("<title>%s</title>%s", message, message);
|
|
} else {
|
|
fputs("\r\n", stdout);
|
|
}
|
|
fflush(stdout);
|
|
exit(0);
|
|
}
|
|
|
|
#define CGIENVLEN 21
|
|
|
|
static const char *cgivars[CGIENVLEN] = {
|
|
"GATEWAY_INTERFACE=",
|
|
"SERVER_PROTOCOL=",
|
|
"SERVER_SOFTWARE=",
|
|
"SERVER_NAME=",
|
|
"SERVER_PORT=",
|
|
"REQUEST_METHOD=",
|
|
"REQUEST_URI=",
|
|
"SCRIPT_NAME=",
|
|
"REMOTE_ADDR=",
|
|
"REMOTE_PORT=",
|
|
"REMOTE_IDENT=",
|
|
"HTTP_USER_AGENT=",
|
|
"HTTP_COOKIE=",
|
|
"HTTP_REFERER=",
|
|
"HTTP_ACCEPT_ENCODING=",
|
|
"AUTH_TYPE=",
|
|
"CONTENT_TYPE=",
|
|
"CONTENT_LENGTH=",
|
|
"QUERY_STRING=",
|
|
"PATH_INFO=",
|
|
"PATH_TRANSLATED="
|
|
};
|
|
|
|
static int
|
|
iscgivar(const char *s)
|
|
{
|
|
int sl = strlen(s);
|
|
register unsigned int i = 0;
|
|
|
|
for (; i < CGIENVLEN; i++)
|
|
if (!strncmp(s, cgivars[i], sl))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int
|
|
elen(register const char *const *e)
|
|
{
|
|
register unsigned int i = 0;
|
|
while (e[i])
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
/*
|
|
* I'm not crazy about all these static variables. But the idea,
|
|
* which seems to work, is that you pass in things like it was
|
|
* a read call, and then just keep passing that same stuff in over
|
|
* and over until it returns 0.
|
|
*
|
|
* Further down this winds up looking pretty nice. In here, sort
|
|
* of gross.
|
|
*/
|
|
|
|
static char *lastbuf = NULL;
|
|
static int found = 0;
|
|
static int bare = 1; /* LF here would be bare */
|
|
static int bufsize = 0;
|
|
|
|
if (! buf) {
|
|
lastbuf = NULL;
|
|
return 0;
|
|
}
|
|
|
|
if (lastbuf != buf) {
|
|
lastbuf = buf;
|
|
bare = 1;
|
|
found = 0;
|
|
bufsize = *buflen;
|
|
*buflen = 0;
|
|
}
|
|
|
|
while (*buflen + bare < bufsize) {
|
|
int c = fgetc(f);
|
|
|
|
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;
|
|
}
|
|
buf[(*buflen)++] = c;
|
|
|
|
if (found == 2) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
env_append(const char *key, const char *val)
|
|
{
|
|
static char buf[MAXHEADERLEN * 2 + PATH_MAX + 200];
|
|
static char *p = buf;
|
|
char *ret = p;
|
|
|
|
if (!key) {
|
|
p = buf;
|
|
return NULL;
|
|
}
|
|
|
|
p = stpcpy(p, key);
|
|
*(p++) = '=';
|
|
if (val) {
|
|
p = stpcpy(p, val) + 1;
|
|
} else {
|
|
*(p++) = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
do_cgi(const char *pathinfo, const char *const *envp)
|
|
{
|
|
const char *method_name[] = { "?", "GET", "HEAD", "POST" };
|
|
register unsigned int en = elen(envp);
|
|
char **cgi_arg;
|
|
register int i;
|
|
char **cgi_env =
|
|
(char **) alloca((CGIENVLEN + en + 1) * sizeof(char *));
|
|
|
|
cgi_env[0] = "GATEWAY_INTERFACE=CGI/1.1";
|
|
cgi_env[1] = "SERVER_PROTOCOL=HTTP/1.0";
|
|
cgi_env[2] = "SERVER_SOFTWARE=" FNORD;
|
|
|
|
i = 3;
|
|
env_append(NULL, NULL);
|
|
cgi_env[i++] = env_append("SERVER_NAME", host);
|
|
cgi_env[i++] = env_append("SERVER_PORT", port);
|
|
cgi_env[i++] = env_append("REQUEST_METHOD", method_name[method]);
|
|
cgi_env[i++] = env_append("REQUEST_URI", uri);
|
|
cgi_env[i++] = env_append("SCRIPT_NAME", path);
|
|
if (remote_ip)
|
|
cgi_env[i++] = env_append("REMOTE_ADDR", remote_ip);
|
|
if (remote_port)
|
|
cgi_env[i++] = env_append("REMOTE_PORT", remote_port);
|
|
if (remote_ident)
|
|
cgi_env[i++] = env_append("REMOTE_IDENT", remote_ident);
|
|
if (ua)
|
|
cgi_env[i++] = env_append("HTTP_USER_AGENT", ua);
|
|
if (cookie)
|
|
cgi_env[i++] = env_append("HTTP_COOKIE", cookie);
|
|
if (refer)
|
|
cgi_env[i++] = env_append("HTTP_REFERER", refer);
|
|
if (accept_enc)
|
|
cgi_env[i++] = env_append("HTTP_ACCEPT_ENCODING", accept_enc);
|
|
if (auth_type)
|
|
cgi_env[i++] = env_append("AUTH_TYPE", auth_type);
|
|
if (content_type)
|
|
cgi_env[i++] = env_append("CONTENT_TYPE", content_type);
|
|
if (content_type)
|
|
cgi_env[i++] = env_append("CONTENT_LENGTH", content_len);
|
|
if (args)
|
|
cgi_env[i++] = env_append("QUERY_STRING", args);
|
|
if (pathinfo) {
|
|
char *rp = realpath(pathinfo, NULL);
|
|
|
|
cgi_env[i++] = env_append("PATH_INFO", pathinfo);
|
|
cgi_env[i++] = env_append("PATH_TRANSLATED", rp ? rp : pathinfo);
|
|
if (rp)
|
|
free(rp);
|
|
}
|
|
|
|
{
|
|
unsigned int j = 0;
|
|
for (; j < en; j++)
|
|
if (!iscgivar(envp[j]))
|
|
cgi_env[++i] = (char *) envp[j];
|
|
}
|
|
cgi_env[++i] = 0;
|
|
|
|
/*
|
|
* argv
|
|
*/
|
|
if (args && (strchr(args, '=') == 0)) {
|
|
int n = 3;
|
|
for (i = 0; args[i]; ++i)
|
|
if (args[i] == '+')
|
|
++n;
|
|
cgi_arg = alloca(n * sizeof(char *));
|
|
cgi_arg[n = 1] = args;
|
|
for (i = 0; args[i]; ++i) {
|
|
if (args[i] == '+') {
|
|
args[i] = 0;
|
|
++i;
|
|
cgi_arg[++n] = args + i;
|
|
}
|
|
}
|
|
cgi_arg[++n] = 0;
|
|
} else {
|
|
cgi_arg = alloca(2 * sizeof(char *));
|
|
cgi_arg[1] = 0;
|
|
}
|
|
|
|
{
|
|
char tmp[PATH_MAX];
|
|
|
|
i = strrchr(rpath, '/') - rpath;
|
|
if (i) {
|
|
strncpy(tmp, rpath + 1, i);
|
|
tmp[i] = 0;
|
|
chdir(tmp);
|
|
}
|
|
}
|
|
|
|
{
|
|
char tmp[PATH_MAX];
|
|
|
|
/*
|
|
* program name
|
|
*/
|
|
cgi_arg[0] = tmp;
|
|
tmp[0] = '.';
|
|
strcpy(tmp + 1, rpath + i);
|
|
|
|
/*
|
|
* start cgi
|
|
*/
|
|
execve(cgi_arg[0], cgi_arg, cgi_env);
|
|
raise(SIGQUIT); /* gateway unavailable. */
|
|
}
|
|
}
|
|
|
|
static void
|
|
cgi_child(int sig)
|
|
{
|
|
int n,
|
|
pid = waitpid(0, &n, WNOHANG);
|
|
if (pid > 0) {
|
|
if (WIFSIGNALED(n)) {
|
|
if (WTERMSIG(n) == SIGALRM)
|
|
badrequest(504, "Gateway Time-out",
|
|
"Gateway has hit the Time-out.");
|
|
else
|
|
badrequest(502, "Bad Gateway",
|
|
"Gateway broken or unavailable.");
|
|
}
|
|
}
|
|
signal(SIGCHLD, cgi_child);
|
|
}
|
|
|
|
static void
|
|
start_cgi(int nph, const char *pathinfo, const char *const *envp)
|
|
{
|
|
// XXX: Is it safe to reuse headerbuf from main?
|
|
size_t size = 0;
|
|
int pid;
|
|
char cgiheader[BUFFER_SIZE];
|
|
size_t cgiheaderlen = BUFFER_SIZE;
|
|
int cin[2];
|
|
int cout[2];
|
|
FILE *cinf;
|
|
|
|
if (pipe(cin) || pipe(cout) || !(cinf = fdopen(cin[0], "rb"))) {
|
|
badrequest(500, "Internal Server Error",
|
|
"Server Resource problem.");
|
|
}
|
|
|
|
pid = fork();
|
|
if (-1 == pid) {
|
|
badrequest(500, "Internal Server Error",
|
|
"Unable to fork.");
|
|
}
|
|
if (pid) {
|
|
/* Parent */
|
|
int passthru = nph;
|
|
|
|
fcntl(cin[0], F_SETFL, O_NONBLOCK);
|
|
signal(SIGCHLD, cgi_child);
|
|
signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
|
|
|
|
close(cin[1]);
|
|
close(cout[0]);
|
|
|
|
alarm(CGI_TIMEOUT);
|
|
|
|
while (1) {
|
|
int nfds;
|
|
fd_set rfds, wfds;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_ZERO(&wfds);
|
|
FD_SET(cin[0], &rfds);
|
|
nfds = cin[0];
|
|
|
|
if (post_len) {
|
|
/* have post data */
|
|
FD_SET(cout[1], &wfds);
|
|
if (cout[1] > nfds) {
|
|
nfds = cout[1];
|
|
}
|
|
} else if (cout[1] >= 0) {
|
|
close(cout[1]); /* no post data */
|
|
cout[1] = -1;
|
|
}
|
|
|
|
if (-1 == select(nfds+1, &rfds, &wfds, NULL, NULL)) {
|
|
break;
|
|
}
|
|
|
|
if (FD_ISSET(cin[0], &rfds)) {
|
|
if (passthru) {
|
|
size_t len;
|
|
|
|
/* Re-use this big buffer */
|
|
len = fread(cgiheader, 1, sizeof cgiheader, cinf);
|
|
if (0 == len) {
|
|
/* CGI is done */
|
|
break;
|
|
}
|
|
fwrite(cgiheader, 1, len, stdout);
|
|
size += len;
|
|
} else {
|
|
int ret;
|
|
|
|
ret = read_header(cinf, cgiheader, &cgiheaderlen);
|
|
if (0 == ret) {
|
|
/* Call read_header again */
|
|
} else if (-1 == ret) {
|
|
/* EOF or error */
|
|
badrequest(500, "CGI Error",
|
|
"CGI output too weird");
|
|
} else {
|
|
/* Entire header is in memory now */
|
|
passthru = 1;
|
|
|
|
/* XXX: I think we need to look for Location:
|
|
* anywhere, but fnord got away with checking
|
|
* only the first header field, so I will too.
|
|
*/
|
|
if (memcmp(cgiheader, "Location: ", 10) == 0) {
|
|
retcode = 302;
|
|
printf
|
|
("HTTP/1.0 302 CGI-Redirect\r\nConnection: close\r\n");
|
|
fwrite(cgiheader, 1, cgiheaderlen, stdout);
|
|
dolog(0);
|
|
exit(0);
|
|
}
|
|
|
|
retcode = 200;
|
|
printf("HTTP/1.0 200 OK\r\nServer: "
|
|
FNORD
|
|
"\r\nPragma: no-cache\r\nConnection: close\r\n");
|
|
signal(SIGCHLD, SIG_IGN);
|
|
fwrite(cgiheader, 1, cgiheaderlen, stdout);
|
|
}
|
|
}
|
|
} else if (FD_ISSET(cout[1], &wfds)) {
|
|
/*
|
|
* write to cgi the post data
|
|
*/
|
|
if (post_len) {
|
|
size_t len;
|
|
char buf[BUFFER_SIZE];
|
|
size_t nmemb = min(BUFFER_SIZE, post_len);
|
|
|
|
len = fread(buf, 1, nmemb, stdin);
|
|
if (len < 1) {
|
|
break;
|
|
}
|
|
post_len -= len;
|
|
write(cout[1], buf, len);
|
|
} else {
|
|
close(cout[1]);
|
|
}
|
|
}
|
|
}
|
|
alarm(0);
|
|
|
|
fflush(stdout);
|
|
dolog(size);
|
|
cork(0);
|
|
} else {
|
|
/* Child */
|
|
|
|
close(cout[1]);
|
|
close(cin[0]);
|
|
|
|
dup2(cout[0], 0);
|
|
dup2(cin[1], 1);
|
|
|
|
close(cout[0]);
|
|
close(cin[1]);
|
|
|
|
do_cgi(pathinfo, envp);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
static 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))));
|
|
}
|
|
|
|
static struct stat st;
|
|
|
|
/*
|
|
* try to return a file
|
|
*/
|
|
static int
|
|
doit(char *headerbuf, size_t headerlen, char *url)
|
|
{
|
|
int fd = -1;
|
|
char *accept;
|
|
|
|
while (url[0] == '/')
|
|
++url;
|
|
if ((fd = open(url, O_RDONLY)) >= 0) {
|
|
if (fstat(fd, &st))
|
|
goto bad;
|
|
/*
|
|
* no directories
|
|
*/
|
|
if (S_ISDIR(st.st_mode))
|
|
goto bad;
|
|
/*
|
|
* see if the document has been changed
|
|
*/
|
|
{
|
|
char *field =
|
|
header(headerbuf, headerlen, "If-Modified-Since");
|
|
|
|
if (field) {
|
|
time_t ims;
|
|
|
|
ims = timerfc(field);
|
|
if ((ims != (time_t) - 1) && (st.st_mtime <= ims)) {
|
|
retcode = 304;
|
|
goto bad;
|
|
}
|
|
}
|
|
}
|
|
rangestart = 0;
|
|
rangeend = st.st_size;
|
|
if ((accept = header(headerbuf, headerlen, "Range"))) {
|
|
/*
|
|
* format: "bytes=17-23", "bytes=23-"
|
|
*/
|
|
if (!strncmp(accept, "bytes=", 6)) {
|
|
accept += 6;
|
|
rangestart = strtorange(accept, &accept, 10);
|
|
if (*accept == '-') {
|
|
++accept;
|
|
if (*accept) {
|
|
rangeend = strtorange(accept, &accept, 10);
|
|
if (!*accept) {
|
|
rangeend = st.st_size;
|
|
} else {
|
|
++rangeend;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (rangestart > rangeend || rangeend > st.st_size) {
|
|
retcode = 416;
|
|
goto bad;
|
|
}
|
|
}
|
|
return fd;
|
|
bad:
|
|
if (fd >= 0)
|
|
close(fd);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
redirectboilerplate()
|
|
{
|
|
printf
|
|
("HTTP/1.0 301 Go Away\r\nConnection: close\r\nContent-Length: 0\r\nLocation: ");
|
|
}
|
|
|
|
static void
|
|
handleredirect(const char *url, const char *origurl)
|
|
{
|
|
char symlink[1024];
|
|
int len;
|
|
|
|
while (*url == '/')
|
|
++url;
|
|
if ((len = readlink(url, symlink, 1023)) > 0) {
|
|
/*
|
|
* el-cheapo redirection
|
|
*/
|
|
redirectboilerplate();
|
|
printf("%.*s", len, symlink);
|
|
retcode = 301;
|
|
printf("\r\n\r\n");
|
|
dolog(0);
|
|
fflush(stdout);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdl_encode_html(const char *s, unsigned int sl)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sl; ++i) {
|
|
unsigned char ch = s[i];
|
|
if (ch > 159) {
|
|
printf("&#%u;", ch);
|
|
} else if ((ch > 128) || (ch < 32)) {
|
|
putchar('_');
|
|
} else if (ch == '"')
|
|
fputs(""", stdout);
|
|
else if (ch == '&')
|
|
fputs("&", stdout);
|
|
else if (ch == '<')
|
|
fputs("<", stdout);
|
|
else if (ch == '>')
|
|
fputs(">", stdout);
|
|
else
|
|
putchar(ch);
|
|
}
|
|
}
|
|
static void
|
|
hdl_encode_uri(const char *s, unsigned int sl)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sl; ++i) {
|
|
unsigned char ch = s[i];
|
|
if ((ch != '%') && (ch > 32) && (ch < 127))
|
|
putchar(ch);
|
|
else
|
|
printf("%%%02x", ch);
|
|
}
|
|
}
|
|
static void
|
|
handledirlist(const char *origurl)
|
|
{
|
|
DIR *dir;
|
|
unsigned int nl;
|
|
const char *nurl = origurl;
|
|
|
|
while (nurl[0] == '/')
|
|
++nurl;
|
|
if (nurl <= origurl)
|
|
return;
|
|
nl = strlen(nurl);
|
|
if (nl && (nurl[nl - 1] != '/'))
|
|
return;
|
|
if (!stat(nl ? nurl : ".", &st) && (S_ISDIR(st.st_mode))
|
|
&& ((st.st_mode & S_IRWXO) == 5)) {
|
|
if (nl)
|
|
chdir(nurl);
|
|
if ((dir = opendir("."))) {
|
|
struct dirent *de;
|
|
unsigned int i,
|
|
size = 32 + nl;
|
|
fputs("HTTP/1.0 200 OK\r\nServer: " FNORD
|
|
"\r\nConnection: close\r\n", stdout);
|
|
fputs("Content-Type: text/html; charset=utf-8\r\n", stdout);
|
|
fputs("\r\n<h3>Directory Listing: /", stdout);
|
|
hdl_encode_html(nurl, nl);
|
|
fputs("</h3>\n<pre>\n", stdout);
|
|
if (nl != 0) {
|
|
for (i = nl - 2; i > 0; --i)
|
|
if (nurl[i] == '/')
|
|
break;
|
|
fputs("<a href=\"", stdout);
|
|
fputs("/", stdout);
|
|
hdl_encode_uri(nurl, i);
|
|
if (i > 0)
|
|
fputs("/", stdout);
|
|
fputs("\">Parent directory", stdout);
|
|
fputs("</a>\n", stdout);
|
|
size += 40 + i;
|
|
}
|
|
while ((de = readdir(dir))) {
|
|
char symlink[1024];
|
|
char *p = de->d_name;
|
|
unsigned int pl,
|
|
dl = strlen(de->d_name);
|
|
pl = dl;
|
|
if (de->d_name[0] == '.')
|
|
continue; /* hidden files -> skip */
|
|
if (lstat(de->d_name, &st))
|
|
continue; /* can't stat -> skip */
|
|
if (S_ISDIR(st.st_mode))
|
|
fputs("[DIR] ", stdout);
|
|
else if (S_ISLNK(st.st_mode)) {
|
|
if ((pl = readlink(de->d_name, symlink, 1023)) < 1)
|
|
continue;
|
|
p = symlink;
|
|
fputs("[LNK] ", stdout); /* a symlink to *
|
|
* something ... */
|
|
} else if (S_ISREG(st.st_mode))
|
|
fputs("[TXT] ", stdout);
|
|
else
|
|
continue; /* not a file we can provide -> skip */
|
|
/*
|
|
* write a href
|
|
*/
|
|
fputs("<a href=\"", stdout);
|
|
hdl_encode_uri(p, pl);
|
|
if (S_ISDIR(st.st_mode))
|
|
fputs("/", stdout), ++size;
|
|
fputs("\">", stdout);
|
|
if (de->d_name[0] == ':')
|
|
de->d_name[0] = '.'; /* fnord special ... */
|
|
hdl_encode_html(de->d_name, dl);
|
|
fputs("</a>\n", stdout);
|
|
size += 22 + (dl << 1);
|
|
}
|
|
closedir(dir);
|
|
fputs("</pre>\n", stdout);
|
|
fflush(stdout);
|
|
retcode = 200;
|
|
dolog(size);
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
handleindexcgi(const char *testurl, const char *origurl, char *space)
|
|
{
|
|
unsigned int ul,
|
|
ol = strlen(origurl);
|
|
char *test;
|
|
while (testurl[0] == '/')
|
|
++testurl, --ol;
|
|
ul = strlen(testurl);
|
|
if (strcmp(testurl + ol, "index.html"))
|
|
return 0; /* no request for index.html */
|
|
test = space;
|
|
++test;
|
|
ul -= 4;
|
|
memcpy(test, testurl, ul);
|
|
test[ul] = 'c';
|
|
test[++ul] = 'g';
|
|
test[++ul] = 'i';
|
|
test[++ul] = 0;
|
|
if (stat(test, &st))
|
|
return 0; /* no index.cgi present */
|
|
ul = 1;
|
|
if (st.st_gid == getegid())
|
|
ul = 010;
|
|
if (st.st_uid == geteuid())
|
|
ul = 0100;
|
|
if (!(st.st_mode & ul))
|
|
return 0; /* should be executable */
|
|
*(--test) = '/';
|
|
|
|
strncpy(rpath, test, sizeof rpath);
|
|
return 1; /* Wow... now start "index.cgi" */
|
|
}
|
|
|
|
static void
|
|
get_ucspi_env(void)
|
|
{
|
|
char *ucspi = getenv("PROTO");
|
|
if (ucspi) {
|
|
int protolen = strlen(ucspi);
|
|
char *buf = alloca(protolen + 20);
|
|
|
|
strcpy(buf, ucspi);
|
|
|
|
strcpy(buf + protolen, "REMOTEIP");
|
|
remote_ip = getenv(buf);
|
|
|
|
strcpy(buf + protolen, "REMOTEPORT");
|
|
remote_port = getenv(buf);
|
|
|
|
strcpy(buf + protolen, "REMOTEINFO");
|
|
remote_ident = getenv(buf);
|
|
}
|
|
}
|
|
|
|
static int
|
|
findcgi(const char *c)
|
|
{
|
|
return (c[0] == '.' && c[1] == 'c' &&
|
|
c[2] == 'g' && c[3] == 'i' && (c[4] == '/' || c[4] == 0));
|
|
}
|
|
|
|
static int
|
|
serve_read_write(int fd)
|
|
{
|
|
char tmp[4096];
|
|
char *tmp2;
|
|
int len;
|
|
off_t todo = rangeend - rangestart;
|
|
if (rangestart)
|
|
lseek(fd, rangestart, SEEK_SET);
|
|
|
|
while (todo > 0) {
|
|
int olen;
|
|
|
|
olen = len;
|
|
tmp2 = tmp;
|
|
while (len > 0) {
|
|
int written;
|
|
|
|
if ((written = write(1, tmp2, len)) < 0) {
|
|
return -1;
|
|
}
|
|
len -= written;
|
|
tmp2 += written;
|
|
}
|
|
todo -= olen;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
serve_mmap(int fd)
|
|
{
|
|
off_t mapstart,
|
|
maplen;
|
|
unsigned long mapofs;
|
|
char *map,
|
|
*tmp2;
|
|
|
|
mapstart = rangestart & (~(off_t) 0xfff); /* round down to 4k page */
|
|
maplen = rangeend - mapstart;
|
|
mapofs = rangestart - mapstart;
|
|
if (maplen > 64 * 1024 * 1024)
|
|
maplen = 64 * 1024 * 1024;
|
|
map = mmap(0, maplen, PROT_READ, MAP_PRIVATE, fd, mapstart);
|
|
if (map == MAP_FAILED) {
|
|
if (errno == EINVAL && mapstart) {
|
|
/*
|
|
* try rounded to 64k pages
|
|
*/
|
|
mapstart = rangestart & 0xffff;
|
|
maplen = rangeend - mapstart;
|
|
mapofs = rangestart - mapstart;
|
|
map = mmap(0, maplen, PROT_READ, MAP_PRIVATE, fd, mapstart);
|
|
if (map == MAP_FAILED)
|
|
/*
|
|
* didn't work, use read/write instead.
|
|
*/
|
|
return serve_read_write(fd);
|
|
} else
|
|
return serve_read_write(fd);
|
|
}
|
|
|
|
while (rangestart < rangeend) {
|
|
int len;
|
|
|
|
len = maplen - mapofs;
|
|
tmp2 = map + mapofs;
|
|
while (len > 0) {
|
|
int written;
|
|
|
|
if ((written = write(1, tmp2, len)) < 0) {
|
|
alarm(0);
|
|
return -1;
|
|
}
|
|
len -= written;
|
|
tmp2 += written;
|
|
}
|
|
rangestart += maplen - mapofs;
|
|
mapstart += maplen;
|
|
munmap(map, maplen);
|
|
mapofs = 0;
|
|
maplen = rangeend - mapstart;
|
|
if (maplen) {
|
|
if (maplen > 64 * 1024 * 1024)
|
|
maplen = 64 * 1024 * 1024;
|
|
map = mmap(0, maplen, PROT_READ, MAP_SHARED, fd, mapstart);
|
|
if (map == MAP_FAILED)
|
|
/*
|
|
* can't happen, really
|
|
*/
|
|
return serve_read_write(fd);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* write from offset "rangestart" to offset "rangeend" to fd #1
|
|
*/
|
|
static int
|
|
serve_static_data(int fd)
|
|
{
|
|
off_t len = rangeend - rangestart;
|
|
|
|
if (len < 4096) { /* for small files, sendfile is actually
|
|
* slower */
|
|
char tmp[4096];
|
|
if (rangestart)
|
|
lseek(fd, rangestart, SEEK_SET);
|
|
read(fd, tmp, len); /* if read fails, we can't back down now.
|
|
* We already committed on the
|
|
* content-length */
|
|
fwrite(tmp, len, 1, stdout);
|
|
fflush(stdout);
|
|
return 0;
|
|
}
|
|
#ifdef USE_SENDFILE
|
|
{
|
|
off_t offset = rangestart;
|
|
|
|
cork(1);
|
|
fflush(stdout);
|
|
{
|
|
off_t l = rangeend - rangestart;
|
|
do {
|
|
off_t c;
|
|
c = (l > (1ul << 31)) ? 1ul << 31 : l;
|
|
if (sendfile(1, fd, &offset, c) == -1)
|
|
# ifdef USE_MMAP
|
|
return serve_mmap(fd);
|
|
# else
|
|
return serve_read_write(fd);
|
|
# endif
|
|
l -= c;
|
|
} while (l);
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
fflush(stdout);
|
|
|
|
/* XXX: Is this really the right place to uncork? */
|
|
cork(0);
|
|
# ifdef USE_MMAP
|
|
return serve_mmap(fd);
|
|
# else
|
|
return serve_read_write(fd);
|
|
# endif
|
|
#endif
|
|
}
|
|
|
|
|
|
int
|
|
main(int argc, char *argv[], const char *const *envp)
|
|
{
|
|
char headerbuf[MAXHEADERLEN];
|
|
char *url,
|
|
*nurl;
|
|
int doauth = 0;
|
|
int docgi = 0;
|
|
int dokeepalive = 1;
|
|
int dirlist = 0;
|
|
int redirect = 0;
|
|
int portappend = 0;
|
|
size_t headerlen;
|
|
|
|
{
|
|
int opt;
|
|
|
|
while (-1 != (opt = getopt(argc, argv, "acdhkprv"))) {
|
|
switch (opt) {
|
|
case 'a':
|
|
doauth = 1;
|
|
break;
|
|
case 'c':
|
|
docgi = 1;
|
|
break;
|
|
case 'd':
|
|
dirlist = 1;
|
|
break;
|
|
case 'k':
|
|
dokeepalive = 0;
|
|
break;
|
|
case 'p':
|
|
portappend = 1;
|
|
break;
|
|
case 'r':
|
|
redirect = 1;
|
|
break;
|
|
case 'v':
|
|
printf(FNORD "\n");
|
|
return 0;
|
|
default:
|
|
fprintf(stderr, "Usage: %s [OPTIONS]\n",
|
|
argv[0]);
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "-a Enable authentication\n");
|
|
fprintf(stderr, "-c Enable CGI\n");
|
|
fprintf(stderr, "-d Enable directory listing\n");
|
|
fprintf(stderr, "-k No default keepalive with HTTP/1.1\n");
|
|
fprintf(stderr, "-p Append port to hostname directory\n");
|
|
fprintf(stderr, "-r Enable symlink redirection\n");
|
|
fprintf(stderr, "-v Print version and exit\n");
|
|
return 69;
|
|
}
|
|
}
|
|
}
|
|
|
|
setbuffer(stdout, stdout_buf, sizeof stdout_buf);
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
get_ucspi_env();
|
|
|
|
handlenext:
|
|
|
|
/* This is cancelled by later calls to alarm */
|
|
alarm(READTIMEOUT);
|
|
|
|
headerlen = sizeof headerbuf;
|
|
/* Clear static variables. This is such a kludge. I should fix it. */
|
|
read_header(NULL, NULL, NULL);
|
|
switch (read_header(stdin, headerbuf, &headerlen)) {
|
|
case -1:
|
|
return 0;
|
|
break;
|
|
case 0:
|
|
badrequest(400, "Bad Request", "Header too long");
|
|
break;
|
|
}
|
|
|
|
if (headerlen < 10)
|
|
badrequest(400, "Bad Request", "That does not look like HTTP");
|
|
|
|
if (!memcmp(headerbuf, "GET /", 5)) {
|
|
method = GET;
|
|
path = headerbuf + 4;
|
|
} else if (!memcmp(headerbuf, "POST /", 6)) {
|
|
method = POST;
|
|
path = headerbuf + 5;
|
|
} else if (!memcmp(headerbuf, "HEAD /", 6)) {
|
|
method = HEAD;
|
|
path = headerbuf + 5;
|
|
} else
|
|
badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
|
|
|
|
url = path;
|
|
|
|
{
|
|
/*
|
|
* 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");
|
|
}
|
|
if (httpversion > 0) {
|
|
keepalive = dokeepalive;
|
|
} else {
|
|
keepalive = 0;
|
|
}
|
|
|
|
/*
|
|
* demangle path in-place
|
|
*/
|
|
{
|
|
register char *tmp,
|
|
*d;
|
|
for (tmp = d = url; *tmp; ++tmp) {
|
|
if (*tmp == '?') {
|
|
args = tmp + 1;
|
|
break;
|
|
}
|
|
if (*tmp == ' ')
|
|
break;
|
|
if (*tmp == '%') {
|
|
int a,
|
|
b;
|
|
a = fromhex(tmp[1]);
|
|
b = fromhex(tmp[2]);
|
|
if (a >= 0 && b >= 0) {
|
|
*d = (a << 4) + b;
|
|
tmp += 2;
|
|
} else
|
|
*d = *tmp;
|
|
} else
|
|
*d = *tmp;
|
|
if (d > url + 1 && *d == '/' && d[-1] == ':'
|
|
&& d[-2] == '/')
|
|
d -= 2;
|
|
if (d > url && *d == '/' && d[-1] == '/')
|
|
--d;
|
|
if (d > url && *d == '.' && d[-1] == '/')
|
|
*d = ':';
|
|
++d;
|
|
}
|
|
*d = 0;
|
|
}
|
|
|
|
strncpy(rpath, url, sizeof rpath);
|
|
}
|
|
|
|
{
|
|
char *tmp;
|
|
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(headerbuf, headerlen, "Cookie");
|
|
auth_type = header(headerbuf, headerlen, "Authorization");
|
|
if (method == POST) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
port = getenv("TCPLOCALPORT");
|
|
if (!port)
|
|
port = "80";
|
|
{
|
|
char *Buf;
|
|
int i;
|
|
host = header(headerbuf, headerlen, "Host");
|
|
if (!host)
|
|
i = 100;
|
|
else
|
|
i = strlen(host) + 7;
|
|
Buf = alloca(i);
|
|
if (!host) {
|
|
char *ip = getenv("TCPLOCALIP");
|
|
if (!ip)
|
|
ip = "127.0.0.1";
|
|
if (strlen(ip) + strlen(port) > 90)
|
|
exit(101);
|
|
if (portappend) {
|
|
sprintf(Buf, "%s:%s", ip, port);
|
|
} else {
|
|
strcpy(Buf, ip);
|
|
}
|
|
host = Buf;
|
|
} else {
|
|
char *colon = strchr(host, ':');
|
|
|
|
if (portappend && !colon) {
|
|
sprintf(Buf, "%s:%s", host, port);
|
|
host = Buf;
|
|
} else if (!portappend && colon) {
|
|
*colon = '\0';
|
|
}
|
|
}
|
|
for (i = strlen(host); i >= 0; --i)
|
|
if ((host[i] = tolower(host[i])) == '/')
|
|
hostb0rken:
|
|
badrequest(400, "Bad Request", "Invalid host header");
|
|
if (host[0] == '.')
|
|
goto hostb0rken;
|
|
if (keepalive > 0) {
|
|
if ((rootdir = open(".", O_RDONLY)) < 0)
|
|
keepalive = -1;
|
|
}
|
|
if (chdir(host)) {
|
|
if (redirect) {
|
|
char symlink[1024];
|
|
int linklen;
|
|
if ((linklen =
|
|
readlink(host, symlink, sizeof symlink)) > 0) {
|
|
/*
|
|
* it is a broken symlink. Do a redirection
|
|
*/
|
|
redirectboilerplate();
|
|
if (symlink[0] == '=') {
|
|
fwrite(symlink + 1, linklen - 1, 1, stdout);
|
|
} else {
|
|
fwrite(symlink, linklen, 1, stdout);
|
|
while (url[0] == '/')
|
|
++url;
|
|
fputs(url, stdout);
|
|
}
|
|
retcode = 301;
|
|
fputs("\r\n\r\n", stdout);
|
|
dolog(0);
|
|
fflush(stdout);
|
|
exit(0);
|
|
}
|
|
}
|
|
if (chdir("default") && argc < 2) {
|
|
badrequest(404, "Not Found",
|
|
"This host is not served here.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (doauth) {
|
|
char *auth_script = ".http-auth";
|
|
struct stat st;
|
|
|
|
if (!stat(auth_script, &st)) {
|
|
pid_t child;
|
|
const char *authorization;
|
|
|
|
authorization = header(headerbuf, headerlen, "Authorization");
|
|
child = fork();
|
|
if (child < 0) {
|
|
badrequest(500, "Internal Server Error",
|
|
"Server Resource problem.");
|
|
} else if (child == 0) {
|
|
dup2(2, 1);
|
|
setenv("HTTP_AUTHORIZATION", authorization, 1);
|
|
execl(auth_script, auth_script, host, url, NULL);
|
|
exit(1);
|
|
} else {
|
|
int status;
|
|
pid_t childr;
|
|
|
|
while ((childr = waitpid(child, &status, 0)) < 0
|
|
&& errno == EINTR);
|
|
if (childr != child)
|
|
badrequest(500, "Internal Server Error",
|
|
"Server system problem.");
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
|
|
retcode = 401;
|
|
dolog(0);
|
|
fputs("HTTP/1.0 401 Authorization Required\r\n"
|
|
"WWW-Authenticate: Basic realm=\"", stdout);
|
|
fputs(host, stdout);
|
|
fputs("\"\r\nConnection: close\r\n\r\n"
|
|
"Access to this site is restricted.\r\n"
|
|
"Please provide credentials.\r\n", stdout);
|
|
fflush(stdout);
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nurl = url + strlen(url);
|
|
if (nurl > url)
|
|
--nurl;
|
|
if (*nurl == '/') {
|
|
int i;
|
|
nurl = alloca(strlen(url) + 12);
|
|
i = sprintf(nurl, "%sindex.html", url);
|
|
url = nurl;
|
|
nurl = url + i;
|
|
}
|
|
nurl -= 3;
|
|
|
|
if (docgi) {
|
|
char *tmp,
|
|
*pathinfo;
|
|
|
|
pathinfo = 0;
|
|
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;
|
|
pathinfo = tmp;
|
|
}
|
|
if (findcgi(nurl)) {
|
|
int i;
|
|
|
|
if ((method == HEAD))
|
|
badrequest(400, "Bad Request",
|
|
"Illegal HTTP method for Gateway call.");
|
|
cork(1);
|
|
for (i = nurl - url; i > -1; --i) {
|
|
if ((nurl[0] == '/') && (nurl[1] == 'n')
|
|
&& (nurl[2] == 'p') && (nurl[3] == 'h')
|
|
&& (nurl[4] == '-'))
|
|
start_cgi(1, pathinfo, envp); /* start a NPH-CGI
|
|
*/
|
|
--nurl;
|
|
}
|
|
indexcgi:
|
|
start_cgi(0, pathinfo, envp); /* start a CGI */
|
|
}
|
|
}
|
|
|
|
{
|
|
int fd;
|
|
if ((fd = doit(headerbuf, headerlen, url)) >= 0) {
|
|
/*
|
|
* file was there
|
|
*/
|
|
retcode = 200;
|
|
dolog(st.st_size);
|
|
if (rangestart || rangeend != st.st_size)
|
|
fputs("HTTP/1.0 206 Partial Content\r\n", stdout);
|
|
else
|
|
fputs("HTTP/1.0 200 OK\r\n", stdout);
|
|
printf("Server: %s\r\n", FNORD);
|
|
printf("Content-Type: %s\r\n", getmimetype(url));
|
|
switch (keepalive) {
|
|
case -1:
|
|
fputs("Connection: close\r\n", stdout);
|
|
break;
|
|
case 1:
|
|
fputs("Connection: Keep-Alive\r\n", stdout);
|
|
break;
|
|
}
|
|
printf("Content-Length: %llu\r\n",
|
|
(unsigned long long) (rangeend - rangestart));
|
|
{
|
|
/*
|
|
* glibc's gmtime parses tzinfo, resulting in 9
|
|
* additional syscalls. uclibc doesn't do this.
|
|
* I presume dietlibc doesn't either.
|
|
*/
|
|
struct tm *x = gmtime(&st.st_mtime);
|
|
/*
|
|
* "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);
|
|
}
|
|
if (rangestart || rangeend != st.st_size) {
|
|
printf
|
|
("Accept-Ranges: bytes\r\nContent-Range: bytes %llu-%llu/%llu\r\n",
|
|
(unsigned long long) rangestart,
|
|
(unsigned long long) rangeend - 1,
|
|
(unsigned long long) st.st_size);
|
|
}
|
|
fputs("\r\n", stdout);
|
|
|
|
for (; post_len; post_len -= 1) {
|
|
getchar();
|
|
}
|
|
|
|
if (method == GET || method == POST) {
|
|
int ret;
|
|
|
|
alarm(WRITETIMEOUT);
|
|
ret = serve_static_data(fd);
|
|
alarm(0);
|
|
|
|
switch (ret) {
|
|
case 0:
|
|
break;
|
|
case -1:
|
|
goto error500;
|
|
case 1:
|
|
return 1;
|
|
}
|
|
cork(0);
|
|
if (keepalive > 0) {
|
|
close(fd);
|
|
fchdir(rootdir);
|
|
close(rootdir);
|
|
goto handlenext;
|
|
}
|
|
exit(0);
|
|
error500:
|
|
retcode = 500;
|
|
} else {
|
|
fflush(stdout);
|
|
}
|
|
} else {
|
|
retcode = 404;
|
|
}
|
|
}
|
|
switch (retcode) {
|
|
case 404:
|
|
{
|
|
char *space = alloca(strlen(url) + 2);
|
|
|
|
if (handleindexcgi(url, path, space))
|
|
goto indexcgi;
|
|
handleredirect(url, path);
|
|
if (dirlist) {
|
|
handledirlist(path);
|
|
}
|
|
badrequest(404, "Not Found", "No such file or directory.");
|
|
}
|
|
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 0;
|
|
}
|