#!/bin/sh

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ----------------------------------------------------------------------
##
##                        === gosher ===
##
##  gosher is a simple Gopher server in a POSIX shell script:
##
##     $ ./gosher [<GOPHERDIR> [<HOSTNAME> [<PORT>]]]
##
##  If GOPHERDIR is not specified, "./" is assumed. 
##  If HOSTNAME is not specified, "localhost" is used.
##  If PORT is not specified, the default port "70" is used.
##
##  (c) 2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org>
##
##

######################

##
## If the script is called with basename "gosher", launch the netcat
## server...
##

##
## NETCAT: the netcat command to use, and any additional option
##
### Original netcat
##NETCAT="nc.traditional"
##
### ncat (from nmap)
##NETCAT="ncat"
##
### Openbsd netcat
NETCAT="nc.openbsd"

##
## STYLE: The way in which netcat will talk to gosher_serve
##
### fork with "-c" (Does *not* work with OpenBSD netcat!!!!!)
#STYLE='fork'
##
### use named pipes (Does *not* work with original netcat!!!!!)
STYLE='pipe'

### prefix of the input FIFO
IPREFIX=/tmp/inf_

DEBUG=
#DEBUG=yes


[ -n "$DEBUG" ] && {
	set -e
	set -x
}


## function
cleanup(){
	[ -n "$INF"  ] &&  [ -p "$INF"  ] && rm -f "${INF}"
	exit 1
}

MYNAME=$(basename "$0")
MYDIR=$(dirname "$(realpath "$0")")
NETCAT=$(which "$NETCAT")

if [ -z "${MYNAME#gosher}" ]; then
	## we are called as gosher -- launch the server
	GOPHERDIR=${1:-"./"}
	HOSTNAME=${2:-"localhost"}
	PORT=${3:-70}
	
	[ ! -f "${NETCAT}" ] || [ ! -x "${NETCAT}" ] && {
		echo "Wrong NETCAT -- Exiting" >&2
		exit 2
	}
	
	if [ -f "${MYDIR}/gosher_serve" ] ||  [ -h "${MYDIR}/gosher_serve" ]; then
		GOSHER_SERVE="${MYDIR}/gosher_serve"
		trap cleanup 0 HUP INT TRAP TERM QUIT
	
		INF="${IPREFIX}$$"
		[ "$STYLE" = "pipe" ] && {
			mkfifo -m 600 "$INF"
# shellcheck disable=SC2050
			while [ 1 -eq 1 ]; do
# shellcheck disable=SC2094
				${GOSHER_SERVE} "${GOPHERDIR}" \
				   "${HOSTNAME}" "${PORT}" <"$INF" |\
				    ${NETCAT} -vvvvv -l -p "${PORT}" >"$INF"
			done
			rm -f $INF
			exit 0
		}
		[ "$STYLE" = 'fork' ] && {
# shellcheck disable=SC2050
			while [ 1 -eq 1 ]; do
				${NETCAT} -vv -l -p "$PORT" -c \
				   "${GOSHER_SERVE} ${GOPHERDIR} ${HOSTNAME} ${PORT}"
			done
			exit 0
		}
		echo "Wrong STYLE specified -- Exiting" >&2
		exit 2
	else
		echo "Cannot find gosher_serve -- Exiting">&2
		exit 3
	fi
fi

######################

##
## ...otherwise, serve a request
##


## function
invalid_selector(){
	sel="$1"
	echo "[$(date +%Y-%m-%d\ %H:%M:%S)|ERROR|${sel}|\"\"]" >&2
	printf "3Error: Invalid selector\tErr\t\t\r\n.\r\n"
# shellcheck disable=SC1117
	exec 1>&-
	exec 2>&-
	exit 1
}

## function
serve_selector(){
	sel="$1"

	cat "${sel}"
	echo "[$(date +%Y-%m-%d\ %H:%M:%S)|SELECTOR|${sel}|\"\"]" >&2
	exec 1>&-
	exec 2>&-
	exit 0
}


### transform a .gph file into a gophermap
## function
serve_index(){
        IDX=$1
        IFS='
'
	
	echo "[$(date +%Y-%m-%d\ %H:%M:%S)|GPH|${IDX}|\"\"]" >&2
        while read -r line; do
                rline=$(echo "$line" | sed -r -e 's/\r//g')
                case "$rline" in
                        '['*)
                                echo "$rline" | sed -r -e 's/\[//g;s/\]//g;s/\|/\t/g;s/\t//;s/$/\r/g'
                        ;;
                       t*)
                               echo "$rline" | cut -c 2-
                       ;;
                        *)
                                echo "$line"
                esac
        done < "$IDX"
# shellcheck disable=SC1117
	printf ".\r\n"
	exec 1>&-
	exec 2>&-
	exit 0
}

### Serve an HTML URL through a redirect page
## function
serve_redirect(){
	url=$1
	
	echo "[$(date +%Y-%m-%d\ %H:%M:%S)|REDIRECT|${url}|\"\"]" >&2
	cat<< EOF
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>gopher redirect</title>
  </head>
  <body>
	Click to be redirected to: <a href="$url">$url</a>
  </body>
EOF
	exec 1>&-
	exec 2>&-
	exit 0

}

### Serve a CGI -- Set the environment and run the corresponding script
## function
serve_cgi(){
	script_name=$( echo "$1" | sed -r 's:^/+::')
	query_string="$2"
	echo "[$(date +%Y-%m-%d\ %H:%M:%S)|CGI|${script_name}|\"${query_string}\"]" >&2
        GATEWAY_INTERFACE="CGI/1.1"
        PATH_INFO="${script_name}"
        PATH_TRANSLATED="${script_name}"
	[ -n "${QUERY_STRING}" ] && QUERY_STRING="${query_string}"
        REMOTE_ADDR=
        REMOTE_HOST=
        REQUEST_METHOD="GET"
        SCRIPT_NAME="${script_name}"
        SERVER_NAME="${HOSTNAME}"
        SERVER_PORT="${PORT}"
        SERVER_PROTOCOL="gopher/1.0"
        SERVER_SOFTWARE="gosher"
        ####X_GOPHER_SEARCH= search (See above.)
	export GATEWAY_INTERFACE PATH_INFO PATH_TRANSLATED QUERY_STRING
	export REMOTE_ADDR REMOTE_HOST REQUEST_METHOD SCRIPT_NAME 
	export SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
	${GOPHERDIR}${script_name} "" "${query_string}" "${HOSTNAME}" "${PORT}" 2>&1
	exec 1>&-
	exec 2>&-
	exit $? 
}


GOPHERDIR=${1:-"./"}
HOSTNAME=${2:-"localhost"}
PORT=${3:-"70"}

read -r selector

selector=$(echo "$selector" | sed -r 's:\$.*::g;s:\r::g' )

[ -n "$DEBUG" ] && {
	echo "iGOPHERDIR: ${GOPHERDIR}"
	echo "iselector: \"${selector}\""
}

case $selector in
	URL:*)
		## it's a special URL selector
		url=$(echo "$selector" | cut -d ":" -f 2- | sed 's:^//::g')
		serve_redirect "$url"
        ;;
	/?*.cgi*)
		## it's a CGI
		script_name=$(echo "$selector" | cut -d "?" -f 1)
		query_string=$(echo "$selector" | cut -d "?" -f 2)
		[ "${script_name}" = "${query_string}" ] && query_string=""
		RP1=$(realpath "${GOPHERDIR}""${script_name}" || "")
# shellcheck disable=SC2181
		[ $? -eq 0 ] || invalid_selector "${selector}"
		RP2=$(realpath "${GOPHERDIR}")"${script_name}"
		RP2=$(echo "${RP2}" | sed -r 's/\/+/\//g')
# shellcheck disable=SC2181
		[ $? -eq 0 ] || invalid_selector "${selector}"
		[ -n "$DEBUG" ] && {
			echo "iRP1: ${RP1}"
			echo "iRP2: ${RP2}"
		}
		
		[ "${RP1}" = "${RP2}" ] && {
			[ -x "${RP1}" ] && {
				serve_cgi "${script_name}" "${query_string}"
			}
		}
		invalid_selector "${selector}"
	;;
	/?*|"")
		## it's a regular selector
		RP1=$(realpath "${GOPHERDIR}"/"${selector}" || "")
# shellcheck disable=SC2181
		[ $? -eq 0 ] || invalid_selector "$selector"
		RP2=$(realpath "${GOPHERDIR}")"${selector}"
# shellcheck disable=SC2181
		[ $? -eq 0 ] || invalid_selector "$selector"
		[ -n "$DEBUG" ] && {
			echo "iRP1: ${RP1}"
			echo "iRP2: ${RP2}"
		}
		
		if [ "${RP1}" = "${RP2}" ]; then
			if [ -f "${RP1}" ]; then
				if [ -n "$(echo "${RP1}" | sed -n '/\.gph$/p')" ]; then 
					serve_index "${RP1}"
				else
					serve_selector "${RP1}"
				fi
			elif [ -d "${RP1}" ]; then
				[ -f "${RP1}/gophermap" ] && serve_selector "${RP1}/gophermap"
				[ -f "${RP1}/index.gph" ] && serve_index "${RP1}/index.gph"
			fi
		fi
		invalid_selector "$selector"
	;;
	*)
		## we don't know what it is -- try to default to a
		## gophermap
		[ -f "${GOPHERDIR}/gophermap" ] && serve_selector "${GOPHERDIR}/gophermap"
		[ -f "${GOPHERDIR}/index.gph" ] && serve_index "${GOPHERDIR}/index.gph"
		echo "got invalid selector: \"$selector\"" >&2
		invalid_selector "/"
	;;
esac

