#! /bin/ash
#####Configuration section#####
#The fqdn variable defines the hostname of the gopher server you'll be proxying to gemini.
readonly fqdn=gopher.zcrayfish.soy
#The port variable defines the TCP port of the gopher server you'll be proxying to gemini.
readonly port=70
#The full path to the gophermap2gemini.awk script
readonly gophermap2gemini=/usr/local/bin/gophermap2gemini.awk
#Use curl, or use the gopher daemon directly
readonly usecurl=false
#full path to gopher daemon
readonly gopherd=/usr/sbin/gophernicus
#command options to pass to the gopher daemon
readonly gopherd_options="-h $fqdn -nv -nf -np -f /srv/gopher/filters -e pdf=P -e webp=I -e eml=m -e uue=6 -o utf-8 -w 74"
####End of configuration section, use caution if editing below this line####
### Export some environs to anything we run, could be useful
export REMOTE_HOST
export REMOTE_PORT
export REMOTE_ADDR="$REMOTE_HOST"
###
readonly baseurl="gemini://$fqdn"
#how many bytes are at the beginning of the URL?
readonly baseurllength="${#baseurl}"
#readonly baseurllength=$((${#baseurl}-1))
# Gather request
read -t 30 -r url badclient
####Request validation####
#Did we time out... If so just exit, gemini does not have a timeout code
test "$?" != "0" && exit
# Reject requests with garbage past the URL.
test ! -z "$badclient" && printf '%s\15\12' "59 BAD REQUEST; Garbage past URL in request." && exit
# See if request is for defined FQDN.
test "$(echo "$url" | head -c "$baseurllength")" != "$baseurl" && printf '%s\15\12' "59 BAD REQUEST; $baseurl URLs only please." && exit
####End of request validation####
# If it all looks good, find out what they want
filename=$(echo "$url" | tail -c +$((${#baseurl}+1)) | sed -e 's/%2c/,/gi' -e 's/%20/ /g' -e 's/\r$//g' -e 's/%3b/;/i')
# ^ ^ ^ ^ ^ ^
# curl fails without carriage return removal!!!!
filename2=$(echo "$filename" | tail -c +3)
readonly filename
# Extract query string, if present, and export it in case if non-gophernicus gopherd
QUERY_STRING="$(echo "$url" | grep "?" | cut -d? -f2-)"
export QUERY_STRING
export SEARCHREQUEST="$QUERY_STRING"
###WIP BELOW HERE####
case "$filename" in
/favicon.ico)
printf '%s\15\12' "51 NOT FOUND; favicon.ico" && exit;;
/2*)
printf '%s\15\12' "50 PERMANENT FAILURE; gophertype 2 CCSO not supported" && exit;;
/7*)
if [ -z "$QUERY_STRING" ]
then printf '%s\15\12' "10 This is a searchable gopher index. Enter search keywords"
else
mimetype="20 text/gemini; "
if [ "$usecurl" = "true" ] ; then
xyzzy="$(curl -q --disable -s --output - "gopher://$fqdn:$port$filename" | awk -f $gophermap2gemini | \
sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )"
else
# shellcheck disable=2086
xyzzy="$(echo "$filename2" | ${gopherd} ${gopherd_options} | awk -f $gophermap2gemini | \
sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )"
fi
fi
;;
###START OF DUMB / NON-INTELLIGENT GOPHER TYPES###
/[04569IMPdghps]*)
case "$filename" in
/0/stylesheet.css)
mimetype="20 text/css";;
/0*)
mimetype="20 text/plain";;
/4*)
mimetype="20 application/mac-binhex40";;
/5*|/6*|/9*|/d*)
mimetype="20 application/octet-stream";;
/I*.webp)
mimetype="20 image/webp";;
/I*)
mimetype="20 image/jpeg";;
/M*.mht|/M*.mhtml|/m*.mht|m*.mhtml)
mimetype="20 multipart/related";;
/M*)
mimetype="20 message/rfc822";;
/P*)
mimetype="20 application/pdf";;
/g*)
mimetype="20 image/gif";;
/h*|/H*)
mimetype="20 text/html";;
/p*)
mimetype="20 image/png";;
/s*.mp3)
mimetype="20 audio/mpeg";;
/s*.m4a)
mimetype="20 video/mp4";;
/s*)
mimetype="20 application/octet-stream";;
/';'*.webm)
mimetype="20 video/webm";;
esac
#We now have enough information to pull in anything we're not converting to gemini markup
if [ "$usecurl" = "true" ] ; then
xyzzy="$(curl -q --disable -s --output - "gopher://$fqdn:$port$filename" | base64)"
else
# shellcheck disable=2086
xyzzy="$(echo "$filename2" | ${gopherd} ${gopherd_options} | base64)"
fi
isdumb="true"
;;
###END OF DUMB / NON-INTELLIGENT GOPHER TYPES###
*)
#Convert gophermap to gemini markup
mimetype="20 text/gemini; "
if [ "$usecurl" = "true" ] ; then
xyzzy="$(curl -q --disable -s --output - "gopher://$fqdn:$port$filename" | awk -f $gophermap2gemini | \
sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )"
else
# shellcheck disable=2086
xyzzy="$(echo "$filename2" | ${gopherd} ${gopherd_options} | awk -f $gophermap2gemini | \
sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )"
fi
;;
esac
###OUTPUT SECTION###
#We'll use the output of curl to determine if gopher content is 404d
#Non gophermap content may be binary, base64 it so the shell can deal with it
if [ "$isdumb" = "true" ] ; then
precontent=$(echo "$xyzzy" | base64 -d)
else
precontent="$xyzzy"
fi
#See if there's an error
is40=$(echo "$precontent" | head -n1 | grep "Error: Access denied!")
is51=$(echo "$precontent" | head -n1 | grep "Error: File or directory not found!")
#Respond to any errors
if [ -n "$is40" ] ; then
printf '%s\15\12' '40 TEMPORARY FAILURE; Error: Access denied!' && exit
fi
if [ -n "$is51" ] ; then
printf '%s\15\12' '51 NOT FOUND; Error: File or directory not found!' && exit
fi
#If no errors, then we go here
printf '%s\15\12' "$mimetype"
if [ "$isdumb" = "true" ] ; then
echo "$xyzzy" | base64 -d
else
echo "$xyzzy"
fi