minihttpd additions - randomcrap - random crap programs of varying quality
 (HTM) git clone git://git.codemadness.org/randomcrap
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit b3f6ae5a47e6af99aebc0110bba78065b2a00330
 (DIR) parent e6ef7e822e8e88d5ee345bb02fe9bf06e743911c
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Thu, 14 Aug 2025 18:39:40 +0200
       
       minihttpd additions
       
       - Only execute scripts in cgi-bin ($scriptdir).
       - Do not set Content-Length, it was inefficient also.
       - date: use date format, not the -R option (it was not portable with BSD).
       - Parse/split query string from path.
       - Set some CGI variables.
       - Percent decode the path, so spaces in filenames work.
       - Add simple HTML dirlisting.
       
       Diffstat:
         A minihttpd/cgi-bin/example.sh        |      12 ++++++++++++
         D minihttpd/htdocs/hello.txt          |       1 -
         D minihttpd/htdocs/test.sh            |       7 -------
         M minihttpd/httpd.sh                  |     112 +++++++++++++++++++++----------
       
       4 files changed, 88 insertions(+), 44 deletions(-)
       ---
 (DIR) diff --git a/minihttpd/cgi-bin/example.sh b/minihttpd/cgi-bin/example.sh
       @@ -0,0 +1,12 @@
       +#!/bin/sh
       +printf 'HTTP/1.0 200 OK\r\n'
       +printf 'Date: %s\r\n' "$(TZ=UTC date +'%a, %d %b %Y %H:%M:%S +0000')"
       +printf 'Connection: close\r\n'
       +printf 'Content-Type: %s\r\n' "text/plain"
       +printf '\r\n'
       +
       +echo "The time is:"
       +date
       +echo ""
       +echo "The environment is:"
       +env
 (DIR) diff --git a/minihttpd/htdocs/hello.txt b/minihttpd/htdocs/hello.txt
       @@ -1 +0,0 @@
       -Hello World
 (DIR) diff --git a/minihttpd/htdocs/test.sh b/minihttpd/htdocs/test.sh
       @@ -1,7 +0,0 @@
       -#!/bin/sh
       -printf 'HTTP/1.0 200 OK\r\n'
       -printf 'Date: %s\r\n' "$(TZ=UTC date -R)"
       -printf 'Connection: close\r\n'
       -printf 'Content-Type: %s\r\n' "text/plain"
       -printf '\r\n'
       -echo "The date time is: $(TZ=UTC date -R)"
 (DIR) diff --git a/minihttpd/httpd.sh b/minihttpd/httpd.sh
       @@ -1,39 +1,24 @@
        #!/bin/sh
       -# insecure mini httpd intended for local testing, do not expose to the interwebs.
       +# insecure mini httpd intended for local testing.
        # Dependencies: socat, file, UNIX tools, etc.
        
       -# httpstatus(msg)
       -httpstatus() {
       -        printf 'HTTP/1.0 %s \r\n' "$1"
       -        printf 'Date: %s\r\n' "$(TZ=UTC date -R)"
       -        printf 'Connection: close\r\n'
       -        printf 'Content-Type: text/plain\r\n'
       -        printf '\r\n'
       -        printf '%s\n' "$1"
       +# httpheader(msg) {
       +httpheader() {
       +        printf 'HTTP/1.0 %s \r\nDate: %s\r\nConnection: close\r\n'\
       +                "$1" "$(TZ=UTC date +'%a, %d %b %Y %H:%M:%S +0000')"
        }
        
       -# notfound()
       -notfound() {
       -        httpstatus "404 Not Found"
       -}
       -
       -# internalerror()
       -internalerror() {
       -        httpstatus "500 Internal Server Error"
       +# httpstatus(msg)
       +httpstatus() {
       +        httpheader "$1"
       +        printf 'Content-Type: text/plain\r\n\r\n%s\n' "$1"
        }
        
        # servedata(file)
        servedata() {
       -        file="$1"
       -        contentlen=$(wc -c < "$file")
       -
       -        printf 'HTTP/1.0 200 OK\r\n'
       -        printf 'Date: %s\r\n' "$(TZ=UTC date -R)"
       -        printf 'Connection: close\r\n'
       -        printf 'Content-Length: %d\r\n' "$contentlen"
       -        printf 'Content-Type: %s\r\n' "$(file -bi "$file")"
       -        printf '\r\n'
       -        cat "$file"
       +        httpheader '200 OK'
       +        printf 'Content-Type: %s\r\n\r\n' "$(file -bi "$1")"
       +        cat "$1"
        }
        
        # servefile(file)
       @@ -41,39 +26,94 @@ servefile() {
                servedata "$1"
        }
        
       +# servedir(dir)
       +servedir() {
       +        if cd "$1" >/dev/null 2>/dev/null; then
       +                httpheader '200 OK'
       +                printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
       +                ls -a1p | LC_ALL=C awk -v "dir=$1" '
       +function encodehtml(s) {
       +        gsub("&", "\\&amp;", s);
       +        gsub("\"", "\\&quot;", s);
       +        gsub("'"'"'", "\\&#39;", s);
       +        gsub("<", "\\&lt;", s);
       +        gsub(">", "\\&gt;", s);
       +        return s;
       +}
       +BEGIN { print "<pre>"; }
       +{
       +        name = encodehtml($0);
       +        printf("<a href=\"%s\">%s</a>\n", name, name);
       +}
       +END { print "</pre>"; }
       +'
       +        else
       +                httpstatus '403 Forbidden'
       +        fi
       +}
       +
        # servescript(file)
        servescript() {
                t="$(mktemp)"
                if "$1" > "$t"; then
                        cat "$t"
                else
       -                internalerror
       +                httpstatus '500 Internal Server Error'
                fi
                rm -f "$t"
        }
        
       +# percentdecode(str)
       +percentdecode() {
       +        printf '%s' "$1" | sed 's@+@ @g;s@%@\\x@g' | xargs -0 printf '%b'
       +}
       +
        if test "$1" = ""; then
                script="$(readlink -f "$0")"
                socat TCP4-LISTEN:8080,reuseaddr,fork "SYSTEM:'$script httpd'"
        elif test "$1" = "httpd"; then
       +        rootdir="$(dirname "$(readlink -f "$0")")"
       +        htdocsdir="${rootdir}/htdocs"
       +        scriptdir="${rootdir}/cgi-bin"
       +
                IFS=" " read -r method request proto
                while IFS=": " read -r key value; do
                        test "$value" = "" && break
                done
        
       -        if test "$request" != "/"; then
       -                file="htdocs${request}"
       +        query="${request}?"
       +        query="${query#*\?}"
       +        query="${query%\?}"
       +        requestpath="${request%\?*}"
       +
       +        if test "$requestpath" = "/"; then
       +                file="index.txt"
                else
       -                file="htdocs/index.txt"
       +                file="${requestpath#/}"
       +                file="$(percentdecode "$file")"
                fi
       +        realfile="${htdocsdir}/${file}"
       +        basename="$(basename "$realfile")"
       +        scriptname="/cgi-bin/${basename}" # only execute scripts in cgi-bin
        
       -        if test -f "$file"; then
       -                if test -x "$file"; then
       -                        servescript "$file"
       +        if test -d "$realfile"; then
       +                if test "$file" != "${file%/}"; then
       +                        servedir "$realfile"
                        else
       -                        servefile "$file"
       +                        # redirect: append / for dirlisting.
       +                        httpheader '302 Found'
       +                        printf 'Location: %s\r\n\r\n' "/$file/"
                        fi
       +        elif test "$requestpath" = "${scriptname}" && test -x "${scriptdir}/${basename}"; then
       +                # a few CGI variables (RFC3875).
       +                QUERY_STRING="$query"\
       +                        REMOTE_ADDR="$SOCAT_PEERADDR"\
       +                        REQUEST_METHOD="$method"\
       +                        SERVER_PROTOCOL="$proto"\
       +                        servescript "${scriptdir}/${basename}"
       +        elif test -f "$realfile"; then
       +                servefile "$realfile"
                else
       -                notfound
       +                httpstatus "404 Not Found"
                fi
        fi