1: #!/bin/sh 2: # sh-httpd - A small shell-script web server with CGI 1.1 support 3: # Copyright (C) 2000 Charles Steinkuehler <charles@steinkuehler.net> 4: # 5: # This program is free software; you can redistribute it and/or modify it 6: # under the terms of the GNU General Public License as published by the 7: # Free Software Foundation; either version 2 of the License, or (at your 8: # option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. 9: # 10: # This program is distributed in the hope that it will be useful, but 11: # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12: # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13: # for more details. 14: 15: VERSION=0.4 16: NAME="ShellHTTPD" 17: 18: . /etc/sh-httpd.conf 19: 20: TAB=' ' 21: CR=`echo -e -n "\r"` 22: LF=' 23: ' 24: IFS=" $TAB$LF" 25: OIFS=$IFS 26: PATH="/sbin:/bin:/usr/sbin:/usr/bin" 27: 28: TMPSUFFIX=$$ 29: 30: qt () { "$@" >/dev/null 2>&1 ; } 31: 32: bname() { 33: local IFS='/' 34: set -- $1 35: eval rc="\$$#" 36: [ "$rc" = "" ] && eval rc="\$$(($# - 1))" 37: echo "$rc" 38: } 39: 40: dname() { 41: echo "$1" | sed '/^\/$/c\ 42: / 43: s/\/*$// 44: s/[^/]*$// 45: /./!c\ 46: . 47: s/\/$//' 48: } 49: 50: 51: rm_temp() { 52: qt rm /tmp/sh-httpd.*$TMPSUFFIX 53: } 54: 55: kill_jobs() { 56: local IFS SIG JOB 57: SIG=$1 58: IFS=$LF 59: for JOB in `jobs` ; do 60: IFS=' ' 61: set -- $JOB 62: kill -$SIG $2 63: done 64: } 65: 66: abort() { 67: local CNT 68: qt kill_jobs 15 69: CNT=1 70: while [ -n "`jobs`" ] ; do 71: sleep 1 72: CNT=$(( ${CNT} + 1 )) 73: [ "$CNT" -ge 5 ] && { qt kill_jobs 9 ; break ; } 74: done 75: rm_temp 76: print_error "$@" 77: } 78: 79: toupper() { 80: echo "$1" | sed y/\ 81: abcdefghijklmnopqrstuvwxyz-/\ 82: ABCDEFGHIJKLMNOPQRSTUVWXYZ_/ 83: } 84: 85: log() { 86: # $1 = response code 87: # $2 = response size 88: [ -z "$LOGFILE" ] && return 89: [ -f "$LOGFILE" -a ! -w "$LOGFILE" ] && return 90: echo "${REMOTE_HOST:-$REMOTE_ADDR} - - [$REQ_DATE] \"$REQUEST\" \ 91: ${1:--} ${2:--} \"${HTTP_REFERER:--}\" \"${HTTP_USER_AGENT:--}\"" \ 92: >> "$LOGFILE" 93: } 94: 95: print_header() { 96: echo -e "HTTP/1.0 $1\r" 97: echo -e "Server: $NAME/$VERSION\r" 98: echo -e "Date: $REQ_DATE\r" 99: echo -e "Connection: close\r" 100: } 101: 102: print_error() { 103: local COUNT 104: 105: print_header "$1 $2" 106: echo -e "Content-type: text/html\r" 107: echo -e "\r" 108: echo "<HTML><HEAD><TITLE>$1 $2</TITLE></HEAD>" 109: echo "<BODY BGCOLOR=\"#cc9999\"><H2>$1 $2</H2>" 110: echo "$3" 111: count=1 112: while [ $count -le 5 ] ; do 113: echo "<!-- Padding to override IE 'friendly error pages' -->" 114: echo "<!-- Response must be longer than 0x200 bytes -->" 115: count=$(( $count + 1 )) 116: done 117: echo "</BODY></HTML>" 118: exit 1 119: } 120: 121: guess_content_type() { 122: set -- `sed -n "/${1##*.}[ $TAB]/P" $MIME_TYPES` 123: echo "Content-type: ${2:-$DEFCONTENT}" 124: } 125: 126: export_cgi() { 127: # HTTP_ headers exported previously 128: 129: export SERVER_SOFTWARE="$NAME/$VERSION" 130: export SERVER_NAME # Set in /etc/sh-httpd.conf 131: export SERVER_ADDR # Set in /etc/sh-httpd.conf 132: export GATEWAY_INTERFACE="CGI/1.1" 133: export SERVER_PROTOCOL="${PROTOCOL:-HTTP/0.9}" 134: export SERVER_PORT # Set in /etc/sh-httpd.conf 135: export REQUEST_METHOD="$COMMAND" 136: export REQUEST_URI="$URI" 137: export DOCUMENT_ROOT="$DOCROOT" 138: 139: # Set previously: 140: export PATH_INFO 141: export PATH_TRANSLATED 142: export SCRIPT_NAME 143: export SCRIPT_FILENAME 144: export QUERY_STRING 145: export REMOTE_HOST 146: export REMOTE_ADDR 147: export REMOTE_PORT 148: 149: # Currently not supported: 150: #export AUTH_TYPE 151: #export REMOTE_USER 152: #export REMOTE_IDENT 153: #export CONTENT_TYPE 154: #export CONTENT_LENGTH 155: } 156: 157: file_stats() { 158: set -- `TZ=UDC ls -ln $LSDATEFLAG $1` 159: LEN=$5 160: MOD="$6 $7 $8 $9 ${10}" 161: } 162: 163: do_cgi() { 164: # Verify CGI script is executible 165: [ ! -x "$LOCALURL" ] && { 166: log 403 0 167: print_error 403 "Forbidden" "Document not executible: $URL" 168: } 169: 170: SCRIPT_NAME=$URL 171: SCRIPT_FILENAME="$DOCROOT$URL" 172: export_cgi 173: OUTPUT=/tmp/sh-httpd.$TMPSUFFIX 174: 175: # Setup command line args, if appropriate 176: case $QUERY_STRING in 177: *=*) set -- "" 178: ;; 179: *) IFS='+' 180: set -- $QUERY_STRING 181: IFS=$OIFS 182: ;; 183: esac 184: 185: $LOCALURL "$@" > $OUTPUT & 186: 187: CNT=1 188: while [ -n "`jobs`" ] ; do 189: sleep 1 190: CNT=$(( $CNT + 1 )) 191: if [ "$CNT" -ge "$TIMEOUT" ] ; then 192: log 500 0 193: abort 500 "Internal Server Error" "CGI Timeout: $URL" 194: fi 195: done 196: 197: file_stats $OUTPUT 198: STATUS="200 OK" 199: case $FILE in 200: nph-*) if [ -n "${PROTOCOL}" ] ; then 201: read VERSION STATUS REASON 202: echo "$VERSION $STATUS $REASON" 203: fi 204: while read -r HEADER HEADERDATA 205: [ "x${HEADER%${CR}}" != x ] 206: do 207: echo "$HEADER $HEADERDATA" 208: done 209: echo -e "\r" 210: log "${STATUS%${STATUS#???}}" "$LEN" 211: [ "$COMMAND" != HEAD ] && cat 212: ;; 213: *) HEADERS="" 214: IFS=' :' 215: CONTENT="$DEFCONTENT" 216: while read -r HEADER HEADERDATA 217: [ "x${HEADER%${CR}}" != x ] 218: do 219: HEADERU=`toupper "${HEADER%$CR}"` 220: HEADERDATA="${HEADERDATA%$CR}" 221: case ${HEADERU} in 222: STATUS) STATUS="$HEADERDATA" ;; 223: LOCATION) LOC="$HEADERDATA" ;; 224: CONTENT_TYPE) CONTENT="$HEADERDATA" ;; 225: *) HEADERS="$HEADERS$HEADER: $HEADERDATA$CR$LF" ;; 226: esac 227: done 228: IFS=$OIFS 229: if [ -n "$LOC" ] ; then 230: if [ "x$LOC" != "x${LOC#*://}" ] ; then 231: STATUS="302 Moved Temporarily" 232: else 233: # Send different file 234: URI=$LOC 235: unset DIR NURL LEN CGI 236: do_get 237: exit 238: fi 239: fi 240: 241: log "${STATUS%${STATUS#???}}" "$LEN" 242: print_header "$STATUS" 243: echo -e "Content-type: $CONTENT\r" 244: [ "x${LOC}" != "x${LOC#*://}" ] && 245: echo -e "Location: $LOC\r" 246: echo -n "$HEADERS" 247: echo -e "\r" 248: [ "$COMMAND" != HEAD ] && cat 249: ;; 250: esac < $OUTPUT 251: 252: qt rm $OUTPUT 253: } 254: 255: do_get() { 256: local DIR NURL LEN CGI VERSION STATUS REASON 257: 258: if [ ! -d $DOCROOT ]; then 259: log 404 0 260: print_error 404 "Not Found" "No such file or directory" 261: fi 262: 263: # Split URI into base and query string at ? 264: IFS='?' 265: set -- $URI 266: QUERY_STRING="$2" 267: URL="$1" 268: IFS=$OIFS 269: 270: # Test for CGI prefix pattern & split into URL and extra path info 271: for pattern in $SCRIPT_ALIAS ; do 272: if [ "x$URL" != "x${URL#$pattern*/}" ] ; then 273: NURL=${URL#$pattern*/} 274: IFS='/' 275: set -- $NURL 276: PATH_INFO=${NURL#$1} 277: PATH_TRANSLATED=$DOCROOT$PATH_INFO 278: URL=${URL%$PATH_INFO} 279: CGI="Y" 280: break 281: fi 282: done 283: IFS=$OIFS 284: 285: # Use default file if URL ends in trailing slash 286: if [ -z "${URL##*/}" ]; then 287: for index in $DEFINDEX ; do 288: NURL=$URL$index 289: [ -f "$DOCROOT/$NURL" ] && break 290: done 291: URL=$NURL 292: fi 293: 294: DIR="`dname $URL`" 295: FILE="`bname $URL`" 296: 297: # Check for existance of directory 298: if [ ! -d "$DOCROOT/$DIR" ]; then 299: log 404 0 300: print_error 404 "Not Found" "Directory not found: $DIR" 301: else 302: cd "$DOCROOT/$DIR" 303: LOCALURL="`pwd`/$FILE" 304: fi 305: 306: # Verify URL is not outside DOCROOT and file exists 307: [ "x$LOCALURL" = "x${LOCALURL#$DOCROOT}" -o ! -f "$LOCALURL" ] && { 308: log 404 0 309: print_error 404 "Not Found" "File not found: $URL" 310: } 311: 312: # Verify we can read the file 313: [ ! -r "$LOCALURL" ] && { 314: log 403 0 315: print_error 403 "Forbidden" "Access prohibited: $URL" 316: } 317: 318: # Test for CGI suffix 319: if [ "$CGI" != "Y" ] ; then 320: for pattern in ${SCRIPT_SUFFIX} ; do 321: if [ "$FILE" != "${FILE%$pattern}" ] ; then 322: CGI="Y" 323: break 324: fi 325: done 326: fi 327: 328: if [ "$CGI" = "Y" ] ; then 329: do_cgi 330: else 331: print_header "200 OK" 332: guess_content_type $LOCALURL 333: file_stats $LOCALURL 334: echo -e "Content-length: $LEN\r" 335: echo -e "Last-modified: $MOD\r\n\r" 336: log 200 $LEN 337: [ "$COMMAND" != HEAD ] && cat $LOCALURL 338: fi 339: sleep 1 340: } 341: 342: read_request() { 343: local HEADER 344: local HEADERDATA 345: 346: read COMMAND URI PROTOCOL 347: 348: COMMAND=${COMMAND%"${CR}"} 349: URI=${URI%"$CR"} 350: PROTOCOL=${PROTOCOL%"${CR}"} 351: REQUEST="$COMMAND $URI $PROTOCOL" 352: 353: if [ -n "$PROTOCOL" ] ; then 354: IFS=' :' 355: while read -r HEADER HEADERDATA 356: [ "x$HEADER" != "x$CR" ] 357: do 358: HEADER=`toupper "$HEADER"` 359: HEADERDATA="${HEADERDATA%${CR}}" 360: setvar HTTP_$HEADER "$HEADERDATA" 361: export HTTP_$HEADER 362: done 363: IFS=$OIFS 364: fi 365: REQ_DATE="`date -uR`" 366: 367: case $COMMAND in 368: GET|HEAD) do_get ;; 369: *) print_error 501 "Not Implemented" "$COMMAND" ;; 370: esac 371: } 372: 373: # 374: # main() 375: # 376: 377: # Don't send any shell error messages to the client! 378: exec 2>/dev/null 379: 380: trap "rm_temp" 0 381: 382: set -- `getpeername -n` 383: REMOTE_ADDR=$1 384: REMOTE_HOST="" 385: REMOTE_PORT=$2 386: 387: # Check client address against access list 388: while [ -n "$CLIENT_ADDRS" ] ; do 389: for pattern in $CLIENT_ADDRS ; do 390: if [ "x$REMOTE_ADDR" != "x${REMOTE_ADDR##${pattern}}" ] ; then 391: break 2 392: fi 393: done 394: exit 1 395: done 396: 397: read_request 398: 399: exit 0
last modified 22/03/2012 | Introduction | Table of Contents (frame/no frame) |
Printable (single file) |
© Dartmouth College |