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]}" +} +``` + 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" diff --git a/src/misc.sh b/src/misc.sh index 30caf1b..6d6b7ac 100755 --- a/src/misc.sh +++ b/src/misc.sh @@ -71,3 +71,58 @@ 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 + + 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 +} diff --git a/src/server.sh b/src/server.sh index 4810a05..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]='' @@ -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")" 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 @@ -112,11 +104,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 @@ -266,12 +264,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..3b327ef 100644 --- a/tests/01-http-basic.sh +++ b/tests/01-http-basic.sh @@ -30,19 +30,26 @@ EOF match="nyaa" } -server_get_random() { +server_get_array() { prepare() { cat <<"EOF" > app/webroot/meow.shs #!/bin/bash -echo "${get_data[meow]}" +http_array meow ref +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_array() { + tst() { + curl -s "localhost:1337/meow.shs" -d 'meow=nyaa&meow=second+element&meow=meow' + } } server_post_param() { @@ -202,6 +209,9 @@ subtest_list=( server_get_param server_post_param + server_get_array + server_post_array + # currently functionally equivalent server_patch_dummy server_put_dummy