diff --git a/bash/shttpd b/bash/shttpd new file mode 100755 index 0000000..544c584 --- /dev/null +++ b/bash/shttpd @@ -0,0 +1,126 @@ +#!/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,,} + hdr_val=${line#*: } client_req[hdr-$hdr_key]=$hdr_val + 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 + 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