start CGI (does not compile)

This commit is contained in:
Neale Pickett 2012-03-07 20:55:27 -07:00
parent ae8112f5c5
commit c5550f134b
6 changed files with 610 additions and 11 deletions

View File

@ -4,7 +4,7 @@ CFLAGS = -DFNORD='"eris/$(VERSION)"' -Wall -Werror
all: eris all: eris
eris: eris.c strings.c mime.c time.c eris: eris.c strings.c mime.c time.c cgi.c
$(CC) $(CFLAGS) -o $@ $< $(CC) $(CFLAGS) -o $@ $<
test: eris test: eris

179
cgi.c Normal file
View File

@ -0,0 +1,179 @@
void
sigchld(int sig)
{
while (waitpid(0, NULL, WNOHANG) > 0);
}
static void
cgi_child(const char *relpath)
{
setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
setenv("SERVER_SOFTWARE", FNORD, 1);
setenv("REQUEST_URI", path, 1);
setenv("SERVER_NAME", host, 1);
setenv("SCRIPT_NAME", relpath, 1);
setenv("REMOTE_ADDR", remote_ip, 1);
setenv("REMOTE_PORT", remote_port, 1);
setenv("REMOTE_IDENT", remote_ident, 1);
setenv("CONTENT_TYPE", content_type, 1);
{
char cl[20];
snprintf(cl, sizeof cl, "%llu", (unsigned long long) content_length);
setenv("CONTENT_LENGTH", cl, 1);
}
execl(relpath, relpath, NULL);
exit(1);
}
void
cgi_parent(int cin, int cout)
{
FILE *cinf = fdopen(cin, "rb");
int passthru = nph;
fcntl(child_in, F_SETFL, O_NONBLOCK);
signal(SIGCHLD, sigchld);
signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
/* Eris is not this smart yet */
keepalive = 0;
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);
}
void
serve_cgi(char *relpath)
{
size_t size = 0;
int pid;
char buf[BUFFER_SIZE];
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]);
alarm(CHILD_TIMEOUT);
cgi_parent(cin[0], cout[1]);
alarm(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);
}
}

41
eris.c
View File

@ -45,10 +45,6 @@
#define DUMP_p(v) DUMPf("%s = %p", #v, v) #define DUMP_p(v) DUMPf("%s = %p", #v, v)
#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), 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 */ /* Wait this long (seconds) for a valid HTTP request */
#define READTIMEOUT 2 #define READTIMEOUT 2
@ -77,6 +73,7 @@ int redirect = 0;
int portappend = 0; int portappend = 0;
/* Variables that persist between requests */ /* Variables that persist between requests */
int cwd;
int keepalive = 0; int keepalive = 0;
char *remote_ip = NULL; char *remote_ip = NULL;
char *remote_port = NULL; char *remote_port = NULL;
@ -93,8 +90,8 @@ char *user_agent;
char *refer; char *refer;
char *path; char *path;
int http_version; int http_version;
char *content_type;
size_t content_length; size_t content_length;
char *query_string = NULL;
off_t range_start, range_end; off_t range_start, range_end;
time_t ims = 0; time_t ims = 0;
@ -102,6 +99,11 @@ time_t ims = 0;
#define BUFFER_SIZE 8192 #define BUFFER_SIZE 8192
char stdout_buf[BUFFER_SIZE]; char stdout_buf[BUFFER_SIZE];
#include "strings.c"
#include "mime.c"
#include "time.c"
#include "cgi.c"
/* /*
* TCP_CORK is a Linux extension to work around a TCP problem. * TCP_CORK is a Linux extension to work around a TCP problem.
* http://www.baus.net/on-tcp_cork has a good description. * http://www.baus.net/on-tcp_cork has a good description.
@ -496,6 +498,7 @@ handle_request()
path = NULL; path = NULL;
range_start = 0; range_start = 0;
range_end = 0; range_end = 0;
content_type = NULL;
content_length = 0; content_length = 0;
alarm(READTIMEOUT); alarm(READTIMEOUT);
@ -520,10 +523,16 @@ handle_request()
badrequest(405, "Method Not Allowed", "Unsupported HTTP method."); badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
} }
if (docgi) {
p[-2] = 0;
setenv("REQUEST_METHOD", p, 1);
}
/* Interpret path into fspath. */ /* Interpret path into fspath. */
path = p - 1; path = p - 1;
{ {
FILE *f = fmemopen(fspath, sizeof fspath, "w"); FILE *f = fmemopen(fspath, sizeof fspath, "w");
char *query_string = NULL;
fprintf(f, "./"); fprintf(f, "./");
for (; *p != ' '; p += 1) { for (; *p != ' '; p += 1) {
@ -561,9 +570,14 @@ handle_request()
} }
fputc(0, f); fputc(0, f);
fclose(f); fclose(f);
}
*(p++) = 0; /* NULL-terminate path */ *(p++) = 0; /* NULL-terminate path */
if (docgi && query_string) {
setenv("QUERY_STRING", query_string, 1);
}
}
http_version = -1; 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'; http_version = p[7] - '0';
@ -577,6 +591,9 @@ handle_request()
} else { } else {
keepalive = 0; keepalive = 0;
} }
if (docgi) {
setenv("SERVER_PROTOCOL", p, 1);
}
/* Read header fields */ /* Read header fields */
{ {
@ -617,7 +634,9 @@ handle_request()
} }
/* Set up CGI environment variables */ /* Set up CGI environment variables */
if (docgi) {
setenv(cgi_name, val, 1); setenv(cgi_name, val, 1);
}
/* By default, re-use buffer space */ /* By default, re-use buffer space */
base = cgi_name; base = cgi_name;
@ -632,6 +651,11 @@ handle_request()
} else if (! strcmp(name, "REFERER")) { } else if (! strcmp(name, "REFERER")) {
refer = val; refer = val;
base = name + len + 1; base = name + len + 1;
} else if (! strcmp(name, "CONTENT_TYPE")) {
content_type = val;
base = name + len + 1;
} else if (! strcmp(name, "CONTENT_LENGTH")) {
content_length = (size_t) strtoull(val, NULL, 10);
} else if (! strcmp(name, "CONNECTION")) { } else if (! strcmp(name, "CONNECTION")) {
if (! strcasecmp(val, "keep-alive")) { if (! strcasecmp(val, "keep-alive")) {
keepalive = 1; keepalive = 1;
@ -640,8 +664,6 @@ handle_request()
} }
} else if (! strcmp(name, "IF_MODIFIED_SINCE")) { } else if (! strcmp(name, "IF_MODIFIED_SINCE")) {
ims = timerfc(val); ims = timerfc(val);
} else if (! strcmp(name, "CONTENT_LENGTH")) {
content_length = (size_t) strtoull(val, NULL, 10);
} else if (! strcmp(name, "RANGE")) { } else if (! strcmp(name, "RANGE")) {
/* Range: bytes=17-23 */ /* Range: bytes=17-23 */
/* Range: bytes=23- */ /* Range: bytes=23- */
@ -700,9 +722,10 @@ handle_request()
int int
main(int argc, char *argv[], const char *const *envp) main(int argc, char *argv[], const char *const *envp)
{ {
int cwd = open(".", O_RDONLY);
parse_options(argc, argv); parse_options(argc, argv);
cwd = open(".", O_RDONLY);
setbuffer(stdout, stdout_buf, sizeof stdout_buf); setbuffer(stdout, stdout_buf, sizeof stdout_buf);
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);

65
mime.c Normal file
View File

@ -0,0 +1,65 @@
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;
}

130
strings.c Normal file
View File

@ -0,0 +1,130 @@
int
endswith(char *haystack, char *needle)
{
char *h, *n;
for (h = haystack; *h; h++);
for (n = needle; *n; n++);
if (h - haystack < n - needle) {
return 0;
}
while (n >= needle) {
if (*(n--) != *(h--)) {
return 0;
}
}
return 1;
}
/** Replace whitespace with underscores for logging */
static void
sanitize(char *s)
{
if (!s) {
return;
}
for (; *s; s += 1) {
if (isspace(*s)) {
*s = '_';
}
}
}
/** 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)
{
size_t len;
*val = NULL;
for (len = 0; buf[len]; len += 1) {
if (! *val) {
if (buf[len] == '\n') {
/* Blank line or incorrectly-formatted header */
return 0;
} else 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;
}
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;
}
void
html_esc(FILE *f, char *s)
{
for (; *s; s += 1) {
switch (*s) {
case '<':
fprintf(f, "&lt;");
break;
case '>':
fprintf(f, "&gt;");
break;
case '&':
fprintf(f, "&amp;");
break;
default:
fputc(*s, f);
break;
}
}
}
void
url_esc(FILE *f, char *s)
{
for (; *s; s += 1) {
switch (*s) {
case '%':
case 127:
case -127 ... 31:
fprintf(f, "%%%02x", *s);
break;
default:
fputc(*s, f);
break;
}
}
}

202
time.c Normal file
View File

@ -0,0 +1,202 @@
/*
* Code in this file is from mathopd <http://www.mathopd.org/>
*
* 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 const char days[] = "SunMonTueWedThuFriSat";
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
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))));
}
char *
rfctime(time_t t, char *buf)
{
struct tm *tp;
/* ntp: in glibc, this triggers a bunch of needless I/O. */
tp = gmtime(&t);
if (tp == 0) {
strcpy(buf, "?");
return buf;
}
strftime(buf, 31, "%a, %d %b %Y %H:%M:%S GMT", tp);
return buf;
}