#!/bin/bash
#
# unhttpd: A really dumb web server. :^)
#
# http://ioioio.net/unhttpd/
# http://ducks.openverse.com/~andy/unhttpd/
#
# Copyright (C) 2004
# Andy Goth <unununium@openverse.com>
#
# This code is available under the GNU General Public License; see COPYING.

# Edit these...
prefix=http://localhost      # Default URL prefix.
root=~/public_html           # Where your files are.

sp=" "      # Space.
cr=$'\r'    # Carriage return.  (HTTP terminates lines with \r\n.)
lf=$'\n'    # Line feed.
tab=$'\t'   # Tab. 

version=unhttpd/0.1
author_name="Andy Goth"
author_email=unununium@openverse.com

# Beginning of this month last year.
rightnow=$(date +%s)
lastyear=$(date -d"$(($(date +%Y) - 1))-$(date +%m)-31" +%s)

# Read the first line.  Example: "GET / HTTP/1.1".
IFS=$sp$cr$lf read cmd euri ver
[ "$(tr A-Z a-z <<< $cmd)" != get ] && exit 1

# Read the rest of the client request, which is terminated by a blank line.
while true; do
    IFS=$sp$cr$lf read -r key val
    [ -z $key ] && break

    key=$(tr A-Z a-z <<< $key)
    val=$(tr -d $cr$lf <<< $val)
    case $key in
    range:)
        # Client doesn't want the whole file, just a byte range.
        beg=$(awk -F '[=-]' '{print $2}' <<< $val)
        end=$(awk -F '[=-]' '{print $3}' <<< $val)
        range=1
        ;;
    host:)
        # Virtual host. :^)
        prefix=http://$(sed 's/:80$//' <<< $val)
        ;;
    esac
done

# $uri = uniform resource indicator; $euri = escaped uri.
uri=$(echo $euri | urlesc -r | sed -r 's:(^|/)\.($|/):/:g;s://*:/:g')

if echo $uri | egrep -q '(^|/)\.\.($|/)'; then
    # Crack attempt.
    file=/tmp/unhttpd.$$.html
    code="404 file does not exist"
    echo "<html><body>nice try, lamer</body></html>" > $file
elif [ -d $root$uri ]; then
    if [ "${uri:(-1)}" != / ]; then
        # Redirect.
        file=/tmp/unhttpd.$$.html
        code="301 moved permanently"
        redir=$(urlesc <<< $prefix$uri/)
        echo -n "<html><body>document moved permanently to " > $file
        echo    "<a href=\"$redir\">here</a></body></html>" >> $file
    else
        # Attempt to send index.html.
        uri=${uri}index.html
    fi
fi

if [ -z "$code" ]; then
    if [ -f $root$uri ]; then
        if [ -r $root$uri ]; then
            # Send file.
            file=$root$uri
            code="200 OK"
        else
            # Uh oh, forbidden.
            file=/tmp/unhttpd.$$.html
            code="403 forbidden"
            echo "<html><body>it is forbidden</body></html>" > $file
        fi
    else
        # File doesn't exist.  Try to generate it.
        dir=$(dirname $uri)
        [ $dir == . -o $dir == / ] && dir=

        if [ ! -d $root$dir ]; then
            # Bad directory.
            uri=404
        fi

        case $uri in
        */index.tar|*/index.tar.gz|*/index.tar.bz2)
            # Directory archive.
            code="200 OK"

            # Compression?
            case $uri in
            *.tar) cmp=""; file=/tmp/unhttpd.$$.tar     ;;
            *.gz ) cmp=z ; file=/tmp/unhtppd.$$.tar.gz  ;;
            *.bz2) cmp=j ; file=/tmp/unhttpd.$$.tar.bz2 ;;
            esac

            # Make an archive that extracts into a subdirectory.
            (cd $root$dir;
            tdir=$(basename $PWD);
            cd ..;
            tar c$cmp $tdir) > $file
            ;;
        */index.txt)
            # Textual listing.
            code="200 OK"
            file=/tmp/unhttpd.$$.txt

            # List URL's for ordinary files in current directory.
            (cd $root$dir
            for node in *; do
                [ -f $node ] || continue
                echo $(urlesc <<< $prefix$dir/$node)
            done) > $file
            ;;
        */index.m3u)
            # mpegurl listing.
            code="200 OK"
            file=/tmp/unhttpd.$$.txt

            # List URL's for ordinary files in current directory.
            (cd $root$dir
            echo "#EXTM3U"
            for node in *; do
                [ -f $node ] || continue
                case ${node} in
                *.mp3)
                    mp3info -p'#EXTINF:%S,%a - %t\n' $node
                    ;;
              # *.ogg 
              #     # Untested.
              #     ogginfo -v $node > /tmp/unhttp.$$.tmp
              #     artist=$(sed "/^${tab}artist=/s/.*=//p" /tmp/unhttp.$$.tmp)
              #     title=$(sed "/^${tab}title=/s/.*=//p"   /tmp/unhttp.$$.tmp)
              #     length=$(sed "/^${tab}Playback length: /s/.*: //p" \
              #     /tmp/unhttp.$$.tmp | awk -FS 'm|:|s' '{print $1 * 60 + $3}')
              #     echo "#EXTINF:$length,$artist - $title"
                *)
                    echo "#EXTINF:0,$node"
                    ;;
                esac
                echo $(urlesc <<< $prefix$dir/$node)
            done) > $file
            ;;
        */index.html|*/index.html?*)
            # Pretty HTML listing.
            code="200 OK"
            file=/tmp/unhttpd.$$.html

            if echo $euri | fgrep -q ?; then
                query=$(sed 's:.*?::' <<< $euri)
                name=$(sed -r 's:.*name=([^&]*)($|\&).*:\1:' <<< $query)
                name=$(urlesc -r <<< $name)
                flag=$(sed -r 's:.*flag=([^&]*)($|\&).*:\1:' <<< $query)
                flag=$(urlesc -r <<< $flag)
                [ "$flag" != -regex ] && flag=-name
                files=$(cd $root$dir; find . $flag "$name" | sed s:..:: | sort)
            else
                files=$(cd $root$dir; echo * | sort)
            fi

            (cd $root$dir
            echo "<html><head><title>$prefix$dir</title><body>"

            path=
            sdir=$(sed 's:/: :g' <<< $dir)
            echo -n "Navigation: "
            for node in "" $sdir; do
                path=$path$node/
                echo -n "<a href=\"$(urlesc <<< $prefix${path})\">"
                if [ -z $node ]; then
                    echo -n "$(htmlesc <<< $prefix/)</a>"
                else
                    echo -n "$(htmlesc <<< $node/)</a>"
                fi
            done
            echo -n "<hr />"
            
            if [ ! -z $query ]; then
                echo
                echo -n "Results: (<b>find "
                [ "${dir}" == "" ] && echo -n "/" || echo -n $dir
                echo -n " $flag \"$name\"</b>)"
            fi

            echo "<pre>"

            dcount=0
            for node in $files; do
                [ -d $node ] || continue

                dtime=$(stat -c%Y $node)
                [ $dtime -le $lastyear -o \
                  $dtime -gt $rightnow ] && dfmt=" %Y" || dfmt=%R

                echo -n "           $(date -r$node +"%b %e $dfmt")  "
                echo -n "<a href=\"$(urlesc <<< $prefix$dir/$node/)\">"
                echo    "$(htmlesc <<< $node)</a>/"

                ((++dcount))
            done

            fsize=0
            fcount=0
            for node in $files; do
                [ -f $node ] || continue

                size=$(stat -c%s $node)
                ftime=$(stat -c%Y $node)
                [ $ftime -le $lastyear -o \
                  $ftime -gt $rightnow ] && dfmt=" %Y" || dfmt=%R

                echo -n "$(printf %9d $size)  "
                echo -n "$(date -r$node +"%b %e $dfmt")  "
                echo -n "<a href=\"$(urlesc <<< $prefix$dir/$node)\">"
                echo    "$(htmlesc <<< $node)</a>"

                ((fsize += $size))
                ((++fcount))
            done

            echo -n "</pre>"

            if [ ! -z $query ]; then
                echo "<a href=\"$prefix$dir/index.html\">Reset search</a><br />"
            fi

            echo -n "<b>$dcount</b> director"
            [ $dcount -ne 1 ] && echo -n ies || echo -n y
            echo -n ", <b>$fcount</b> file"; [ $fcount -ne 1 ] && echo -n s
            echo -n ", <b>$fsize</b> byte";  [ $fsize  -ne 1 ] && echo -n s

            echo "<hr />"
            echo -n "Plain-text listing: "
            echo -n "<a href=\"$prefix$dir/index.txt\">index.txt</a> "
            echo "(doesn't include directories)<br />"
            echo "Directory archive:"
            echo "<a href=\"$prefix$dir/index.tar\">index.tar</a>"
            echo "<a href=\"$prefix$dir/index.tar.gz\">index.tar.gz</a>"
            echo "<a href=\"$prefix$dir/index.tar.bz2\">index.tar.bz2</a><br />"
            echo "<form action=\"$prefix$dir/index.html\" method=\"get\">"
            echo "Find: <input type=\"entry\" name=\"name\" value=\"$name\" />"
            echo "<input type=\"submit\" value=\"-name\" name=\"flag\" />"
            echo "<input type=\"submit\" value=\"-regex\" name=\"flag\" />"
            echo "</form><hr />"
            echo -n "<small>$version by $author_name &lt;<a href=\"mailto:"
            echo "$author_email\">$author_email</a>&gt;<br/>$(date)</small>"
            echo "</html></body>") > $file
            ;;
        */index.htm|*/Default.htm)
            # Idiot proofing. :^)
            file=/tmp/unhttpd.$$.html
            code="301 moved permanently"
            redir=$(urlesc <<< $prefix$dir/index.html)
            echo -n "<html><body>grow a clue, then go " > $file
            echo    "<a href=\"$redir\">here</a></body></html>" >> $file
            ;;
        404|*)
            # Last resort.
            file=/tmp/unhttpd.$$.html
            code="404 file does not exist"
            echo "<html><body>try harder</body></html>" > $file
            ;;
        esac
    fi
fi

# Calculate the content type.
case $file in
*.html) ctype=text/html         ;;
*.tar ) ctype=application/x-tar ;;
*.tgz | *.tar.gz ) ctype=application/x-tgz ;;
*.tbz | *.tar.bz2) ctype=application/x-tbz ;;
*.zip ) ctype=application/x-zip ;;
*.ogg ) ctype=application/ogg   ;;
*.mp3 ) ctype=audio/mpeg        ;;
*     ) ctype=text/plain        ;;
esac

# Calculate the file length, content length, and range values.
flen=$(stat -c%s $file)
[ -z "$beg" -o ${beg:-0} -lt 0     ] && beg=0
[ -z "$end" -o ${end:-0} -ge $flen ] && end=$(($flen - 1))
clen=$(($end - $beg + 1))

# Finally, send it!
echo "HTTP/1.1 $code$cr"
echo "Server: $version$cr"
[ -z $redir ] || echo "Location: $redir$cr"
echo "Date: $(date +"%a, %d %b %Y %T %Z")$cr"
echo "Connection: close$cr"
echo "Content-Type: $ctype$cr"
echo "Content-Length: $clen$cr"
[ -z $range ] || echo "Content-Range: bytes $beg-$end/$flen$cr"
echo "$cr"
range $beg $end < $file

# Clean up the temporary file, if it exists.
case $file in 
/tmp/unhttpd.$$.*) rm $file ;;
esac

# vim: set ts=4 sts=4 sw=4 tw=80 et:
