#!/bin/bash # shellcheck disable=SC2034 # false flags nameref params # shellcheck disable=SC2178 # 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 redirect_stdout() { local output="$1"; [ -z "$output" ] && output="/dev/null"; exec 5<&1; _exprash_redirect_stdout="$output"; exec 1>>"$_exprash_redirect_stdout"; 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() { [ "$_exprash_route_handled" -eq 1 ] && return 1; [ -n "$_exprash_error_message" ] && return 1; # Reset params _exprash_params=(); _path_match "$1" _exprash_params || return 1; _exprash_route_handled=1; return 0; } function use() { [ "$_exprash_route_handled" -eq 1 ] && return 1; [ -n "$_exprash_error_message" ] && return 1; _exprash_route_handled=1; return 0; } # ===================== # Error Route Functions # ===================== # $1: path function get_error() { [ "$REQUEST_METHOD" != "GET" ] && return 1; all_error "$1"; } # $1: path function post_error() { [ "$REQUEST_METHOD" != "POST" ] && return 1; all_error "$1"; } # $1: path function put_error() { [ "$REQUEST_METHOD" != "PUT" ] && return 1; all_error "$1"; } # $1: path function delete_error() { [ "$REQUEST_METHOD" != "DELETE" ] && return 1; all_error "$1"; } # $1: path function all_error() { [ "$_exprash_route_handled" -eq 1 ] && return 1; [ -z "$_exprash_error_message" ] && return 1; # Reset params _exprash_params=(); _path_match "$1" _exprash_params || return 1; _exprash_route_handled=1; return 0; } function use_error() { [ "$_exprash_route_handled" -eq 1 ] && return 1; [ -z "$_exprash_error_message" ] && return 1; _exprash_route_handled=1; return 0; } # ==== # Next # ==== # $1 (optional): error message function next() { _exprash_route_handled=0; [ -n "$1" ] && _exprash_error_message="$1"; } # ============= # App Functions # ============= function get_error_message() { printf '%s' "$_exprash_error_message"; } function has_error_message() { [ -n "$_exprash_error_message" ]; } # ================= # Request Functions # ================= # $1: param name function param() { printf '%s\n' "${_exprash_params[$1]}"; } function has_param() { [[ -v "_exprash_params[$1]" ]]; } # $1: key # $2: (optional) index for accessing array parameters function query() { _multi_get _exprash_query "$1" "$2" } # $1: key function has_query() { _multi_has _exprash_query "$1" } # $1: key function len_query() { _multi_len _exprash_query "$1" } # Call this function to parse URLencoded request bodies function use_body() { if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]]; then _parse_url_encoded _exprash_body fi } # $1: key # $2: (optional) index for accessing array parameters function body() { _multi_get _exprash_body "$1" "$2" } # $1: key function has_body() { _multi_has _exprash_body "$1" } # $1: key function len_body() { _multi_len _exprash_body "$1" } # $1: key function cookie() { printf '%s' "${_exprash_cookies[$1]}" } # $1: key function has_cookie() { [[ -v "_exprash_cookies[$1]" ]] } # $1: key # $2: value function set_cookie() { _exprash_set_cookies["$1"]="$2" } # ================== # Response Functions # ================== function send() { if [ "$_exprash_headers_sent" -eq 0 ]; then send_headers _exprash_headers_sent=1 fi _send_raw } function send_json() { set_header 'Content-Type' 'application/json' send } # $1: Path function redirect() { local path="$1" if [ "$path" == 'back' ]; then path="${HTTP_REFERER:-/}" fi status '302' set_header 'Location' "$path" printf 'Redirecting to: %s' "$path" | send } # $1: satus code function status() { set_header "Status" "$1" } # $1: Header Name # $2: Header Value function set_header() { _exprash_headers["$1"]="$2" } function send_headers() { printf '%s\n' "Content-Type: ${_exprash_headers['Content-Type']}" | \ _send_raw for key in "${!_exprash_headers[@]}"; do [ "$key" == 'Content-Type' ] && continue printf '%s\n' "${key}: ${_exprash_headers[$key]}" | \ _send_raw done; for key in "${!_exprash_set_cookies[@]}"; do printf '%s\n' "Set-Cookie: ${key}=${_exprash_set_cookies[$key]}" | \ _send_raw done; printf '\n' | _send_raw } # ======= # Session # ======= # Call this function to automatically manage sessions # $1: (optional) session dir, defaults to "session" in the current directory function use_session() { local session_dir=$1 # Create default session directory if [ -z "$session_dir" ]; then session_dir="./session" mkdir -p "$session_dir" fi # Setup session globals _exprash_use_session=1 _exprash_session_dir=$session_dir _load_session || _create_session || return 1 } # Set a session variable # $1: variable name # $2: (optional) vairable value function session() { local name="$1" if [[ -v 2 ]]; then local value="$2" _exprash_session["$name"]="$value" else printf '%s' "${_exprash_session["$name"]}" fi } # ========= # Internals # ========= function _send_raw() { [ -n "$_exprash_redirect_stdout" ] && exec >&5; cat; [ -n "$_exprash_redirect_stdout" ] && exec 1>>"$_exprash_redirect_stdout"; } # $1: path # $2: (nameref) array function _path_to_array() { readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .); } # $1: route # $2: (nameref) associative array for params function _path_match() { [ -z ${PATH_INFO+x} ] && return 1; [ -z ${1+x} ] && return 1; local path="$PATH_INFO"; local route="$1"; # Params associative array local -n route_params="$2"; local path_arr; _path_to_array "$path" path_arr; local route_arr; _path_to_array "$route" route_arr; # Get max path length local route_len=${#route_arr[@]}; local path_len=${#path_arr[@]}; local maxLen=$(( route_len >= path_len ? route_len : path_len )); for ((i=0; i "$session_file" } # ================= # Utility Functions # ================= # $1 | stdin: string to trim function _trim() { local str="${1-"$(< /dev/stdin)"}" # trim leading spaces str="${str#"${str%%[![:space:]]*}"}" # trim trailing spaces str="${str%"${str##*[![:space:]]}"}" printf '%s' "$str" } # ======== # Shutdown # ======== function _exprash_shutdown() { _save_session } # ============== # Initialization # ============== function _exprash_init() { _exprash_redirect_stdout='' declare -gA _exprash_params=() declare -gA _exprash_body=() declare -gA _exprash_query=() declare -gA _exprash_headers=() declare -gA _exprash_cookies=() declare -gA _exprash_set_cookies=() _exprash_use_session=0 _exprash_session_dir='' _exprash_sessionId='' declare -gA _exprash_session=() _exprash_session_cookie_name='exprash_session' _exprash_route_handled=0 _exprash_error_message='' _exprash_headers_sent=0 _exprash_headers['Content-Type']='text/html' # Parse query string _parse_url_encoded _exprash_query "$QUERY_STRING" # Parse cookies _parse_cookies _exprash_cookies "$HTTP_COOKIE" } # Shutdown trap trap _exprash_shutdown EXIT # Initialize exprash _exprash_init