Compare commits

..

2 Commits

Author SHA1 Message Date
75b395ffa2 Finished adding sessions and updating examples 2023-03-09 17:44:11 -07:00
29d58016e0 Added sessions 2023-03-06 02:07:09 -07:00
10 changed files with 533 additions and 205 deletions

View File

@ -12,7 +12,7 @@ Here is some leaked source code for my brand new Cat website. Do not steal!
#!/bin/bash #!/bin/bash
source exprash.sh; source exprash.sh;
redirectStdout 'log'; redirect_stdout 'log';
declare -A cats; declare -A cats;
cats[calico]="Calico"; cats[calico]="Calico";
@ -50,11 +50,11 @@ get '/cats/*' && {
printf '<h1>Error: Cannot find that cat</h1>\n' | send; printf '<h1>Error: Cannot find that cat</h1>\n' | send;
} }
getError '/cats/*' && { get_error '/cats/*' && {
printf '<h1>%s</h1>' "$(errorMessage)" | send; printf '<h1>%s</h1>' "$(get_error_message)" | send;
} }
(use || useError) && { (use || use_error) && {
printf '<h1>404</h1>' | send; printf '<h1>404</h1>' | send;
} }
``` ```

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
source exprash.sh; source exprash.sh;
redirectStdout 'log'; redirect_stdout 'log';
declare -A cats; declare -A cats;
cats[calico]="Calico"; cats[calico]="Calico";
@ -39,10 +39,10 @@ get '/cats/*' && {
printf '<h1>Error: Cannot find that cat</h1>\n' | send; printf '<h1>Error: Cannot find that cat</h1>\n' | send;
} }
getError '/cats/*' && { get_error '/cats/*' && {
printf '<h1>%s</h1>' "$(errorMessage)" | send; printf '<h1>%s</h1>' "$(get_error_message)" | send;
} }
(use || useError) && { (use || use_error) && {
printf '<h1>404</h1>' | send; printf '<h1>404</h1>' | send;
} }

86
examples/login.sh Normal file
View File

@ -0,0 +1,86 @@
#!/bin/bash
source exprash.sh;
redirect_stdout 'log';
use_session
use_body
username='admin'
password='password'
# Authorization middleware
function is_authorized() {
if [ "$(session 'authorized')" != "1" ]; then
next 'unauthorized'
return 1
fi
}
get '/' && {
if [ "$(session 'authorized')" == "1" ]; then
html="<h1>Welcome $username</h1>"
html+="<a href='admin'>Click Here For Secrets</a><br /><br />"
html+="<a href='logout'>Logout</a>"
else
html="<h1>Welcome</h1>"
html+="<p>You must login to learn secrets</p>"
html+="<a href='login'>Login</a>"
fi
printf '%s\n' "$html" | send
}
get '/admin' && is_authorized && {
html='<h1>Here are all of my secrets:</h1>'
html+="<ul><li>Rabbits are soft.</li></ul>"
html+="<a href='.'>Go Home</a>"
printf '%s' "$html" | send
}
get '/login' && {
html="<h1>Login:</h1>"
html+='<form method="POST" action="login">'
html+=' <input type="text" name="username" placeholder="username" />'
html+=' <input type="password" name="password" placeholder="password" />'
html+=' <input type="submit" value="login" />'
html+='</form>'
printf '%s\n' "$html" | send
}
get '/incorrect-password' && {
if [ "$(session 'authorized')" == "1" ]; then
redirect '.'
else
html="<h1>Incorrect Password</h1>"
html+="<p>Try again:</p>"
html+="<a href='login'>Login</a>"
printf '%s\n' "$html" | send
fi
}
post '/login' && {
post_user=$(body 'username')
post_pass=$(body 'password')
if [ "$post_user" == "$username" ] && [ "$post_pass" == "$password" ]; then
session 'authorized' 1
redirect '.'
else
redirect 'incorrect-password'
fi
}
get '/logout' && {
session 'authorized' 0
redirect '.'
}
(use || use_error) && {
if [ "$(get_error_message)" == "unauthorized" ]; then
html='<h1>Error: Access Denied</h1>'
html+='<a href='login'>Click here to login</a>'
printf '%s' "$html" | send
else
status '404'
printf '<h1>404</h1>' | send
fi
}

View File

@ -1,10 +1,24 @@
#!/bin/bash #!/bin/bash
source "$(dirname "$0")/src/exprash.sh"; file_dir="$(dirname "$0")"
source "$(dirname "$0")/tests/utils.sh"; source "$file_dir/src/exprash.sh"
printf '%s\n' "Routes:"; source "$file_dir/tests/utils.sh"
source "$(dirname "$0")/tests/routes.sh";
printf '%s\n' "Routes:"
source "$file_dir/tests/routes.sh"
printf '\n'
printf '%s\n' "Query String:"
source "$file_dir/tests/query.sh"
printf '\n'
printf '%s\n' "Cookies:"
source "$file_dir/tests/cookies.sh"
printf '\n'
printf '%s\n' "Session:"
source "$file_dir/tests/session.sh"
printf '\n'
printf '\n';
testSummary; testSummary;

View File

@ -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
@ -13,13 +14,13 @@ shopt -s lastpipe
# =============== # ===============
# $1: Log file for redirecting stdout to # $1: Log file for redirecting stdout to
function redirectStdout() { function redirect_stdout() {
local output="$1"; local output="$1";
[ -z "$output" ] && output="/dev/null"; [ -z "$output" ] && output="/dev/null";
exec 5<&1; exec 5<&1;
_exprashRedirectStdout="$output"; _exprash_redirect_stdout="$output";
exec 1>>"$_exprashRedirectStdout"; exec 1>>"$_exprash_redirect_stdout";
printf '%s: %s\n' "${REQUEST_METHOD:-UNKNOWN}" "${PATH_INFO:-/}" printf '%s: %s\n' "${REQUEST_METHOD:-UNKNOWN}" "${PATH_INFO:-/}"
} }
@ -53,23 +54,23 @@ function delete() {
# $1: path # $1: path
function all() { function all() {
[ "$_exprashRouteHandled" -eq 1 ] && return 1; [ "$_exprash_route_handled" -eq 1 ] && return 1;
[ -n "$_exprashErrorMessage" ] && return 1; [ -n "$_exprash_error_message" ] && return 1;
# Reset params # Reset params
_exprashParams=(); _exprash_params=();
_pathMatch "$1" _exprashParams || return 1; _path_match "$1" _exprash_params || return 1;
_exprashRouteHandled=1; _exprash_route_handled=1;
return 0; return 0;
} }
function use() { function use() {
[ "$_exprashRouteHandled" -eq 1 ] && return 1; [ "$_exprash_route_handled" -eq 1 ] && return 1;
[ -n "$_exprashErrorMessage" ] && return 1; [ -n "$_exprash_error_message" ] && return 1;
_exprashRouteHandled=1; _exprash_route_handled=1;
return 0; return 0;
} }
@ -78,48 +79,48 @@ function use() {
# ===================== # =====================
# $1: path # $1: path
function getError() { function get_error() {
[ "$REQUEST_METHOD" != "GET" ] && return 1; [ "$REQUEST_METHOD" != "GET" ] && return 1;
allError "$1"; all_error "$1";
} }
# $1: path # $1: path
function postError() { function post_error() {
[ "$REQUEST_METHOD" != "POST" ] && return 1; [ "$REQUEST_METHOD" != "POST" ] && return 1;
allError "$1"; all_error "$1";
} }
# $1: path # $1: path
function putError() { function put_error() {
[ "$REQUEST_METHOD" != "PUT" ] && return 1; [ "$REQUEST_METHOD" != "PUT" ] && return 1;
allError "$1"; all_error "$1";
} }
# $1: path # $1: path
function deleteError() { function delete_error() {
[ "$REQUEST_METHOD" != "DELETE" ] && return 1; [ "$REQUEST_METHOD" != "DELETE" ] && return 1;
allError "$1"; all_error "$1";
} }
# $1: path # $1: path
function allError() { function all_error() {
[ "$_exprashRouteHandled" -eq 1 ] && return 1; [ "$_exprash_route_handled" -eq 1 ] && return 1;
[ -z "$_exprashErrorMessage" ] && return 1; [ -z "$_exprash_error_message" ] && return 1;
# Reset params # Reset params
_exprashParams=(); _exprash_params=();
_pathMatch "$1" _exprashParams || return 1; _path_match "$1" _exprash_params || return 1;
_exprashRouteHandled=1; _exprash_route_handled=1;
return 0; return 0;
} }
function useError() { function use_error() {
[ "$_exprashRouteHandled" -eq 1 ] && return 1; [ "$_exprash_route_handled" -eq 1 ] && return 1;
[ -z "$_exprashErrorMessage" ] && return 1; [ -z "$_exprash_error_message" ] && return 1;
_exprashRouteHandled=1; _exprash_route_handled=1;
return 0; return 0;
} }
@ -129,20 +130,20 @@ function useError() {
# $1 (optional): error message # $1 (optional): error message
function next() { function next() {
_exprashRouteHandled=0; _exprash_route_handled=0;
[ -n "$1" ] && _exprashErrorMessage="$1"; [ -n "$1" ] && _exprash_error_message="$1";
} }
# ============= # =============
# App Functions # App Functions
# ============= # =============
function errorMessage() { function get_error_message() {
printf '%s' "$_exprashErrorMessage"; printf '%s' "$_exprash_error_message";
} }
function hasErrorMessage() { function has_error_message() {
[ -n "$_exprashErrorMessage" ]; [ -n "$_exprash_error_message" ];
} }
# ================= # =================
@ -151,63 +152,77 @@ function hasErrorMessage() {
# $1: param name # $1: param name
function param() { function param() {
printf '%s\n' "${_exprashParams[$1]}"; printf '%s\n' "${_exprash_params[$1]}";
} }
function hasParam() { function has_param() {
[[ -v "_exprashParams[$1]" ]]; [[ -v "_exprash_params[$1]" ]];
} }
# $1: key # $1: key
# $2: (optional) index for accessing array parameters # $2: (optional) index for accessing array parameters
function query() { function query() {
_multiGet _exprashQuery "$1" "$2" _multi_get _exprash_query "$1" "$2"
} }
# $1: key # $1: key
function hasQuery() { function has_query() {
_multiHas _exprashQuery "$1" _multi_has _exprash_query "$1"
} }
# $1: key # $1: key
function lenQuery() { function len_query() {
_multiLen _exprashQuery "$1" _multi_len _exprash_query "$1"
}
# $1: key
# $2: (optional) index for accessing array parameters
function body() {
_multiGet _exprashBody "$1" "$2"
}
# $1: key
function hasBody() {
_multiHas _exprashBody "$1"
}
# $1: key
function lenBody() {
_multiLen _exprashBody "$1"
} }
# Call this function to parse URLencoded request bodies # Call this function to parse URLencoded request bodies
function bodyParser() { function use_body() {
if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]]; then if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]];
_parseUrlEncoded _exprashBody then
_parse_url_encoded _exprash_body
fi 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 # Response Functions
# ================== # ==================
function send() { function send() {
if [ "$_exprashHeadersSent" -eq 0 ]; then if [ "$_exprash_headers_sent" -eq 0 ]; then
sendHeaders send_headers
_exprashHeadersSent=1 _exprash_headers_sent=1
fi fi
_sendRaw _send_raw
} }
function sendJson() { function send_json() {
setHeader 'Content-Type' 'application/json' set_header 'Content-Type' 'application/json'
send send
} }
@ -219,49 +234,91 @@ function redirect() {
fi fi
status '302' status '302'
setHeader 'Location' "$path" set_header 'Location' "$path"
printf 'Redirecting to: %s' "$path" | send printf 'Redirecting to: %s' "$path" | send
} }
# $1: satus code # $1: satus code
function status() { function status() {
setHeader "Status" "$1" set_header "Status" "$1"
} }
# $1: Header Name # $1: Header Name
# $2: Header Value # $2: Header Value
function setHeader() { function set_header() {
_exprashHeaders["$1"]="$2" _exprash_headers["$1"]="$2"
} }
function sendHeaders() { function send_headers() {
printf '%s\n' "Content-Type: ${_exprashHeaders['Content-Type']}" | _sendRaw printf '%s\n' "Content-Type: ${_exprash_headers['Content-Type']}" | \
for key in "${!_exprashHeaders[@]}"; do _send_raw
for key in "${!_exprash_headers[@]}"; do
[ "$key" == 'Content-Type' ] && continue [ "$key" == 'Content-Type' ] && continue
printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw printf '%s\n' "${key}: ${_exprash_headers[$key]}" | \
_send_raw
done; done;
printf '\n' | _sendRaw 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 # Internals
# ========= # =========
function _sendRaw() { function _send_raw() {
[ -n "$_exprashRedirectStdout" ] && exec >&5; [ -n "$_exprash_redirect_stdout" ] && exec >&5;
cat; cat;
[ -n "$_exprashRedirectStdout" ] && exec 1>>"$_exprashRedirectStdout"; [ -n "$_exprash_redirect_stdout" ] && exec 1>>"$_exprash_redirect_stdout";
} }
# $1: path # $1: path
# $2: (nameref) array # $2: (nameref) array
function _pathToArray() { function _path_to_array() {
readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .); readarray -t "$2" < <(printf '%s\n' "$1" | tr '/' '\n' | grep .);
} }
# $1: route # $1: route
# $2: (nameref) associative array for params # $2: (nameref) associative array for params
function _pathMatch() { function _path_match() {
[ -z ${PATH_INFO+x} ] && return 1; [ -z ${PATH_INFO+x} ] && return 1;
[ -z ${1+x} ] && return 1; [ -z ${1+x} ] && return 1;
@ -269,33 +326,33 @@ function _pathMatch() {
local route="$1"; local route="$1";
# Params associative array # Params associative array
local -n routeParams="$2"; local -n route_params="$2";
local pathArr; local path_arr;
_pathToArray "$path" pathArr; _path_to_array "$path" path_arr;
local routeArr; local route_arr;
_pathToArray "$route" routeArr; _path_to_array "$route" route_arr;
# Get max path length # Get max path length
local routeLen=${#routeArr[@]}; local route_len=${#route_arr[@]};
local pathLen=${#pathArr[@]}; local path_len=${#path_arr[@]};
local maxLen=$(( routeLen >= pathLen ? routeLen : pathLen )); local maxLen=$(( route_len >= path_len ? route_len : path_len ));
for ((i=0; i<maxLen; i++)); do for ((i=0; i<maxLen; i++)); do
local routeComponent="${routeArr[$i]}"; local route_component="${route_arr[$i]}";
local pathComponent="${pathArr[$i]}"; local path_component="${path_arr[$i]}";
# If route component starts with ":" # If route component starts with ":"
if [[ "$routeComponent" == :* ]]; then if [[ "$route_component" == :* ]] && [ -n "$path_component" ]; then
routeParams["${routeComponent:1}"]="$pathComponent"; route_params["${route_component:1}"]="$path_component";
elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then elif [[ "$route_component" == '*' ]] && [ -n "$path_component" ]; then
continue; continue;
elif [[ "$routeComponent" == '**' ]] && [ -n "$pathComponent" ]; then elif [[ "$route_component" == '**' ]] && [ -n "$path_component" ]; then
break; break;
else else
# Confirm paths match # Confirm paths match
[ "$routeComponent" != "$pathComponent" ] && return 1; [ "$route_component" != "$path_component" ] && return 1;
fi; fi;
done; done;
@ -309,110 +366,205 @@ function _pathMatch() {
# $1: (nameref) associative array # $1: (nameref) associative array
# $2: key # $2: key
# $3: value # $3: value
function _multiAdd() { function _multi_add() {
local -n multiArr="$1" local -n multi_arr="$1"
local key="$2" local key="$2"
local value="$3" local value="$3"
local i=0 local i=0
while [[ -v "multiArr[$i,$key]" ]]; do while [[ -v "multi_arr[$i,$key]" ]]; do
let i++ (( i++ ))
done done
multiArr["$i,$key"]="$value" multi_arr["$i,$key"]="$value"
} }
# $1: (nameref) associative array # $1: (nameref) associative array
# $2: key # $2: key
function _multiLen() { function _multi_len() {
local -n multiArr="$1" local -n multi_arr="$1"
local key="$2" local key="$2"
local value="$3"
local i=0 local i=0
while [[ -v "multiArr[$i,$key]" ]]; do while [[ -v "multi_arr[$i,$key]" ]]; do
let i++ (( i++ ))
done done
printf '%s' "$i" printf '%s' "$i"
} }
# $1: (nameref) associative array # $1: (nameref) associative array
# $2: key # $2: key
function _multiHas() { function _multi_has() {
local -n multiArr="$1" local -n multi_arr="$1"
local key="$2" local key="$2"
[[ -v "multiArr[0,$key]" ]] [[ -v "multi_arr[0,$key]" ]]
} }
# $1: (nameref) associative array # $1: (nameref) associative array
# $2: key # $2: key
# $3: index # $3: index
function _multiGet() { function _multi_get() {
local -n multiArr="$1" local -n multi_arr="$1"
local key="$2" local key="$2"
local i="${3:-0}"; local i="${3:-0}";
printf '%s' "${multiArr[$i,$key]}" printf '%s' "${multi_arr[$i,$key]}"
} }
# ================== # ==================
# URL Encoded Parser # URL Encoded Parser
# ================== # ==================
# $1: urlencoded string # $1 | stdin: urlencoded string
decodeUri () { decode_uri () {
local i="${*//+/ }"; local input_str="${1-"$(cat)"}"
echo -e "${i//%/\\x}"; input_str="${input_str//+/ }";
echo -e "${input_str//%/\\x}";
} }
# $1: multi associative array # $1: (nameref) multi associative array
function _parseUrlEncoded() { # $2 | stdin: url encoded data
function _parse_url_encoded() {
local -n parsedArr="$1" local -n parsedArr="$1"
local url_encoded_str="${2-"$(cat)"}"
local pair name value
while IFS= read -d '&' -r pair || [ "$pair" ]; do while IFS= read -d '&' -r pair || [ "$pair" ]; do
name=$(decodeUri "${pair%%=*}") name=$(decode_uri "${pair%%=*}")
value=$(decodeUri "${pair#*=}") value=$(decode_uri "${pair#*=}")
if [ -n "$name" ]; then if [ -n "$name" ]; then
_multiAdd parsedArr "$name" "$value" _multi_add parsedArr "$name" "$value"
fi; fi;
done done <<< "$url_encoded_str"
} }
# ======= # =======
# Globals # Cookies
# ======= # =======
# Setup globals # $1: (nameref) associative array
_exprashRedirectStdout='' # $2 | stdin: cookie string to parse
function _parse_cookies() {
local -n parsedArr="$1"
local cookie_str="${2-"$(< /dev/stdin)"}"
# Route Parameters local pair name value
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'
while IFS= read -d ';' -r pair || [ "$pair" ]; do
name="$(_trim "${pair%%=*}" | decode_uri)"
value="$(_trim "${pair#*=}" | decode_uri)"
if [ -n "$name" ]; then
parsedArr["$name"]="$value"
fi
done <<< "$cookie_str"
} }
# Set up route globals # =======
_exprashResetRouteGlobals # Session
# =======
function _load_session() {
[ "$_exprash_use_session" -eq 1 ] || return 1
[ -n "$_exprash_session_dir" ] || return 1
has_cookie "$_exprash_session_cookie_name" || return 1
local session_id
session_id="$(cookie "$_exprash_session_cookie_name")"
local session_file="${_exprash_session_dir%/}/$session_id.session"
# shellcheck disable=SC1090
source "$session_file" || return 1
# Set globals
_exprash_sessionId=$session_id
}
function _create_session() {
[ "$_exprash_use_session" -eq 1 ] || return 1
[ -n "$_exprash_session_dir" ] || return 1
# mktemp args
local args=()
args+=('-p' "$_exprash_session_dir")
args+=("$(printf 'X%.0s' {1..32}).session")
local session_file
session_file=$(mktemp -u "${args[@]}") || return 1
local session_file_name=${session_file##*/}
local session_id=${session_file_name%%.*}
# Set cookie
set_cookie "$_exprash_session_cookie_name" "$session_id"
# Set globals
_exprash_sessionId=$session_id
_exprash_session=()
}
function _save_session() {
[ "$_exprash_use_session" -eq 1 ] || return 1
[ -n "$_exprash_session_dir" ] || return 1
[ -n "$_exprash_sessionId" ] || return 1
local session_file="${_exprash_session_dir%/}/${_exprash_sessionId}.session"
declare -p _exprash_session | sed '1 s/\([^-]*-\)/\1g/' > "$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 # 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
# Parse query string
_parseUrlEncoded _exprashQuery < <(echo "$QUERY_STRING")

22
tests/cookies.sh Executable file
View File

@ -0,0 +1,22 @@
# shellcheck disable=SC2154,SC2034
it "Should parse cookies" "$({
HTTP_COOKIE='bob=gob; job=tob'
_exprash_init
output=$(declare -p _exprash_cookies)
expected_output='declare -A _exprash_cookies=([bob]="gob" [job]="tob" )'
[ "$output" == "$expected_output" ]
})"
it "Should get cookie value by name" "$({
HTTP_COOKIE='bob=gob; job=tob'
_exprash_init
[ "$(cookie 'bob')" == 'gob' ]
})"
it "Should determine if a cookie exists by name" "$({
HTTP_COOKIE='bob=gob; job=tob'
_exprash_init
has_cookie 'bob' && ! has_cookie 'kob'
})"

8
tests/query.sh Normal file
View File

@ -0,0 +1,8 @@
# shellcheck disable=SC2154,SC2034
it "Should parse query string" "$({
QUERY_STRING='ohmy=zsh&exit=vim'
_exprash_init
has_query 'ohmy' && ! has_query 'ohno' && [ "$(query 'exit')" == 'vim' ]
})"

View File

@ -1,55 +1,64 @@
it "Should match plain route" $({ # shellcheck disable=SC2154,SC2034
_exprashResetRouteGlobals
it "Should match plain route" "$({
PATH_INFO='/plain/route' PATH_INFO='/plain/route'
_exprash_init
all '/plain/route' all '/plain/route'
}) })"
it "Should not match incorrect plain route" $({ it "Should not match incorrect plain route" "$({
_exprashResetRouteGlobals
PATH_INFO='/plain/WRONG' PATH_INFO='/plain/WRONG'
_exprash_init
! all '/plain/route' ! all '/plain/route'
}) })"
it "Should extract parameter" $({ it "Should extract parameter" "$({
_exprashResetRouteGlobals
PATH_INFO='/cats/calico/pet' PATH_INFO='/cats/calico/pet'
all '/cats/:cat/pet' && hasParam 'cat' && [ "$(param 'cat')" == 'calico' ] _exprash_init
}) all '/cats/:cat/pet' && has_param 'cat' && [ "$(param 'cat')" == 'calico' ]
})"
it "Should match wildcard route" $({ it "Should match wildcard route" "$({
_exprashResetRouteGlobals
PATH_INFO='/cats/calico/pet' PATH_INFO='/cats/calico/pet'
_exprash_init
all '/cats/*/pet' all '/cats/*/pet'
}) })"
it "Should not match incorrect wildcard route" $({ it "Should not match incorrect wildcard route" "$({
_exprashResetRouteGlobals
PATH_INFO='/cats/calico/' PATH_INFO='/cats/calico/'
_exprash_init
! all '/cats/*/pet' ! all '/cats/*/pet'
}) })"
it "Should match multi-wildcard route" $({ it "Should match multi-wildcard route" "$({
_exprashResetRouteGlobals
PATH_INFO='/cats/calico/pet/donkey' PATH_INFO='/cats/calico/pet/donkey'
_exprash_init
all '/cats/**' all '/cats/**'
}) })"
it "Should not match incorrect multi-wildcard route" $({ it "Should not match incorrect multi-wildcard route" "$({
_exprashResetRouteGlobals
PATH_INFO='/INCORRECT/calico/pet/donkey' PATH_INFO='/INCORRECT/calico/pet/donkey'
_exprash_init
! all '/cats/**' ! all '/cats/**'
}) })"
it "Should match get route" $({ it "Should not match path shorter than route" "$({
_exprashResetRouteGlobals PATH_INFO='/year'
_exprash_init
! all '/year/:year'
})"
it "Should match get route" "$({
REQUEST_METHOD='GET' REQUEST_METHOD='GET'
PATH_INFO='/simple/route' PATH_INFO='/simple/route'
_exprash_init
get '/simple/route' get '/simple/route'
}) })"
it "Should not match get route with incorrect method" $({ it "Should not match get route with incorrect method" "$({
_exprashResetRouteGlobals
REQUEST_METHOD='POST' REQUEST_METHOD='POST'
PATH_INFO='/simple/route' PATH_INFO='/simple/route'
_exprash_init
! get '/simple/route' ! get '/simple/route'
}) })"

30
tests/session.sh Normal file
View File

@ -0,0 +1,30 @@
# shellcheck disable=SC2154,SC2034
test_session_dir='/tmp/exprash_test_sessions'
mkdir -p "$test_session_dir"
it "Should set session directory" "$({
_exprash_init
use_session "$test_session_dir"
[ "$_exprash_session_dir" == "$test_session_dir" ]
})"
it "Should save and load session" "$({
_exprash_init
use_session "$test_session_dir"
session 'data' 'value'
session_id=$_exprash_session_id
cookie_value=${_exprash_set_cookies[$_exprash_session_cookie_name]}
_exprash_shutdown
# Forcibly clear session data just in case (but it should also get cleared by
# _exprash_init)
_exprash_session=()
_exprash_session_id=''
HTTP_COOKIE="${_exprash_session_cookie_name}=$cookie_value"
_exprash_init
use_session "$test_session_dir"
[ "$(session 'data')" == "value" ]
})"

View File

@ -1,23 +1,30 @@
_testPassCount=0; _testPassCount=0
_testTotalCount=0; _testTotalCount=0
# $1: Message
# $2: Subshell output
function it() { function it() {
local exitCode=$?; local exitCode=$?
local message=$1; local message="$1"
local status; local output="$2"
local status
_testTotalCount=$((_testTotalCount+1)); _testTotalCount=$((_testTotalCount+1))
if [ "$exitCode" -eq 0 ]; then if [ "$exitCode" -eq 0 ]; then
status='✓'; status='✓'
_testPassCount=$((_testPassCount+1)); _testPassCount=$((_testPassCount+1))
else else
status='✗'; status='✗'
fi; fi
printf '%s %s\n' "$status" "$message"; if [ -n "$output" ]; then
printf '%s\n' "$output"
fi
printf '%s %s\n' "$status" "$message"
} }
function testSummary() { function testSummary() {
printf 'Passed: %s of %s tests\n' "$_testPassCount" "$_testTotalCount"; printf 'Passed: %s of %s tests\n' "$_testPassCount" "$_testTotalCount"
} }