eris/httpd.c

1678 lines
42 KiB
C
Raw Normal View History

2011-08-16 14:36:11 -06:00
/* 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/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <dirent.h>
#include <sys/mman.h>
#include <limits.h>
#include "fmt.h"
#include "buffer.h"
#include "byte.h"
#include "scan.h"
/* uncomment the following line to enable support for CGI */
// #define CGI
#ifdef CGI
/* uncomment the following line to enable support for "index.cgi"
* That is: if "index.html" is not present then look for "index.cgi" */
#define INDEX_CGI
#endif
/* the following switch will make fnord normalize the Host: HTTP header
* from "foo" to "foo:80" */
#define NORMALIZE_HOST
/* uncomment the following line to enable support for autogenerated
* directory-listings for directories without index */
/* #define DIR_LIST */
#ifdef DIR_LIST
/* uncomment the following line to enable support for system symlink
* dereferencingr.
* HOPE YOU KNOW WHAT YOU'RE LINKING !
*
* e.g.: if a file foo is a symlink to /etc/passwd and you don't have a
* chroot enviroment then the system-wide /etc/passwd is provided !!!
*
* If the symlink is dangling OR this option is not active the symlink is
* provided as a new http-uri.
*
* e.g.: is foo a symlink to /etc/passwd than the clinet gets a href to
* http://<vhost>/etc/passwd */
/* #define SYSTEM_SYMLINK_DEREF */
#endif
/* uncomment the following line to get full-host redirection.
* If a file is not found locally, and $REDIRECT_HOST is set, fnord will
* issue a redirect to strcat($REDIRECT_HOST,uri). Otherwise, if
* $REDIRECT_URI is set, fnord will issue a redirect to $REDIRECT_URI.
* Only if those fail will a 404 error be returned. */
#define OLD_STYLE_REDIRECT
/* uncomment the following line to get full-host redirection.
* if the virtual host directory/symlink is a broken symlink, fnord will
* issue a redirect. If the contents of the symlink starts with an
* equal sign ('='), fnord will throw away the URI part. */
#define REDIRECT
/* uncomment the following line to make fnord tarpit queries from
* EmailSiphon (an email harvester for spammers) */
#define TARPIT
/* uncomment the following line to make fnord chroot to the current
* working directory and drop privileges */
#define CHROOT
/* uncomment the following line to make fnord support connection
* keep-alive */
#define KEEPALIVE
/* the following is the time in seconds that fnord should wait for a
* valid HTTP request */
#define READTIMEOUT 20
/* 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 */
/* defining USE_SENDFILE enables zero-copy TCP on Linux for static
* files. I measured over 320 meg per second with apache bench over
* localhost with sendfile and keep-alive. However, sendfile does not
* work with large files and may be considered cheating ;-)
* Also, sendfile is a blocking operation. Thus, no timeout handling. */
#define USE_SENDFILE
#ifndef __linux__
#undef USE_SENDFILE
#endif
#ifdef USE_SENDFILE
#include <sys/sendfile.h>
#endif
#ifndef O_NDELAY
#define O_NDELAY O_NONBLOCK
#endif
#define USE_MMAP
#ifndef _POSIX_MAPPED_FILES
#undef USE_MMAP
#endif
enum { UNKNOWN, GET, HEAD, POST } method;
#ifdef TCP_CORK
static int corked;
#endif
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 *url; /* string between GET and HTTP/1.0, demangled */
char *ua="?"; /* user-agent */
char *refer; /* Referrer: header */
char *accept_enc; /* Accept-Encoding */
int httpversion; /* 0 == 1.0, 1 == 1.1 */
#ifdef KEEPALIVE
int keepalive=0; /* should we keep the connection alive? */
int rootdir; /* fd of root directory, so we can fchdir back for keep-alive */
#endif
#ifdef CGI
char *cookie; /* Referrer: header */
char *uri; /* copy of url before demangling */
char *content_type;
char *content_len;
char *auth_type;
char *post_miss;
unsigned long post_mlen;
unsigned long post_len=0;
#endif
#if _FILE_OFFSET_BITS == 64
static unsigned long long rangestart, rangeend; /* for ranged queries */
#define scan_range scan_ulonglong
#define buffer_putrange buffer_putulonglong
#include "scan_ulonglong.c"
#include "fmt_ulonglong.c"
#include "buffer_putulonglong.c"
#else
static unsigned long rangestart, rangeend; /* for ranged queries */
#define scan_range scan_ulong
#define buffer_putrange buffer_putulong
#endif
static const char days[] = "SunMonTueWedThuFriSat";
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
#define MAXHEADERLEN 8192
char* remote_ip;
#ifdef CGI
char* remote_port;
char* remote_ident;
#endif
static void sanitize(char* ua) { /* replace strings with underscores for logging */
int j;
for (j=0; ua[j]; ++j) if (isspace(ua[j])) ua[j]='_';
}
static int buffer_put2digits(buffer* b,unsigned int i) {
char x[2];
x[0]=(i/10)+'0';
x[1]=(i%10)+'0';
return buffer_put(b,x,2);
}
static void dolog(off_t len) { /* write a log line to stderr */
#ifdef COLF
time_t t=time(0);
struct tm* x=localtime(&t);
int l=-(timezone/60);
buffer_puts(buffer_2,remote_ip?remote_ip:"0.0.0.0");
buffer_puts(buffer_2," - - [");
buffer_put2digits(buffer_2,x->tm_mday);
buffer_puts(buffer_2,"/");
buffer_put(buffer_2,months+3*x->tm_mon,3);
buffer_puts(buffer_2,"/");
buffer_put2digits(buffer_2,(x->tm_year+1900)/100);
buffer_put2digits(buffer_2,(x->tm_year+1900)%100);
buffer_puts(buffer_2,":");
buffer_put2digits(buffer_2,x->tm_hour);
buffer_puts(buffer_2,":");
buffer_put2digits(buffer_2,x->tm_min);
buffer_puts(buffer_2,":");
buffer_put2digits(buffer_2,x->tm_sec);
buffer_puts(buffer_2,l>=0?" +":" -");
if (l<0) l=-l;
buffer_put2digits(buffer_2,l/60);
buffer_put2digits(buffer_2,l%60);
buffer_puts(buffer_2,"] \"");
switch (method) {
case GET: buffer_puts(buffer_2,"GET "); break;
case POST: buffer_puts(buffer_2,"POST "); break;
case HEAD: buffer_puts(buffer_2,"HEAD "); break;
default: buffer_puts(buffer_2,"? "); break;
}
buffer_puts(buffer_2,url);
buffer_puts(buffer_2,httpversion?" HTTP/1.1\" ":" HTTP/1.0\" ");
buffer_putulong(buffer_2,retcode);
buffer_putspace(buffer_2);
buffer_putrange(buffer_2,len);
#else
buffer_puts(buffer_2,remote_ip?remote_ip:"0.0.0.0");
buffer_putspace(buffer_2);
buffer_putulong(buffer_2,retcode);
buffer_putspace(buffer_2);
buffer_putrange(buffer_2,len);
buffer_putspace(buffer_2);
sanitize(host);
buffer_puts(buffer_2,host);
buffer_putspace(buffer_2);
sanitize(ua);
buffer_puts(buffer_2,ua);
buffer_putspace(buffer_2);
if (!refer) refer="none";
sanitize(refer);
buffer_puts(buffer_2,refer);
buffer_putspace(buffer_2);
if (url)
buffer_puts(buffer_2,url);
else
buffer_puts(buffer_2,"(null)");
#endif
buffer_puts(buffer_2,"\n");
buffer_flush(buffer_2);
}
/* output an error message and exit */
static void badrequest(long code,const char *httpcomment,const char *message) {
retcode=code;
dolog(0);
buffer_puts(buffer_1,"HTTP/1.0 ");
buffer_putulong(buffer_1,code);
buffer_putspace(buffer_1);
buffer_puts(buffer_1,httpcomment);
buffer_puts(buffer_1,"\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n");
buffer_puts(buffer_1,message);
buffer_flush(buffer_1);
exit(0);
}
#ifdef CGI
#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) {
register unsigned int i=0;
for (;i<CGIENVLEN;i++)
if (str_start(s, cgivars[i])) return 1;
return 0;
}
static unsigned int elen(register const char *const *e) {
register unsigned int i=0;
while (e[i]) i++;
return i;
}
static void do_cgi(const char* pathinfo,const char* const* envp) {
const char *method_name[]={ "?", "GET", "HEAD", "POST"};
char cgi_env_buf[MAXHEADERLEN*2+PATH_MAX+200];
register unsigned int en=elen(envp);
char *tmp=cgi_env_buf;
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;
cgi_env[3]=tmp;
tmp+=str_copy(tmp,"SERVER_NAME=");
tmp+=str_copy(tmp,host);
*tmp=0; ++tmp;
cgi_env[4]=tmp;
tmp+=str_copy(tmp,"SERVER_PORT=");
tmp+=str_copy(tmp,port);
*tmp=0; ++tmp;
cgi_env[5]=tmp;
tmp+=str_copy(tmp,"REQUEST_METHOD=");
tmp+=str_copy(tmp,method_name[method]);
*tmp=0; ++tmp;
cgi_env[6]=tmp;
tmp+=str_copy(tmp,"REQUEST_URI=");
tmp+=str_copy(tmp,uri);
*tmp=0; ++tmp;
cgi_env[7]=tmp;
tmp+=str_copy(tmp,"SCRIPT_NAME=");
tmp+=str_copy(tmp,url);
*tmp=0; ++tmp;
i=7;
if (remote_ip) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"REMOTE_ADDR=");
tmp+=str_copy(tmp,remote_ip);
*tmp=0; ++tmp;
}
if (remote_port) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"REMOTE_PORT=");
tmp+=str_copy(tmp,remote_port);
*tmp=0; ++tmp;
}
if (remote_ident) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"REMOTE_IDENT=");
tmp+=str_copy(tmp,remote_ident);
*tmp=0; ++tmp;
}
if (ua) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"HTTP_USER_AGENT=");
tmp+=str_copy(tmp,ua);
*tmp=0; ++tmp;
}
if (cookie) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"HTTP_COOKIE=");
tmp+=str_copy(tmp,cookie);
*tmp=0; ++tmp;
}
if (refer) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"HTTP_REFERER=");
tmp+=str_copy(tmp,refer);
*tmp=0; ++tmp;
}
if (accept_enc) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"HTTP_ACCEPT_ENCODING=");
tmp+=str_copy(tmp,accept_enc);
*tmp=0; ++tmp;
}
if (auth_type) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"AUTH_TYPE=");
tmp+=str_copy(tmp,auth_type);
*tmp=0; ++tmp;
}
if (content_type) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"CONTENT_TYPE=");
tmp+=str_copy(tmp,content_type);
*tmp=0; ++tmp;
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"CONTENT_LENGTH=");
tmp+=str_copy(tmp,content_len);
*tmp=0; ++tmp;
}
if (args) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"QUERY_STRING=");
tmp+=str_copy(tmp,args);
*tmp=0; ++tmp;
}
if (pathinfo) {
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"PATH_INFO=");
tmp+=str_copy(tmp,pathinfo);
*tmp=0; ++tmp;
cgi_env[++i]=tmp;
tmp+=str_copy(tmp,"PATH_TRANSLATED=");
tmp+=realpath(pathinfo,tmp)?str_len(tmp):str_copy(tmp,pathinfo);
++tmp;
}
{
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 && (args[str_chr(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;
}
i=strrchr(url,'/')-url;
strncpy(tmp,url+1,i);
tmp[i]=0;
chdir(tmp);
/* program name */
cgi_arg[0]=tmp;
tmp[0]='.';
tmp[str_copy(tmp+1,url+i)+1]=0;
/* 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 cgi_send_correct_http(const char*s,unsigned int sl) {
unsigned int i;
char ch=0;
for (i=0;i<sl;++i) {
if ((s[i]=='\r')&&(s[i+1]=='\n')) {
++i; goto out_nl;
}
else {
if (s[i]!='\n') buffer_put(buffer_1,s+i,1);
else {
out_nl: buffer_put(buffer_1,"\r\n",2);
if (ch=='\n') { ++i; break; }
}
}
ch=s[i];
}
buffer_put(buffer_1,s+i,sl-i);
}
static void start_cgi(int nph,const char* pathinfo,const char *const *envp) {
size_t size=0;
int n;
int pid;
char ibuf[8192],obuf[8192];
int fd[2],df[2];
if (pipe(fd)||pipe(df)) {
badrequest(500,"Internal Server Error","Server Resource problem.");
}
if ((pid=fork())) {
if (pid>0) {
struct pollfd pfd[2];
int nr=1;
int startup=1;
signal(SIGCHLD,cgi_child);
signal(SIGPIPE,SIG_IGN); /* NO! no signal! */
close(df[0]);
close(fd[1]);
pfd[0].fd=fd[0];
pfd[0].events=POLLIN;
pfd[0].revents=0;
pfd[1].fd=df[1];
pfd[1].events=POLLOUT;
pfd[1].revents=0;
if (post_len) ++nr; /* have post data */
else close(df[1]); /* no post data */
while(poll(pfd,nr,-1)!=-1) {
/* read from cgi */
if (pfd[0].revents&POLLIN) {
if (!(n=read(fd[0],ibuf,sizeof(ibuf)))) break;
if (n<0) goto cgi_500;
/* startup */
if (startup) {
startup=0;
if (nph) { /* NPH-CGI */
buffer_put(buffer_1,ibuf,n);
scan_ulong(ibuf+9,&retcode); /* only get error code / str_len("HTTP/x.x ")==9 */
}
else { /* CGI */
if (byte_diff(ibuf,10,"Location: ")==0) {
retcode=302;
buffer_puts(buffer_1,"HTTP/1.0 302 CGI-Redirect\r\nConnection: close\r\n");
signal(SIGCHLD,SIG_IGN);
cgi_send_correct_http(ibuf,n);
buffer_flush(buffer_1);
dolog(0);
exit(0);
}
else {
retcode=200;
buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nPragma: no-cache\r\nConnection: close\r\n");
signal(SIGCHLD,SIG_IGN);
cgi_send_correct_http(ibuf,n);
}
}
}
/* non startup */
else {
buffer_put(buffer_1,ibuf,n);
}
size+=n;
if (pfd[0].revents&POLLHUP) break;
}
/* write to cgi the post data */
else if (nr>1 && pfd[1].revents&POLLOUT) {
if (post_miss) {
write(df[1],post_miss,post_mlen);
post_miss=0;
}
else if (post_mlen<post_len) {
n=read(0,obuf,sizeof(obuf));
if (n<1) goto cgi_500;
post_mlen+=n;
write(df[1],obuf,n);
}
else {
--nr;
close(df[1]);
}
}
else if (pfd[0].revents&POLLHUP) break;
else {
cgi_500: if (startup)
badrequest(500,"Internal Server Error","Looks like the CGI crashed.");
else {
buffer_puts(buffer_1,"\n\n");
buffer_puts(buffer_1,"Looks like the CGI crashed.");
buffer_puts(buffer_1,"\n\n");
break;
}
}
}
buffer_flush(buffer_1);
dolog(size);
#ifdef TCP_CORK
{
int zero=0;
setsockopt(1,IPPROTO_TCP,TCP_CORK,&zero,sizeof(zero));
}
#endif
}
}
else {
close(df[1]);
close(fd[0]);
dup2(df[0],0);
dup2(fd[1],1);
close(df[0]);
close(fd[1]);
alarm(CGI_TIMEOUT);
do_cgi(pathinfo,envp);
}
exit(0);
}
#endif
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=str_len(hname);
int i;
char* c;
// printf("buflen %d, slen %d\n",buflen,slen);
for (i=0; i<buflen-slen-2; ++i) {
// printf("%.5s %s\n",buf+i,hname);
if (!strncasecmp(buf+i,hname,slen)) {
// printf("a %.20s\n",buf+i);
if (i && buf[i-1]!='\n') continue;
// printf("b %.20s\n",buf+i);
if (buf[i+slen]!=':' || buf[i+slen+1]!=' ') continue;
// printf("c %.20s\n",buf+i);
c=buf+i+slen+2;
i+=slen+2;
for (; i<buflen; ++i)
// printf("%c\n",buf[i]);
if (buf[i]==0 || buf[i]=='\n' || buf[i]=='\r') {
buf[i]=0;
break;
}
return c;
}
}
return 0;
}
static char* encoding=0;
static char* mimetype="text/plain";
static struct mimeentry { const char* name, *type; } mimetab[] = {
{ "html", "text/html" },
{ "htm", "text/html" },
{ "css", "text/css" },
{ "dvi", "application/x-dvi" },
{ "ps", "application/postscript" },
{ "pdf", "application/pdf" },
{ "gif", "image/gif" },
{ "png", "image/png" },
{ "jpeg", "image/jpeg" },
{ "bild", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "mpeg", "video/mpeg" },
{ "mpg", "video/mpeg" },
{ "avi", "video/x-msvideo" },
{ "mov", "video/quicktime" },
{ "qt", "video/quicktime" },
{ "mp3", "audio/mpeg" },
{ "ogg", "audio/x-oggvorbis" },
{ "wav", "audio/x-wav" },
{ "pac", "application/x-ns-proxy-autoconfig" },
{ "sig", "application/pgp-signature" },
{ "torrent", "application/x-bittorrent" },
{ "class", "application/octet-stream" },
{ "js", "application/x-javascript" },
{ "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" },
{ 0 } };
/* try to find out MIME type and content encoding.
* This is called twice, once for the actual URL and once for URL.gz.
* If the actual URL already ende with .gz, return
* application/octet-stream to make sure the client can download the
* file even if he does not support gzip encoding */
static void getmimetype(char* url,int explicit) {
char save;
int ext;
ext=str_len(url);
while (ext>0 && url[ext]!='.' && url[ext]!='/') --ext;
if (url[ext]=='.') {
++ext;
if (str_equal(url+ext,"bz2")) goto octetstream;
if (str_equal(url+ext,"gz")) {
if (!encoding) {
if (explicit) goto octetstream;
encoding="gzip";
save=url[ext-1];
url[ext-1]=0;
getmimetype(url,explicit);
url[ext-1]=save;
} else
octetstream:
mimetype="application/octet-stream";
} else {
int i;
for (i=0; mimetab[i].name; ++i)
if (str_equal(mimetab[i].name,url+ext)) {
mimetype=(char*)mimetab[i].type;
break;
}
}
}
}
static int matchcommalist(const char* needle,const char* haystack) {
/* needle: "text/html",
* haystack: the accept header, "text/html, text/plain\r\n" */
/* return nonzero if match was found */
int len=str_len(needle);
if (!byte_equal(needle,len,haystack)) return 0;
switch (haystack[len]) {
case ';': case ',': case '\r': case '\n': case 0: return 1;
}
return 0;
}
static int findincommalist(const char* needle,const char* haystack) {
const char* accept;
for (accept=haystack; accept;) {
/* format: foo/bar, */
const char *tmp=accept;
int final;
while (*tmp) {
if (*tmp==';') break; else
if (*tmp==',') break;
++tmp;
}
final=(*tmp==0 || *tmp==';');
if (matchcommalist("*/*",accept)) break;
if (matchcommalist(haystack,accept)) break;
accept=tmp+1;
if (final) return 0;
}
return 1;
}
static int parsetime(const char*c,struct tm* x) {
unsigned long tmp;
c+=scan_ulong(c,&tmp); x->tm_hour=tmp;
if (*c!=':') return -1; ++c;
c+=scan_ulong(c,&tmp); x->tm_min=tmp;
if (*c!=':') return -1; ++c;
c+=scan_ulong(c,&tmp); x->tm_sec=tmp;
if (*c!=' ') return -1;
return 0;
}
static time_t parsedate(const char*c) {
struct tm x;
int i;
unsigned long tmp;
if (!c) return (time_t)-1;
/* "Sun, 06 Nov 1994 08:49:37 GMT",
* "Sunday, 06-Nov-94 08:49:37 GMT" and
* "Sun Nov 6 08:49:37 1994" */
if (c[3]==',') c+=5; else
if (c[6]==',') c+=8; else {
c+=4;
for (i=0; i<12; ++i) {
// fprintf(stderr,"comparing %s to %.3s\n",c,months+i*3);
if (!strncasecmp(c,months+i*3,3)) {
x.tm_mon=i; break;
}
}
c+=4; if (*c==' ') ++c;
c+=scan_ulong(c,&tmp); x.tm_mday=tmp;
++c;
if (parsetime(c,&x)) return (time_t)-1;
c+=9;
c+=scan_ulong(c,&tmp); x.tm_year=tmp-1900;
goto done;
}
c+=scan_ulong(c,&tmp); x.tm_mday=tmp;
++c;
for (i=0; i<12; ++i)
if (!strncasecmp(c,months+i*3,3)) {
x.tm_mon=i; break;
}
c+=4;
c+=scan_ulong(c,&tmp);
if (tmp>1000) x.tm_year=tmp-1900; else
if (tmp<70) x.tm_year=tmp+100; else
x.tm_year=tmp;
++c;
if (parsetime(c,&x)) return (time_t)-1;
done:
x.tm_wday=x.tm_yday=x.tm_isdst=0;
return mktime(&x);
}
static struct stat st;
/* try to return a file */
static int doit(char* buf,int buflen,char* url,int explicit) {
int fd=-1;
char* accept;
time_t ims;
while (url[0]=='/') ++url;
getmimetype(url,explicit);
{
char *b=buf;
int l=buflen;
for (;;) {
char *h=header(b,l,"Accept");
if (!h) goto ok;
if (findincommalist(mimetype,h)) goto ok;
l-=(h-b)+1;
b=h+1;
}
retcode=406; goto bad;
}
ok:
if (encoding) { /* see if client accepts the encoding */
char *tmp=header(buf,buflen,"Accept-Encoding");
if (!tmp || !strstr(tmp,"gzip"))
{ retcode=406; goto bad; }
}
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 peer accepts MIME type */
/* see if the document has been changed */
ims=parsedate(header(buf,buflen,"If-Modified-Since"));
if (ims!=(time_t)-1 && st.st_mtime<=ims) { retcode=304; goto bad; }
rangestart=0; rangeend=st.st_size;
if ((accept=header(buf,buflen,"Range"))) {
/* format: "bytes=17-23", "bytes=23-" */
if (!strncmp(accept,"bytes=",6)) {
int i;
accept+=6;
i=scan_range(accept,&rangestart);
if (i) {
accept+=i;
if (*accept=='-') {
++accept;
if (*accept) {
i=scan_range(accept,&rangeend);
if (!i) 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() {
buffer_puts(buffer_1,"HTTP/1.0 301 Go Away\r\nConnection: close\r\nLocation: ");
}
static void handleredirect(const char *url,const char* origurl) {
char symlink[1024];
int len;
#ifdef OLD_STYLE_REDIRECT
char* env;
#endif
while (*url=='/') ++url;
if ((len=readlink(url,symlink,1023))>0) {
/* el-cheapo redirection */
redirectboilerplate();
buffer_put(buffer_1,symlink,len);
#ifdef OLD_STYLE_REDIRECT
fini:
#endif
retcode=301;
buffer_puts(buffer_1,"\r\n\r\n");
dolog(0);
buffer_flush(buffer_1);
exit(0);
}
#ifdef OLD_STYLE_REDIRECT
if ((env=getenv("REDIRECT_HOST"))) {
redirectboilerplate();
buffer_puts(buffer_1,env);
while (*origurl=='/') ++origurl;
buffer_puts(buffer_1,origurl);
goto fini;
} else if ((env=getenv("REDIRECT_URI"))) {
redirectboilerplate();
buffer_puts(buffer_1,env);
goto fini;
}
#endif
}
#ifdef DIR_LIST
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) {
encode_dec:
buffer_puts(buffer_1,"&#");
buffer_putulong(buffer_1,ch);
buffer_puts(buffer_1,";");
}
else if ((ch>128)||(ch<32)) {
buffer_put(buffer_1," ",1);
}
else if (ch=='"') buffer_puts(buffer_1,"&quot;");
else if (ch=='&') buffer_puts(buffer_1,"&amp;");
else if (ch=='<') buffer_puts(buffer_1,"&lt;");
else if (ch=='>') buffer_puts(buffer_1,"&gt;");
else buffer_put(buffer_1,&ch,1);
}
}
static int buffer_puthex(unsigned int i) {
unsigned int t;
char x[4];
t='0'|(i>>4)&0xf;
if (t>'9') t+=39;
i='0'|(i&0xf);
if (i>'9') i+=39;
x[0]='%';
x[1]=t;
x[2]=i;
return buffer_put(buffer_1,x,3);
}
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))
buffer_put(buffer_1,&ch,1);
else
buffer_puthex(ch);
}
}
static void handledirlist(const char*origurl) {
DIR*dir;
unsigned int nl=str_len(origurl);
const char*nurl=origurl;
url=(char*)origurl;
while (nurl[0]=='/') ++nurl;
if (nurl<=origurl) return;
nl=str_len(nurl);
if (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;
buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nConnection: close\r\n");
buffer_puts(buffer_1,"Content-Type: text/html\r\n");
buffer_puts(buffer_1,"\r\n<h3>Directory Listing: /");
hdl_encode_html(nurl,nl);
buffer_puts(buffer_1,"</h3>\n<pre>\n");
if (nl!=0) {
for (i=nl-2;i>0;--i) if (nurl[i]=='/') break;
buffer_puts(buffer_1,"<a href=\"");
buffer_puts(buffer_1,"/");
hdl_encode_uri(nurl,i);
if (i>0) buffer_puts(buffer_1,"/");
buffer_puts(buffer_1,"\">Parent directory");
buffer_puts(buffer_1,"</a>\n");
size+=40+i;
}
while(de=readdir(dir)) {
char symlink[1024];
char*p=de->d_name;
unsigned int pl,dl=str_len(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)) buffer_puts(buffer_1,"[DIR] ");
else if (S_ISLNK(st.st_mode)) {
#ifdef SYSTEM_SYMLINK_DEREF
if (stat(de->d_name,&st)) /* dangling symlink */
#endif
{
if ((pl=readlink(de->d_name,symlink,1023))<1) continue;
p=symlink;
}
buffer_puts(buffer_1,"[LNK] "); /* a symlink to something ... */
}
else if (S_ISREG(st.st_mode)) buffer_puts(buffer_1,"[TXT] ");
else continue; /* not a file we can provide -> skip */
/* write a href */
buffer_puts(buffer_1,"<a href=\"");
hdl_encode_uri(p,pl);
if (S_ISDIR(st.st_mode)) buffer_puts(buffer_1,"/"),++size;
buffer_puts(buffer_1,"\">");
if (de->d_name[0]==':') de->d_name[0]='.'; /* fnord special ... */
hdl_encode_html(de->d_name,dl);
buffer_puts(buffer_1,"</a>\n");
size+=22+(dl<<1);
}
closedir(dir);
buffer_puts(buffer_1,"</pre>\n");
buffer_flush(buffer_1);
retcode=200;
dolog(size);
exit(0);
}
}
}
#endif
#ifdef INDEX_CGI
static int handleindexcgi(const char *testurl,const char* origurl,char* space) {
unsigned int ul,ol=str_len(origurl);
char*test;
while (testurl[0]=='/') ++testurl,--ol;
ul=str_len(testurl);
if (str_diff(testurl+ol,"index.html")) return 0; /* no request for index.html */
test=space;
++test;
ul-=4;
byte_copy(test,ul,testurl);
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)='/';
url=test;
return 1; /* Wow... now start "index.cgi" */
}
#endif
static void get_ucspi_env(void) {
char* ucspi=getenv("PROTO");
if (ucspi) {
char* buf=alloca(str_len(ucspi)+20);
unsigned int tmp=str_copy(buf,ucspi);
buf[tmp+str_copy(buf+tmp,"REMOTEIP")]=0;
remote_ip=getenv(buf);
#ifdef CGI
buf[tmp+str_copy(buf+tmp,"REMOTEPORT")]=0;
remote_port=getenv(buf);
buf[tmp+str_copy(buf+tmp,"REMOTEINFO")]=0;
remote_ident=getenv(buf);
#endif
}
}
#ifdef CGI
static int findcgi(const char* c) {
return (c[0]=='.' && c[1]=='c' &&
c[2]=='g' && c[3]=='i' &&
(c[4]=='/' || c[4]==0));
}
#endif
static int serve_read_write(int fd) {
char tmp[4096];
struct pollfd duh;
time_t now,fini;
char* tmp2;
int len;
off_t todo=rangeend-rangestart;
duh.fd=1;
duh.events=POLLOUT;
if (rangestart) lseek(fd,rangestart,SEEK_SET);
while (todo>0) {
int olen;
fini=time(&now)+WRITETIMEOUT;
len=read(fd,tmp,todo>4096?4096:todo);
olen=len;
tmp2=tmp;
while (len>0) {
int written;
switch (poll(&duh,1,(fini-now)*1000)) {
case 0: if (now<fini) continue; /* fall through */
case -1: return 1; /* timeout or error */
}
if ((written=write(1,tmp2,len))<0) return -1;
len-=written;
tmp2+=written;
time(&now);
}
todo-=olen;
}
}
static int serve_mmap(int fd) {
off_t mapstart,maplen;
unsigned long mapofs;
char* map, *tmp2;
struct pollfd duh;
time_t now,fini;
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) {