You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
413 lines
7.6 KiB
413 lines
7.6 KiB
#!/bin/bash |
|
# shellcheck disable=SC2034 # false flags nameref params |
|
|
|
# ====================== |
|
# Required shell options |
|
# ====================== |
|
|
|
# Necessary for piping to "send" funciton |
|
shopt -s lastpipe |
|
|
|
# =============== |
|
# Setup Functions |
|
# =============== |
|
|
|
# $1: Log file for redirecting stdout to |
|
function redirectStdout() { |
|
local output="$1"; |
|
[ -z "$output" ] && output="/dev/null"; |
|
|
|
exec 5<&1; |
|
_exprashRedirectStdout="$output"; |
|
exec 1>>"$_exprashRedirectStdout"; |
|
printf '%s: %s\n' "${REQUEST_METHOD:-UNKNOWN}" "${PATH_INFO:-/}" |
|
} |
|
|
|
# ============= |
|
# Route Function |
|
# ============= |
|
|
|
# $1: path |
|
function get() { |
|
[ "$REQUEST_METHOD" != "GET" ] && return 1; |
|
all "$1"; |
|
} |
|
|
|
# $1: path |
|
function post() { |
|
[ "$REQUEST_METHOD" != "POST" ] && return 1; |
|
all "$1"; |
|
} |
|
|
|
# $1: path |
|
function put() { |
|
[ "$REQUEST_METHOD" != "PUT" ] && return 1; |
|
all "$1"; |
|
} |
|
|
|
# $1: path |
|
function delete() { |
|
[ "$REQUEST_METHOD" != "DELETE" ] && return 1; |
|
all "$1"; |
|
} |
|
|
|
# $1: path |
|
function all() { |
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1; |
|
[ -n "$_exprashErrorMessage" ] && return 1; |
|
|
|
# Reset params |
|
_exprashParams=(); |
|
|
|
_pathMatch "$1" _exprashParams || return 1; |
|
_exprashRouteHandled=1; |
|
|
|
return 0; |
|
} |
|
|
|
function use() { |
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1; |
|
[ -n "$_exprashErrorMessage" ] && return 1; |
|
|
|
_exprashRouteHandled=1; |
|
return 0; |
|
} |
|
|
|
# ===================== |
|
# Error Route Functions |
|
# ===================== |
|
|
|
# $1: path |
|
function getError() { |
|
[ "$REQUEST_METHOD" != "GET" ] && return 1; |
|
allError "$1"; |
|
} |
|
|
|
# $1: path |
|
function postError() { |
|
[ "$REQUEST_METHOD" != "POST" ] && return 1; |
|
allError "$1"; |
|
} |
|
|
|
# $1: path |
|
function putError() { |
|
[ "$REQUEST_METHOD" != "PUT" ] && return 1; |
|
allError "$1"; |
|
} |
|
|
|
# $1: path |
|
function deleteError() { |
|
[ "$REQUEST_METHOD" != "DELETE" ] && return 1; |
|
allError "$1"; |
|
} |
|
|
|
# $1: path |
|
function allError() { |
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1; |
|
[ -z "$_exprashErrorMessage" ] && return 1; |
|
|
|
# Reset params |
|
_exprashParams=(); |
|
|
|
_pathMatch "$1" _exprashParams || return 1; |
|
_exprashRouteHandled=1; |
|
|
|
return 0; |
|
} |
|
|
|
function useError() { |
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1; |
|
[ -z "$_exprashErrorMessage" ] && return 1; |
|
|
|
_exprashRouteHandled=1; |
|
return 0; |
|
} |
|
|
|
# ==== |
|
# Next |
|
# ==== |
|
|
|
# $1 (optional): error message |
|
function next() { |
|
_exprashRouteHandled=0; |
|
[ -n "$1" ] && _exprashErrorMessage="$1"; |
|
} |
|
|
|
# ============= |
|
# App Functions |
|
# ============= |
|
|
|
function errorMessage() { |
|
printf '%s' "$_exprashErrorMessage"; |
|
} |
|
|
|
function hasErrorMessage() { |
|
[ -n "$_exprashErrorMessage" ]; |
|
} |
|
|
|
# ================= |
|
# Request Functions |
|
# ================= |
|
|
|
# $1: param name |
|
function param() { |
|
printf '%s\n' "${_exprashParams[$1]}"; |
|
} |
|
|
|
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 |
|
# ================== |
|
|
|
function send() { |
|
if [ "$_exprashHeadersSent" -eq 0 ]; then |
|
sendHeaders |
|
_exprashHeadersSent=1 |
|
fi |
|
|
|
_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" |
|
} |
|
|
|
function sendHeaders() { |
|
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 |
|
done; |
|
printf '\n' | _sendRaw |
|
} |
|
|
|
# ========= |
|
# Internals |
|
# ========= |
|
|
|
function _sendRaw() { |
|
[ -n "$_exprashRedirectStdout" ] && exec >&5; |
|
cat; |
|
[ -n "$_exprashRedirectStdout" ] && exec 1>>"$_exprashRedirectStdout"; |
|
} |
|
|
|
# $1: path |
|
# $2: (nameref) array |
|
function _pathToArray() { |
|
readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .); |
|
} |
|
|
|
# $1: route |
|
# $2: (nameref) associative array for params |
|
function _pathMatch() { |
|
[ -z ${PATH_INFO+x} ] && return 1; |
|
[ -z ${1+x} ] && return 1; |
|
|
|
local path="$PATH_INFO"; |
|
local route="$1"; |
|
|
|
# Params associative array |
|
local -n routeParams="$2"; |
|
|
|
local pathArr; |
|
_pathToArray "$path" pathArr; |
|
|
|
local routeArr; |
|
_pathToArray "$route" routeArr; |
|
|
|
# Get max path length |
|
local routeLen=${#routeArr[@]}; |
|
local pathLen=${#pathArr[@]}; |
|
local maxLen=$(( routeLen >= pathLen ? routeLen : pathLen )); |
|
|
|
for ((i=0; i<maxLen; i++)); do |
|
local routeComponent="${routeArr[$i]}"; |
|
local pathComponent="${pathArr[$i]}"; |
|
|
|
# If route component starts with ":" |
|
if [[ "$routeComponent" == :* ]]; then |
|
routeParams["${routeComponent:1}"]="$pathComponent"; |
|
elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then |
|
continue; |
|
elif [[ "$routeComponent" == '**' ]] && [ -n "$pathComponent" ]; then |
|
break; |
|
else |
|
# Confirm paths match |
|
[ "$routeComponent" != "$pathComponent" ] && return 1; |
|
fi; |
|
done; |
|
|
|
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 |
|
# ======= |
|
|
|
# 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")
|
|
|