171 lines
4.7 KiB
Bash
171 lines
4.7 KiB
Bash
#!/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.1
|
|
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
|