Compare commits

...

6 commits

Author SHA1 Message Date
sdomi
b5320a169d misc: http_array will make up an array when called on normal params 2025-11-09 01:49:50 +01:00
sdomi
3bacddd8a4 docs: mention HTTP 2025-11-09 01:15:55 +01:00
sdomi
37770a1735 server: somewhat safer HTTP arrays 2025-11-09 01:14:54 +01:00
sdomi
dadfebb128 server: parse urlencoded arrays 2025-11-09 00:39:52 +01:00
sdomi
7e40fd14e0 server: reduce amount of basename calls in vhost logic 2025-10-31 13:53:13 +01:00
sdomi
4501f44691 docs: improve utils page 2025-10-10 18:15:24 +02:00
7 changed files with 150 additions and 25 deletions

View file

@ -15,6 +15,7 @@ We have some guides and general documentation in the [docs](docs/) directory. Am
- [HTTP Router](docs/router.md) - [HTTP Router](docs/router.md)
- [Template engine](docs/template.md) - [Template engine](docs/template.md)
- [Script integrations](docs/util.md) - [Script integrations](docs/util.md)
- [HTTP request API](docs/http.md)
- [List of security fixes](docs/sec-fixes/) - [List of security fixes](docs/sec-fixes/)
## Dependencies ## Dependencies

43
docs/http.md Normal file
View file

@ -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 <param_name> <output_name>`. 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]}"
}
```

View file

@ -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 Some of those (especially notORM) are also useful in a CLI environment, to help with migrations and
other administrative tasks. other administrative tasks.
## Invocation
Utils integrate into HTTP.sh through calling the main script, with the utility name as the first 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]` parameter. So, for `meow.sh` the invocation would be `./http.sh meow`, or `./http.sh meow [params]`
if the util takes any parameters. if the util takes any parameters.
@ -19,6 +21,27 @@ Of note:
A list of utilities can be obtained by calling `./http.sh utils`. 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>
Action can be one of:
meow - Mrrrrrrrp!
help - This message."
exit 1
fi
# script continues here...
```
## Built-in utils ## Built-in utils
The following scripts are generic helpers shipped with HTTP.sh. We hope they'll come in handy. The following scripts are generic helpers shipped with HTTP.sh. We hope they'll come in handy.

View file

@ -37,7 +37,7 @@ if [[ "$1" == "init" ]]; then # will get replaced with proper parameter parsing
fi fi
source config/master.sh 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" touch "${cfg[namespace]}/config.sh" "${cfg[namespace]}/workers/example/control"
cp ".resources/config.sh" "${cfg[namespace]}/config.sh" cp ".resources/config.sh" "${cfg[namespace]}/config.sh"
cp ".resources/routes.sh" "${cfg[namespace]}/routes.sh" cp ".resources/routes.sh" "${cfg[namespace]}/routes.sh"

View file

@ -71,3 +71,58 @@ function url_decode() {
function worker_add() { 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
while read -d'&' i; do
name="${i%%=*}"
if [[ "$name" ]]; then
value="${i#*=}"
if [[ "${ref["$name"]}" ]]; then # array mode
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]"
else
local -n arr="${http_array_refs["$name"]}"
fi
arr+=("$(url_decode "$value")")
else
ref["$name"]="$(url_decode "$value")"
fi
fi
done <<< "$1"
}
# Safely receive a reference to a HTTP urlencoded array
#
# http_array(name, out_ref)
http_array() {
[[ ! "$1" || ! "$2" ]] && return 1
if [[ ! "${http_array_refs[$1]}" ]]; then
declare -ga $2
local -n ref=$2
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
}

View file

@ -23,6 +23,7 @@ declare -A cookies # cookies!
declare -A get_data # all GET params declare -A get_data # all GET params
declare -A post_data # all POST params declare -A post_data # all POST params
declare -A params # parsed router data 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[status]=210 # Mommy always said that I was special
r[req_headers]='' r[req_headers]=''
@ -44,16 +45,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")" r[url]="$(sed -E 's/^ *//;s/HTTP\/[0-9]+\.[0-9]+//;s/ //g;s/\/*\r//g;s/\/\/*/\//g' <<< "$param")"
unset IFS unset IFS
if [[ "${r[url]}" == *'?'* ]]; then _param_parse "${r[url]#*\?}&" get_data
while read -d'&' i; do
name="${i%%=*}"
if [[ "$name" ]]; then
value="${i#*=}"
get_data[$name]="$(url_decode "$value")"
fi
done <<< "${r[url]#*\?}&"
fi
else else
exit 1 # TODO: throw 400 here exit 1 # TODO: throw 400 here
fi fi
@ -112,11 +104,17 @@ if [[ -n "${headers["host"]}" ]]; then
r[host]="${headers["host"]}" r[host]="${headers["host"]}"
r[host_portless]="${headers["host"]%%:*}" r[host_portless]="${headers["host"]%%:*}"
if [[ -f "config/$(basename -- ${r[host]})" ]]; then cfg_temp="config/$(basename -- ${r[host]})"
source "config/$(basename -- ${r[host]})"
elif [[ -f "config/$(basename -- ${r[host_portless]})" ]]; then if [[ -f "$cfg_temp" ]]; then
source "config/$(basename -- ${r[host_portless]})" 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 fi
unset cfg_temp
fi fi
if [[ "${headers["connection"]}" == *"upgrade"* && "${headers["upgrade"]}" == "websocket" ]]; then if [[ "${headers["connection"]}" == *"upgrade"* && "${headers["upgrade"]}" == "websocket" ]]; then
@ -266,12 +264,7 @@ if [[ "${r[post]}" == true ]] && [[ "${r[status]}" == 200 || "${r[status]}" ==
if [[ "${r[payload_type]}" == "urlencoded" ]]; then if [[ "${r[payload_type]}" == "urlencoded" ]]; then
unset IFS unset IFS
while read -r -d'&' i; do _param_parse "${data}&" post_data
name="${i%%=*}"
value="${i#*=}"
post_data[$name]="$(url_decode "$value")"
echo post_data[$name]="$value" >/dev/stderr
done <<< "${data}&"
else else
# this is fine? # this is fine?
post_data[0]="${data%\&}" post_data[0]="${data%\&}"

View file

@ -30,19 +30,26 @@ EOF
match="nyaa" match="nyaa"
} }
server_get_random() { server_get_array() {
prepare() { prepare() {
cat <<"EOF" > app/webroot/meow.shs cat <<"EOF" > app/webroot/meow.shs
#!/bin/bash #!/bin/bash
echo "${get_data[meow]}" http_array meow ref
echo "${ref[1]}"
EOF EOF
} }
tst() { 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_array() {
tst() {
curl -s "localhost:1337/meow.shs" -d 'meow=nyaa&meow=second+element&meow=meow'
}
} }
server_post_param() { server_post_param() {
@ -202,6 +209,9 @@ subtest_list=(
server_get_param server_get_param
server_post_param server_post_param
server_get_array
server_post_array
# currently functionally equivalent # currently functionally equivalent
server_patch_dummy server_patch_dummy
server_put_dummy server_put_dummy