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 |