Example script: sh-httpd.ksh


   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