Added sessions
This commit is contained in:
		
							parent
							
								
									b8a8b0e258
								
							
						
					
					
						commit
						29d58016e0
					
				
							
								
								
									
										24
									
								
								run_tests.sh
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								run_tests.sh
									
									
									
									
									
								
							| @ -1,10 +1,24 @@ | ||||
| #!/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 "$(dirname "$0")/tests/routes.sh"; | ||||
| source "$file_dir/tests/utils.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; | ||||
|  | ||||
							
								
								
									
										228
									
								
								src/exprash.sh
									
									
									
									
									
								
							
							
						
						
									
										228
									
								
								src/exprash.sh
									
									
									
									
									
								
							| @ -1,5 +1,6 @@ | ||||
| #!/bin/bash | ||||
| # shellcheck disable=SC2034 # false flags nameref params | ||||
| # shellcheck disable=SC2178 # false flags nameref params | ||||
| 
 | ||||
| # ====================== | ||||
| # Required shell options | ||||
| @ -172,6 +173,12 @@ function lenQuery() { | ||||
|   _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 | ||||
| # $2: (optional) index for accessing array parameters | ||||
| function body() { | ||||
| @ -186,11 +193,18 @@ function lenBody() { | ||||
|   _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 | ||||
| # $1: key | ||||
| function cookie() { | ||||
|   printf '%s' "${_exprashCookies[$1]}" | ||||
| } | ||||
| # $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 | ||||
|     printf '%s\n' "${key}: ${_exprashHeaders[$key]}" | _sendRaw | ||||
|   done; | ||||
|   for key in "${!_exprashSetCookies[@]}"; do | ||||
|     printf '%s\n' "Set-Cookie: ${key}=${_exprashSetCookies[$key]}" | _sendRaw | ||||
|   done; | ||||
|   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 | ||||
| # ========= | ||||
| @ -287,7 +340,7 @@ function _pathMatch() { | ||||
|     local pathComponent="${pathArr[$i]}"; | ||||
| 
 | ||||
|     # If route component starts with ":" | ||||
|     if [[ "$routeComponent" == :* ]]; then | ||||
|     if [[ "$routeComponent" == :* ]] && [ -n "$pathComponent" ]; then | ||||
|       routeParams["${routeComponent:1}"]="$pathComponent"; | ||||
|     elif [[ "$routeComponent" == '*' ]] && [ -n "$pathComponent" ]; then | ||||
|       continue; | ||||
| @ -315,7 +368,7 @@ function _multiAdd() { | ||||
|   local value="$3" | ||||
|   local i=0 | ||||
|   while [[ -v "multiArr[$i,$key]" ]]; do | ||||
|     let i++ | ||||
|     (( i++ )) | ||||
|   done | ||||
|   multiArr["$i,$key"]="$value" | ||||
| } | ||||
| @ -325,10 +378,9 @@ function _multiAdd() { | ||||
| function _multiLen() { | ||||
|   local -n multiArr="$1" | ||||
|   local key="$2" | ||||
|   local value="$3" | ||||
|   local i=0 | ||||
|   while [[ -v "multiArr[$i,$key]" ]]; do | ||||
|     let i++ | ||||
|     (( i++ )) | ||||
|   done | ||||
|   printf '%s' "$i" | ||||
| } | ||||
| @ -355,15 +407,19 @@ function _multiGet() { | ||||
| # URL Encoded Parser | ||||
| # ================== | ||||
| 
 | ||||
| # $1: urlencoded string | ||||
| # $1 | stdin: urlencoded string | ||||
| decodeUri () { | ||||
|   local i="${*//+/ }"; | ||||
|   echo -e "${i//%/\\x}"; | ||||
|   local input_str="${1-"$(< /dev/stdin)"}" | ||||
|   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() { | ||||
|   local -n parsedArr="$1" | ||||
|   local url_encoded_str="${2-"$(< /dev/stdin)"}" | ||||
|   local pair name value | ||||
| 
 | ||||
|   while IFS= read -d '&' -r pair || [ "$pair" ]; do | ||||
|     name=$(decodeUri "${pair%%=*}") | ||||
| @ -371,48 +427,140 @@ function _parseUrlEncoded() { | ||||
|     if [ -n "$name" ]; then | ||||
|       _multiAdd parsedArr "$name" "$value" | ||||
|     fi; | ||||
|   done | ||||
|   done <<< "$url_encoded_str" | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| # ======= | ||||
| # Globals | ||||
| # Cookies | ||||
| # ======= | ||||
| 
 | ||||
| # Setup globals | ||||
| # $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" | ||||
| } | ||||
| 
 | ||||
| # ======= | ||||
| # Session | ||||
| # ======= | ||||
| 
 | ||||
| function _loadSession() { | ||||
|   [ "$_exprashUseSession" -eq 1 ] || return 1 | ||||
|   [ -n "$_exprashSessionDir" ] || return 1 | ||||
|   hasCookie "$_exprashSessionCookieName" || return 1 | ||||
| 
 | ||||
|   local session_id | ||||
|   session_id="$(cookie "$_exprashSessionCookieName")" | ||||
|   local session_file="${_exprashSessionDir%/}/$session_id.session" | ||||
| 
 | ||||
|   # shellcheck disable=SC1090 | ||||
|   source "$session_file" || return 1 | ||||
| 
 | ||||
|   # Set globals | ||||
|   _exprashSessionId=$session_id | ||||
| } | ||||
| 
 | ||||
| function _createSession() { | ||||
|   [ "$_exprashUseSession" -eq 1 ] || return 1 | ||||
|   [ -n "$_exprashSessionDir" ] || return 1 | ||||
| 
 | ||||
|   # mktemp args | ||||
|   local args=() | ||||
|   args+=('-p' "$_exprashSessionDir") | ||||
|   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 | ||||
|   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" | ||||
| } | ||||
| 
 | ||||
| # ================= | ||||
| # 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 | ||||
| # ============== | ||||
| function _exprashInit() { | ||||
|   _exprashRedirectStdout='' | ||||
| 
 | ||||
| # Route Parameters | ||||
| declare -gA _exprashParams | ||||
|   declare -gA _exprashParams=() | ||||
|   declare -gA _exprashBody=() | ||||
|   declare -gA _exprashQuery=() | ||||
|   declare -gA _exprashHeaders=() | ||||
|   declare -gA _exprashCookies=() | ||||
|   declare -gA _exprashSetCookies=() | ||||
| 
 | ||||
| # Body Parameters | ||||
| declare -gA _exprashBody | ||||
| 
 | ||||
| # Query Parameters | ||||
| declare -gA _exprashQuery | ||||
| 
 | ||||
| # Headers   | ||||
| declare -gA _exprashHeaders | ||||
| 
 | ||||
| function _exprashResetRouteGlobals() { | ||||
|   _exprashParams=() | ||||
|   _exprashBody=() | ||||
|   _exprashQuery=() | ||||
|   _exprashHeaders=() | ||||
|   _exprashUseSession=0 | ||||
|   _exprashSessionDir='' | ||||
|   _exprashSessionId='' | ||||
|   declare -gA _exprashSession=() | ||||
|   _exprashSessionCookieName='exprash_session' | ||||
| 
 | ||||
|   _exprashRouteHandled=0 | ||||
|   _exprashErrorMessage='' | ||||
|   _exprashHeadersSent=0 | ||||
|   _exprashHeaders['Content-Type']='text/html' | ||||
| 
 | ||||
|   # Parse query string | ||||
|   _parseUrlEncoded _exprashQuery "$QUERY_STRING" | ||||
| 
 | ||||
|   # Parse cookies | ||||
|   _parseCookies _exprashCookies "$HTTP_COOKIE" | ||||
| } | ||||
| 
 | ||||
| # Set up route globals | ||||
| _exprashResetRouteGlobals | ||||
| # Shutdown trap | ||||
| trap _exprashShutdown EXIT | ||||
| 
 | ||||
| # ============== | ||||
| # Initialization | ||||
| # ============== | ||||
| # Initialize exprash | ||||
| _exprashInit | ||||
| 
 | ||||
| # Parse query string | ||||
| _parseUrlEncoded _exprashQuery < <(echo "$QUERY_STRING") | ||||
|  | ||||
							
								
								
									
										20
									
								
								tests/cookies.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										20
									
								
								tests/cookies.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,20 @@ | ||||
| it "Should parse cookies" "$({ | ||||
|   HTTP_COOKIE='bob=gob; job=tob' | ||||
|   _exprashInit | ||||
|   output=$(declare -p _exprashCookies) | ||||
|   expected_output='declare -A _exprashCookies=([bob]="gob" [job]="tob" )' | ||||
|   [ "$output" == "$expected_output" ] | ||||
| })" | ||||
| 
 | ||||
| it "Should get cookie value by name" "$({ | ||||
|   HTTP_COOKIE='bob=gob; job=tob' | ||||
|   _exprashInit | ||||
|   [ "$(cookie 'bob')" == 'gob' ] | ||||
| })" | ||||
| 
 | ||||
| it "Should determine if a cookie exists by name" "$({ | ||||
|   HTTP_COOKIE='bob=gob; job=tob' | ||||
|   _exprashInit | ||||
|   hasCookie 'bob' && ! hasCookie 'kob' | ||||
| })" | ||||
| 
 | ||||
							
								
								
									
										6
									
								
								tests/query.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								tests/query.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| it "Should parse query string" "$({ | ||||
|   QUERY_STRING='ohmy=zsh&exit=vim' | ||||
|   _exprashInit | ||||
|   hasQuery 'ohmy' && ! hasQuery 'ohno' && [ "$(query 'exit')" == 'vim' ] | ||||
| })" | ||||
| 
 | ||||
| @ -1,55 +1,62 @@ | ||||
| it "Should match plain route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/plain/route' | ||||
|   _exprashInit | ||||
|   all '/plain/route' | ||||
| }) | ||||
| 
 | ||||
| it "Should not match incorrect plain route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/plain/WRONG' | ||||
|   _exprashInit | ||||
|   ! all '/plain/route' | ||||
| }) | ||||
| 
 | ||||
| it "Should extract parameter" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/cats/calico/pet' | ||||
|   _exprashInit | ||||
|   all '/cats/:cat/pet' && hasParam 'cat' && [ "$(param 'cat')" == 'calico' ] | ||||
| }) | ||||
| 
 | ||||
| it "Should match wildcard route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/cats/calico/pet' | ||||
|   _exprashInit | ||||
|   all '/cats/*/pet' | ||||
| }) | ||||
| 
 | ||||
| it "Should not match incorrect wildcard route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/cats/calico/' | ||||
|   _exprashInit | ||||
|   ! all '/cats/*/pet' | ||||
| }) | ||||
| 
 | ||||
| it "Should match multi-wildcard route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/cats/calico/pet/donkey' | ||||
|   _exprashInit | ||||
|   all '/cats/**' | ||||
| }) | ||||
| 
 | ||||
| it "Should not match incorrect multi-wildcard route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   PATH_INFO='/INCORRECT/calico/pet/donkey' | ||||
|   _exprashInit | ||||
|   ! all '/cats/**' | ||||
| }) | ||||
| 
 | ||||
| it "Should not match path shorter than route" $({ | ||||
|   PATH_INFO='/year' | ||||
|   _exprashInit | ||||
|   ! all '/year/:year' | ||||
| }) | ||||
| 
 | ||||
| it "Should match get route" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   REQUEST_METHOD='GET' | ||||
|   PATH_INFO='/simple/route' | ||||
|   _exprashInit | ||||
|   get '/simple/route' | ||||
| }) | ||||
| 
 | ||||
| it "Should not match get route with incorrect method" $({ | ||||
|   _exprashResetRouteGlobals | ||||
|   REQUEST_METHOD='POST' | ||||
|   PATH_INFO='/simple/route' | ||||
|   _exprashInit | ||||
|   ! get '/simple/route' | ||||
| }) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										30
									
								
								tests/session.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								tests/session.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| # shellcheck disable=SC2154,SC2034 | ||||
| 
 | ||||
| testSessionDir='/tmp/exprash_test_sessions' | ||||
| mkdir -p "$testSessionDir" | ||||
| 
 | ||||
| it "Should set session directory" "$({ | ||||
|   _exprashInit | ||||
|   useSession "$testSessionDir" | ||||
|   [ "$_exprashSessionDir" == "$testSessionDir" ] | ||||
| })" | ||||
| 
 | ||||
| it "Should save and load session" "$({ | ||||
|   _exprashInit | ||||
|   useSession "$testSessionDir" | ||||
|   session 'data' 'value' | ||||
|   session_id=$_exprashSessionId | ||||
|   cookie_value=${_exprashSetCookies[$_exprashSessionCookieName]} | ||||
|   _exprashShutdown | ||||
| 
 | ||||
|   # Forcibly clear session data just in case (but it should also get cleared by | ||||
|   # _exprashInit) | ||||
|   _exprashSession=() | ||||
|   _exprashSessionId='' | ||||
| 
 | ||||
|   HTTP_COOKIE="${_exprashSessionCookieName}=$cookie_value" | ||||
|   _exprashInit | ||||
|   useSession "$testSessionDir" | ||||
|   [ "$(session 'data')" == "value" ] | ||||
| })" | ||||
| 
 | ||||
| @ -1,23 +1,30 @@ | ||||
| _testPassCount=0; | ||||
| _testTotalCount=0; | ||||
| _testPassCount=0 | ||||
| _testTotalCount=0 | ||||
| 
 | ||||
| # $1: Message | ||||
| # $2: Subshell output | ||||
| function it() { | ||||
|   local exitCode=$?; | ||||
|   local message=$1; | ||||
|   local status; | ||||
|   local exitCode=$? | ||||
|   local message="$1" | ||||
|   local output="$2" | ||||
|   local status | ||||
| 
 | ||||
|   _testTotalCount=$((_testTotalCount+1)); | ||||
|   _testTotalCount=$((_testTotalCount+1)) | ||||
| 
 | ||||
|   if [ "$exitCode" -eq 0 ]; then | ||||
|     status='✓'; | ||||
|     _testPassCount=$((_testPassCount+1)); | ||||
|     status='✓' | ||||
|     _testPassCount=$((_testPassCount+1)) | ||||
|   else | ||||
|     status='✗'; | ||||
|   fi; | ||||
|     status='✗' | ||||
|   fi | ||||
| 
 | ||||
|   printf '%s %s\n' "$status" "$message"; | ||||
|   if [ -n "$output" ]; then | ||||
|     printf '%s\n' "$output" | ||||
|   fi | ||||
|   printf '%s %s\n' "$status" "$message" | ||||
| } | ||||
| 
 | ||||
| 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