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("&", "\\&", s);
+ gsub("\"", "\\"", s);
+ gsub("'"'"'", "\\'", s);
+ gsub("<", "\\<", s);
+ gsub(">", "\\>", 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