|
|
@ -1,5 +1,6 @@ |
|
|
|
#!/bin/bash |
|
|
|
#!/bin/bash |
|
|
|
# shellcheck disable=SC2034 # false flags nameref params |
|
|
|
# shellcheck disable=SC2034 # false flags nameref params |
|
|
|
|
|
|
|
# shellcheck disable=SC2178 # false flags nameref params |
|
|
|
|
|
|
|
|
|
|
|
# ====================== |
|
|
|
# ====================== |
|
|
|
# Required shell options |
|
|
|
# Required shell options |
|
|
@ -172,6 +173,12 @@ function lenQuery() { |
|
|
|
_multiLen _exprashQuery "$1" |
|
|
|
_multiLen _exprashQuery "$1" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Call this function to parse URLencoded request bodies |
|
|
|
|
|
|
|
function useBody() { |
|
|
|
|
|
|
|
if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]]; then |
|
|
|
|
|
|
|
_parseUrlEncoded _exprashBody |
|
|
|
|
|
|
|
fi |
|
|
|
|
|
|
|
} |
|
|
|
# $1: key |
|
|
|
# $1: key |
|
|
|
# $2: (optional) index for accessing array parameters |
|
|
|
# $2: (optional) index for accessing array parameters |
|
|
|
function body() { |
|
|
|
function body() { |
|
|
@ -186,11 +193,18 @@ function lenBody() { |
|
|
|
_multiLen _exprashBody "$1" |
|
|
|
_multiLen _exprashBody "$1" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# Call this function to parse URLencoded request bodies |
|
|
|
# $1: key |
|
|
|
function bodyParser() { |
|
|
|
function cookie() { |
|
|
|
if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]]; then |
|
|
|
printf '%s' "${_exprashCookies[$1]}" |
|
|
|
_parseUrlEncoded _exprashBody |
|
|
|
} |
|
|
|
fi |
|
|
|
# $1: key |
|
|
|
|
|
|
|
function hasCookie() { |
|
|
|
|
|
|
|
[[ -v "_exprashCookies[$1]" ]] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
# $1: key |
|
|
|
|
|
|
|
# $2: value |
|
|
|
|
|
|
|
function setCookie() { |
|
|
|
|
|
|
|
_exprashSetCookies["$1"]="$2" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# ================== |
|
|
|
# ================== |
|
|
@ -240,9 +254,48 @@ function sendHeaders() { |
|
|
|
[ "$key" == 'Content-Type' ] && continue |
|
|
|
[ "$key" == 'Content-Type' ] && continue |
|
|
|
printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw |
|
|
|
printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw |
|
|
|
done; |
|
|
|
done; |
|
|
|
|
|
|
|
for key in "${!_exprashSetCookies[@]}"; do |
|
|
|
|
|
|
|
printf '%s\n' "Set-Cookie: ${key}=${_exprashSetCookies[$key]}" | _sendRaw |
|
|
|
|
|
|
|
done; |
|
|
|
printf '\n' | _sendRaw |
|
|
|
printf '\n' | _sendRaw |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ======= |
|
|
|
|
|
|
|
# Session |
|
|
|
|
|
|
|
# ======= |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Call this function to automatically manage sessions |
|
|
|
|
|
|
|
# $1: (optional) session dir, defaults to "session" in the current directory |
|
|
|
|
|
|
|
function useSession() { |
|
|
|
|
|
|
|
local session_dir=$1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create default session directory |
|
|
|
|
|
|
|
if [ -z "$session_dir" ]; then |
|
|
|
|
|
|
|
session_dir="./session" |
|
|
|
|
|
|
|
mkdir -p "$session_dir" |
|
|
|
|
|
|
|
fi |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Setup session globals |
|
|
|
|
|
|
|
_exprashUseSession=1 |
|
|
|
|
|
|
|
_exprashSessionDir=$session_dir |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_loadSession || _createSession || 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" |
|
|
|
|
|
|
|
_exprashSession["$name"]="$value" |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
printf '%s' "${_exprashSession["$name"]}" |
|
|
|
|
|
|
|
fi |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# ========= |
|
|
|
# ========= |
|
|
|
# Internals |
|
|
|
# Internals |
|
|
|
# ========= |
|
|
|
# ========= |
|
|
@ -287,7 +340,7 @@ function _pathMatch() { |
|
|
|
local pathComponent="${pathArr[$i]}"; |
|
|
|
local pathComponent="${pathArr[$i]}"; |
|
|
|
|
|
|
|
|
|
|
|
# If route component starts with ":" |
|
|
|
# If route component starts with ":" |
|
|
|
if [[ "$routeComponent" == :* ]]; then |
|
|
|
if [[ "$routeComponent" == :* ]] && [ -n "$pathComponent" ]; then |
|
|
|
routeParams["${routeComponent:1}"]="$pathComponent"; |
|
|
|
routeParams["${routeComponent:1}"]="$pathComponent"; |
|
|
|
elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then |
|
|
|
elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then |
|
|
|
continue; |
|
|
|
continue; |
|
|
@ -315,7 +368,7 @@ function _multiAdd() { |
|
|
|
local value="$3" |
|
|
|
local value="$3" |
|
|
|
local i=0 |
|
|
|
local i=0 |
|
|
|
while [[ -v "multiArr[$i,$key]" ]]; do |
|
|
|
while [[ -v "multiArr[$i,$key]" ]]; do |
|
|
|
let i++ |
|
|
|
(( i++ )) |
|
|
|
done |
|
|
|
done |
|
|
|
multiArr["$i,$key"]="$value" |
|
|
|
multiArr["$i,$key"]="$value" |
|
|
|
} |
|
|
|
} |
|
|
@ -325,10 +378,9 @@ function _multiAdd() { |
|
|
|
function _multiLen() { |
|
|
|
function _multiLen() { |
|
|
|
local -n multiArr="$1" |
|
|
|
local -n multiArr="$1" |
|
|
|
local key="$2" |
|
|
|
local key="$2" |
|
|
|
local value="$3" |
|
|
|
|
|
|
|
local i=0 |
|
|
|
local i=0 |
|
|
|
while [[ -v "multiArr[$i,$key]" ]]; do |
|
|
|
while [[ -v "multiArr[$i,$key]" ]]; do |
|
|
|
let i++ |
|
|
|
(( i++ )) |
|
|
|
done |
|
|
|
done |
|
|
|
printf '%s' "$i" |
|
|
|
printf '%s' "$i" |
|
|
|
} |
|
|
|
} |
|
|
@ -355,15 +407,19 @@ function _multiGet() { |
|
|
|
# URL Encoded Parser |
|
|
|
# URL Encoded Parser |
|
|
|
# ================== |
|
|
|
# ================== |
|
|
|
|
|
|
|
|
|
|
|
# $1: urlencoded string |
|
|
|
# $1 | stdin: urlencoded string |
|
|
|
decodeUri () { |
|
|
|
decodeUri () { |
|
|
|
local i="${*//+/ }"; |
|
|
|
local input_str="${1-"$(< /dev/stdin)"}" |
|
|
|
echo -e "${i//%/\\x}"; |
|
|
|
input_str="${input_str//+/ }"; |
|
|
|
|
|
|
|
echo -e "${input_str//%/\\x}"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# $1: multi associative array |
|
|
|
# $1: (nameref) multi associative array |
|
|
|
|
|
|
|
# $2 | stdin: url encoded data |
|
|
|
function _parseUrlEncoded() { |
|
|
|
function _parseUrlEncoded() { |
|
|
|
local -n parsedArr="$1" |
|
|
|
local -n parsedArr="$1" |
|
|
|
|
|
|
|
local url_encoded_str="${2-"$(< /dev/stdin)"}" |
|
|
|
|
|
|
|
local pair name value |
|
|
|
|
|
|
|
|
|
|
|
while IFS= read -d '&' -r pair || [ "$pair" ]; do |
|
|
|
while IFS= read -d '&' -r pair || [ "$pair" ]; do |
|
|
|
name=$(decodeUri "${pair%%=*}") |
|
|
|
name=$(decodeUri "${pair%%=*}") |
|
|
@ -371,48 +427,140 @@ function _parseUrlEncoded() { |
|
|
|
if [ -n "$name" ]; then |
|
|
|
if [ -n "$name" ]; then |
|
|
|
_multiAdd parsedArr "$name" "$value" |
|
|
|
_multiAdd parsedArr "$name" "$value" |
|
|
|
fi; |
|
|
|
fi; |
|
|
|
done |
|
|
|
done <<< "$url_encoded_str" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ======= |
|
|
|
|
|
|
|
# Cookies |
|
|
|
|
|
|
|
# ======= |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# $1: (nameref) associative array |
|
|
|
|
|
|
|
# $2 | stdin: cookie string to parse |
|
|
|
|
|
|
|
function _parseCookies() { |
|
|
|
|
|
|
|
local -n parsedArr="$1" |
|
|
|
|
|
|
|
local cookie_str="${2-"$(< /dev/stdin)"}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
local pair name value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while IFS= read -d ';' -r pair || [ "$pair" ]; do |
|
|
|
|
|
|
|
name="$(_trim "${pair%%=*}" | decodeUri)" |
|
|
|
|
|
|
|
value="$(_trim "${pair#*=}" | decodeUri)" |
|
|
|
|
|
|
|
if [ -n "$name" ]; then |
|
|
|
|
|
|
|
parsedArr["$name"]="$value" |
|
|
|
|
|
|
|
fi |
|
|
|
|
|
|
|
done <<< "$cookie_str" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# ======= |
|
|
|
# ======= |
|
|
|
# Globals |
|
|
|
# Session |
|
|
|
# ======= |
|
|
|
# ======= |
|
|
|
|
|
|
|
|
|
|
|
# Setup globals |
|
|
|
function _loadSession() { |
|
|
|
_exprashRedirectStdout='' |
|
|
|
[ "$_exprashUseSession" -eq 1 ] || return 1 |
|
|
|
|
|
|
|
[ -n "$_exprashSessionDir" ] || return 1 |
|
|
|
|
|
|
|
hasCookie "$_exprashSessionCookieName" || return 1 |
|
|
|
|
|
|
|
|
|
|
|
# Route Parameters |
|
|
|
local session_id |
|
|
|
declare -gA _exprashParams |
|
|
|
session_id="$(cookie "$_exprashSessionCookieName")" |
|
|
|
|
|
|
|
local session_file="${_exprashSessionDir%/}/$session_id.session" |
|
|
|
|
|
|
|
|
|
|
|
# Body Parameters |
|
|
|
# shellcheck disable=SC1090 |
|
|
|
declare -gA _exprashBody |
|
|
|
source "$session_file" || return 1 |
|
|
|
|
|
|
|
|
|
|
|
# Query Parameters |
|
|
|
# Set globals |
|
|
|
declare -gA _exprashQuery |
|
|
|
_exprashSessionId=$session_id |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# Headers |
|
|
|
function _createSession() { |
|
|
|
declare -gA _exprashHeaders |
|
|
|
[ "$_exprashUseSession" -eq 1 ] || return 1 |
|
|
|
|
|
|
|
[ -n "$_exprashSessionDir" ] || return 1 |
|
|
|
|
|
|
|
|
|
|
|
function _exprashResetRouteGlobals() { |
|
|
|
# mktemp args |
|
|
|
_exprashParams=() |
|
|
|
local args=() |
|
|
|
_exprashBody=() |
|
|
|
args+=('-p' "$_exprashSessionDir") |
|
|
|
_exprashQuery=() |
|
|
|
args+=("$(printf 'X%.0s' {1..32}).session") |
|
|
|
_exprashHeaders=() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_exprashRouteHandled=0 |
|
|
|
local session_file |
|
|
|
_exprashErrorMessage='' |
|
|
|
session_file=$(mktemp -u "${args[@]}") || return 1 |
|
|
|
_exprashHeadersSent=0 |
|
|
|
local session_file_name=${session_file##*/} |
|
|
|
_exprashHeaders['Content-Type']='text/html' |
|
|
|
local session_id=${session_file_name%%.*} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set cookie |
|
|
|
|
|
|
|
setCookie "$_exprashSessionCookieName" "$session_id" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set globals |
|
|
|
|
|
|
|
_exprashSessionId=$session_id |
|
|
|
|
|
|
|
_exprashSession=() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function _saveSession() { |
|
|
|
|
|
|
|
[ "$_exprashUseSession" -eq 1 ] || return 1 |
|
|
|
|
|
|
|
[ -n "$_exprashSessionDir" ] || return 1 |
|
|
|
|
|
|
|
[ -n "$_exprashSessionId" ] || return 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
local session_file="${_exprashSessionDir%/}/${_exprashSessionId}.session" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
declare -p _exprashSession | sed '1 s/\([^-]*-\)/\1g/' > "$session_file" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# Set up route globals |
|
|
|
# ================= |
|
|
|
_exprashResetRouteGlobals |
|
|
|
# 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 _exprashShutdown() { |
|
|
|
|
|
|
|
_saveSession |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# ============== |
|
|
|
# ============== |
|
|
|
# Initialization |
|
|
|
# Initialization |
|
|
|
# ============== |
|
|
|
# ============== |
|
|
|
|
|
|
|
function _exprashInit() { |
|
|
|
|
|
|
|
_exprashRedirectStdout='' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
declare -gA _exprashParams=() |
|
|
|
|
|
|
|
declare -gA _exprashBody=() |
|
|
|
|
|
|
|
declare -gA _exprashQuery=() |
|
|
|
|
|
|
|
declare -gA _exprashHeaders=() |
|
|
|
|
|
|
|
declare -gA _exprashCookies=() |
|
|
|
|
|
|
|
declare -gA _exprashSetCookies=() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_exprashUseSession=0 |
|
|
|
|
|
|
|
_exprashSessionDir='' |
|
|
|
|
|
|
|
_exprashSessionId='' |
|
|
|
|
|
|
|
declare -gA _exprashSession=() |
|
|
|
|
|
|
|
_exprashSessionCookieName='exprash_session' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_exprashRouteHandled=0 |
|
|
|
|
|
|
|
_exprashErrorMessage='' |
|
|
|
|
|
|
|
_exprashHeadersSent=0 |
|
|
|
|
|
|
|
_exprashHeaders['Content-Type']='text/html' |
|
|
|
|
|
|
|
|
|
|
|
# Parse query string |
|
|
|
# Parse query string |
|
|
|
_parseUrlEncoded _exprashQuery < <(echo "$QUERY_STRING") |
|
|
|
_parseUrlEncoded _exprashQuery "$QUERY_STRING" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Parse cookies |
|
|
|
|
|
|
|
_parseCookies _exprashCookies "$HTTP_COOKIE" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Shutdown trap |
|
|
|
|
|
|
|
trap _exprashShutdown EXIT |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Initialize exprash |
|
|
|
|
|
|
|
_exprashInit |
|
|
|
|
|
|
|
|
|
|
|