grimoire/bash/shttpd

127 lines
3.9 KiB
Bash
Executable file

#!/usr/bin/env bash
declare -A clients http_codes=(
[200]=OK [400]="Bad Request" [403]=Forbidden
[404]="Not Found" [405]="Method Not Allowed"
)
enable -f "${SHERVER_SO_PATH-.}"/sherver.so sherver_{bind,select,accept}
if ! sherver-bind -p 8080 -h 0.0.0.0 -v server_fd; then
exit
fi
while true; do
if ! sherver-select -iv ready_fds "$server_fd" "${!clients[@]}"; then
continue
fi
for fd in "${ready_fds[@]}"; do
if (( fd == server_fd )) && sherver-accept -v client_info "$server_fd"; then
declare -A __client_{req,resp}_"${client_info[0]}"_
declare -n client_req=__client_req_${client_info[0]}_
client_req[ip]=${client_info[1]}
client_req[port]=${client_info[2]}
clients[${client_info[0]}]=0
else
declare -n client_req=__client_req_${fd}_
declare -n client_resp=__client_resp_${fd}_
if ! IFS= read -ru "$fd" line || (( ! ${#line} )); then
unset "__client_"{req,resp}"_${fd}_" "clients[$fd]"
exec {fd}>&-
else
(( clients[$fd] += ${#line} + 1 ))
line=${line%$'\r'}
if [[ ! -v client_req[method] ]]; then
read -ra req_line <<< "$line"
client_req[method]=${req_line[0],,}
client_req[req-file]=${req_line[1]}
client_req[version]=${req_line[2]-HTTP/1.0}
case ${client_req[method]} in
get)
client_resp[has-payload]=yes
;;
head)
client_resp[has-payload]=no
;;
*)
client_req[error]=yes
client_resp[code]=405
esac
if [[ ${client_req[req-file]} = /* ]]; then
client_req[real-file]=.${client_req[req-file]}
if [[ ${client_req[req-file]} = */ ]]; then
client_req[real-file]+=index.html
elif [[ -d client_req[real-file] ]]; then
client_req[real-file]+=/index.html
fi
else
client_req[error]=yes
client_resp[code]=400
fi
if [[ ${client_req[error]} != yes ]]; then
if [[ -f ${client_req[real-file]} ]]; then
if [[ -r ${client_req[real-file]} ]]; then
client_resp[code]=200
else
client_req[error]=yes
client_resp[code]=403
fi
else
client_req[error]=yes
client_resp[code]=404
fi
fi
elif [[ $line = *": "* ]]; then
hdr_key=${line%%:*} hdr_key=${hdr_key,,}
client_req[hdr-$hdr_key]=${line#*: }
elif [[ -z $line ]]; then
req_host=${client_req[hdr-host]-"$HOSTNAME"}
ver=${client_req[version]} code=${client_resp[code]}
if [[ ${client_req[error]} = yes ]]; then
client_req[real-file]=./error.html
client_resp[has-payload]=yes
fi
payload=$(<"${client_req[real-file]}")
client_resp[hdr-Content-Length]=${#payload}
case ${client_req[req-file]} in
*.html)
client_resp[hdr-Content-Type]=text/html
;;
*.txt)
client_resp[hdr-Content-Type]=text/plain
;;
*)
client_resp[hdr-Content-Type]=application/octet-stream
esac
printf -v 'client_resp[hdr-Date]' '%(%Y-%m-%d %H:%M:%S)T' -1
printf '%s %d %s\r\n' "$ver" "$code" "${http_codes[$code]}" >&"$fd"
for hdr in "${!client_resp[@]}"; do
if [[ $hdr = hdr-* ]]; then
printf '%s: %s\r\n' "${hdr#hdr-}" "${client_resp[$hdr]}" >&"$fd"
fi
done
printf '\r\n' >&"$fd"
if [[ ${client_resp[has-payload]} = yes ]]; then
printf %s "$payload" >&"$fd"
fi
printf '[%s] %s has requested http://%s%s (%s)\n' "${client_resp[hdr-Date]}" "${client_req[ip]}" "$req_host" "${client_req[req-file]}" "${client_req[real-file]}"
fi
fi
fi
done
done