#!/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 } function sendJson() { setHeader 'Content-Type' 'application/json' send } # $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