#!/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/\</g' 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/\</g' | awk -v "uri=$uri" -v "hostname=$hostname" '
BEGIN {
FS=" "
print " |
"
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"
}
# }}}