2022-09-28 22:05:02 -07:00
|
|
|
#!/bin/bash
|
|
|
|
# shellcheck disable=SC2034 # false flags nameref params
|
|
|
|
|
2022-09-29 22:35:02 -07:00
|
|
|
# ======================
|
|
|
|
# Required shell options
|
|
|
|
# ======================
|
2022-09-29 21:52:56 -07:00
|
|
|
|
|
|
|
# Necessary for piping to "send" funciton
|
|
|
|
shopt -s lastpipe
|
|
|
|
|
|
|
|
# ===============
|
|
|
|
# Setup Functions
|
|
|
|
# ===============
|
|
|
|
|
2022-09-29 22:35:02 -07:00
|
|
|
# $1: Log file for redirecting stdout to
|
2022-09-29 21:52:56 -07:00
|
|
|
function redirectStdout() {
|
|
|
|
local output="$1";
|
|
|
|
[ -z "$output" ] && output="/dev/null";
|
|
|
|
|
|
|
|
exec 5<&1;
|
2022-09-29 22:35:02 -07:00
|
|
|
_exprashRedirectStdout="$output";
|
2022-09-29 21:52:56 -07:00
|
|
|
exec 1>>"$_exprashRedirectStdout";
|
|
|
|
printf '%s: %s\n' "${REQUEST_METHOD:-UNKNOWN}" "${PATH_INFO:-/}"
|
|
|
|
}
|
|
|
|
|
2022-09-28 22:05:02 -07:00
|
|
|
# =============
|
2022-09-29 21:52:56 -07:00
|
|
|
# Route Function
|
2022-09-28 22:05:02 -07:00
|
|
|
# =============
|
|
|
|
|
|
|
|
# $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() {
|
2022-09-29 21:52:56 -07:00
|
|
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1;
|
|
|
|
[ -n "$_exprashErrorMessage" ] && return 1;
|
2022-09-28 22:05:02 -07:00
|
|
|
|
|
|
|
# Reset params
|
2022-09-29 21:52:56 -07:00
|
|
|
_exprashParams=();
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
_pathMatch "$1" _exprashParams || return 1;
|
2022-09-29 21:52:56 -07:00
|
|
|
_exprashRouteHandled=1;
|
2022-09-28 22:05:02 -07:00
|
|
|
|
2022-09-28 22:33:08 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function use() {
|
2022-09-29 21:52:56 -07:00
|
|
|
[ "$_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=();
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
_pathMatch "$1" _exprashParams || return 1;
|
2022-09-29 21:52:56 -07:00
|
|
|
_exprashRouteHandled=1;
|
|
|
|
|
2022-09-28 22:05:02 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
function useError() {
|
|
|
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1;
|
|
|
|
[ -z "$_exprashErrorMessage" ] && return 1;
|
|
|
|
|
|
|
|
_exprashRouteHandled=1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# ====
|
|
|
|
# Next
|
|
|
|
# ====
|
|
|
|
|
|
|
|
# $1 (optional): error message
|
2022-09-28 22:05:02 -07:00
|
|
|
function next() {
|
2022-09-29 21:52:56 -07:00
|
|
|
_exprashRouteHandled=0;
|
|
|
|
[ -n "$1" ] && _exprashErrorMessage="$1";
|
|
|
|
}
|
|
|
|
|
|
|
|
# =============
|
|
|
|
# App Functions
|
|
|
|
# =============
|
|
|
|
|
|
|
|
function errorMessage() {
|
|
|
|
printf '%s' "$_exprashErrorMessage";
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasErrorMessage() {
|
|
|
|
[ -n "$_exprashErrorMessage" ];
|
2022-09-28 22:05:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
# =================
|
|
|
|
# Request Functions
|
|
|
|
# =================
|
|
|
|
|
|
|
|
# $1: param name
|
|
|
|
function param() {
|
2022-09-29 21:52:56 -07:00
|
|
|
printf '%s\n' "${_exprashParams[$1]}";
|
2022-09-28 22:05:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function hasParam() {
|
2022-09-29 21:52:56 -07:00
|
|
|
[[ -v "_exprashParams[$1]" ]];
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
# $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
|
|
|
|
}
|
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
# ==================
|
|
|
|
# Response Functions
|
|
|
|
# ==================
|
|
|
|
|
|
|
|
function send() {
|
|
|
|
if [ "$_exprashHeadersSent" -eq 0 ]; then
|
|
|
|
sendHeaders
|
|
|
|
_exprashHeadersSent=1
|
|
|
|
fi
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
_sendRaw
|
|
|
|
}
|
|
|
|
|
2022-10-16 14:05:43 -07:00
|
|
|
function sendJson() {
|
|
|
|
setHeader 'Content-Type' 'application/json'
|
|
|
|
send
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
# $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"
|
2022-09-29 21:52:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
# $1: Header Name
|
|
|
|
# $2: Header Value
|
|
|
|
function setHeader() {
|
2022-10-13 19:31:24 -07:00
|
|
|
_exprashHeaders["$1"]="$2"
|
2022-09-29 21:52:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function sendHeaders() {
|
2022-10-13 19:31:24 -07:00
|
|
|
printf '%s\n' "Content-Type: ${_exprashHeaders['Content-Type']}" | _sendRaw
|
2022-09-29 21:52:56 -07:00
|
|
|
for key in "${!_exprashHeaders[@]}"; do
|
|
|
|
[ "$key" == 'Content-Type' ] && continue
|
2022-10-13 19:31:24 -07:00
|
|
|
printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw
|
2022-09-29 21:52:56 -07:00
|
|
|
done;
|
2022-10-13 19:31:24 -07:00
|
|
|
printf '\n' | _sendRaw
|
2022-09-28 22:05:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
# =========
|
|
|
|
# Internals
|
|
|
|
# =========
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
function _sendRaw() {
|
2022-09-29 21:52:56 -07:00
|
|
|
[ -n "$_exprashRedirectStdout" ] && exec >&5;
|
|
|
|
cat;
|
|
|
|
[ -n "$_exprashRedirectStdout" ] && exec 1>>"$_exprashRedirectStdout";
|
|
|
|
}
|
|
|
|
|
2022-09-28 22:05:02 -07:00
|
|
|
# $1: path
|
|
|
|
# $2: (nameref) array
|
2022-10-13 19:31:24 -07:00
|
|
|
function _pathToArray() {
|
2022-09-28 22:05:02 -07:00
|
|
|
readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .);
|
|
|
|
}
|
|
|
|
|
|
|
|
# $1: route
|
|
|
|
# $2: (nameref) associative array for params
|
2022-10-13 19:31:24 -07:00
|
|
|
function _pathMatch() {
|
2022-09-28 22:05:02 -07:00
|
|
|
[ -z ${PATH_INFO+x} ] && return 1;
|
|
|
|
[ -z ${1+x} ] && return 1;
|
|
|
|
|
|
|
|
local path="$PATH_INFO";
|
|
|
|
local route="$1";
|
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
# Params associative array
|
|
|
|
local -n routeParams="$2";
|
|
|
|
|
2022-09-28 22:05:02 -07:00
|
|
|
local pathArr;
|
2022-10-13 19:31:24 -07:00
|
|
|
_pathToArray "$path" pathArr;
|
2022-09-28 22:05:02 -07:00
|
|
|
|
|
|
|
local routeArr;
|
2022-10-13 19:31:24 -07:00
|
|
|
_pathToArray "$route" routeArr;
|
2022-09-28 22:05:02 -07:00
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
# Get max path length
|
|
|
|
local routeLen=${#routeArr[@]};
|
|
|
|
local pathLen=${#pathArr[@]};
|
|
|
|
local maxLen=$(( routeLen >= pathLen ? routeLen : pathLen ));
|
2022-09-28 22:05:02 -07:00
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
for ((i=0; i<maxLen; i++)); do
|
2022-09-28 22:05:02 -07:00
|
|
|
local routeComponent="${routeArr[$i]}";
|
|
|
|
local pathComponent="${pathArr[$i]}";
|
|
|
|
|
|
|
|
# If route component starts with ":"
|
|
|
|
if [[ "$routeComponent" == :* ]]; then
|
|
|
|
routeParams["${routeComponent:1}"]="$pathComponent";
|
2022-09-29 21:52:56 -07:00
|
|
|
elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then
|
|
|
|
continue;
|
|
|
|
elif [[ "$routeComponent" == '**' ]] && [ -n "$pathComponent" ]; then
|
|
|
|
break;
|
2022-09-28 22:05:02 -07:00
|
|
|
else
|
|
|
|
# Confirm paths match
|
|
|
|
[ "$routeComponent" != "$pathComponent" ] && return 1;
|
|
|
|
fi;
|
|
|
|
done;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
# ==================================
|
|
|
|
# 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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-28 22:05:02 -07:00
|
|
|
# =======
|
|
|
|
# Globals
|
|
|
|
# =======
|
2022-09-29 21:52:56 -07:00
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
# Setup globals
|
|
|
|
_exprashRedirectStdout=''
|
|
|
|
|
|
|
|
# Route Parameters
|
|
|
|
declare -gA _exprashParams
|
|
|
|
|
|
|
|
# Body Parameters
|
|
|
|
declare -gA _exprashBody
|
|
|
|
|
|
|
|
# Query Parameters
|
|
|
|
declare -gA _exprashQuery
|
|
|
|
|
|
|
|
# Headers
|
|
|
|
declare -gA _exprashHeaders
|
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
function _exprashResetRouteGlobals() {
|
2022-10-13 19:31:24 -07:00
|
|
|
_exprashParams=()
|
|
|
|
_exprashBody=()
|
|
|
|
_exprashQuery=()
|
|
|
|
_exprashHeaders=()
|
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
_exprashRouteHandled=0
|
|
|
|
_exprashErrorMessage=''
|
|
|
|
_exprashHeadersSent=0
|
|
|
|
_exprashHeaders['Content-Type']='text/html'
|
2022-10-13 19:31:24 -07:00
|
|
|
|
2022-09-29 21:52:56 -07:00
|
|
|
}
|
2022-10-13 19:31:24 -07:00
|
|
|
|
|
|
|
# Set up route globals
|
2022-09-29 21:52:56 -07:00
|
|
|
_exprashResetRouteGlobals
|
|
|
|
|
2022-10-13 19:31:24 -07:00
|
|
|
# ==============
|
|
|
|
# Initialization
|
|
|
|
# ==============
|
|
|
|
|
|
|
|
# Parse query string
|
|
|
|
_parseUrlEncoded _exprashQuery < <(echo "$QUERY_STRING")
|