From 4501f44691176f989671b88edb2799f965ddbe27 Mon Sep 17 00:00:00 2001 From: sdomi Date: Fri, 10 Oct 2025 18:15:24 +0200 Subject: [PATCH 1/6] docs: improve utils page --- docs/util.md | 23 +++++++++++++++++++++++ http.sh | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/util.md b/docs/util.md index b11dc2c..94b5052 100644 --- a/docs/util.md +++ b/docs/util.md @@ -4,6 +4,8 @@ HTTP.sh provides a number of useful APIs designed to make interfacing with HTTP Some of those (especially notORM) are also useful in a CLI environment, to help with migrations and other administrative tasks. +## Invocation + Utils integrate into HTTP.sh through calling the main script, with the utility name as the first parameter. So, for `meow.sh` the invocation would be `./http.sh meow`, or `./http.sh meow [params]` if the util takes any parameters. @@ -19,6 +21,27 @@ Of note: A list of utilities can be obtained by calling `./http.sh utils`. +## Creating your own + +Simply create a shell script in your namespace's util directory (that's usually `app/util/`), and +mark it as executable. It has to have `.sh` as an extension, but shebang currently doesn't matter. + +It's recommended that if your util takes any positional parameters, it should check for $1 being +set, and display a help message. An example of such script is listed below. + +``` +if [[ ! "$1" || "$1" == "help" ]]; then + echo "usage: $0 $HTTPSH_SCRIPTNAME + +Action can be one of: + meow - Mrrrrrrrp! + help - This message." + exit 1 +fi + +# script continues here... +``` + ## Built-in utils The following scripts are generic helpers shipped with HTTP.sh. We hope they'll come in handy. diff --git a/http.sh b/http.sh index 02a220a..2e81d4c 100755 --- a/http.sh +++ b/http.sh @@ -37,7 +37,7 @@ if [[ "$1" == "init" ]]; then # will get replaced with proper parameter parsing fi source config/master.sh - mkdir -p "${cfg[namespace]}/${cfg[root]}" "${cfg[namespace]}/workers/example" "${cfg[namespace]}/views" "${cfg[namespace]}/templates" + mkdir -p "${cfg[namespace]}/${cfg[root]}" "${cfg[namespace]}/workers/example" "${cfg[namespace]}/views" "${cfg[namespace]}/templates" "${cfg[namespace]}/util/" touch "${cfg[namespace]}/config.sh" "${cfg[namespace]}/workers/example/control" cp ".resources/config.sh" "${cfg[namespace]}/config.sh" cp ".resources/routes.sh" "${cfg[namespace]}/routes.sh" From 7e40fd14e015dccac60ab7638c0f7bf39bb63f49 Mon Sep 17 00:00:00 2001 From: sdomi Date: Fri, 31 Oct 2025 13:51:28 +0100 Subject: [PATCH 2/6] server: reduce amount of basename calls in vhost logic --- src/server.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/server.sh b/src/server.sh index 4810a05..a6bbb78 100755 --- a/src/server.sh +++ b/src/server.sh @@ -112,11 +112,17 @@ if [[ -n "${headers["host"]}" ]]; then r[host]="${headers["host"]}" r[host_portless]="${headers["host"]%%:*}" - if [[ -f "config/$(basename -- ${r[host]})" ]]; then - source "config/$(basename -- ${r[host]})" - elif [[ -f "config/$(basename -- ${r[host_portless]})" ]]; then - source "config/$(basename -- ${r[host_portless]})" + cfg_temp="config/$(basename -- ${r[host]})" + + if [[ -f "$cfg_temp" ]]; then + source "$cfg_temp" + elif [[ "${r[host]}" != "${r[host_portless]}" ]]; then + cfg_temp_portless="config/$(basename -- ${r[host_portless]})" + [[ -f "$cfg_temp_portless" ]] && source "$cfg_temp_portless" + unset cfg_temp_portless fi + + unset cfg_temp fi if [[ "${headers["connection"]}" == *"upgrade"* && "${headers["upgrade"]}" == "websocket" ]]; then From dadfebb128dd6f03ab57757989818944dfc34a28 Mon Sep 17 00:00:00 2001 From: sdomi Date: Sun, 9 Nov 2025 00:39:52 +0100 Subject: [PATCH 3/6] server: parse urlencoded arrays --- src/misc.sh | 34 ++++++++++++++++++++++++++++++++++ src/server.sh | 18 ++---------------- tests/01-http-basic.sh | 30 ++++++++++++++++++++++++++---- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/misc.sh b/src/misc.sh index 30caf1b..c79cf0c 100755 --- a/src/misc.sh +++ b/src/misc.sh @@ -71,3 +71,37 @@ function url_decode() { function worker_add() { : } + +# internal function +# common GET/POST application/x-www-form-urlencoded parser +# +# _param_parse(input, destination_ref) +_param_parse() { + [[ ! "$1" || ! "$2" ]] && return 1 + local -n ref="$2" + + local i name value + local -A array_refs + + while read -d'&' i; do + name="${i%%=*}" + if [[ "$name" ]]; then + value="${i#*=}" + if [[ "${ref["$name"]}" ]]; then # array mode + if [[ ! "${array_refs["$name"]}" ]]; then + array_refs["$name"]=_param_$RANDOM + local -n arr="${array_refs["$name"]}" + + arr=("${ref["$name"]}") + ref["$name"]="${array_refs["$name"]}" + else + local -n arr="${array_refs["$name"]}" + fi + + arr+=("$(url_decode "$value")") + else + ref["$name"]="$(url_decode "$value")" + fi + fi + done <<< "$1" +} diff --git a/src/server.sh b/src/server.sh index a6bbb78..f8b2ea1 100755 --- a/src/server.sh +++ b/src/server.sh @@ -44,16 +44,7 @@ if [[ "${param,,}" =~ ^(get|post|patch|put|delete|meow) ]]; then # TODO: OPTIONS r[url]="$(sed -E 's/^ *//;s/HTTP\/[0-9]+\.[0-9]+//;s/ //g;s/\/*\r//g;s/\/\/*/\//g' <<< "$param")" unset IFS - if [[ "${r[url]}" == *'?'* ]]; then - while read -d'&' i; do - name="${i%%=*}" - if [[ "$name" ]]; then - value="${i#*=}" - get_data[$name]="$(url_decode "$value")" - fi - done <<< "${r[url]#*\?}&" - fi - + _param_parse "${r[url]#*\?}&" get_data else exit 1 # TODO: throw 400 here fi @@ -272,12 +263,7 @@ if [[ "${r[post]}" == true ]] && [[ "${r[status]}" == 200 || "${r[status]}" == if [[ "${r[payload_type]}" == "urlencoded" ]]; then unset IFS - while read -r -d'&' i; do - name="${i%%=*}" - value="${i#*=}" - post_data[$name]="$(url_decode "$value")" - echo post_data[$name]="$value" >/dev/stderr - done <<< "${data}&" + _param_parse "${data}&" post_data else # this is fine? post_data[0]="${data%\&}" diff --git a/tests/01-http-basic.sh b/tests/01-http-basic.sh index e89b6c5..046a949 100644 --- a/tests/01-http-basic.sh +++ b/tests/01-http-basic.sh @@ -30,19 +30,20 @@ EOF match="nyaa" } -server_get_random() { +server_get_array() { prepare() { cat <<"EOF" > app/webroot/meow.shs #!/bin/bash -echo "${get_data[meow]}" +declare -n ref="${get_data[meow]}" +echo "${ref[1]}" EOF } tst() { - curl -s "localhost:1337/meow.shs?meow=nyaa" + curl -s "localhost:1337/meow.shs?meow=nyaa&meow=second+element&meow=meow" } - match="nyaa" + match="second element" } server_post_param() { @@ -60,6 +61,24 @@ EOF match="nyaa" } +server_post_array() { + prepare() { + cat <<"EOF" > app/webroot/meow.shs +#!/bin/bash +declare -n ref="${post_data[meow]}" +declare -p "post_data" >&2 +declare -p "${post_data[meow]}" >&2 +echo "${ref[1]}" +EOF + } + + tst() { + curl -s "localhost:1337/meow.shs" -d 'meow=nyaa&meow=second+element&meow=meow' + } + + match="second element" +} + server_patch_dummy() { prepare() { cat <<"EOF" > app/webroot/meow.shs @@ -202,6 +221,9 @@ subtest_list=( server_get_param server_post_param + server_get_array + server_post_array + # currently functionally equivalent server_patch_dummy server_put_dummy From 37770a173500a700512308281e80766c5239dd62 Mon Sep 17 00:00:00 2001 From: sdomi Date: Sun, 9 Nov 2025 01:14:54 +0100 Subject: [PATCH 4/6] server: somewhat safer HTTP arrays --- src/misc.sh | 22 ++++++++++++++++------ src/server.sh | 1 + tests/01-http-basic.sh | 26 +++++++------------------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/misc.sh b/src/misc.sh index c79cf0c..c2ba819 100755 --- a/src/misc.sh +++ b/src/misc.sh @@ -81,21 +81,20 @@ _param_parse() { local -n ref="$2" local i name value - local -A array_refs while read -d'&' i; do name="${i%%=*}" if [[ "$name" ]]; then value="${i#*=}" if [[ "${ref["$name"]}" ]]; then # array mode - if [[ ! "${array_refs["$name"]}" ]]; then - array_refs["$name"]=_param_$RANDOM - local -n arr="${array_refs["$name"]}" + if [[ ! "${http_array_refs["$name"]}" ]]; then + http_array_refs["$name"]=_param_$RANDOM + local -n arr="${http_array_refs["$name"]}" arr=("${ref["$name"]}") - ref["$name"]="${array_refs["$name"]}" + ref["$name"]="[array]" else - local -n arr="${array_refs["$name"]}" + local -n arr="${http_array_refs["$name"]}" fi arr+=("$(url_decode "$value")") @@ -105,3 +104,14 @@ _param_parse() { fi done <<< "$1" } + + +# Safely receive a reference to a HTTP urlencoded array +# +# http_array(name, out_ref) +http_array() { + [[ ! "$1" || ! "$2" ]] && return 1 + [[ ! "${http_array_refs[$1]}" ]] && return 1 + + declare -gn $2=${http_array_refs[$1]} +} diff --git a/src/server.sh b/src/server.sh index f8b2ea1..ebe16c9 100755 --- a/src/server.sh +++ b/src/server.sh @@ -23,6 +23,7 @@ declare -A cookies # cookies! declare -A get_data # all GET params declare -A post_data # all POST params declare -A params # parsed router data +declare -A http_array_refs # references to GET/POST arrays r[status]=210 # Mommy always said that I was special r[req_headers]='' diff --git a/tests/01-http-basic.sh b/tests/01-http-basic.sh index 046a949..3b327ef 100644 --- a/tests/01-http-basic.sh +++ b/tests/01-http-basic.sh @@ -34,7 +34,7 @@ server_get_array() { prepare() { cat <<"EOF" > app/webroot/meow.shs #!/bin/bash -declare -n ref="${get_data[meow]}" +http_array meow ref echo "${ref[1]}" EOF } @@ -46,6 +46,12 @@ EOF match="second element" } +server_post_array() { + tst() { + curl -s "localhost:1337/meow.shs" -d 'meow=nyaa&meow=second+element&meow=meow' + } +} + server_post_param() { prepare() { cat <<"EOF" > app/webroot/meow.shs @@ -61,24 +67,6 @@ EOF match="nyaa" } -server_post_array() { - prepare() { - cat <<"EOF" > app/webroot/meow.shs -#!/bin/bash -declare -n ref="${post_data[meow]}" -declare -p "post_data" >&2 -declare -p "${post_data[meow]}" >&2 -echo "${ref[1]}" -EOF - } - - tst() { - curl -s "localhost:1337/meow.shs" -d 'meow=nyaa&meow=second+element&meow=meow' - } - - match="second element" -} - server_patch_dummy() { prepare() { cat <<"EOF" > app/webroot/meow.shs From 3bacddd8a4de6153dd704f205bde827992ea94d1 Mon Sep 17 00:00:00 2001 From: sdomi Date: Sun, 9 Nov 2025 01:15:55 +0100 Subject: [PATCH 5/6] docs: mention HTTP --- README.md | 1 + docs/http.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docs/http.md diff --git a/README.md b/README.md index d06583c..cd5306c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ We have some guides and general documentation in the [docs](docs/) directory. Am - [HTTP Router](docs/router.md) - [Template engine](docs/template.md) - [Script integrations](docs/util.md) +- [HTTP request API](docs/http.md) - [List of security fixes](docs/sec-fixes/) ## Dependencies diff --git a/docs/http.md b/docs/http.md new file mode 100644 index 0000000..0dcd150 --- /dev/null +++ b/docs/http.md @@ -0,0 +1,43 @@ +# HTTP - the proto, the API + +very work in progress file. + +--- + +## GET/POST parameters + +- `${get_data["param"]}` +- `${post_data["param"]}` + +Case-insensitive. If K/V pairs aren't used (but a string is provided, just without `=`) then +it can be accessed through `${get_data}` / `${post_data}` (array element 0). + +### Arrays + +HTTP does arrays through concatenating multiple parameters with the same name. In our case, values +are passed to a secondary array, and a reference to it is left for application use. + +``` +GET asdf/?a=1&a=2&a=3&b=1 +``` + +will result in: + +``` +declare -A get_data=([b]="1" [a]="[array]" ) +``` + +To get the value set out of an array, call `http_array `. For instance: + +``` +#!/bin/bash +if ! http_array a array; then + echo "Not an array" + return +fi + +for (( i=0; i<${#array[@]}; i++ )) { + echo "a[$i]=${array[i]}" +} +``` + From b5320a169de9d24cdfbc07ccd1514db2a91b9566 Mon Sep 17 00:00:00 2001 From: sdomi Date: Sun, 9 Nov 2025 01:49:50 +0100 Subject: [PATCH 6/6] misc: http_array will make up an array when called on normal params --- src/misc.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/misc.sh b/src/misc.sh index c2ba819..6d6b7ac 100755 --- a/src/misc.sh +++ b/src/misc.sh @@ -111,7 +111,18 @@ _param_parse() { # http_array(name, out_ref) http_array() { [[ ! "$1" || ! "$2" ]] && return 1 - [[ ! "${http_array_refs[$1]}" ]] && return 1 + if [[ ! "${http_array_refs[$1]}" ]]; then + declare -ga $2 + local -n ref=$2 - declare -gn $2=${http_array_refs[$1]} + if [[ "${post_data[$1]}" ]]; then + ref=("${post_data[$1]}") + elif [[ "${get_data[$1]}" ]]; then + ref=("${get_data[$1]}") + else + return 1 + fi + else + declare -gn $2=${http_array_refs[$1]} + fi }