diff --git a/rc/bin/rc-httpd/handlers/cgi b/rc/bin/rc-httpd/handlers/cgi new file mode 100755 index 000000000..96707630c --- /dev/null +++ b/rc/bin/rc-httpd/handlers/cgi @@ -0,0 +1,54 @@ +#!/bin/rc +fn filter_headers{ + response='HTTP/1.1 200 OK'^$cr + lines='' + done=false + while(~ $done false){ + line=`{read} + head=`{echo $line | awk '{print tolower($1)}'} + if(~ $head status:*){ + tmp=`{echo $line | awk '{$1="" ; print}'} + response='HTTP/1.1 '^$"tmp^$cr + } + if not if(~ $line '') + done=true + if not + lines=$"lines^$"line^$cr^' +' + } + echo $response + echo -n $"lines +} + +fn run_cgi { + path=$cgi_path exec $"cgi_bin $params +} + +cgi_bin=$1 +if(! ~ $cgi_bin /*){ + pwd=`{pwd} + cgi_bin=$"pwd ^ / ^ $cgi_bin +} + +cgi_dir=$*($#*) +if(! test -d $cgi_dir){ + cgi_dir=`{basename -d $cgi_dir} + cgi_dir=$"cgi_dir +} + +if(! test -d $"cgi_dir){ + error 500 + exit +} +if(! test -f $cgi_bin -x $cgi_bin){ + error 500 + exit +} +do_log 200 +builtin cd $"cgi_dir +run_cgi | { + filter_headers + emit_extra_headers + echo $cr + exec cat +} diff --git a/rc/bin/rc-httpd/handlers/dir-index b/rc/bin/rc-httpd/handlers/dir-index new file mode 100755 index 000000000..39452bfd7 --- /dev/null +++ b/rc/bin/rc-httpd/handlers/dir-index @@ -0,0 +1,111 @@ +#!/bin/rc +PATH_INFO=`{echo $PATH_INFO | urlencode -d} +full_path=$"FS_ROOT^$"PATH_INFO +full_path=$"full_path +if(! test -d $full_path){ + error 404 + exit +} +if(! test -r $full_path -x $full_path){ + error 503 + exit +} +do_log 200 +builtin cd $full_path +if(~ $"NOINDEXFILE ^ $"NOINDEX ''){ + ifile=index.htm* + if(! ~ $ifile(1) *'*'){ + PATH_INFO=$ifile(1) + FS_ROOT='' + exec serve-static + } +} +title=`{echo $SITE_TITLE | sed s,%s,^$"PATH_INFO^,} +title=$"title +lso=() +switch($2){ +case size + # ls has no option to sort by size + # could pipe it through sort, I suppose +case date + lso=-t +} +echo 'HTTP/1.1 200 OK'^$cr +emit_extra_headers +echo 'Content-type: text/html'^$cr +echo $cr +echo ' +
+"hrsize($6)" | " + print ""$7" | " + print ""$8" | " + print ""$9" | " + $1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9="" + sub("^ *?", "") + print ""$0" | " + print "
" + print " | "$7" | " + print ""$8" | " + print ""$9" | " + $1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9="" + sub("^ *?", "") + print ""$0"/ | " + print "
rc-httpd at' $SERVER_NAME '' + echo ' + + + ' +} + +fn 404{ + do_error '404 Not Found' \ + 'The requested path '^$"location^' was not found on this server.' +} + +fn 500{ + do_error '500 Internal Server Error' \ + 'The server has encountered an internal misconfiguration and is unable to satisfy your request.' +} + +fn 503{ + do_error '503 Forbidden' \ + 'You do not have permission to access '^$"location^' on this server.' +} + +do_log $1 +$1 diff --git a/rc/bin/rc-httpd/handlers/redirect b/rc/bin/rc-httpd/handlers/redirect new file mode 100755 index 000000000..e223091eb --- /dev/null +++ b/rc/bin/rc-httpd/handlers/redirect @@ -0,0 +1,30 @@ +#!/bin/rc +if(~ $#2 0){ + error 500 + exit +} +switch($1){ +case perm* + do_log 301 + echo 'HTTP/1.1 301 Moved Permanently'^$cr +case temp* + do_log 302 + echo 'HTTP/1.1 302 Moved Temporarily'^$cr +case seeother + do_log 303 + echo 'HTTP/1.1 303 See Other'^$cr +case * + error 500 + exit +} +echo 'Location: ' ^ $2 ^ $cr +emit_extra_headers +echo 'Content-type: text/html'^$cr +echo $cr +echo '
' +if(~ $#3 0) + echo 'Browser did not accept redirect.' +if not + echo $3 +echo 'Click here' +echo '' diff --git a/rc/bin/rc-httpd/handlers/serve-static b/rc/bin/rc-httpd/handlers/serve-static new file mode 100755 index 000000000..3f7544286 --- /dev/null +++ b/rc/bin/rc-httpd/handlers/serve-static @@ -0,0 +1,30 @@ +#!/bin/rc +full_path=`{echo $"FS_ROOT^$"PATH_INFO | urlencode -d} +full_path=$"full_path +if(~ $full_path */) + error 503 +if(test -d $full_path){ + redirect perm $"location^'/' \ + 'URL not quite right, and browser did not accept redirect.' + exit +} +if(! test -e $full_path){ + error 404 + exit +} +if(! test -r $full_path){ + error 503 + exit +} +do_log 200 +type=`{file -m $full_path} +if(~ $type text/*) + max_age=3600 # 1 hour +if not + max_age=604800 # 1 week +echo 'HTTP/1.1 200 OK'^$cr +emit_extra_headers +echo 'Content-type: '^$type^$cr +echo 'Cache-control: max-age='^$max_age^$cr +echo $cr +exec cat $full_path diff --git a/rc/bin/rc-httpd/handlers/static-or-cgi b/rc/bin/rc-httpd/handlers/static-or-cgi new file mode 100755 index 000000000..4d8a2d44a --- /dev/null +++ b/rc/bin/rc-httpd/handlers/static-or-cgi @@ -0,0 +1,14 @@ +#!/bin/rc +cgiargs=$* + +fn error{ + if(~ $1 404) + exec cgi $cgiargs + if not + $rc_httpd_dir/handlers/error $1 +} + +if(~ $location */) + exec cgi $cgiargs +if not + exec serve-static diff --git a/rc/bin/rc-httpd/handlers/static-or-index b/rc/bin/rc-httpd/handlers/static-or-index new file mode 100755 index 000000000..f0904f8a9 --- /dev/null +++ b/rc/bin/rc-httpd/handlers/static-or-index @@ -0,0 +1,5 @@ +#!/bin/rc +if(~ $PATH_INFO */) + exec dir-index $params +if not + exec serve-static diff --git a/rc/bin/rc-httpd/rc-httpd b/rc/bin/rc-httpd/rc-httpd new file mode 100755 index 000000000..e2a64948c --- /dev/null +++ b/rc/bin/rc-httpd/rc-httpd @@ -0,0 +1,86 @@ +#!/bin/rc +rc_httpd_dir=/rc/bin/rc-httpd +path=(/bin $rc_httpd_dir/handlers) +cgi_path=/bin +SERVER_PORT=80 # default for CGI scripts, may be overridden by the Host header +extra_headers='Server: rc-httpd' +cr= + +fn do_log{ + echo `{date} :: $SERVER_NAME :: $request :: \ + $HTTP_USER_AGENT :: $1 :: $HTTP_REFERER >[1=2] +} + +fn emit_extra_headers{ + for(header in $extra_headers) + echo $"header^$cr +} + +fn getline{ read | sed 's/'^$"cr^'$//g' } + +fn terminate{ + echo `{date} connection terminated >[1=2] + exit terminate +} + +fn trim_input{ read -c $CONTENT_LENGTH } + +request=`{getline} +if(~ $#request 0) + terminate +REQUEST_METHOD=$request(1) +REQUEST_URI=$request(2) +reqlines='' +HTTP_COOKIE='' +done=false +while(~ $"done false){ + line=`{getline} + if(~ $#line 0) + done=true + reqlines=$"reqlines$"line' +' + h=`{echo $line | awk '{print tolower($1)}'} + switch($h){ + case '' + done=true + case host: + tmp=`{echo $line(2) | sed 's/:/ /'} + SERVER_NAME=$tmp(1) + if(! ~ $#tmp 1) + SERVER_PORT=$tmp(2) + case referer: + HTTP_REFERER=$line(2) + case user-agent: + HTTP_USER_AGENT=`{echo $line | sed 's;[^:]+:[ ]+;;'} + case content-length: + CONTENT_LENGTH=$line(2) + case cookie: + cookie=`{echo $line | sed 's;^[^:]+:[ ]*;;'} + HTTP_COOKIE=$"HTTP_COOKIE^$"cookie^'; ' + } +} +if(~ $REQUEST_URI http://*){ + SERVER_NAME=`{echo $REQUEST_URI | sed ' + s;^http://;; + s;/.*;; + '} + REQUEST_URI=`{echo $REQUEST_URI | sed 's;^http://[^/]+/?;/;'} +} +QUERY_STRING=`{echo $REQUEST_URI | sed 's;[^?]*\??;;'} +params=`{echo $QUERY_STRING | sed 's;\+; ;g'} +location=`{echo $REQUEST_URI | sed 's;\?.*;;'} +location=`{echo $location | sed ' + s;[^/]+/\.\./;/;g + s;/\./;/;g + s;//+;/;g +'} +if(~ $REQUEST_METHOD POST){ + if(! ~ $"CONTENT_LENGTH '') + trim_input | exec $rc_httpd_dir/select-handler + if not{ + echo 'POST without content-length, assuming no keep-alive.' >[1=2] + exec $rc_httpd_dir/select-handler + } +} +if not + . $rc_httpd_dir/select-handler diff --git a/sys/lib/dist/rc/bin/rc-httpd/select-handler b/sys/lib/dist/rc/bin/rc-httpd/select-handler new file mode 100755 index 000000000..a5c6efa3c --- /dev/null +++ b/sys/lib/dist/rc/bin/rc-httpd/select-handler @@ -0,0 +1,71 @@ +#!/bin/rc + +PATH_INFO=$location +FS_ROOT=/sys/doc +exec static-or-index + +## EXAMPLES +# +#SERVER_NAME=`{echo $SERVER_NAME | sed 's/^www\.//g'} +# +#fn do_error{ +# do_log $1 +# echo 'HTTP/1.1 '^$1^$cr +# emit_extra_headers +# echo 'Content-type: text/html'^$cr +# echo $cr +# echo ' +# +#rc-httpd at' $SERVER_NAME '' +# echo ' +# +# +# ' +#} +# +## surprise! +#if(~ $HTTP_REFERER *hiphopstan.com/forum* *slax.*/forum*){ +# PATH_INFO=$location +# FS_ROOT=/usr/sl/www/werc/sites/hotlink +# exec static-or-index +#} +#if(~ $HTTP_REFERER 'http://okturing.com/index.rc?start=100' || {~ $SERVER_NAME okturing.com && ~ $location /index.rc} || ~ $location /qemu/plan9.flp.gz){ +# do_error '27b/6' +# exit +#} +# +## sites +#if(~ $SERVER_NAME 1oct1993.com 9front.org *.9front.org emma.stanleylieber.com flamesgif.com gl.* iawtp.com inri.net massivefictions.com mold.dk osx.* other.* pop.* qualitycountrylyrics.com sp.* stanleylieber.com tcasey.* textadventure.* volksutils.com){ +# PATH_INFO=$location +# FS_ROOT=/usr/sl/www/werc/sites/$SERVER_NAME +# exec static-or-index +#} +#if not if(~ $SERVER_NAME 9front.bell-labs.co bell-labs.co cs.bell-labs.co plan9.bell-labs.co sources.cs.bell-labs.co){ +# PATH_INFO=$location +# FS_ROOT=/usr/sl/www/werc/sites/bell-labs.co +# exec static-or-index +#} +#if not if(~ $SERVER_NAME vr.stanleylieber.com){ +# if(~ $location / /bin/* /etc/* /*htaccess /*htpasswd /index.rc* /lib/* /stats/*){ +# PATH_INFO=$location +# FS_ROOT=/usr/sl/www/werc/sitesvr.stanleylieber.com +# exec cgi /usr/sl/www/werc/sites/vr.stanleylieber.com/index.rc $* +# } +# if not{ +# PATH_INFO=$location +# FS_ROOT=/usr/sl/www/werc/sites/vr.stanleylieber.com +# exec static-or-index +# } +#} +#if not if(~ $SERVER_NAME applied.bell-labs.co *cat-v.org flesh.* img.* linux.* notreally.info okturing.com openbsd.* *osuny.co.uk plan9.* read.* scandisk.bell-labs.co * url.*){ +# PATH_INFO=$location +# FS_ROOT=/usr/sl/www/werc/sites/$SERVER_NAME +# exec static-or-cgi /usr/sl/www/werc/bin/werc.rc +#} +#if not +# error 503 diff --git a/sys/man/8/rc-httpd b/sys/man/8/rc-httpd new file mode 100644 index 000000000..887a90c8c --- /dev/null +++ b/sys/man/8/rc-httpd @@ -0,0 +1,177 @@ +.TH RC-HTTPD 8 +.SH NAME +rc-httpd \- HTTP server +.SH SYNOPSIS +.B rc-httpd/rc-httpd +.SH DESCRIPTION +.I Rc-httpd +serves the requested file or an index of files found under +a website's root directory, or, in the case of CGI, executes +a specified CGI program. +.SH CONFIGURATION +.PP +As all pieces of +.B rc-httpd +are shell scripts, configuration is achieved by setting variables +and adding, removing or modifying commands in various files. +.PP +.B rc-httpd +.PP +.I rc_httpd_dir +must be set to the root of the rc-httpd installation, +the directory containing the rc-httpd script. +.PP +.I path +must include +.I rc_httpd_dir/handlers +ahead of the base system's path elements. +.PP +.I cgi_path +is substituted for +.I path +when cgi scripts are run. (Be sure +to set +.I path +back in rc-based cgi scripts.) +.PP +.I extra_headers +is an optional list of strings to emit when sending http headers. +.PP +.I SERVER_PORT +is the port HTTP is to be served on. +.PP +.B select-handler +.PP +.I PATH_INFO +is the location relative to the website's root directory of the file +to be displayed. +Typically, the +.I location +from the incoming request is honored. +.PP +.I FS_ROOT +sets the root directory of the website. +.PP +.I NOINDEXFILE +instructs the +.B dir-index +module not to +look for +.B index.html +files, otherwise if an +.B index.html +file is found +.B dir-index +will exec +.B serve-static +to serve the file. At present there +is no module to serve an index file but not a directory. +.PP +If you do not want directory indexing at all, replace +.B static-or-index +with +.B serve-static, +which will report 503 forbidden for directories. +.PP +Multiple virtual hosts may be configured by creating conditional +statements that act upon the +.I SERVER_NAME +variable. Fine-grained control of specific request strings may +be configured via a similar method acting upon the +.I location +and/or other variables. +.SH EXAMPLES +The following examples demonstrate possible ways to configure +.BR select-handler. +.PP +Serve static files: +.RS +.EX +if(~ $SERVER_NAME 9front.org){ + PATH_INFO=$location + FS_ROOT=/usr/sl/www/$SERVER_NAME + exec static-or-index +} +.EE +.RE +.PP +CGI: +.RS +.EX +if(~ $SERVER_NAME *cat-v.org){ + PATH_INFO=$location + FS_ROOT=/usr/sl/www/werc/sites/$SERVER_NAME + exec static-or-cgi /usr/sl/www/werc/bin/werc.rc +} +.EE +.RE +.PP +Custom error message for a denied URL: +.RS +.EX +fn do_error{ + do_log $1 + echo 'HTTP/1.1 '^$1^$cr + emit_extra_headers + echo 'Content-type: text/html'^$cr + echo $cr + echo ' +
+rc-httpd at' $SERVER_NAME '' + echo ' + + + ' +} +if(~ $location /v8.tar.bz2){ + do_error '27b/6' + exit +} +.EE +.RE +.SH STARTUP +.I Rc-httpd +is run from a file in the directory scanned by +.IR listen (8), +or called as an argument to +.IR listen1 (8). +The program's standard error may be captured to a log file: +.RS +.EX +exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www +.EE +.RE +.SH FILES +.TF /sys/lib/httpd.rewrite +.TP +.B /rc/bin/rc-httpd/rc-httpd +.TP +.B /rc/bin/rc-httpd/select-handlers +.TP +.B /rc/bin/rc-httpd/handlers/cgi +.TP +.B /rc/bin/rc-httpd/handlers/dir-index +.TP +.B /rc/bin/rc-httpd/handlers/error +.TP +.B /rc/bin/rc-httpd/handlers/redirect +.TP +.B /rc/bin/rc-httpd/handlers/serve-static +.TP +.B /rc/bin/rc-httpd/handlers/static-or-cgi +.TP +.B /rc/bin/rc-httpd/handlers/static-or-index +.TP +.B /rc/bin/service/tcp80 +.TP +.B /sys/log/www +.SH SOURCE +.B /rc/bin/rc-httpd +.SH "SEE ALSO" +.IR rc (1), +.IR listen (8)