11#! /usr/bin/env bash
22
33declare -A HTTP_HEADERS
4+ declare -A QUERY_PARAMS
45declare -A FORM_DATA
56declare -A COOKIES
67
@@ -33,20 +34,25 @@ urldecode() {
3334}
3435
3536function htmx_page() {
36- [[ ${HTTP_HEADERS["HX-Request"]} == " true" ]] || cat << EOF
37+ if [[ -z " $NO_STYLES " ]]; then
38+ STYLE_TEXT=' <link rel="stylesheet" href="/static/style.css">'
39+ fi
40+ [[ ${HTTP_HEADERS["HX-Request"]} == " true" ]] || [[ " $INTERNAL_REQUEST " == " true" ]] || cat << EOF
3741 <!doctype html>
3842 <html>
3943 <head>
40- <link rel="stylesheet" href="/static/style.css">
41- <script src="https://unpkg.com/htmx.org@1.8.6" integrity="sha384-Bj8qm/6B+71E6FQSySofJOUjA/gq330vEqjFx9LakWybUySyI1IQHwPtbTU7bNwx" crossorigin="anonymous"></script>
44+ <meta name="viewport" content="width=device-width, initial-scale=1" />
45+ ${STYLE_TEXT}
46+ <script src="https://unpkg.com/htmx.org@1.9.3" integrity="sha384-lVb3Rd/Ca0AxaoZg5sACe8FJKF0tnUgR2Kd7ehUOG5GCcROv5uBIZsOqovBAcWua" crossorigin="anonymous"></script>
4247 <script src="https://unpkg.com/hyperscript.org@0.9.8"></script>
48+ <script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
4349 </head>
4450 <body>
4551EOF
4652
4753cat # meow
4854
49- [[ $ {HTTP_HEADERS["HX-Request"]} == " true" ]] || cat << EOF
55+ [[ " $INTERNAL_REQUEST " == " true " ]] || [[ $ {HTTP_HEADERS["HX-Request"]} == " true" ]] || [[ ! -z " $HIDE_LOGO " ]] || cat << EOF
5056<div id="signature">
5157<a href="https://github.com/cgsdev0/bash-stack" target="_blank">
5258<svg xmlns="http://www.w3.org/2000/svg" width="224" height="256" viewBox="0 0 224 256"><path fill="#FFF" d="M207.953 52.162L127.317 4.287a30.372 30.372 0 0 0-31.114 0L15.55 52.162A32.165 32.165 0 0 0 0 79.869v95.734a32.165 32.165 0 0 0 15.55 27.691l80.636 47.859a30.388 30.388 0 0 0 31.115 0l80.636-47.859a32.165 32.165 0 0 0 15.566-27.707V79.869a32.165 32.165 0 0 0-15.55-27.707Z"/><path fill="#2F3A3E" d="m208.412 52.277l-80.814-47.98a30.439 30.439 0 0 0-31.184 0l-80.83 47.98A32.236 32.236 0 0 0 0 80.045v95.945a32.236 32.236 0 0 0 15.584 27.752l80.814 47.964a30.455 30.455 0 0 0 31.183 0l80.814-47.964a32.236 32.236 0 0 0 15.6-27.769V80.045a32.236 32.236 0 0 0-15.583-27.768ZM99.23 246.803l-80.814-47.964A26.604 26.604 0 0 1 5.6 175.989V80.046a26.588 26.588 0 0 1 12.816-22.849L99.23 9.216a24.92 24.92 0 0 1 25.536 0l80.749 47.98a26.426 26.426 0 0 1 12.412 18.48c-2.687-5.712-8.723-7.282-15.762-3.236l-76.396 47.316c-9.531 5.551-16.554 11.814-16.57 23.303v94.213c0 6.877 2.767 11.327 7.039 12.638a24.759 24.759 0 0 1-4.24.405a25.034 25.034 0 0 1-12.768-3.512Z"/><path fill="#3AB14A" d="m187.007 185.06l-20.086 12.013a1.47 1.47 0 0 0-.92 1.308v5.28c0 .646.435.904.968.597l20.394-12.4a1.616 1.616 0 0 0 .613-1.615v-4.634c-.016-.598-.484-.856-.969-.55Z"/><path fill="#FFF" d="M144.263 140.832c.646-.323 1.179 0 1.195.92l.064 7.008a12.917 12.917 0 0 1 7.718-.937c.501.13.71.808.517 1.615l-1.534 6.152a2.648 2.648 0 0 1-.694 1.227a1.615 1.615 0 0 1-.404.29a.92.92 0 0 1-.597.098a10.237 10.237 0 0 0-7.444 1.194a9.349 9.349 0 0 0-5.506 8.284c0 3.229 1.615 4.117 7.25 4.214c7.444.13 10.673 3.375 10.754 10.883a26.69 26.69 0 0 1-9.882 20.135l.13 6.878a2.519 2.519 0 0 1-1.18 2.1l-4.068 2.34c-.646.323-1.18 0-1.195-.904v-6.765c-3.488 1.453-7.024 1.792-9.285.888c-.42-.162-.613-.791-.436-1.518l1.47-6.216a2.6 2.6 0 0 1 .726-1.292c.115-.11.246-.203.388-.275a.807.807 0 0 1 .662 0c2.878.78 5.948.392 8.541-1.081a11.173 11.173 0 0 0 6.314-9.688c0-3.488-1.922-4.941-6.459-4.974c-5.861 0-11.303-1.13-11.416-9.688a25.027 25.027 0 0 1 9.462-19.15l-.29-7.04a2.503 2.503 0 0 1 1.178-2.13l4.021-2.568Z"/></svg>
@@ -99,19 +105,60 @@ function publish() {
99105 done
100106}
101107
108+ # encode result
109+ # can encode the following status codes as unix return codes:
110+ # 200-263
111+ # 300-363
112+ # 400-463
113+ # 500-563
114+ function status_code() {
115+ A=${1: 0: 1}
116+ B=${1: 1: 2}
117+ echo $(( ((A - 2 ) << 6 ) + B ))
118+ }
119+
120+ function decode_result() {
121+ ENCODED=${1}
122+ printf " %d%02d" $(( (ENCODED >> 6 ) + 2 )) $(( ENCODED & 63 ))
123+ }
124+
125+ function component() {
126+ local REQUEST_PATH
127+ REQUEST_PATH=" $1 "
128+ route_script=` matchRoute " $REQUEST_PATH " `
129+ INTERNAL_REQUEST=true
130+ result=$( source " pages/${route_script} " )
131+ echo " $result "
132+ }
133+
134+ export -f status_code
135+ export -f component
102136export -f debug
103137export -f subscribe
104138export -f unsubscribe
105139export -f publish
106140export -f htmx_page
107141
142+ readonly URI_REGEX=' (/[^?#]*)(\?([^#]*))?'
143+
108144parseHttpRequest () {
109145 local line
110146
111147 # Read request line
112- read -r REQUEST_METHOD REQUEST_PATH HTTP_VERSION
148+ read -r REQUEST_METHOD REQUEST_PATH_WITH_PARAMS HTTP_VERSION
113149 HTTP_VERSION=" ${HTTP_VERSION%% $' \r ' } "
114- debug " $REQUEST_METHOD $REQUEST_PATH $HTTP_VERSION "
150+ debug " $REQUEST_METHOD $REQUEST_PATH_WITH_PARAMS $HTTP_VERSION "
151+ REQUEST_PATH=$( [[ " $REQUEST_PATH_WITH_PARAMS " =~ $URI_REGEX ]] && echo " ${BASH_REMATCH[1]} " )
152+ REQUEST_QUERY=$( [[ " $REQUEST_PATH_WITH_PARAMS " =~ $URI_REGEX ]] && echo " ${BASH_REMATCH[3]} " )
153+
154+ # Parse query parameters
155+ if [[ ! -z " $REQUEST_QUERY " ]]; then
156+ while read -r -d ' &' line; do
157+ QUERY_PARAMS[" ${line%% =* } " ]=$( urldecode " ${line#* =} " )
158+ done << EOF
159+ ${REQUEST_QUERY} &
160+ EOF
161+ fi
115162
116163 # Read headers
117164 while IFS= read -r line; do
@@ -124,7 +171,7 @@ parseHttpRequest() {
124171 # Read body
125172 CLEN=${HTTP_HEADERS["Content-Length"]}
126173 [[ " $CLEN " =~ ^[0-9]+$ ]] && \
127- test $CLEN -gt 0 && read -N $CLEN REQUEST_BODY;
174+ test $CLEN -gt 0 && read -rN $CLEN REQUEST_BODY;
128175 # Parse Form Data
129176 if [[ ! -z " $REQUEST_BODY " ]] && \
130177 [[ ${HTTP_HEADERS["Content-Type"]} == " application/x-www-form-urlencoded" ]]; then
@@ -150,10 +197,12 @@ writeHttpResponse() {
150197
151198 if [[ ! -f " $FILE_PATH " ]]; then
152199 printf " %s\r\n" " HTTP/1.1 404 Not Found"
200+ printf " %s\r\n" " Server: bash lol"
153201 printf " %s\r\n" " "
154202 return
155203 fi
156204 printf " %s\r\n" " HTTP/1.1 200 OK"
205+ printf " %s\r\n" " Server: bash lol"
157206 if [[ " $REQUEST_PATH " == * " .css" ]]; then
158207 printf " %s\r\n" " Content-Type: text/css"
159208 else
@@ -167,13 +216,15 @@ writeHttpResponse() {
167216 if [[ -z " $route_script " ]]; then
168217 debug " 404 no match found"
169218 printf " %s\r\n" " HTTP/1.1 404 Not Found"
219+ printf " %s\r\n" " Server: bash lol"
170220 printf " %s\r\n" " "
171221 return
172222 fi
173223
174224 if directive_test=$( head -1 " pages/${route_script} " ) ; then
175225 if [[ " $directive_test " == " # sse" ]]; then
176226 printf " %s\r\n" " HTTP/1.1 200 OK"
227+ printf " %s\r\n" " Server: bash lol"
177228 printf " %s\r\n" " Content-Type: text/event-stream"
178229 printf " %s\r\n" " "
179230 source " pages/${route_script} "
@@ -183,18 +234,22 @@ writeHttpResponse() {
183234 fi
184235 fi
185236 result=$( source " pages/${route_script} " )
186- if [[ $? == 0 ]]; then
237+ CODE=$?
238+ if [[ " $CODE " -lt 64 ]]; then
187239 # for i in "${!HTTP_HEADERS[@]}"; do
188240 # debug "%s=%s" "$i" "${HTTP_HEADERS[$i]}"
189241 # done
190242 printf " %s\r\n" " HTTP/1.1 200 OK"
191- printf " %s\r\n" " Content-Type: text/html"
243+ printf " %s\r\n" " Server: bash lol"
244+ [[ -z $CUSTOM_HEADERS ]] && printf " %s\r\n" " Content-Type: text/html"
192245
193246 [[ -z $CUSTOM_HEADERS ]] && printf " %s\r\n" " "
194247 printf " %s" " $result "
195248 else
196- printf " %s\r\n" " HTTP/1.1 404 Not Found"
249+ printf " %s\r\n" " HTTP/1.1 $( decode_result $CODE ) "
250+ printf " %s\r\n" " Server: bash lol"
197251 printf " %s\r\n" " "
252+ printf " %s" " $result "
198253 fi
199254}
200255
@@ -235,9 +290,7 @@ matchRoute() {
235290 done
236291 findDynamicRoutes | while IFS= read -r route; do
237292 routeRegex=" /${route% .sh} "
238- # routeRegex="${routeRegex//\//\\/}"
239293 routeRegex=" ^$( echo " $routeRegex " | sed ' s@\[[^]]*\]@[^\/]+@g' ) $"
240- # debug "THE REGEX IS '$routeRegex'"
241294 [[ " $sanitized " =~ $routeRegex ]] && echo " $route " && return
242295 done
243296 findCatchAllRoutes | while IFS= read -r route; do
@@ -250,5 +303,10 @@ matchRoute() {
250303 done
251304}
252305
306+ export -f findPredefinedRoutes
307+ export -f findDynamicRoutes
308+ export -f findCatchAllRoutes
309+ export -f matchRoute
310+
253311parseHttpRequest
254312writeHttpResponse
0 commit comments