0xddfc2e10…3e91sent to0x165cd37b…5e14·#16,015,501·view on Etherscan
https://github.com/Krowemoh/bash-server
app
sessions
.gitignore
README.md
server.sh
---------------------------------------------------------------------------------------------------------------------------
https://github.com/Krowemoh/bash-server/blob/master/README.md ---->
---------------------------------------------------------------------------------------------------------------------------
# Bash Server
A HTTP server written in bash! This is just a toy to see if I could do it, and you definitely can write a web server as a shell script!
Blog post at:
https://nivethan.dev/devlog/a-web-server-in-bash.html
---------------------------------------------------------------------------------------------------------------------------
<---- https://github.com/Krowemoh/bash-server/blob/master/README.md
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
https://github.com/Krowemoh/bash-server/blob/master/server.sh ---->
---------------------------------------------------------------------------------------------------------------------------
#!/usr/bin/bash
declare -r CR=$'\r'
declare -r LF=$'\n'
declare -r CR_LF="${CR}${LF}"
declare -r DEBUG=1
declare -A function_dictionary=(
[login]=login
[signout]=signout
["^comics$"]=comics
["^comics/(.*)"]=issues
["^comics/(.*)/(.*)"]=issue
)
serve() {
nc --listen --keep-open 0.0.0.0 7999 --sh-exec "./server.sh process"
}
log() {
if [ $DEBUG = 1 ]
then
echo "$1" >&2;
fi
}
check_session() {
if [ ! -f "./sessions/$session_id" ]
then
render_template "./app/login.html"
send_html "$template"
fi
}
process() {
local IFS=$'\n'
# STDIN -> request_headers
get_request_headers
# request_headers -> request_type, request_body, request_cookies
get_request_body_cookies
# request_headers -> requested_resource
handle_requested_resource
}
get_request_headers() {
request_headers=()
while true
do
read header
if [ "$header" = $CR_LF ]
then
break
fi
request_headers=("${request_headers[@]}" "$header")
done
}
get_request_body_cookies() {
request_type="$(echo "${request_headers[0]}" | cut -d" " -f1)"
post_length=0
for i in "${request_headers[@]}"
do
header=$(cut -d":" -f1 <<< "$i")
if [ "$header" = "Content-Length" ]
then
post_length=$(echo "$i" | cut -d":" -f2 | tr -d "$CR" | tr -d "$LF" | tr -d ' ')
elif [ "$header" = "Cookie" ]
then
regex=".*session_id=(.*);?"
[[ "$i" =~ $regex ]]
session_id=$(echo "${BASH_REMATCH[1]}" | tr -d "$CR" | tr -d "$LF")
fi
done
if [ "$post_length" -ne 0 ]
then
IFS= read -n "$post_length" request_body
fi
}
handle_requested_resource() {
regexp=".* (.*) HTTP"
[[ "${request_headers[0]}" =~ $regexp ]]
resource=$(printf "%s" "${BASH_REMATCH[1]}" | sed 's/%20/ /g' | sed "s/%27/'/g")
requested_resource="./app$resource"
if [ -f "$requested_resource" ]
then
send_file "$requested_resource"
fi
requested_resource="${resource:1}"
for x in "${!function_dictionary[@]}"
do
if [[ "$requested_resource" =~ $x ]]
then
${function_dictionary[$x]}
fi
done
render_template "./app/404.html"
send_html "$template"
}
set_response_content_type() {
case "$1" in
"html")
content_type="Content-Type: text/html"
;;
"css")
content_type="Content-Type: text/css"
;;
"js")
content_type="Content-Type: text/javascript"
;;
"ico")
content_type="Content-Type: image/x-icon"
;;
"png")
content_type="Content-Type: image/png"
;;
"jpg" | "jpeg")
content_type="Content-Type: image/jpeg"
;;
"mp4")
content_type="Content-Type: video/mp4"
;;
*)
content_type="Content-Type: text/plain"
;;
esac
}
get_requested_content() {
length=$(stat --printf "%s" "$1")
content_length="Content-Length: $length"
content=$(cat "$1" | sed 's/\\/\\\\/g' | sed 's/%/%%/g' | sed 's/\x00/\\x00/g')
}
set_response_headers() {
version="HTTP/1.1 200 OK"
date="Date: $(date)"
connection="Connection: Closed"
if [ "$cookies" = "" ]
then
response_headers="${version}$CR_LF${date}$CR_LF$1$CR_LF$2$CR_LF${connection}"
else
response_headers="${version}$CR_LF${date}$CR_LF${cookies}$CR_LF$1$CR_LF$2$CR_LF${connection}"
fi
}
build_response() {
response="$1$CR_LF$CR_LF$2"
}
send_response() {
printf -- "$1$CR_LF"
exit
}
send_file() {
# -> content_type
requested_resource="$1"
extension="${requested_resource##*.}"
set_response_content_type "$extension"
# -> data | content_length
get_requested_content "$1"
# -> response_headers
set_response_headers "$content_type" "$content_length"
# -> response
build_response "$response_headers" "$content"
# -> ECHO data | PRINTF data
send_response "$response"
}
send_html() {
content="$1"
content_length="${#content}"
set_response_content_type "html"
set_response_headers "$content_type" "$content_length"
build_response "$response_headers" "$content"
send_response "$response"
}
render_template() {
template=$(eval "cat <<- END
$(cat "$1")
END
")
}
login() {
if [ "$request_type" = "GET" ]
then
content="$(cat ./app/login.html)"
send_html "$content"
else
username=$(echo -n "$request_body" | cut -d'&' -f1 | cut -d'=' -f2)
password=$(echo -n "$request_body" | cut -d'&' -f2 | cut -d'=' -f2)
if [ "$password" = "123" ]
then
session_id=$(uuidgen)
touch "./sessions/$session_id"
cookies="Set-cookie: session_id=$session_id"
fi
render_template "./app/account.html"
send_html "$template"
fi
}
signout() {
if [ -f "./sessions/$session_id" ]
then
rm "./sessions/$session_id"
session_id=""
cookies="Set-cookie: session_id=$session_id"
fi
render_template "./app/login.html"
send_html "$template"
}
comics() {
check_session
render_template "./app/comics.html"
send_html "$template"
}
issues() {
check_session
comic_name="${BASH_REMATCH[1]}"
render_template "./app/issues.html"
send_html "$template"
}
issue() {
check_session
comic_name="${BASH_REMATCH[1]}"
issue_number="${BASH_REMATCH[2]}"
render_template "./app/issue.html"
send_html "$template"
}
"$1"
---------------------------------------------------------------------------------------------------------------------------
<---- https://github.com/Krowemoh/bash-server/blob/master/server.sh
---------------------------------------------------------------------------------------------------------------------------
----
https://github.com/Plasmmer/HTTB.git
forked from: https://github.com/Krowemoh/bash-server
custom install.sh merge README.md TasksOther.txt
httb logo.png Presentation.pptx 'SRC At ETH💎💌.txt' Tasks.txt
---------------------------------------------------------------------------------------------------------------------------
/media/daniell/B/git/Plasmmer/HTTB/README.md ---->
---------------------------------------------------------------------------------------------------------------------------
# <img width="250px" src="logo.png" title="HTTB" alt="HTTB">
The HTTP server purely written in !
Imagine nginx, but written in shell.
"HTTB" stands for Hyper Text Transfer **Bash**.
After installing, run `httb serve`.
To test the example site, run `cd /var/www && git clone https://github.com/Plasmmer/HTTB_sample-site html`
# Purpose
Make it easier to power dynamic pages by using Bash instead of PHP, and the Linux's filesystem/JSON files instead of MySQL.
Its like Hugo, but you turn static HTML into dynamic! Without needing PHP nor other hard languages.
This server shouldn't be used in large sites (about security and speed probably), but as a playground for getting familiar with webservers until migrating to NodeJS/PHP and MySQL; so your product can grow with HTTB then later evolve. HTTB isn't available in C/Rust (yet) and had no security audits.
# Background
Initially this was just a toy for [@Krowemoh](https://github.com/Krowemoh/bash-server) see if they could do it, and definitely this project proved you can write a more-than-basic web server as a shell script!
"More-than-basic" server because it can manage user logins/sessions directly from a JSON file, handle requests, file requests, display 404 error messages.
Soon™: support to Web3.
Blog post at:
https://nivethan.dev/devlog/a-web-server-in-bash.html
---------------------------------------------------------------------------------------------------------------------------
<---- /media/daniell/B/git/Plasmmer/HTTB/README.md
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
/media/daniell/B/git/Plasmmer/HTTB/Tasks.txt ---->
---------------------------------------------------------------------------------------------------------------------------
- use mlq to display error messages at signup and login pages
- signup: email before username
- login: accept both email and username
- signup: Web 2.0 and Web3 tabs; in Web3 tab, form also have ETH address
- login: Web 2.0 and Web3 tabs; in Web3 tab, form accepts only ETH address instead of email/username
- signup: add its entry adding +1 to ID in users.json. Git commit on every new signup. Message: -m "Register new Web 2.0 user" -m "Username: $Username" -m "ID: $ID"
- turn custom/db into a git repo. avoid install.sh overwritting db
- redirect /login.html to /login
- if user is already logged, redirect /login to the logged start page
- fix custom/handle_requested_resource.sh
- MOVED TO: https://github.com/Plasmmer/HTTB/issues/4
- Support for any kind of file, which will include .svg
- Support for HTTPS and Certbot plugin (test in Web3Framework)
- test compiling to c with "shc -f httb" and from the C source, to Rust🔥
- use mlq to display custom error messages in login-error.html and signup-error.html (not writing directly to these files, but pre-rendering)
httb's JSON DB at Floflis Profile site can handle somewhat like 1K simultaneous usr or some more; but before getting closer to limit, httb should abstract its own ways to handle full MySQL databases
- support for download.json
future feature: enter folders listing their files, using Screens Explorer (this will be linked with Screens Explorer's own task through Github). Save the comics.html page and bash functions as example
support for DML
if browser doesn't support DML, parse/convert and send HTML. (this will mean more server resources used)
---------------------------------------------------------------------------------------------------------------------------
<---- /media/daniell/B/git/Plasmmer/HTTB/Tasks.txt
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
/media/daniell/B/git/Plasmmer/HTTB/install.sh ---->
---------------------------------------------------------------------------------------------------------------------------
#!/bin/sh
echo "Installing httb..."
sudo cp -f httb /usr/bin/httb
sudo mkdir /usr/lib/httb
sudo cp -f README.md /usr/lib/httb/README.md
#sudo cp -rf custom /usr/lib/httb/custom/
rsync -av custom/ /usr/lib/httb/custom
#mkdir /usr/lib/httb/custom/db
mkdir /usr/lib/httb/custom/sessions
mkdir /usr/lib/httb/custom/db/users
mkdir /var/www
mkdir /var/www/html
mkdir /etc/httb
cat > /etc/httb/httb.conf.json <<EOF
{
"htmlfolder": "/var/www/html",
"port": "8080"
}
EOF
installfail(){
echo "Installation has failed."
exit 1
}
if [ -f /usr/bin/httb ];then
echo "- Turning httb into an executable..."
sudo chmod +x /usr/bin/httb
echo "Done!"
# if httb babyisalive; then echo "Done! Running \"httb -r '#first' sample.html\" command as example to use it:" && (httb -r '#first' /usr/lib/httb/sample.html &);exit 0; else installfail; fi
else
installfail
fi
---------------------------------------------------------------------------------------------------------------------------
<---- /media/daniell/B/git/Plasmmer/HTTB/install.sh
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
/media/daniell/B/git/Plasmmer/HTTB/httb ---->
---------------------------------------------------------------------------------------------------------------------------
#!/usr/bin/bash
#htmlfolder="/var/www/html"
htmlfolder="$(jq -r '.htmlfolder' /etc/httb/httb.conf.json)"
serverport="$(jq -r '.port' /etc/httb/httb.conf.json)"
declare -r CR=$'\r'
declare -r LF=$'\n'
declare -r CR_LF="${CR}${LF}"
declare -r DEBUG=1
declare -A function_dictionary=(
[signup]=signup
[login]=login
[signout]=signout
["^comics$"]=comics
["^comics/(.*)"]=issues
["^comics/(.*)/(.*)"]=issue
)
serve() {
ncat --listen --keep-open 0.0.0.0 $serverport --sh-exec "httb process"
}
log() {
if [ $DEBUG = 1 ]
then
echo "$1" >&2;
fi
}
check_session() {
if [ ! -f "/usr/lib/httb/custom/sessions/$session_id" ]
then
render_template "$htmlfolder/login.html"
send_html "$template"
fi
}
process() {
local IFS=$'\n'
# STDIN -> request_headers
get_request_headers
# request_headers -> request_type, request_body, request_cookies
get_request_body_cookies
# request_headers -> requested_resource
handle_requested_resource
}
get_request_headers() {
request_headers=()
while true
do
read header
if [ "$header" = $CR_LF ]
then
break
fi
request_headers=("${request_headers[@]}" "$header")
done
}
get_request_body_cookies() {
request_type="$(echo "${request_headers[0]}" | cut -d" " -f1)"
post_length=0
for i in "${request_headers[@]}"
do
header=$(cut -d":" -f1 <<< "$i")
if [ "$header" = "Content-Length" ]
then
post_length=$(echo "$i" | cut -d":" -f2 | tr -d "$CR" | tr -d "$LF" | tr -d ' ')
elif [ "$header" = "Cookie" ]
then
regex=".*session_id=(.*);?"
[[ "$i" =~ $regex ]]
session_id=$(echo "${BASH_REMATCH[1]}" | tr -d "$CR" | tr -d "$LF")
fi
done
if [ "$post_length" -ne 0 ]
then
IFS= read -n "$post_length" request_body
fi
}
handle_requested_resource() {
regexp=".* (.*) HTTP"
[[ "${request_headers[0]}" =~ $regexp ]]
resource=$(printf "%s" "${BASH_REMATCH[1]}" | sed 's/%20/ /g' | sed "s/%27/'/g")
requested_resource="$htmlfolder$resource"
if [ -f "$requested_resource" ]
then
send_file "$requested_resource"
fi
. /usr/lib/httb/custom/./handle_requested_resource.sh
requested_resource="${resource:1}"
for x in "${!function_dictionary[@]}"
do
if [[ "$requested_resource" =~ $x ]]
then
${function_dictionary[$x]}
fi
done
render_template "$htmlfolder/404.html"
send_html "$template"
}
set_response_content_type() {
case "$1" in
"html")
content_type="Content-Type: text/html"
;;
"css")
content_type="Content-Type: text/css"
;;
"js")
content_type="Content-Type: text/javascript"
;;
"ico")
content_type="Content-Type: image/x-icon"
;;
"png")
content_type="Content-Type: image/png"
;;
"jpg" | "jpeg")
content_type="Content-Type: image/jpeg"
;;
"mp4")
content_type="Content-Type: video/mp4"
;;
*)
content_type="Content-Type: text/plain"
;;
esac
}
get_requested_content() {
length=$(stat --printf "%s" "$1")
content_length="Content-Length: $length"
content=$(cat "$1" | sed 's/\\/\\\\/g' | sed 's/%/%%/g' | sed 's/\x00/\\x00/g')
}
set_response_headers() {
version="HTTP/1.1 200 OK"
date="Date: $(date)"
connection="Connection: Closed"
if [ "$cookies" = "" ]
then
response_headers="${version}$CR_LF${date}$CR_LF$1$CR_LF$2$CR_LF${connection}"
else
response_headers="${version}$CR_LF${date}$CR_LF${cookies}$CR_LF$1$CR_LF$2$CR_LF${connection}"
fi
}
build_response() {
response="$1$CR_LF$CR_LF$2"
}
send_response() {
printf -- "$1$CR_LF"
exit
}
send_file() {
# -> content_type
requested_resource="$1"
extension="${requested_resource##*.}"
set_response_content_type "$extension"
# -> data | content_length
get_requested_content "$1"
# -> response_headers
set_response_headers "$content_type" "$content_length"
# -> response
build_response "$response_headers" "$content"
# -> ECHO data | PRINTF data
send_response "$response"
}
send_html() {
content="$1"
content_length="${#content}"
set_response_content_type "html"
set_response_headers "$content_type" "$content_length"
build_response "$response_headers" "$content"
send_response "$response"
}
render_template() {
template=$(eval "cat <<- END
$(cat "$1")
END
")
}
signup() {
if [ "$request_type" = "GET" ]
then
content="$(cat $htmlfolder/signup.html)"
send_html "$content"
else
username=$(echo -n "$request_body" | cut -d'&' -f1 | cut -d'=' -f2)
password=$(echo -n "$request_body" | cut -d'&' -f2 | cut -d'=' -f2)
if ! jq -r ".$username[]" /usr/lib/httb/custom/db/users.json # if the choosen username doesn't already exist, enable signup and register user
then
tmp="$(mktemp)"; cat /usr/lib/httb/custom/db/users.json | jq ". + {"\"$username\"":[{\"id\":\"\",\"pw\":\"$(echo -n "$password" | sha256sum | cut -d' ' -f1)\"}]}" >"$tmp" && mv "$tmp" /usr/lib/httb/custom/db/users.json
mkdir /usr/lib/httb/custom/db/users/$username
render_template "$htmlfolder/signup-success.html"
send_html "$template"
else
render_template "$htmlfolder/signup-error.html"
send_html "$template"
#render_template "$htmlfolder/signup.html"
#echo "$template" > /tmp/mlq_render_template.html
#mlq_template="$(mlq '#error-msg = "<h2>Cannot create account</h2><h3>Username already exist</h3>"' /tmp/mlq_render_template.html)"
#send_html "$mlq_template"
#rm -f /tmp/mlq_render_template.html
fi
fi
}
login() {
if [ "$request_type" = "GET" ]
then
content="$(cat $htmlfolder/login.html)"
send_html "$content"
else
username=$(echo -n "$request_body" | cut -d'&' -f1 | cut -d'=' -f2)
password=$(echo -n "$request_body" | cut -d'&' -f2 | cut -d'=' -f2)
if [ "$(echo -n "$password" | sha256sum | cut -d' ' -f1)" = "$(jq -r ".$username[].pw" /usr/lib/httb/custom/db/users.json)" ] # if the submitted password's hash is equal the hash stored in given user's entry in db, validate login
then
session_id=$(uuidgen)
touch "/usr/lib/httb/custom/sessions/$session_id"
cookies="Set-cookie: session_id=$session_id"
render_template "$htmlfolder/account.html"
send_html "$template"
else
render_template "$htmlfolder/login-error.html"
send_html "$template"
#mlq_template="$(mlq '#errormsg = "<h2>Cannot login</h2><h3>Wrong username/password</h3>"' "$htmlfolder/login.html")"
#echo "$mlq_template" > /tmp/mlq_render_template.html
#render_template "/tmp/mlq_render_template.html"
#rm -f /tmp/mlq_render_template.html
#send_html "$template"
fi
fi
}
signout() {
if [ -f "/usr/lib/httb/custom/sessions/$session_id" ]
then
rm "/usr/lib/httb/custom/sessions/$session_id"
session_id=""
cookies="Set-cookie: session_id=$session_id"
fi
render_template "$htmlfolder/login.html"
send_html "$template"
}
comics() {
check_session
render_template "$htmlfolder/comics.html"
send_html "$template"
}
issues() {
check_session
comic_name="${BASH_REMATCH[1]}"
render_template "$htmlfolder/issues.html"
send_html "$template"
}
issue() {
check_session
comic_name="${BASH_REMATCH[1]}"
issue_number="${BASH_REMATCH[2]}"
render_template "$htmlfolder/issue.html"
send_html "$template"
}
"$1"
---------------------------------------------------------------------------------------------------------------------------
<---- /media/daniell/B/git/Plasmmer/HTTB/httb
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
/media/daniell/B/git/Plasmmer/HTTB/custom/handle_requested_resource.sh ---->
---------------------------------------------------------------------------------------------------------------------------
#!/bin/sh
log "$request_body"
if jq -e . >/dev/null 2>&1 <<<"$json_string"
then
log "Parsed JSON successfully and got something other than false/null"
json_username="$(echo "$request_body" | jq -r '.name')"
json_age="$(echo "$request_body" | jq -r '.age')"
log "Name: $json_username | Age: $json_age"
else
log "Failed to parse JSON, or got false/null"
fi
---------------------------------------------------------------------------------------------------------------------------
<---- /media/daniell/B/git/Plasmmer/HTTB/custom/handle_requested_resource.sh
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
/media/daniell/B/git/Plasmmer/HTTB/custom/db/users.json ---->
---------------------------------------------------------------------------------------------------------------------------
{
"mew": [{
"id": "1",
"pw": "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"
}]
}
---------------------------------------------------------------------------------------------------------------------------
<---- /media/daniell/B/git/Plasmmer/HTTB/custom/db/users.json
---------------------------------------------------------------------------------------------------------------------------