From 73a5498b83011fe1fa67cc233aebca261af63928 Mon Sep 17 00:00:00 2001 From: overflowerror Date: Sat, 18 Nov 2017 01:26:25 +0100 Subject: [PATCH] basic version: index handler, cgi-style dynamic site generation, correct mime type for static files --- 404.html | 10 +++ home/hello-world.sh | 20 +++++ home/info.sh | 31 +++++++ home/stuff/add.sh | 60 +++++++++++++ home/stuff/guestbook.txt | 6 ++ home/test/cgi-test.c | 8 ++ home/test/cgi-test.cgi | Bin 0 -> 8640 bytes home/test/non-executeable-script.sh | 3 + home/test/php-test.php | 4 + home/test/plaintest.txt | 1 + home/test/redirect.sh | 8 ++ index.sh | 65 ++++++++++++++ response.sh | 133 ++++++++++++++++++++++++++++ server.sh | 68 ++++++++++++++ statusString.sh | 72 +++++++++++++++ test-response.sh | 51 +++++++++++ 16 files changed, 540 insertions(+) create mode 100644 404.html create mode 100755 home/hello-world.sh create mode 100755 home/info.sh create mode 100755 home/stuff/add.sh create mode 100644 home/stuff/guestbook.txt create mode 100644 home/test/cgi-test.c create mode 100755 home/test/cgi-test.cgi create mode 100644 home/test/non-executeable-script.sh create mode 100755 home/test/php-test.php create mode 100644 home/test/plaintest.txt create mode 100755 home/test/redirect.sh create mode 100755 index.sh create mode 100755 response.sh create mode 100755 server.sh create mode 100755 statusString.sh create mode 100755 test-response.sh diff --git a/404.html b/404.html new file mode 100644 index 0000000..6c51e5b --- /dev/null +++ b/404.html @@ -0,0 +1,10 @@ + + + + Not found + + +

Not found

+ The resource [path] was not found on server [host]. + + diff --git a/home/hello-world.sh b/home/hello-world.sh new file mode 100755 index 0000000..181e45a --- /dev/null +++ b/home/hello-world.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +statusContainer=$1 +eval "declare -A settings="${2#*=} +eval "declare -A headers="${3#*=} + +echo 200 > $statusContainer + +cat < + + + Hello World + + +

Hello World

+ + + +EOF diff --git a/home/info.sh b/home/info.sh new file mode 100755 index 0000000..5172c9f --- /dev/null +++ b/home/info.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +statusContainer=$1 +eval "declare -A settings="${2#*=} +eval "declare -A headers="${3#*=} + +echo 200 > $statusContainer + +cat < + + + Info + + +

Settings

+EOF +for key in "${!settings[@]}"; do + echo "$key -> ${settings[$key]}
" +done +cat <Headers +EOF +for key in "${!headers[@]}"; do + echo "$key -> ${headers[$key]}
" +done +cat << EOF + + + +EOF diff --git a/home/stuff/add.sh b/home/stuff/add.sh new file mode 100755 index 0000000..5360896 --- /dev/null +++ b/home/stuff/add.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +statusContainer=$1 +eval "declare -A settings="${2#*=} +eval "declare -A headers="${3#*=} + + +if test "${headers[query]}" == ""; then + echo 200 > $statusContainer + + cat< + + + Add to guestbook + + +

Add something to the guestbook

+
+
+
+ +
+ + +EOF + exit +fi + +name="" +text="" + +fields=$(echo ${headers[query]} | tr "&" "\n") +for field in $fields; do + key=$(echo $field | awk -F= '{ print $1 }') + value=$(echo $field | awk -F= '{for (i=2; i<=NF; i++) print $i}') + + if test "$key" = "name"; then + name=$value + elif test "$key" = "text"; then + text=$value + fi +done + +if test "$name" = "" -o "$text" = ""; then + echo 400 > $statusContainer + echo 400 - Bad Request + exit +fi + +cat >> ./guestbook.txt < $statusContainer +echo "Location: ./guestbook.txt" >> $statusContainer diff --git a/home/stuff/guestbook.txt b/home/stuff/guestbook.txt new file mode 100644 index 0000000..8b04d42 --- /dev/null +++ b/home/stuff/guestbook.txt @@ -0,0 +1,6 @@ + +=============================== +Sat Nov 18 01:20:41 CET 2017: overflow + +Welcome. : ) + diff --git a/home/test/cgi-test.c b/home/test/cgi-test.c new file mode 100644 index 0000000..4fbf4ef --- /dev/null +++ b/home/test/cgi-test.c @@ -0,0 +1,8 @@ +#include + +int main() { + + printf("Hello World"); + + return 0; +} diff --git a/home/test/cgi-test.cgi b/home/test/cgi-test.cgi new file mode 100755 index 0000000000000000000000000000000000000000..f2e944b1c6b7aa1dc31516fc10eab89b06981f86 GIT binary patch literal 8640 zcmeHMZ){W76~B%N2@o7Zp$)K&w+=Sw6yrci!&+85f8YfL$U-PgYj`;EOXAJ3Bm0>p zsM65VqDF-lY5l-7ZK^g^>!wO$)1>jKgHfTjA13(FiLINKP3;uAp=DxYOjF~XbKm(n z&(F?UX&?558$F+UfA{>(J@@~ge66czPfZ}85Q6ISic_a*T_lE#^@u1HQ*CM){ccpB zP)iAB)tF@a-b$wlykgd0`#k1c}d7Qwm92dmgo)P6(UT&-$6m&!Q9%Cd$g64r1klSrpd z+LUAsEAJ$7jy0A@Wt7$3zu!vQIeR3PckEpM{*H7uWA`Tp)2ux*md%K^rL27?tUl4} zNGI}nJMYq~lK1v>w|7|EBikc8wWl>oI-O)d|0#v>Qdpl_rU*YHb;A6?xIZO>a7N|{ z@R;CR!iHu7NBUZ$i?bT{JHibQ&f^7P#)D%HaNev3_pTFj9^5-m?s{;Ua=M5n@slyM z24|R4f2lX0t9^O7q0DD4IW@&==G5!;S2TxziaUQoS;fsirQgPlF^X||l(TNm6e(=} zI;DrtF~FhaR`>MvW9IZ9&8c_idi#6M*8Y&HZ=PMbLyO%FB#LVz#QA&U#!k-JJxK}k zY%Pn!=GMFB^n2#i-O!1(+bp23|uv@&pSIU{ocG2{Wb?DEAfdXxQky8B^^cRj!MdUFo>0@i#uZqGsjgt6h2+kv2y7Va)^@5d5S*Hw@Iq@ zQ22$K_E6LF!LE>Tc1e3^%b8j;6g|Dv47E+w?GMFrp|*G^8V_x04;iFK`t6~5y)V2@ z^jFA^ciyqdd*NOS+-re*EpV>|KD-5R4#T+%4B1vnXRHRvh{|C}t~shx4nylCALqOc zlE&VEYaz~idT$^d}5V@uo&_o0#89VHn!r|<3P_?)q&(;iGEGDd4;MGmJ-HkYDhD0w$q1??rE%qf>0Vi@FrLcsr3&M&9IsR8pK`ojpi z@WABfYV}f8{rgJqCFS~S6y|+7zLrk@h8NQDpt_&lQL5n&DBSle1n_?!=q2<~5hJGK1ng_2LxitZmGkS6#yy55I2O8G4Oy zZij2Kq1rvNKL@QVr)4W44tn zjEzka#gno~(M||Yn%UW>ti3%4+v7df!99Bpb@f~Q@%ElBE&`u_tguFw> zV&3P#idRT~HZ$F4M=G-X4bn>Hv({)LljQfw?t`S2Ol7P>-li%pc*j+w^k6V zY7dweJ$a%5`7N~SV`lj?YPq{AlAj!N5`!e2oGV8Wqc>PPH?AU?tYb&w?cL2zVnk9S znL=c+kfOKlR8mDaHJZqesz`D&L-t(hl;jf(a5EPdh52s~(|f zWF$*T$DW{H9&e|dB{#VKSA5Br(1Kogg9VFHX$=14vv-}i&ymW()^B<%^dES8a4p<9CynLJ! zAvZx$q@UkO_#HZvqx|y{frS6q-hJDoIzW8Bqkw-=B9NCTj{`58+)m)*KJf%$d=~&8 z=Tpe@l;;g>!yEYj5q#$WALrPT6lM4iJ;<+6KHp)GHl%?{Quu!V1y+1MVG8&$;X~dK zMU?Na|I@;67d@PxOVY#VHuLkJCyd)fwx!ZJJ1Pa@z9(k1uZXjprq9do6&|F)N}hoK zgFwDWd0zgw@FC$ZO!(8^_VKYFL;k|o;V2jOzUSlP-VTZTEA*v%kW@bqKKc*m3P^u> ze*TYr{Nr*CgM6%1k{j{!f9~VIBz#Ez|A#mD+wV=v4AF=CG|rRwf2Lm_X~?&U+vwrr zJ~JZ^AwyH_u4@F}_hQ`i0jAZ~3@K zUE@koh`OTm3qIsuDaXq%-M6Fwqr`M0;6dK?@o~O2gbzPq20WzaDZ&`)kMm+wh<<%( z4$xJQ0LG#v#EpReX&(;0S7PjYRgX&f$ED#vPMO>2gFn0$ap3s{-!&jV7w>l?JAKAc TE_7E>q#FO06yP8Ke*S*|*;7Q* literal 0 HcmV?d00001 diff --git a/home/test/non-executeable-script.sh b/home/test/non-executeable-script.sh new file mode 100644 index 0000000..a9f4e38 --- /dev/null +++ b/home/test/non-executeable-script.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo Hello World diff --git a/home/test/php-test.php b/home/test/php-test.php new file mode 100755 index 0000000..e542041 --- /dev/null +++ b/home/test/php-test.php @@ -0,0 +1,4 @@ +#!/usr/bin/php + $statusContainer +echo "Location: ../stuff/guestbook.txt" >> $statusContainer diff --git a/index.sh b/index.sh new file mode 100755 index 0000000..c6906aa --- /dev/null +++ b/index.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +server="$1" +host="$2" +rpath="$3" +path="$4" +statuscontainer=$5 + +echo 200 > $statuscontainer + +cat < + + + Index of $rpath + + +

Index of $rpath

+
+ + + + + + + +EOF + +for file in $(ls -a $path); do + if test "$file" = ".." -a "$rpath" = "/"; then + continue; + fi + cat < + + + + + +EOF +done + +cat < +
+ $host ($server) + + +EOF diff --git a/response.sh b/response.sh new file mode 100755 index 0000000..7fe4ab9 --- /dev/null +++ b/response.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +settingsfile=$1 +eval "$(cat $settingsfile)" # declare settings array + +declare -A headers +first=1 +while true; do + IFS=$'\r' read header + if test "$header" = ""; then + break + fi + if test $first = 1; then + headers[method]=$(echo "$header" | awk '{ print $1 }') + headers[http]=$(echo "$header" | awk '{ print $3 }' | awk -F/ '{ print $2} ') + headers[path]=$(echo "$header" | awk '{ print $2 }') + first=0 + continue + fi + headers[$(echo $header | awk -F: '{ print $1}')]="$(echo "$header" | awk '{for (i=2; i<=NF; i++) print $i}')" +done + +rpath="$(realpath -sm "${headers[path]}")" +headers[query]="$(echo "$rpath" | awk -F? '{for (i=2; i<=NF; i++) print $i}')" +rpath="$(echo "$rpath" | awk -F? '{ print $1 }')" + +path="${settings[home]}${rpath}" +path="$(realpath -sm "$path")" + +urlencode() { + python -c "import urllib, sys; print urllib.quote(sys.argv[1])" "$1" +} + +placeholder() { + declare -A tokens + tokens[path]="$rpath ($path)" + tokens[host]="${headers[Host]}" + tokens[server]="${settings[server]}" + + text="$(cat)" + + for key in "${!tokens[@]}"; do + key="$(echo "$key" | sed -e 's/[]\/$*.^|[]/\\&/g')" + value="$(echo "${tokens[$key]}" | sed -e 's/[]\/$*.^|[]/\\&/g')" + + text=$(echo "$text" | sed -e "s/\[$key\]/$value/g") + + done + + echo "$text" +} + +addStatusContainer() { + i=0 + container="/dev/shm/st-cont-$$-"; + while true; do + if test -f $container$i; then + i=$(($i+1)) + continue + fi + container=$container$i + break + done + touch $container + echo $container +} +removeStatusContainer() { + container=$1 + rm $container +} + +isExecutable() { + path=$1 + if test ! -x $path -o ! -f $path; then + return 1 + fi + ext="$(echo $path | awk -F. '{ print $NF }')" + for i in ${settings[executeable]}; do + if test "$ext" = "$i"; then + return 0 + fi + done + return 1 +} + +status=500 +content="" +declare -A responseHeaders +responseHeaders[Content-Type]="text/html" +responseHeaders[Server]="${settings[server]}" + + +if test ! -e "$path"; then + status=404 + + content="$(placeholder < ./404.html)" + +elif test ${settings[index]} = true -a -d "$path"; then + container=$(addStatusContainer) + content=$(./index.sh "${settings[server]}" "${headers[Host]}" "$rpath" "$path" "$container") + status=$(cat $container) + removeStatusContainer $container + +elif $(isExecutable $path); then + container=$(addStatusContainer) + pushd "$(dirname $path)" > /dev/null + content=$($path $container "$(declare -p settings)" "$(declare -p headers)") + popd > /dev/null + status=$(head -n 1 $container) + if test "$status" = ""; then + status=200 + fi + while read line; do + responseHeaders[$(echo $line | awk -F: '{ print $1 }')]="$(echo "$line" | awk '{for (i=2; i<=NF; i++) print $i}')" + done <<< $(tail -n 1 $container) + + removeStatusContainer $container +else + status=200 + responseHeaders['Content-Type']="$(file -b --mime-type $path)" + content=$(cat $path) +fi + +length=$(printf "%s" "$content" | wc -c) + +echo -en "HTTP/1.1 $status $(./statusString.sh $status)\r\n" +echo -en "Content-Length: $length\r\n" +for key in ${!responseHeaders[@]}; do + echo -en "$(urlencode "$key"): $(urlencode "${responseHeaders[$key]}")\r\n" +done +echo -en "\r\n" +printf "%s" "$content" + diff --git a/server.sh b/server.sh new file mode 100755 index 0000000..ba1096b --- /dev/null +++ b/server.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +EXIT_FAILURE=1 +EXIT_SUCCESS=0 + +port=-1 +progname="server" + +help() { + cat << EOF +usage: $progname --port=PORT + +Options: + -p, --port=PORT set port + -h, --home=HOME set home directory + -v, --verbose set to verbose mode + -q, --quiet don't output anything +EOF +} + +verboselevel=0 +echoOnVerbose() { + if test "$verboselevel" -ge $1; then + echo -n "$2" + fi +} + +progname="$0" + +OPTS=$(getopt -o "p:vqh:" -l "port:,verbose,quiet,home:" -- $@) +if test $? != 0; then + exit $EXIT_FAILURE +fi + +eval set -- "$OPTS" + +home="./home/" + +while true; do + case "$1" in + -p|--port) port=$2; shift 2;; + -v|--verbose) verboselevel=$(($verboselevel+1)); shift;; + -q|--quiet) verboselevel=-1; shift;; + -h|--home) home=$2; shift 2;; + --) shift; break;; + esac +done + +if test "$port" -lt 1; then + help + exit $EXIT_FAILURE +fi + +settingsfile="/dev/shm/wserver-$$" +declare -A settings +settings[home]=$home +settings[verbose]=$verboselevel +settings[executeable]="sh php py cgi" +settings[server]="server" +settings[index]="true" +declare -p settings > $settingsfile + +echo "Starting... " +socat $(echoOnVerbose 2 "-vv") tcp-listen:$port,reuseaddr,fork SYSTEM:"./response.sh $settingsfile" > /dev/null + +if test $? != 0; then + exit $EXIT_FAILURE +fi diff --git a/statusString.sh b/statusString.sh new file mode 100755 index 0000000..8ced6a3 --- /dev/null +++ b/statusString.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +status=$1 + +case $status in + 100) echo Continue ;; + 101) Switching Protocols ;; + 102) Processing ;; + + 200) echo OK ;; + 201) echo Created ;; + 202) echo Accepted ;; + 203) echo Non-Authoritative Information ;; + 204) echo No Content ;; + 205) echo Reset Content ;; + 206) echo Partial Content ;; + 207) echo Multi-Status ;; + 208) echo Already Reported ;; + 226) echo IM Used ;; + + 300) echo Multiple Choices ;; + 301) echo Moved Permanently ;; + 302) echo Found ;; + 303) echo See Other ;; + 304) echo Not Modified ;; + 305) echo Use Proxy ;; + 306) echo Switch Proxy ;; + 307) echo Temporary Redirect ;; + 308) echo Permanent Redirect ;; + + 400) echo Bad Request ;; + 401) echo Unauthorized ;; + 402) echo Payment Required ;; + 403) echo Forbidden ;; + 404) echo Not Found ;; + 405) echo Method Not Allowed ;; + 406) echo Not Acceptable ;; + 407) echo Proxy Authentication Required ;; + 408) echo Request Timeout ;; + 409) echo Conflict ;; + 410) echo Gone ;; + 411) echo Length Required ;; + 412) echo Precondition Failed ;; + 413) echo Payload Too Large ;; + 414) echo URI Too Long ;; + 415) echo Unsupported Media Type ;; + 416) echo Range Not Satisfiable ;; + 417) echo Expectation Failed ;; + 418) echo I\'m a teapot ;; + 421) echo Misdirected Request ;; + 422) echo Unprocessable Entity ;; + 423) echo Locked ;; + 424) echo Failed Dependency ;; + 426) echo Upgrade Required ;; + 428) echo Precondition Required ;; + 429) echo Too Many Requests ;; + 431) echo Request Header Fields Too Large ;; + 451) echo Unavailable For Legal Reasons ;; + + 500) echo Internal Server Error ;; + 501) echo Not Implemented ;; + 502) echo Bad Gateway ;; + 503) echo Service Unavailable ;; + 504) echo Gateway Timeout ;; + 505) echo HTTP Version Not Supported ;; + 506) echo Variant Also Negotiates ;; + 507) echo Insufficient Storage ;; + 508) echo Loop Detected ;; + 510) echo Not Extended ;; + 511) echo Network Authentication Required ;; + *) echo Unknown +esac diff --git a/test-response.sh b/test-response.sh new file mode 100755 index 0000000..90fc9ca --- /dev/null +++ b/test-response.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +echo "Start response.sh" 1>&2 + +settingsfile=$1 +eval "$(cat $settingsfile)" # declare settings array + +declare -A headers +first=1 +while true; do + IFS=$'\r' read header + if test "$header" = ""; then + break + fi + if test $first = 1; then + headers[method]=$(echo "$header" | awk '{ print $1 }') + headers[http]=$(echo "$header" | awk '{ print $3 }' | awk -F/ '{ print $2} ') + headers[path]=$(echo "$header" | awk '{ print $2 }') + first=0 + continue + fi + headers[$(echo $header | awk -F: '{ print $1}')]="$(echo "$header" | awk '{for (i=2; i<=NF; i++) print $i}')" +done + +content=" + + + Test + + +

Hallo Welt

+ $(for key in ${!headers[@]}; do echo "$key" "->" "${headers[$key]}" "
"; done) +
+
+ $(for key in ${!settings[@]}; do echo "$key" "->" "${settings[$key]}" "
"; done) + + +" + +length=$(printf "%s" "$content" | wc -c) + +echo "$length" 1>&2 +echo "$content" 1>&2 + +echo -en "HTTP/1.1 200 OK\r\n" +echo -en "Content-Type: text/html\r\n" +echo -en "Server: testserver\r\n" +echo -en "Content-Length: $length\r\n" +echo -en "\r\n" +printf "%s" "$content" +
+ Name + + Type + + Executeable + + Size +
+ $file + + $(file -b $path/$file) + + $(if test ! -d $path/$file; then if test -x $path/$file; then echo yes; else echo no; fi; fi) + + $(if test ! -d $path/$file; then du -kh $path/$file | cut -f1; fi) +