diff --git a/src/exprash.sh b/src/exprash.sh index de9a616..1296b98 100644 --- a/src/exprash.sh +++ b/src/exprash.sh @@ -59,7 +59,7 @@ function all() { # Reset params _exprashParams=(); - pathMatch "$1" _exprashParams || return 1; + _pathMatch "$1" _exprashParams || return 1; _exprashRouteHandled=1; return 0; @@ -109,7 +109,7 @@ function allError() { # Reset params _exprashParams=(); - pathMatch "$1" _exprashParams || return 1; + _pathMatch "$1" _exprashParams || return 1; _exprashRouteHandled=1; return 0; @@ -158,6 +158,41 @@ function hasParam() { [[ -v "_exprashParams[$1]" ]]; } +# $1: key +# $2: (optional) index for accessing array parameters +function queryGet() { + _multiGet _exprashQuery "$1" "$2" +} +# $1: key +function queryHas() { + _multiHas _exprashQuery "$1" +} +# $1: key +function queryLen() { + _multiLen _exprashQuery "$1" +} + +# $1: key +# $2: (optional) index for accessing array parameters +function bodyGet() { + _multiGet _exprashBody "$1" "$2" +} +# $1: key +function bodyHas() { + _multiHas _exprashBody "$1" +} +# $1: key +function bodyLen() { + _multiLen _exprashBody "$1" +} + +# Call this function to parse URLencoded request bodies +function bodyParser() { + if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]]; then + _parseUrlEncoded _exprashBody + fi +} + # ================== # Response Functions # ================== @@ -168,29 +203,46 @@ function send() { _exprashHeadersSent=1 fi - sendRaw + _sendRaw +} + +# $1: Path +function redirect() { + local path="$1" + if [ "$path" == 'back' ]; then + path="${HTTP_REFERER:-/}" + fi + + status '302' + setHeader 'Location' "$path" + printf 'Redirecting to: %s' "$path" | send +} + +# $1: satus code +function status() { + setHeader "Status" "$1" } # $1: Header Name # $2: Header Value function setHeader() { - _exprashHeaders["$1"]="$2"; + _exprashHeaders["$1"]="$2" } function sendHeaders() { - printf '%s\n' "Content-Type: ${_exprashHeaders['Content-Type']}" | sendRaw + printf '%s\n' "Content-Type: ${_exprashHeaders['Content-Type']}" | _sendRaw for key in "${!_exprashHeaders[@]}"; do [ "$key" == 'Content-Type' ] && continue - printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | sendRaw + printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw done; - printf '\n' | sendRaw + printf '\n' | _sendRaw } # ========= # Internals # ========= -function sendRaw() { +function _sendRaw() { [ -n "$_exprashRedirectStdout" ] && exec >&5; cat; [ -n "$_exprashRedirectStdout" ] && exec 1>>"$_exprashRedirectStdout"; @@ -198,13 +250,13 @@ function sendRaw() { # $1: path # $2: (nameref) array -function pathToArray() { +function _pathToArray() { readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .); } # $1: route # $2: (nameref) associative array for params -function pathMatch() { +function _pathMatch() { [ -z ${PATH_INFO+x} ] && return 1; [ -z ${1+x} ] && return 1; @@ -215,10 +267,10 @@ function pathMatch() { local -n routeParams="$2"; local pathArr; - pathToArray "$path" pathArr; + _pathToArray "$path" pathArr; local routeArr; - pathToArray "$route" routeArr; + _pathToArray "$route" routeArr; # Get max path length local routeLen=${#routeArr[@]}; @@ -245,21 +297,117 @@ function pathMatch() { return 0; } +# ================================== +# Multi-dimensional Parameter Arrays +# ================================== + +# $1: (nameref) associative array +# $2: key +# $3: value +function _multiAdd() { + local -n multiArr="$1" + local key="$2" + local value="$3" + local i=0 + while [[ -v "multiArr[$i,$key]" ]]; do + let i++ + done + multiArr["$i,$key"]="$value" +} + +# $1: (nameref) associative array +# $2: key +function _multiLen() { + local -n multiArr="$1" + local key="$2" + local value="$3" + local i=0 + while [[ -v "multiArr[$i,$key]" ]]; do + let i++ + done + printf '%s' "$i" +} + +# $1: (nameref) associative array +# $2: key +function _multiHas() { + local -n multiArr="$1" + local key="$2" + [[ -v "multiArr[0,$key]" ]] +} + +# $1: (nameref) associative array +# $2: key +# $3: index +function _multiGet() { + local -n multiArr="$1" + local key="$2" + local i="${3:-0}"; + printf '%s' "${multiArr[$i,$key]}" +} + +# ================== +# URL Encoded Parser +# ================== + +# $1: urlencoded string +decodeUri () { + local i="${*//+/ }"; + echo -e "${i//%/\\x}"; +} + +# $1: multi associative array +function _parseUrlEncoded() { + local -n parsedArr="$1" + + while IFS= read -d '&' -r pair || [ "$pair" ]; do + name=$(decodeUri "${pair%%=*}") + value=$(decodeUri "${pair#*=}") + if [ -n "$name" ]; then + _multiAdd parsedArr "$name" "$value" + fi; + done +} + + # ======= # Globals # ======= -# Route globals -function _exprashResetRouteGlobals() { - _exprashRouteHandled=0 - _exprashErrorMessage='' - declare -gA _exprashParams - - _exprashHeadersSent=0 - declare -gA _exprashHeaders - _exprashHeaders['Content-Type']='text/html' -} -_exprashResetRouteGlobals - # Setup globals _exprashRedirectStdout='' + +# Route Parameters +declare -gA _exprashParams + +# Body Parameters +declare -gA _exprashBody + +# Query Parameters +declare -gA _exprashQuery + +# Headers +declare -gA _exprashHeaders + +function _exprashResetRouteGlobals() { + _exprashParams=() + _exprashBody=() + _exprashQuery=() + _exprashHeaders=() + + _exprashRouteHandled=0 + _exprashErrorMessage='' + _exprashHeadersSent=0 + _exprashHeaders['Content-Type']='text/html' + +} + +# Set up route globals +_exprashResetRouteGlobals + +# ============== +# Initialization +# ============== + +# Parse query string +_parseUrlEncoded _exprashQuery < <(echo "$QUERY_STRING")