From dadfebb128dd6f03ab57757989818944dfc34a28 Mon Sep 17 00:00:00 2001 From: sdomi Date: Sun, 9 Nov 2025 00:39:52 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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 }