Compare commits
No commits in common. "75b395ffa25f77869f4bee9fc31f6bf925482bf7" and "b8a8b0e258772f02dbea6bb0dfe62191e223e793" have entirely different histories.
75b395ffa2
...
b8a8b0e258
@ -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;
|
||||||
|
|
||||||
redirect_stdout 'log';
|
redirectStdout '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;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_error '/cats/*' && {
|
getError '/cats/*' && {
|
||||||
printf '<h1>%s</h1>' "$(get_error_message)" | send;
|
printf '<h1>%s</h1>' "$(errorMessage)" | send;
|
||||||
}
|
}
|
||||||
|
|
||||||
(use || use_error) && {
|
(use || useError) && {
|
||||||
printf '<h1>404</h1>' | send;
|
printf '<h1>404</h1>' | send;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
source exprash.sh;
|
source exprash.sh;
|
||||||
|
|
||||||
redirect_stdout 'log';
|
redirectStdout '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;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_error '/cats/*' && {
|
getError '/cats/*' && {
|
||||||
printf '<h1>%s</h1>' "$(get_error_message)" | send;
|
printf '<h1>%s</h1>' "$(errorMessage)" | send;
|
||||||
}
|
}
|
||||||
|
|
||||||
(use || use_error) && {
|
(use || useError) && {
|
||||||
printf '<h1>404</h1>' | send;
|
printf '<h1>404</h1>' | send;
|
||||||
}
|
}
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
#!/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
|
|
||||||
}
|
|
||||||
|
|
24
run_tests.sh
24
run_tests.sh
@ -1,24 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
file_dir="$(dirname "$0")"
|
source "$(dirname "$0")/src/exprash.sh";
|
||||||
|
|
||||||
source "$file_dir/src/exprash.sh"
|
source "$(dirname "$0")/tests/utils.sh";
|
||||||
|
|
||||||
source "$file_dir/tests/utils.sh"
|
printf '%s\n' "Routes:";
|
||||||
|
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;
|
||||||
|
440
src/exprash.sh
440
src/exprash.sh
@ -1,6 +1,5 @@
|
|||||||
#!/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
|
||||||
@ -14,13 +13,13 @@ shopt -s lastpipe
|
|||||||
# ===============
|
# ===============
|
||||||
|
|
||||||
# $1: Log file for redirecting stdout to
|
# $1: Log file for redirecting stdout to
|
||||||
function redirect_stdout() {
|
function redirectStdout() {
|
||||||
local output="$1";
|
local output="$1";
|
||||||
[ -z "$output" ] && output="/dev/null";
|
[ -z "$output" ] && output="/dev/null";
|
||||||
|
|
||||||
exec 5<&1;
|
exec 5<&1;
|
||||||
_exprash_redirect_stdout="$output";
|
_exprashRedirectStdout="$output";
|
||||||
exec 1>>"$_exprash_redirect_stdout";
|
exec 1>>"$_exprashRedirectStdout";
|
||||||
printf '%s: %s\n' "${REQUEST_METHOD:-UNKNOWN}" "${PATH_INFO:-/}"
|
printf '%s: %s\n' "${REQUEST_METHOD:-UNKNOWN}" "${PATH_INFO:-/}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,23 +53,23 @@ function delete() {
|
|||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
function all() {
|
function all() {
|
||||||
[ "$_exprash_route_handled" -eq 1 ] && return 1;
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1;
|
||||||
[ -n "$_exprash_error_message" ] && return 1;
|
[ -n "$_exprashErrorMessage" ] && return 1;
|
||||||
|
|
||||||
# Reset params
|
# Reset params
|
||||||
_exprash_params=();
|
_exprashParams=();
|
||||||
|
|
||||||
_path_match "$1" _exprash_params || return 1;
|
_pathMatch "$1" _exprashParams || return 1;
|
||||||
_exprash_route_handled=1;
|
_exprashRouteHandled=1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function use() {
|
function use() {
|
||||||
[ "$_exprash_route_handled" -eq 1 ] && return 1;
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1;
|
||||||
[ -n "$_exprash_error_message" ] && return 1;
|
[ -n "$_exprashErrorMessage" ] && return 1;
|
||||||
|
|
||||||
_exprash_route_handled=1;
|
_exprashRouteHandled=1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,48 +78,48 @@ function use() {
|
|||||||
# =====================
|
# =====================
|
||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
function get_error() {
|
function getError() {
|
||||||
[ "$REQUEST_METHOD" != "GET" ] && return 1;
|
[ "$REQUEST_METHOD" != "GET" ] && return 1;
|
||||||
all_error "$1";
|
allError "$1";
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
function post_error() {
|
function postError() {
|
||||||
[ "$REQUEST_METHOD" != "POST" ] && return 1;
|
[ "$REQUEST_METHOD" != "POST" ] && return 1;
|
||||||
all_error "$1";
|
allError "$1";
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
function put_error() {
|
function putError() {
|
||||||
[ "$REQUEST_METHOD" != "PUT" ] && return 1;
|
[ "$REQUEST_METHOD" != "PUT" ] && return 1;
|
||||||
all_error "$1";
|
allError "$1";
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
function delete_error() {
|
function deleteError() {
|
||||||
[ "$REQUEST_METHOD" != "DELETE" ] && return 1;
|
[ "$REQUEST_METHOD" != "DELETE" ] && return 1;
|
||||||
all_error "$1";
|
allError "$1";
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
function all_error() {
|
function allError() {
|
||||||
[ "$_exprash_route_handled" -eq 1 ] && return 1;
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1;
|
||||||
[ -z "$_exprash_error_message" ] && return 1;
|
[ -z "$_exprashErrorMessage" ] && return 1;
|
||||||
|
|
||||||
# Reset params
|
# Reset params
|
||||||
_exprash_params=();
|
_exprashParams=();
|
||||||
|
|
||||||
_path_match "$1" _exprash_params || return 1;
|
_pathMatch "$1" _exprashParams || return 1;
|
||||||
_exprash_route_handled=1;
|
_exprashRouteHandled=1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function use_error() {
|
function useError() {
|
||||||
[ "$_exprash_route_handled" -eq 1 ] && return 1;
|
[ "$_exprashRouteHandled" -eq 1 ] && return 1;
|
||||||
[ -z "$_exprash_error_message" ] && return 1;
|
[ -z "$_exprashErrorMessage" ] && return 1;
|
||||||
|
|
||||||
_exprash_route_handled=1;
|
_exprashRouteHandled=1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,20 +129,20 @@ function use_error() {
|
|||||||
|
|
||||||
# $1 (optional): error message
|
# $1 (optional): error message
|
||||||
function next() {
|
function next() {
|
||||||
_exprash_route_handled=0;
|
_exprashRouteHandled=0;
|
||||||
[ -n "$1" ] && _exprash_error_message="$1";
|
[ -n "$1" ] && _exprashErrorMessage="$1";
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============
|
# =============
|
||||||
# App Functions
|
# App Functions
|
||||||
# =============
|
# =============
|
||||||
|
|
||||||
function get_error_message() {
|
function errorMessage() {
|
||||||
printf '%s' "$_exprash_error_message";
|
printf '%s' "$_exprashErrorMessage";
|
||||||
}
|
}
|
||||||
|
|
||||||
function has_error_message() {
|
function hasErrorMessage() {
|
||||||
[ -n "$_exprash_error_message" ];
|
[ -n "$_exprashErrorMessage" ];
|
||||||
}
|
}
|
||||||
|
|
||||||
# =================
|
# =================
|
||||||
@ -152,60 +151,46 @@ function has_error_message() {
|
|||||||
|
|
||||||
# $1: param name
|
# $1: param name
|
||||||
function param() {
|
function param() {
|
||||||
printf '%s\n' "${_exprash_params[$1]}";
|
printf '%s\n' "${_exprashParams[$1]}";
|
||||||
}
|
}
|
||||||
|
|
||||||
function has_param() {
|
function hasParam() {
|
||||||
[[ -v "_exprash_params[$1]" ]];
|
[[ -v "_exprashParams[$1]" ]];
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: key
|
# $1: key
|
||||||
# $2: (optional) index for accessing array parameters
|
# $2: (optional) index for accessing array parameters
|
||||||
function query() {
|
function query() {
|
||||||
_multi_get _exprash_query "$1" "$2"
|
_multiGet _exprashQuery "$1" "$2"
|
||||||
}
|
}
|
||||||
# $1: key
|
# $1: key
|
||||||
function has_query() {
|
function hasQuery() {
|
||||||
_multi_has _exprash_query "$1"
|
_multiHas _exprashQuery "$1"
|
||||||
}
|
}
|
||||||
# $1: key
|
# $1: key
|
||||||
function len_query() {
|
function lenQuery() {
|
||||||
_multi_len _exprash_query "$1"
|
_multiLen _exprashQuery "$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
|
# $1: key
|
||||||
# $2: (optional) index for accessing array parameters
|
# $2: (optional) index for accessing array parameters
|
||||||
function body() {
|
function body() {
|
||||||
_multi_get _exprash_body "$1" "$2"
|
_multiGet _exprashBody "$1" "$2"
|
||||||
}
|
}
|
||||||
# $1: key
|
# $1: key
|
||||||
function has_body() {
|
function hasBody() {
|
||||||
_multi_has _exprash_body "$1"
|
_multiHas _exprashBody "$1"
|
||||||
}
|
}
|
||||||
# $1: key
|
# $1: key
|
||||||
function len_body() {
|
function lenBody() {
|
||||||
_multi_len _exprash_body "$1"
|
_multiLen _exprashBody "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: key
|
# Call this function to parse URLencoded request bodies
|
||||||
function cookie() {
|
function bodyParser() {
|
||||||
printf '%s' "${_exprash_cookies[$1]}"
|
if [[ "${HTTP_CONTENT_TYPE,,}" == "application/x-www-form-urlencoded" ]]; then
|
||||||
}
|
_parseUrlEncoded _exprashBody
|
||||||
# $1: key
|
fi
|
||||||
function has_cookie() {
|
|
||||||
[[ -v "_exprash_cookies[$1]" ]]
|
|
||||||
}
|
|
||||||
# $1: key
|
|
||||||
# $2: value
|
|
||||||
function set_cookie() {
|
|
||||||
_exprash_set_cookies["$1"]="$2"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
@ -213,16 +198,16 @@ function set_cookie() {
|
|||||||
# ==================
|
# ==================
|
||||||
|
|
||||||
function send() {
|
function send() {
|
||||||
if [ "$_exprash_headers_sent" -eq 0 ]; then
|
if [ "$_exprashHeadersSent" -eq 0 ]; then
|
||||||
send_headers
|
sendHeaders
|
||||||
_exprash_headers_sent=1
|
_exprashHeadersSent=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_send_raw
|
_sendRaw
|
||||||
}
|
}
|
||||||
|
|
||||||
function send_json() {
|
function sendJson() {
|
||||||
set_header 'Content-Type' 'application/json'
|
setHeader 'Content-Type' 'application/json'
|
||||||
send
|
send
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,91 +219,49 @@ function redirect() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
status '302'
|
status '302'
|
||||||
set_header 'Location' "$path"
|
setHeader 'Location' "$path"
|
||||||
printf 'Redirecting to: %s' "$path" | send
|
printf 'Redirecting to: %s' "$path" | send
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: satus code
|
# $1: satus code
|
||||||
function status() {
|
function status() {
|
||||||
set_header "Status" "$1"
|
setHeader "Status" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: Header Name
|
# $1: Header Name
|
||||||
# $2: Header Value
|
# $2: Header Value
|
||||||
function set_header() {
|
function setHeader() {
|
||||||
_exprash_headers["$1"]="$2"
|
_exprashHeaders["$1"]="$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
function send_headers() {
|
function sendHeaders() {
|
||||||
printf '%s\n' "Content-Type: ${_exprash_headers['Content-Type']}" | \
|
printf '%s\n' "Content-Type: ${_exprashHeaders['Content-Type']}" | _sendRaw
|
||||||
_send_raw
|
for key in "${!_exprashHeaders[@]}"; do
|
||||||
for key in "${!_exprash_headers[@]}"; do
|
|
||||||
[ "$key" == 'Content-Type' ] && continue
|
[ "$key" == 'Content-Type' ] && continue
|
||||||
printf '%s\n' "${key}: ${_exprash_headers[$key]}" | \
|
printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw
|
||||||
_send_raw
|
|
||||||
done;
|
done;
|
||||||
for key in "${!_exprash_set_cookies[@]}"; do
|
printf '\n' | _sendRaw
|
||||||
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 _send_raw() {
|
function _sendRaw() {
|
||||||
[ -n "$_exprash_redirect_stdout" ] && exec >&5;
|
[ -n "$_exprashRedirectStdout" ] && exec >&5;
|
||||||
cat;
|
cat;
|
||||||
[ -n "$_exprash_redirect_stdout" ] && exec 1>>"$_exprash_redirect_stdout";
|
[ -n "$_exprashRedirectStdout" ] && exec 1>>"$_exprashRedirectStdout";
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: path
|
# $1: path
|
||||||
# $2: (nameref) array
|
# $2: (nameref) array
|
||||||
function _path_to_array() {
|
function _pathToArray() {
|
||||||
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 _path_match() {
|
function _pathMatch() {
|
||||||
[ -z ${PATH_INFO+x} ] && return 1;
|
[ -z ${PATH_INFO+x} ] && return 1;
|
||||||
[ -z ${1+x} ] && return 1;
|
[ -z ${1+x} ] && return 1;
|
||||||
|
|
||||||
@ -326,33 +269,33 @@ function _path_match() {
|
|||||||
local route="$1";
|
local route="$1";
|
||||||
|
|
||||||
# Params associative array
|
# Params associative array
|
||||||
local -n route_params="$2";
|
local -n routeParams="$2";
|
||||||
|
|
||||||
local path_arr;
|
local pathArr;
|
||||||
_path_to_array "$path" path_arr;
|
_pathToArray "$path" pathArr;
|
||||||
|
|
||||||
local route_arr;
|
local routeArr;
|
||||||
_path_to_array "$route" route_arr;
|
_pathToArray "$route" routeArr;
|
||||||
|
|
||||||
# Get max path length
|
# Get max path length
|
||||||
local route_len=${#route_arr[@]};
|
local routeLen=${#routeArr[@]};
|
||||||
local path_len=${#path_arr[@]};
|
local pathLen=${#pathArr[@]};
|
||||||
local maxLen=$(( route_len >= path_len ? route_len : path_len ));
|
local maxLen=$(( routeLen >= pathLen ? routeLen : pathLen ));
|
||||||
|
|
||||||
for ((i=0; i<maxLen; i++)); do
|
for ((i=0; i<maxLen; i++)); do
|
||||||
local route_component="${route_arr[$i]}";
|
local routeComponent="${routeArr[$i]}";
|
||||||
local path_component="${path_arr[$i]}";
|
local pathComponent="${pathArr[$i]}";
|
||||||
|
|
||||||
# If route component starts with ":"
|
# If route component starts with ":"
|
||||||
if [[ "$route_component" == :* ]] && [ -n "$path_component" ]; then
|
if [[ "$routeComponent" == :* ]]; then
|
||||||
route_params["${route_component:1}"]="$path_component";
|
routeParams["${routeComponent:1}"]="$pathComponent";
|
||||||
elif [[ "$route_component" == '*' ]] && [ -n "$path_component" ]; then
|
elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then
|
||||||
continue;
|
continue;
|
||||||
elif [[ "$route_component" == '**' ]] && [ -n "$path_component" ]; then
|
elif [[ "$routeComponent" == '**' ]] && [ -n "$pathComponent" ]; then
|
||||||
break;
|
break;
|
||||||
else
|
else
|
||||||
# Confirm paths match
|
# Confirm paths match
|
||||||
[ "$route_component" != "$path_component" ] && return 1;
|
[ "$routeComponent" != "$pathComponent" ] && return 1;
|
||||||
fi;
|
fi;
|
||||||
done;
|
done;
|
||||||
|
|
||||||
@ -366,205 +309,110 @@ function _path_match() {
|
|||||||
# $1: (nameref) associative array
|
# $1: (nameref) associative array
|
||||||
# $2: key
|
# $2: key
|
||||||
# $3: value
|
# $3: value
|
||||||
function _multi_add() {
|
function _multiAdd() {
|
||||||
local -n multi_arr="$1"
|
local -n multiArr="$1"
|
||||||
local key="$2"
|
local key="$2"
|
||||||
local value="$3"
|
local value="$3"
|
||||||
local i=0
|
local i=0
|
||||||
while [[ -v "multi_arr[$i,$key]" ]]; do
|
while [[ -v "multiArr[$i,$key]" ]]; do
|
||||||
(( i++ ))
|
let i++
|
||||||
done
|
done
|
||||||
multi_arr["$i,$key"]="$value"
|
multiArr["$i,$key"]="$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: (nameref) associative array
|
# $1: (nameref) associative array
|
||||||
# $2: key
|
# $2: key
|
||||||
function _multi_len() {
|
function _multiLen() {
|
||||||
local -n multi_arr="$1"
|
local -n multiArr="$1"
|
||||||
local key="$2"
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
local i=0
|
local i=0
|
||||||
while [[ -v "multi_arr[$i,$key]" ]]; do
|
while [[ -v "multiArr[$i,$key]" ]]; do
|
||||||
(( i++ ))
|
let i++
|
||||||
done
|
done
|
||||||
printf '%s' "$i"
|
printf '%s' "$i"
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: (nameref) associative array
|
# $1: (nameref) associative array
|
||||||
# $2: key
|
# $2: key
|
||||||
function _multi_has() {
|
function _multiHas() {
|
||||||
local -n multi_arr="$1"
|
local -n multiArr="$1"
|
||||||
local key="$2"
|
local key="$2"
|
||||||
[[ -v "multi_arr[0,$key]" ]]
|
[[ -v "multiArr[0,$key]" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: (nameref) associative array
|
# $1: (nameref) associative array
|
||||||
# $2: key
|
# $2: key
|
||||||
# $3: index
|
# $3: index
|
||||||
function _multi_get() {
|
function _multiGet() {
|
||||||
local -n multi_arr="$1"
|
local -n multiArr="$1"
|
||||||
local key="$2"
|
local key="$2"
|
||||||
local i="${3:-0}";
|
local i="${3:-0}";
|
||||||
printf '%s' "${multi_arr[$i,$key]}"
|
printf '%s' "${multiArr[$i,$key]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
# URL Encoded Parser
|
# URL Encoded Parser
|
||||||
# ==================
|
# ==================
|
||||||
|
|
||||||
# $1 | stdin: urlencoded string
|
# $1: urlencoded string
|
||||||
decode_uri () {
|
decodeUri () {
|
||||||
local input_str="${1-"$(cat)"}"
|
local i="${*//+/ }";
|
||||||
input_str="${input_str//+/ }";
|
echo -e "${i//%/\\x}";
|
||||||
echo -e "${input_str//%/\\x}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: (nameref) multi associative array
|
# $1: multi associative array
|
||||||
# $2 | stdin: url encoded data
|
function _parseUrlEncoded() {
|
||||||
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=$(decode_uri "${pair%%=*}")
|
name=$(decodeUri "${pair%%=*}")
|
||||||
value=$(decode_uri "${pair#*=}")
|
value=$(decodeUri "${pair#*=}")
|
||||||
if [ -n "$name" ]; then
|
if [ -n "$name" ]; then
|
||||||
_multi_add parsedArr "$name" "$value"
|
_multiAdd parsedArr "$name" "$value"
|
||||||
fi;
|
fi;
|
||||||
done <<< "$url_encoded_str"
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# =======
|
# =======
|
||||||
# Cookies
|
# Globals
|
||||||
# =======
|
# =======
|
||||||
|
|
||||||
# $1: (nameref) associative array
|
# Setup globals
|
||||||
# $2 | stdin: cookie string to parse
|
_exprashRedirectStdout=''
|
||||||
function _parse_cookies() {
|
|
||||||
local -n parsedArr="$1"
|
|
||||||
local cookie_str="${2-"$(< /dev/stdin)"}"
|
|
||||||
|
|
||||||
local pair name value
|
# Route Parameters
|
||||||
|
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
|
||||||
# Session
|
_exprashResetRouteGlobals
|
||||||
# =======
|
|
||||||
|
|
||||||
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")
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
# 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'
|
|
||||||
})"
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
# 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' ]
|
|
||||||
})"
|
|
||||||
|
|
@ -1,64 +1,55 @@
|
|||||||
# shellcheck disable=SC2154,SC2034
|
it "Should match plain route" $({
|
||||||
|
_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'
|
||||||
_exprash_init
|
all '/cats/:cat/pet' && hasParam 'cat' && [ "$(param 'cat')" == 'calico' ]
|
||||||
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 not match path shorter than route" "$({
|
it "Should match get route" $({
|
||||||
PATH_INFO='/year'
|
_exprashResetRouteGlobals
|
||||||
_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'
|
||||||
})"
|
})
|
||||||
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
# 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" ]
|
|
||||||
})"
|
|
||||||
|
|
@ -1,30 +1,23 @@
|
|||||||
_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 output="$2"
|
local status;
|
||||||
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;
|
||||||
|
|
||||||
if [ -n "$output" ]; then
|
printf '%s %s\n' "$status" "$message";
|
||||||
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user