#!/bin/bash
# Author: (c) Colas Nahaboo http://colas.nahaboo.net with a MIT License.
# See https://github.com/ColasNahaboo/cgibashopts
# Uses the CGI env variables REQUEST_METHOD CONTENT_TYPE QUERY_STRING

export CGIBASHOPTS_RELEASE=4.1.3
export CGIBASHOPTS_VERSION="${CGIBASHOPTS_RELEASE%%.*}"
cr=$'\r'
nl=$'\n'
export FORMS=
export FORMFILES=
export FORMQUERY=

# parse options
uploads=true
tmpfs=/tmp
OPTIONS='nd:'
OPTIND=1
while getopts "${OPTIONS}" _o;do
    case "$_o" in
        n) uploads=false;;
        d) tmpfs="$OPTARG";;
        *) echo "unknown option: $_o"; exit 1;;
    esac
done
shift $((OPTIND-1))

if "$uploads"; then
    export CGIBASHOPTS_DIR="$tmpfs/cgibashopts-files.$$"
    CGIBASHOPTS_TMP="$CGIBASHOPTS_DIR.tmp"
    cgibashopts_clean() { 
	[ -n "$CGIBASHOPTS_DIR" ] && [ -d "$CGIBASHOPTS_DIR" ] && rm -rf "$CGIBASHOPTS_DIR"
    }
    trap cgibashopts_clean 0
else
    CGIBASHOPTS_TMP=/dev/null
fi

# emulates bashlib param function. -f operate on uploaded file paths
param () { 
    if [ "$1" = -f ]; then
        shift
        if [ $# -eq 0 ]; then echo "$FORMFILES"
        elif [ $# -eq 1 ]; then eval echo "\$FORMFILE_$1"
        else declare -x "FORMFILE_$1=$*"
	fi
    else
	if [ $# -eq 0 ]; then echo "$FORMS"
	elif [ $# -eq 1 ]; then eval echo "\$FORM_$1"
	else declare -x "FORM_$1=$*"
	fi
    fi
}

# decodes the %XX url encoding in $1, same as urlencode -d but faster
# removes carriage returns to force unix newlines, converts + into space
urldecode() {
    local v="${1//+/ }" d r=''
    while [ -n "$v" ]; do
        if [[ $v =~ ^([^%]*)%([0-9a-fA-F][0-9a-fA-F])(.*)$ ]]; then
            eval d="\$'\x${BASH_REMATCH[2]}'"
	    [ "$d" = "$cr" ] && d=
            r="$r${BASH_REMATCH[1]}$d"
            v="${BASH_REMATCH[3]}"
        else
            r="$r$v"
            break
        fi
    done
    echo "$r"
}

# the reverse of urldecode above
urlencode() {
    local length="${#1}" i c
    for (( i = 0; i < length; i++ )); do
        c="${1:i:1}"
        case $c in
            [a-zA-Z0-9.~_-]) echo -n "$c" ;;
            *) printf '%%%02X' "'$c" ;;
        esac
    done
}

if [ "${REQUEST_METHOD:-}" = POST ]; then
    if [[ ${CONTENT_TYPE:-} =~ ^multipart/form-data[\;,][[:space:]]*boundary=([^\;,]+) ]]; then
	sep="--${BASH_REMATCH[1]}"
	OIFS="$IFS"; IFS=$'\r'
	while read -r line; do
	    if [[ $line =~ ^Content-Disposition:\ *form-data[\;,]\ *name=\"([^\"]+)\"(\;\ *filename=\"([^\"]+)\")? ]]; then
		var="${BASH_REMATCH[1]}"
		val="${BASH_REMATCH[3]}"
		[[ $val =~ [%+] ]] && val=$(urldecode "$val")
		type=
		read -r line
		while [ -n "$line" ]; do
		    if [[ $line =~ ^Content-Type:\ *text/plain ]]; then
			type=txt
		    elif [[ $line =~ ^Content-Type: ]]; then # any other type
			type=bin
		    fi
		    read -r line
		done
		if [ "$type" = bin ]; then # binary file upload
		    # binary-read stdin till next step. -u and q stops reading immediately
                    # When changed, update code copy in tests/filefield-runaway.test
		    sed -u -n -e "{:loop p; n;/^$sep/q; b loop}" >$CGIBASHOPTS_TMP
		    [ $CGIBASHOPTS_TMP != /dev/null ] && \
			truncate -s -2 $CGIBASHOPTS_TMP # remove last \r\n
		elif [ "$type" = txt ]; then # text file upload
		    lp=
		    while read -r line; do
			[[ $line =~ ^"$sep" ]] && break
			echo -n "$lp$line"
			lp="$nl"
		    done >$CGIBASHOPTS_TMP
		else # string, possibly multi-line
		    val=
		    while read -r line; do
			[[ $line =~ ^"$sep" ]] && break
			val="$val${val:+$nl}${line}"
		    done
		fi
		if [ -n "$type" ]; then
		    if [ $CGIBASHOPTS_TMP != /dev/null ]; then
			if [ -n "$val" ]; then
                            # a file was uploaded, even empty
			    [ -n "$FORMFILES" ] || mkdir -p "$CGIBASHOPTS_DIR"
			    FORMFILES="$FORMFILES${FORMFILES:+ }$var"
			    declare -x "FORMFILE_$var=$CGIBASHOPTS_DIR/${var}"
			    mv $CGIBASHOPTS_TMP "$CGIBASHOPTS_DIR/${var}"
			else
			    rm -f $CGIBASHOPTS_TMP
			fi
		    fi
		fi
		FORMS="$FORMS${FORMS:+ }$var"
		declare -x "FORM_$var=$val"
	    fi
	done
	s="${QUERY_STRING:-}"
	IFS="$OIFS"
	unset OIFS
    else
	stdin=$(cat) # indirection to avoid issues with terminating newlines
	s="${stdin}&${QUERY_STRING:-}"
	unset stdin
    fi
else
    s="${QUERY_STRING:-}"
fi

# regular (no file uploads) arguments processing
if [[ $s =~ = ]]; then # modern & (or ;) separated list of key=value
    while [[ $s =~ ^([^=]*)=([^\&\;]*)[\;\&]*(.*)$ ]]; do
	var="${BASH_REMATCH[1]//[^a-zA-Z_0-9]/}"
	val="${BASH_REMATCH[2]}"
	s="${BASH_REMATCH[3]}"
	if [[ $var =~ ^[_[:alpha:]] ]]; then # ignore invalid vars
	    [[ $val =~ [%+] ]] && val=$(urldecode "$val")
	    FORMS="$FORMS${FORMS:+ }$var"
	    declare -x "FORM_$var=$val"
	fi
    done
else # legacy indexed search
    FORMQUERY=$(urldecode "$s")
fi

# clean up our local variables
unset sep line var val type s lp
