#!/bin/sh # # hhttpd # # Copyright (c) 2021 Hayden Hamilton # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # git://haydenvh.com/hhttpd ## README ## # Run script from xinetd # Script will serve files from the directory provided as it's first argument # for example, if the script is run as `/home/httpd/bin/httpd.sh /srv/www` # files from the directory /srv/www/ will be served. # # converts *.gph files into html, and has a gateway for external gopher content ## end README ## ## configuration ## # default hostname hostname="haydenvh.com" # name or path to torsocks script, to use for the gopher proxy, if set #torgopher="" torgopher="torsocks" # name or path to torsocks script. If set, use /?tor=.... #torweb="" torweb="torsocks" # redirect to hidden service, if set onionhost="zl7kawb6nd3aqzrwo5cz57id7rnp3k7rvsme5seopv65lcgm56ey2oyd.onion" # map file extension to mime mimemap(){ # "Thanks" web.. # Oh, and "thanks" people who want mimetypes in gopher.... you're the "best" # # If you think mimetypes are a good thing, ponder this, how does a webserver # set the mimetype? # Well... as you can see below, it looks at the file extensions. Hard? No. # So why can't the client do that? The answer is it can, and therefore, # mimetypes are a case of complexity-for-the-sake-of-complexity™ # # hhttpd - the only httpd that has aggressive comments inside it's source code case "$1" in png|gif|jpeg|jpg|webp) mime="image/$1" ;; flac|mp3|ogg|m4a|m3u) mime="audio/$1" ;; xml) mime="application/xml" ;; rss|xhtml|svg) mime="application/$1+xml" ;; iso) mime="application/x-iso9660-image" ;; pdf) mime="application/pdf" ;; *) mime="text/plain" ;; esac } # tsv list # vhost="REGEX path/relative/to/argv[1] # REGEX /absolute/path" vhost="blog.haydenvh.com alcl/" vhost="git.haydenvh.com git/o/" # tsv list # redirect="REGEX protocol://a.b/c" redirect=".*\.exe https://windows.sucks" ## end configuration ## echo(){ printf "%s\n" "$@" } headers(){ [ ! -z "$1" ] && printf "HTTP/1.1 $1\r\n" || printf "HTTP/1.1 200 OK\r\n" printf "Date: $(date '+%a, %d %b %Y %H:%M:%S %Z')\r\n" printf "Gopher: gopher://$hostname\r\n" printf "Server: hhttpd\r\n" printf "Server-Source: gopher://haydenvh.com/0/scripts/other/httpd.sh\r\n" printf "Server-Source: http://haydenvh.com/scripts/other/httpd.sh\r\n" printf "Running-On: $(uname -o) $(uname -m) $(sed 's/.*="//g;s/"$//g' < /etc/os-release | head -n 1)\r\n" [ "$onionhost" != "" ] && printf "Onion-Location: ${onionhost}/$(echo "$location" | sed 's~^/~~')\r\n" printf "Connection: close\r\n" } gopher2html(){ file=$(cat <<- CSS CSS sed 's/>/\>/g;s/Srch | "$2" (no search function in hhttpd)
" else if ($1 == "3") printf "ERROR | "$2"
" else if ($4 == "server") printf " "type" | "$2"
" else print " "type" | "$2"
" } // { if (link == 1) print " | "$0"
" link=1 } ' | deansi | sed 's/ | t/ | /;s/ / \ /g'; echo "
   This page was generated via hhttpd (http://haydenvh.com/scripts/other/httpd.sh) from geomyidae gopher content.
   You should be using gopher directly, if possible.

") echo "$file" | grep '|' >/dev/null || return404 headers printf "Content-Type: text/html; charset=utf-8\r\n" printf "Content-Length: $(echo "$file" | awk '{printf("%s\r\n", $0)}' | wc -c)\r\n" printf "\r\n" echo "$file" | awk '{printf("%s\r\n", $0)}' exit 0 } gopher2html2(){ uri="$1" grab=$(echo "$uri" | sed -E 's~^gopher://~~;s~^~~;s~/.~~;s~([^/]*)(/.*)~\2\\r\\n | \1~;s~:([0-9]*)$~ \1~;s~([^0-9]*)$~\1 70~') file=$(cat <<- CSS

CSS curl -qLs "gopher://$uri" | head -n -1 | sed 's/^./& /;s/>/\>/g;s/" printf(" | You are at: %s
\n", uri) print " |
" print " |
" } // { link=0 switch ($1) { case "0": type = "Text"; break; case "1": type = "Dir "; break; case "2": type = "CCSO"; break; case "4": case "5": case "9": type = "Bin "; break; case "7": type = "Srch"; break; case "8": case "T": type = "Teln"; break; case "+": type = "Alt "; break; case "g": case "I": type = "Img "; break; case "h": type = "HTML"; break; case "s": type = "Snd "; break; case "d": type = "Doc "; break; default: type = "?["$1"]"; } if ($1 == "7") printf " Srch | "$2" (since you are veiwing this via the web, you are unable to interact with gopher search engines)
" else if ($1 == "i") print " | "$2"
" else print " "type" | "$2"
" } // { if (link == 1) print " | "$0"
" link=1 } ' | deansi | sed 's/ | t/ | /;s/ / \ /g'; echo "
   This page was generated via hhttpd's (http://haydenvh.com/scripts/other/httpd.sh) external gopher gateway.
You should be using gopher directly, if possible.

") echo "$file" | grep '|' >/dev/null || return404 headers printf "Content-Type: text/html; charset=utf-8\r\n" printf "Content-Length: $(echo "$file" | awk '{printf("%s\r\n", $0)}' | wc -c)\r\n" printf "\r\n" echo "$file" | awk '{printf("%s\r\n", $0)}' exit 0 } return404(){ [ -f $dir/404.txt ] && file="$dir/404.txt" || { [ -f $olddir/404.txt ] && file="$olddir/404.txt" || file=/dev/null } serve "text/plain" "404 Not Found" < $file exit 0 } deansi(){ sed 's/\[[^m]*m//g' } dirlist(){ cd "$1" ls | while IFS= read -r file do [ -d $file ] && echo "[1|$file|$location/$file|server|port]" [ -f $file ] && { echo "$file" | grep '\.dcgi$' && echo "[1|$file|$location/$file|server|port]" && continue ftype=$(file "$file") echo "$ftype" | grep -i 'text' >/dev/null && echo "[0|$file|$location/$file|server|port]" && continue echo "[9|$file|$location/$file|server|port]" } done | gopher2html exit 0 } serve(){ [ -z "$2" ] && printf "HTTP/1.1 200 OK\r\n" || printf "HTTP/1.1 $2\r\n" printf "Date: $(date '+%a, %d %b %Y %H:%M:%S %Z')\r\n" printf "Gopher: gopher://$hostname\r\n" printf "Server: hhttpd (haydenh's httpd)\r\n" printf "Server-Source: gopher://haydenvh.com/0/scripts/other/httpd.sh\r\n" printf "Server-Source: http://haydenvh.com/scripts/other/httpd.sh\r\n" printf "Running-On: $(uname -o) $(uname -m) $(sed 's/.*="//g;s/"$//g' < /etc/os-release | head -n 1)\r\n" printf "Connection: close\r\n" [ -z $1 ] && printf "Content-Type: text/plain; charset=utf-8\r\n" || printf "Content-Type: $1\r\n" printf "Content-Length: $(wc -c)\r\n" printf "\r\n" cat exit 0 } redir(){ loc=$(echo "$1" | tr -s '/') printf "HTTP/1.1 301 Moved Permanently\r\n" echo "$1" | grep '[a-zA-Z0-9]*://' >/dev/null && { printf "Location: $1\r\n" } || { printf "Location: http://${hostname}${loc}\r\n" [ "$onionhost" != "" ] && printf "Onion-Location: http://${onionhost}${loc}\r\n" } printf "Gopher: gopher://$hostname\r\n" printf "Date: $(date '+%a, %d %b %Y %H:%M:%S %Z')\r\n" printf "Server: hhttpd (haydenh's httpd)\r\n" printf "Server-Source: gopher://haydenvh.com/0/scripts/other/httpd.sh\r\n" printf "Server-Source: http://haydenvh.com/scripts/other/httpd.sh\r\n" printf "Running-On: $(uname -o) $(uname -m) $(sed 's/.*="//g;s/"$//g' < /etc/os-release | head -n 1)\r\n" printf "Connection: close\r\n" printf "Content-Type: text/plain\r\n" printf "\r\n" printf "Yeah, please go where the Location: header tells you, or if you're\r\n" printf "using curl or something, append the -L flag\r\n" exit 0 } getonly(){ printf "HTTP/1.1 405 Method Not Allowed\r\n" printf "\r\n" printf "get requests only\r\n" exit 0 } # {{{ dealing with header dir="$1" head=$(awk '// {print}; /^[[:space:]]*$/ {exit}' | tr -d '\r') location=$(echo "$head" | awk '/^GET/ {print $2}') echo "$head" | grep '^[Hh]ost:' >/dev/null && hostname=$(echo "$head" | awk '$1 = /^[Hh]ost:/ {print $2}') [ "$location" = "" ] && getonly # }}} # {{{ map vhosts olddir="$dir" vhostout=$(echo "$vhost" | awk -F" " -v "host=$hostname" 'host ~ $1 {print $2}' | head -n 1) [ ! -z $vhostout ] && { echo "$vhostout" | grep '^/' && dir="$vhostout" || dir="$dir/$vhostout" } # }}} # {{{ map redirections redirectout=$(echo "$redirect" | awk -F" " -v "loc=http://${hostname}${location}" 'loc ~ $1 {print $2}' | head -n 1) [ ! -z "$redirectout" ] && redir "$redirectout" # }}} # {{{ if dir [ -d $dir/$location ] && { [ -f $dir/$location/index.html ] && redir "$location/index.html" [ -f $dir/$location/index.txt ] && redir "$location/index.txt" [ -f $dir/$location/index.gph ] && redir "$location/index.gph" [ -f $dir/$location/index.dcgi ] && redir "$location/index.dcgi" dirlist "$dir/$location" # }}} } || { # {{{ if file/special echo "$location" | grep '\.html$' >/dev/null && { serve "text/html" < $dir/$location exit } echo "$location" | grep -E '/\?gopher=[^/]*/[^1]|/\?gopher=gopher://[^/]*/[^1]' >/dev/null && { tmp=$(mktemp) $torgopher curl "gopher://$(echo "$location" | sed 's/.*\?gopher=//;s~//~/~g')" 2>/dev/null > $tmp serve < $tmp } echo "$location" | grep -E '/\?tor=' >/dev/null && [ "$torweb" != "" ] && { tmp=$(mktemp) content=$($torweb curl -Li "$(echo "$location" | sed 's/.*\?tor=//')" 2>/dev/null) mime=$(echo "$content" | grep -i ^content-type: | head -n 1 | awk '{$1="";print $0}') sedscript=$(echo "$content" | awk \ -v "root=$(echo "http://${hostname}${location}" | awk 'BEGIN {RS="/"}; p == "n" {next}; /^?tor=/ {p="n"}; 1 {print $0 "/"}' | tr -d '\n')" \ -v "rel=${location}/" \ -v "gopher=http://$hostname/?gopher=" \ 'BEGIN { RS="[ >\n]" } /^href="\/[^\/]|^src="\/[^\/]/ { orig=$0 rep=sprintf("%s%s", "href=\"", root) sub(/href="/, rep, $0) rep=sprintf("%s%s", "src=\"", root) sub(/src="/, rep, $0) print "s~" orig "~" $0 "~g" next } /^href="[^\/]|^src="[^\/]/ { if ($1 ~ /"(http|https|gopher):\/\//) next orig=$0 rep=sprintf("%s/%s", "href=\"", rel) sub(/href="/, rep, $0) rep=sprintf("%s/%s", "src=\"", rel) sub(/src="/, rep, $0) print "s~" orig "~" $0 "~g" }' | sed -E 's~([^:])//*~\1/~g') echo "$content" | awk '/^[[:space:]]*$/ {p="y"}; // {if (p == "y") print}' | sed "$sedscript" > $tmp serve "$mime" < $tmp } echo "$location" | grep '/\?gopher=' >/dev/null && gopher2html2 "$(echo "$location" | sed 's~.*\?gopher=~~;s~//~/~g;s~%2F~/~g;s~%3A~:~g' | sed 's~gopher://~~')" && exit [ ! -f $dir/$location ] && { [ -f $olddir/$location ] && dir=$olddir || return404 } echo "$location" | grep '\.dcgi$' >/dev/null && { cd $(dirname $dir/$location) [ -x $dir/$location ] && { $dir/$location | gopher2html } || serve "text/plain" < $dir/$location exit 0 } echo "$location" | grep '\.cgi$' >/dev/null && { cd $(dirname $dir/$location) [ -x $dir/$location ] && { tmp=$(mktemp) $dir/$location > $tmp serve "text/plain" < $tmp rm $tmp } || serve "text/plain" < $dir/$location exit 0 } echo "$location" | grep '\.gph$' >/dev/null && gopher2html "$location" < "$dir/$location" mimemap "$(echo "$location" | awk -F"." '{print $NF}')" serve "$mime" < "$dir/$location" } # }}}