ref: c877493fb00717de1a22860d88efb75ebf14ebec
parent: 0b212ed5054cd08bbc14a12936c07bc1ff5890a2
author: stanley lieber <[email protected]>
date: Mon Feb 4 11:40:05 EST 2013
add rc-httpd
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/cgi
@@ -1,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
+}
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/dir-index
@@ -1,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 '<html>
+<head>
+<title>'^$title^'</title>
+<style type="text/css">
+ .size {
+ text-align: right;
+ padding-right: 4pt;
+ }
+ .day {
+ text-align: right;
+ padding-right: 3pt;
+ }
+ .datetime {
+ text-align: right;
+ }
+ .name {
+ text-align: right;
+ padding-left: 3pt;
+ }
+</style>
+</head>
+<body>'
+echo '<h1>'^$title^'</h1>'
+if(! ~ $PATH_INFO /)
+ echo '<a href="../">Parent directory</a>'
+echo '<table>'
+ls -lQ $lso | awk '
+function urlencode(loc){
+ # very minimal encoding, just enough for our static-file purposes
+ url=loc
+ gsub("%", "%25", url) # this one first!
+ gsub("\\$", "%24", url)
+ gsub("&", "%26", url)
+ gsub("\\+", "%2B", url)
+ gsub("\\?", "%3F", url)
+ gsub(" ", "%20", url)
+ gsub("\"", "%22", url)
+ gsub("#", "%23", url)
+ return url
+}
+function hrsize(size){
+ if(size > 1073741824) return sprintf("%.1fGB", size/1073741824)
+ if(size > 10485760) return sprintf("%iMB", size/1048576)
+ if(size > 1048576) return sprintf("%.1fMB", size/1048576)
+ if(size > 10240) return sprintf("%iKB", size/1024)
+ if(size > 1024) return sprintf("%.1fKB", size/1024)
+ return sprintf("%iB", size)
+}
+/^-/ {
+ print "<tr>"
+ print "<td class=\"size\">"hrsize($6)"</td>"
+ print "<td class=\"month\">"$7"</td>"
+ print "<td class=\"day\">"$8"</td>"
+ print "<td class=\"datetime\">"$9"</td>"
+ $1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9=""
+ sub("^ *?", "")
+ print "<td><a class=\"file name\" href=\""urlencode($0)"\">"$0"</a></td>"
+ print "</tr>"
+ $0=""
+}
+/^d/ {
+ print "<tr>"
+ print "<td class=\"size\"> </td>"
+ print "<td class=\"month\">"$7"</td>"
+ print "<td class=\"day\">"$8"</td>"
+ print "<td class=\"datetime\">"$9"</td>"
+ $1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9=""
+ sub("^ *?", "")
+ print "<td><a class=\"dir name\" href=\""urlencode($0)"/\">"$0"/</a></td>"
+ print "</tr>"
+}'
+echo '</table>
+
+</body>
+</html>'
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/error
@@ -1,0 +1,38 @@
+#!/bin/rc
+# DO NOT make this script callable directly from the web!
+fn do_error{
+ echo 'HTTP/1.1 '^$1^$cr
+ emit_extra_headers
+ echo 'Content-type: text/html'^$cr
+ echo $cr
+ echo '<html>
+<head>
+<title>'^$1^'</title>
+</head>
+<body>
+<h1>'^$1^'</h1>'
+ echo $2
+ echo '<p><i>rc-httpd at' $SERVER_NAME '</i>'
+ echo '
+ </body>
+ </html>
+ '
+}
+
+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
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/redirect
@@ -1,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 '<html><body>'
+if(~ $#3 0)
+ echo 'Browser did not accept redirect.'
+if not
+ echo $3
+echo '<a href="'^$"location^'/">Click here</a>'
+echo '</body></html>'
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/serve-static
@@ -1,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
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/static-or-cgi
@@ -1,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
--- /dev/null
+++ b/rc/bin/rc-httpd/handlers/static-or-index
@@ -1,0 +1,5 @@
+#!/bin/rc
+if(~ $PATH_INFO */)
+ exec dir-index $params
+if not
+ exec serve-static
--- /dev/null
+++ b/rc/bin/rc-httpd/rc-httpd
@@ -1,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
--- /dev/null
+++ b/sys/lib/dist/rc/bin/rc-httpd/select-handler
@@ -1,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 '<html>
+#<head>
+#<title>'^$1^'</title>
+#</head>
+#<body>
+#<h1>'^$1^'</h1>'
+# echo $2
+# echo '<p><i>rc-httpd at' $SERVER_NAME '</i>'
+# echo '
+# </body>
+# </html>
+# '
+#}
+#
+## 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
--- /dev/null
+++ b/sys/man/8/rc-httpd
@@ -1,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 '<html>
+<head>
+<title>'^$1^'</title>
+</head>
+<body>
+<h1>'^$1^'</h1>'
+ echo $2
+ echo '<p><i>rc-httpd at' $SERVER_NAME '</i>'
+ echo '
+ </body>
+ </html>
+ '
+}
+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)