eris/eris.c

1026 lines
22 KiB
C
Raw Normal View History

2012-02-13 20:54:27 -07:00
/*
* simple httpd to be started from tcpserver
*/
2011-08-16 14:36:11 -06:00
#define _FILE_OFFSET_BITS 64
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
2012-03-07 18:10:16 -07:00
#include <sys/types.h>
2011-08-16 14:36:11 -06:00
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
2012-02-13 20:54:27 -07:00
#include <stdio.h>
2011-08-16 14:36:11 -06:00
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <grp.h>
#include <errno.h>
2012-02-23 22:53:26 -07:00
#include <sys/select.h>
2011-08-16 14:36:11 -06:00
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <dirent.h>
#include <limits.h>
2012-10-30 17:45:19 -06:00
#include "strings.h"
#include "mime.h"
#include "timerfc.h"
2012-10-30 19:10:55 -06:00
#include "version.h"
2012-10-30 17:45:19 -06:00
2012-02-29 17:22:09 -07:00
#ifdef __linux__
# include <sys/sendfile.h>
#else
# define sendfile(a, b, c, d) -1
#endif
2012-02-23 22:53:26 -07:00
#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif
2012-02-13 20:54:27 -07:00
/*
* 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)
2012-02-17 17:50:05 -07:00
#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)
2012-02-17 17:50:05 -07:00
#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v)
2012-02-29 17:22:09 -07:00
/* Wait this long (seconds) for a valid HTTP request */
2012-02-17 08:24:04 -07:00
#define READTIMEOUT 2
2011-08-16 14:36:11 -06:00
2012-03-13 20:40:28 -06:00
/* Wait this long (seconds) for a non-file send to complete */
#define WRITETIMEOUT 10
/* Quit if we can't write at least this many bytes per second */
#define MIN_WRITE_RATE 2560
2011-08-16 14:36:11 -06:00
2012-02-29 17:22:09 -07:00
/* Wait this long for CGI to complete */
#define CGI_TIMEOUT (5*60)
2011-08-16 14:36:11 -06:00
2012-03-13 20:40:28 -06:00
/* How long each sendfile call can take */
#define SENDFILE_TIMEOUT ((int)(SIZE_MAX / MIN_WRITE_RATE))
2012-02-29 17:22:09 -07:00
/* Maximum size of a request header (the whole block) */
#define MAXHEADERLEN 8192
2011-08-16 14:36:11 -06:00
2012-02-29 17:22:09 -07:00
/* Maximum size of a request line */
#define MAXREQUESTLEN 2048
2011-08-16 14:36:11 -06:00
2012-03-06 22:10:24 -07:00
/* Maximum number of header fields */
#define MAXHEADERFIELDS 60
2012-10-30 17:45:19 -06:00
#define BUFFER_SIZE 8192
2012-02-23 22:53:26 -07:00
/*
2012-02-29 17:22:09 -07:00
* Options
2012-02-23 22:53:26 -07:00
*/
2013-02-11 11:28:56 -07:00
int doauth = 0;
int docgi = 0;
int doidx = 0;
int nochdir = 0;
int redirect = 0;
int portappend = 0;
2014-12-08 14:26:48 -07:00
char *connector = NULL;
2013-02-11 11:28:56 -07:00
2012-02-29 17:22:09 -07:00
/* Variables that persist between requests */
2013-02-11 10:59:16 -07:00
int cwd;
int keepalive = 0;
char *remote_addr = NULL;
char *remote_ident = NULL;
2012-02-29 17:22:09 -07:00
2012-03-07 18:10:16 -07:00
/*
* Things that are really super convenient to have globally.
* These could be put into a struct for a threading version
* of eris.
*/
2014-12-08 14:26:48 -07:00
enum { GET, POST, HEAD, CONNECT } method;
2012-02-29 17:22:09 -07:00
char *host;
char *user_agent;
char *refer;
char *path;
int http_version;
2012-03-07 20:55:27 -07:00
char *content_type;
2012-03-07 18:10:16 -07:00
size_t content_length;
off_t range_start, range_end;
time_t ims;
2011-08-16 14:36:11 -06:00
2012-02-23 22:53:26 -07:00
#define BUFFER_SIZE 8192
2012-02-24 17:03:09 -07:00
2012-02-29 17:22:09 -07:00
/** Log a request */
2012-10-30 17:45:19 -06:00
void
2012-02-29 17:22:09 -07:00
dolog(int code, off_t len)
2013-02-11 10:59:16 -07:00
{ /* write a log line to stderr */
sanitize(host);
sanitize(user_agent);
sanitize(refer);
2012-02-13 20:54:27 -07:00
2013-02-11 10:59:16 -07:00
fprintf(stderr, "%s %d %lu %s %s %s %s\n",
remote_addr, code, (unsigned long) len, host, user_agent, refer, path);
2011-08-16 14:36:11 -06:00
}
2012-03-07 18:10:16 -07:00
void
header(unsigned int code, const char *httpcomment)
{
2013-02-11 10:59:16 -07:00
printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment);
2013-02-11 11:28:56 -07:00
printf("Server: %s\r\n", FNORD);
2013-02-11 10:59:16 -07:00
printf("Connection: %s\r\n", keepalive?"keep-alive":"close");
2013-02-11 11:28:56 -07:00
2012-03-07 18:10:16 -07:00
}
void
eoh()
{
2013-02-11 10:59:16 -07:00
printf("\r\n");
2012-03-07 18:10:16 -07:00
}
2012-02-13 20:54:27 -07:00
/*
* output an error message and exit
*/
2012-10-30 17:45:19 -06:00
void
2012-02-13 20:54:27 -07:00
badrequest(long code, const char *httpcomment, const char *message)
{
2013-02-11 10:59:16 -07:00
size_t msglen = 0;
keepalive = 0;
header(code, httpcomment);
if (message) {
msglen = (strlen(message) * 2) + 15;
2014-10-24 22:27:21 -06:00
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n",
2013-02-11 10:59:16 -07:00
(unsigned long) msglen);
printf("<title>%s</title>%s", message, message);
}
printf("\r\n");
fflush(stdout);
dolog(code, msglen);
exit(0);
2011-08-16 14:36:11 -06:00
}
void
env(const char *k, const char *v)
{
2013-02-11 10:59:16 -07:00
if (v) {
setenv(k, v, 1);
}
}
2012-03-07 22:16:02 -07:00
2012-03-07 18:10:16 -07:00
void
not_found()
2012-02-13 20:54:27 -07:00
{
2013-02-11 10:59:16 -07:00
char msg[] = "The requested URL does not exist here.";
header(404, "Not Found");
printf("Content-Type: text/html\r\n");
printf("Content-Length: %lu\r\n", (unsigned long) sizeof msg);
printf("\r\n");
printf("%s\n", msg); /* sizeof msg includes the NULL */
dolog(404, sizeof msg);
fflush(stdout);
2011-08-16 14:36:11 -06:00
}
2012-05-16 12:02:51 -06:00
char *
proto_getenv(char *proto, char *name)
{
2013-02-11 10:59:16 -07:00
char buf[80];
2012-05-16 12:02:51 -06:00
2013-02-11 10:59:16 -07:00
snprintf(buf, sizeof buf, "%s%s", proto, name);
return getenv(buf);
2012-05-16 12:02:51 -06:00
}
2012-02-29 17:22:09 -07:00
void
get_ucspi_env()
2012-02-13 20:54:27 -07:00
{
2013-02-11 10:59:16 -07:00
char *ucspi = getenv("PROTO");
if (ucspi) {
char *p;
/* Busybox, as usual, has the right idea */
if ((p = proto_getenv(ucspi, "REMOTEADDR"))) {
remote_addr = strdup(p);
} else {
char *ip = proto_getenv(ucspi, "REMOTEIP");
char *port = proto_getenv(ucspi, "REMOTEPORT");
2015-02-26 15:47:37 -07:00
if (! ip) {
// stunnel
ip = getenv("REMOTE_HOST");
port = getenv("REMOTE_PORT");
}
2013-02-11 10:59:16 -07:00
if (ip) {
char buf[80];
snprintf(buf, sizeof buf, "%s:%s", ip, port);
remote_addr = strdup(buf);
}
}
if ((p = proto_getenv(ucspi, "REMOTEINFO"))) {
remote_ident = strdup(p);
}
}
2012-02-29 17:22:09 -07:00
}
2012-02-17 17:50:05 -07:00
2012-02-29 17:22:09 -07:00
void
parse_options(int argc, char *argv[])
{
2013-02-11 10:59:16 -07:00
int opt;
2014-12-08 14:26:48 -07:00
while (-1 != (opt = getopt(argc, argv, "acdhkpro:v."))) {
2013-02-11 10:59:16 -07:00
switch (opt) {
case 'a':
doauth = 1;
break;
case 'c':
docgi = 1;
break;
case 'd':
doidx = 1;
break;
case '.':
nochdir = 1;
break;
case 'p':
portappend = 1;
break;
case 'r':
redirect = 1;
break;
2014-12-08 14:26:48 -07:00
case 'o':
connector = optarg;
break;
2013-02-11 10:59:16 -07:00
case 'v':
2013-02-11 11:28:56 -07:00
printf("%s\n", FNORD);
2013-02-11 10:59:16 -07:00
exit(0);
case 'h':
default:
fprintf(stderr, "Usage: %s [OPTIONS]\n",
argv[0]);
fprintf(stderr, "\n");
2014-12-08 14:26:48 -07:00
fprintf(stderr, "-a Enable authentication\n");
fprintf(stderr, "-c Enable CGI\n");
fprintf(stderr, "-d Enable directory listing\n");
fprintf(stderr, "-. Serve out of ./ (no vhosting)\n");
fprintf(stderr, "-p Append port to hostname directory\n");
fprintf(stderr, "-r Enable symlink redirection\n");
fprintf(stderr, "-o HANDLER Path to HTTP CONNECT handler\n");
fprintf(stderr, "-v Print version and exit\n");
2013-02-11 10:59:16 -07:00
exit(69);
}
}
2012-02-29 17:22:09 -07:00
}
2012-02-13 20:54:27 -07:00
2012-10-30 17:45:19 -06:00
/*
* CGI stuff
*/
static void
sigchld(int sig)
{
while (waitpid(0, NULL, WNOHANG) > 0);
2012-02-29 17:22:09 -07:00
}
2012-02-13 20:54:27 -07:00
2012-10-30 17:45:19 -06:00
static void
sigalarm_cgi(int sig)
{
/* send this out regardless of whether we've already sent a header,
* to maybe help with debugging */
badrequest(504, "Gateway Timeout", "The CGI is being too slow.");
}
static void
cgi_child(const char *relpath)
{
env("GATEWAY_INTERFACE", "CGI/1.1");
env("SERVER_SOFTWARE", FNORD);
env("REQUEST_URI", path);
env("SERVER_NAME", host);
env("SCRIPT_NAME", relpath);
env("REMOTE_ADDR", remote_addr);
env("REMOTE_IDENT", remote_ident);
if (content_length) {
char cl[20];
snprintf(cl, sizeof cl, "%llu", (unsigned long long) content_length);
env("CONTENT_LENGTH", cl);
env("CONTENT_TYPE", content_type);
}
2012-10-30 19:10:55 -06:00
/* Try to change to CGI's directory */
2012-10-30 17:45:19 -06:00
{
char *delim = strrchr(relpath, '/');
if (delim) {
*delim = '\0';
2012-10-30 19:10:55 -06:00
if (0 == chdir(relpath)) {
relpath = delim + 1;
}
2012-10-30 17:45:19 -06:00
}
}
execl(relpath, relpath, NULL);
exit(1);
}
void
cgi_parent(int cin, int cout, int passthru)
{
char cgiheader[BUFFER_SIZE];
size_t cgiheaderlen = 0;
FILE *cinf = fdopen(cin, "rb");
size_t size = 0;
int header_sent = 0;
int code = 200;
fcntl(cin, F_SETFL, O_NONBLOCK);
signal(SIGCHLD, sigchld);
signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
2014-10-24 22:27:21 -06:00
signal(SIGALRM, sigalarm_cgi);
2012-10-30 17:45:19 -06:00
while (1) {
int nfds;
fd_set rfds, wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(cin, &rfds);
nfds = cin;
if (content_length) {
/* have post data */
FD_SET(cout, &wfds);
if (cout > nfds) {
nfds = cout;
}
} else if (cout >= 0) {
close(cout); /* no post data */
cout = -1;
}
2014-10-24 22:27:21 -06:00
alarm(CGI_TIMEOUT);
2012-10-30 17:45:19 -06:00
if (-1 == select(nfds+1, &rfds, &wfds, NULL, NULL)) {
break;
}
if (FD_ISSET(cin, &rfds)) {
if (passthru) {
/* Pass everything through verbatim */
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);
/* Naively assume the CGI knows best about sending stuff */
fflush(stdout);
size += len;
} else {
/* Interpret header fields */
size_t readlen = (sizeof cgiheader) - cgiheaderlen;
if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) {
/* EOF or error */
badrequest(500, "CGI Error", "CGI output too weird");
}
cgiheaderlen = strlen(cgiheader);
if ('\n' == cgiheader[cgiheaderlen - 1]) {
/* We read a whole line */
size_t len;
char *val;
len = extract_header_field(cgiheader, &val, 0);
if (! len) {
/* We've read the entire header block */
passthru = 1;
eoh();
} else {
if (! header_sent) {
if (! strcasecmp(cgiheader, "Location")) {
header(302, "CGI Redirect");
printf("%s: %s\r\n\r\n", cgiheader, val);
dolog(302, 0);
exit(0);
} else if (! strcasecmp(cgiheader, "Status")) {
2014-06-23 16:15:32 -06:00
char *txt;
2012-10-30 17:45:19 -06:00
2014-06-23 16:15:32 -06:00
if (val) {
code = (int)strtol(val, &txt, 10);
} else {
code = 0;
}
if (code < 100) {
header(500, "Internal Error");
printf("CGI returned Status: %d\n", code);
dolog(500, 0);
exit(0);
}
for (; *txt == ' '; txt += 1);
2012-10-30 17:45:19 -06:00
header(code, txt);
} else {
header(200, "OK");
printf("Pragma: no-cache\r\n");
}
header_sent = 1;
}
printf("%s: %s\r\n", cgiheader, val);
cgiheaderlen = 0;
}
}
}
} else if (FD_ISSET(cout, &wfds)) {
/*
* write to cgi the post data
*/
if (content_length) {
size_t len;
char buf[BUFFER_SIZE];
size_t nmemb = min(BUFFER_SIZE, content_length);
2012-10-30 19:10:55 -06:00
char *p = buf;
2012-10-30 17:45:19 -06:00
len = fread(buf, 1, nmemb, stdin);
if (len < 1) {
break;
}
content_length -= len;
2012-10-30 19:10:55 -06:00
while (len > 0) {
size_t wlen = write(cout, p, len);
if (wlen == -1) {
break;
}
len -= wlen;
p += wlen;
}
2012-10-30 17:45:19 -06:00
} else {
close(cout);
}
}
}
fflush(stdout);
2014-06-23 16:15:32 -06:00
dolog(code, size);
2012-10-30 17:45:19 -06:00
}
void
serve_cgi(char *relpath)
{
int pid;
int cin[2];
int cout[2];
if (pipe(cin) || pipe(cout)) {
badrequest(500, "Internal Server Error", "Server Resource problem.");
}
pid = fork();
if (-1 == pid) {
badrequest(500, "Internal Server Error", "Unable to fork.");
}
if (pid) {
close(cin[1]);
close(cout[0]);
/* Eris is not this smart yet */
keepalive = 0;
cgi_parent(cin[0], cout[1], 0);
exit(0);
} else {
close(cwd);
close(cout[1]);
close(cin[0]);
dup2(cout[0], 0);
dup2(cin[1], 1);
close(cout[0]);
close(cin[1]);
cgi_child(relpath);
}
}
/*
* Main HTTPd
*/
2012-03-07 18:10:16 -07:00
2014-06-23 16:15:32 -06:00
ssize_t
2012-03-07 18:10:16 -07:00
fake_sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
{
2013-02-11 10:59:16 -07:00
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: %m (req %s). Dying.\n", path);
exit(0);
}
l -= m;
}
2014-06-23 16:15:32 -06:00
return l;
2012-03-07 18:10:16 -07:00
}
void
serve_file(int fd, char *filename, struct stat *st)
{
2013-02-11 10:59:16 -07:00
off_t len, remain;
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
if (method == POST) {
badrequest(405, "Method Not Supported", "POST is not supported by this URL");
}
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
if (st->st_mtime <= ims) {
header(304, "Not Changed");
2013-02-11 11:28:56 -07:00
dolog(304, 0);
2013-02-11 10:59:16 -07:00
eoh();
return;
}
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
header(200, "OK");
printf("Content-Type: %s\r\n", getmimetype(filename));
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
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);
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
{
struct tm *tp;
char buf[40];
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
tp = gmtime(&(st->st_mtime));
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S GMT", tp);
printf("Last-Modified: %s\r\n", buf);
}
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
eoh();
fflush(stdout);
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
if (method == HEAD) {
return;
}
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
for (remain = len; remain; ) {
size_t count = min(remain, SIZE_MAX);
ssize_t sent;
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
alarm(SENDFILE_TIMEOUT);
sent = sendfile(1, fd, &range_start, count);
if (-1 == sent) {
2014-06-23 16:15:32 -06:00
sent = fake_sendfile(1, fd, &range_start, count);
2013-02-11 10:59:16 -07:00
}
remain -= sent;
}
2012-03-07 18:10:16 -07:00
2013-02-11 10:59:16 -07:00
dolog(200, len);
2012-03-07 18:10:16 -07:00
}
void
serve_idx(int fd, char *path)
{
2013-02-11 10:59:16 -07:00
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);
2013-02-11 11:28:56 -07:00
printf("</h1><pre>\n");
2013-02-11 10:59:16 -07:00
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>");
2012-03-07 18:10:16 -07:00
2013-02-11 11:28:56 -07:00
dolog(200, 0);
2012-03-07 18:10:16 -07:00
}
void
find_serve_file(char *relpath)
{
2013-02-11 10:59:16 -07:00
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/\r\n", 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;
env("PATH_INFO", p);
*p = 0;
if (! stat(relpath, &st)) {
close(fd);
return serve_cgi(relpath);
}
}
}
return not_found();
}
2012-03-07 18:10:16 -07:00
}
2012-02-17 17:50:05 -07:00
2012-02-29 17:22:09 -07:00
void
handle_request()
{
2013-02-11 10:59:16 -07:00
char request[MAXREQUESTLEN];
char fspath[PATH_MAX];
char buf[MAXHEADERLEN];
char *p;
/* Initialize globals */
host = NULL;
user_agent = NULL;
refer = NULL;
path = NULL;
range_start = 0;
range_end = 0;
content_type = NULL;
content_length = 0;
ims = 0;
alarm(READTIMEOUT);
/* Read request line first */
request[0] = 0;
if (NULL == fgets(request, sizeof request, stdin)) {
/* They must have hung up! */
exit(0);
}
if (!strncmp(request, "GET /", 5)) {
method = GET;
2014-12-08 14:26:48 -07:00
p = request + 4;
2013-02-11 10:59:16 -07:00
} else if (!strncmp(request, "POST /", 6)) {
method = POST;
2014-12-08 14:26:48 -07:00
p = request + 5;
2013-02-11 10:59:16 -07:00
} else if (!strncmp(request, "HEAD /", 6)) {
method = HEAD;
2014-12-08 14:26:48 -07:00
p = request + 5;
} else if (connector && !strncmp(request, "CONNECT ", 8)) {
method = CONNECT;
p = request + 8;
2013-02-11 10:59:16 -07:00
} else {
/* This also handles the case where fgets does nothing */
badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
}
if (docgi) {
2014-12-08 14:37:54 -07:00
p[-1] = 0;
2013-02-11 10:59:16 -07:00
env("REQUEST_METHOD", request);
}
/* Interpret path into fspath. */
2014-12-08 14:26:48 -07:00
path = p;
2013-02-11 10:59:16 -07:00
{
char *fsp = fspath;
char *query_string = NULL;
*(fsp++) = '.';
for (; *p != ' '; p += 1) {
2013-02-14 16:39:14 -07:00
char c = *p;
switch (c) {
case 0:
badrequest(413, "Request Entity Too Large", "The HTTP request was too long");
case '\n':
badrequest(505, "Version Not Supported", "HTTP/0.9 not supported");
case '?':
query_string = p + 1;
break;
case '%':
if ((! query_string) && p[1] && p[2]) {
int a = fromhex(p[1]);
int b = fromhex(p[2]);
if ((a >= 0) && (b >= 0)) {
c = (a << 4) | b;
p += 2;
2013-02-11 10:59:16 -07:00
}
2013-02-14 16:39:14 -07:00
}
break;
}
2013-02-11 10:59:16 -07:00
2013-02-14 16:39:14 -07:00
if ((! query_string) && (fsp - fspath + 1 < sizeof fspath)) {
*(fsp++) = c;
2013-02-11 10:59:16 -07:00
}
}
*fsp = 0;
/* Change "/." to "/:" to keep "hidden" files such and prevent directory traversal */
while ((fsp = strstr(fspath, "/."))) {
*(fsp+1) = ':';
}
*(p++) = 0; /* NULL-terminate path */
if (docgi && query_string) {
env("QUERY_STRING", query_string);
}
}
http_version = -1;
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) {
keepalive = 1;
} else {
keepalive = 0;
}
if (docgi) {
p[8] = 0;
env("SERVER_PROTOCOL", p);
}
/* Read header fields */
{
char *base = buf;
char *lastchar = base + (sizeof buf) - 2;
int nheaders = 0;
*lastchar = 0;
while (1) {
char *cgi_name = base;
char *p;
int plen = (sizeof buf) - (base - buf);
char *name, *val;
size_t len;
/* 40 is totally arbitrary here. */
if (plen < 40) {
badrequest(431, "Request Header Too Large", "The HTTP header block was too large");
}
if (nheaders++ >= MAXHEADERFIELDS) {
badrequest(431, "Request Header Too Large", "Too many HTTP Headers");
}
strcpy(cgi_name, "HTTP_");
plen -= 5;
p = cgi_name + 5;
if (NULL == fgets(p, plen, stdin)) {
badrequest(500, "OS Error", "OS error reading headers");
}
if (*lastchar) {
badrequest(431, "Request Header Too Large", "An HTTP header field was too large");
}
len = extract_header_field(p, &val, 1);
if (! len) {
/* blank line */
break;
}
if (! val) {
badrequest(400, "Invalid header", "Unable to parse header block");
}
name = p;
/* Set up CGI environment variables */
if (docgi) {
env(cgi_name, val);
}