mirror of
https://git.sakamoto.pl/laudom/http.sh.git
synced 2025-08-03 12:23:34 +02:00
Compare commits
8 commits
9ad46350af
...
f2d72ef6ee
Author | SHA1 | Date | |
---|---|---|---|
|
f2d72ef6ee | ||
|
c6d3dfa045 | ||
|
d5aaa1c265 | ||
|
f9e1be8a90 | ||
|
6a9ec3bf71 | ||
|
e9f5ab52d2 | ||
|
f889062633 | ||
|
3080e38cad |
6 changed files with 394 additions and 36 deletions
|
@ -13,6 +13,7 @@ We have some guides and general documentation in the [docs](docs/) directory. Am
|
|||
- [CLI usage](docs/running.md)
|
||||
- [Tests](docs/tests.md)
|
||||
- [HTTP Router](docs/router.md)
|
||||
- [Template engine](docs/template.md)
|
||||
- [List of security fixes](docs/sec-fixes/)
|
||||
|
||||
## Dependencies
|
||||
|
|
193
docs/template-examples.md
Normal file
193
docs/template-examples.md
Normal file
|
@ -0,0 +1,193 @@
|
|||
# HTTP.sh: template usage examples
|
||||
|
||||
## Basic example
|
||||
|
||||
Create a new .shs file with the following contents:
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
declare -A str
|
||||
str[title]="Hello, world!"
|
||||
str[test]="meow"
|
||||
|
||||
render str "${cfg[namespace]}/templates/main.htm"
|
||||
```
|
||||
|
||||
`render` is the core of the templating engine; it takes an assoc array, iterates over it, applies
|
||||
additional magic and outputs the response directly to stdout. It is likely the final thing you want
|
||||
to run in your script.
|
||||
|
||||
The script above has referenced an HTML file; For this example, we put it under
|
||||
`app/templates/main.htm`, but you're free to use any directory structure for this.
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{.test}}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Boolean if statements
|
||||
|
||||
Following is an example script which simulates a coin toss:
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
declare -A str
|
||||
str[title]="Coin flip!"
|
||||
|
||||
if (( RANDOM%2 == 0 )); then
|
||||
str[?random]=_
|
||||
fi
|
||||
|
||||
render str "${cfg[namespace]}/templates/main.htm"
|
||||
```
|
||||
|
||||
And the corresponding template:
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{start ?random}}
|
||||
It's heads!
|
||||
{{else ?random}}
|
||||
It's tails!
|
||||
{{end ?random}}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||

|
||||
|
||||
50% of the time the variable will be set, 50% it won't. Hence, it will display either heads or tails :)
|
||||
|
||||
Of note: if you hate repeating yourself, this template can be done inline:
|
||||
|
||||
```
|
||||
It's {{start ?random}}heads{{else ?random}}tails{{end ?random}}!
|
||||
```
|
||||
|
||||
The effect is exactly the same. This is quite useful for adding CSS classes.
|
||||
|
||||
## Loop example
|
||||
|
||||
This API is pending a rewrite due to how convoluted it is.
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
declare -A str
|
||||
str[title]="foreach example"
|
||||
|
||||
nested_declare list # "array of arrays"
|
||||
declare -A elem # temporary element
|
||||
for i in {1..32}; do
|
||||
elem[item]="$i" # assign $i to the temporary element
|
||||
nested_add list elem # add elem to list; this creates a copy you can't modify
|
||||
done
|
||||
# once we have a full list of elements, assign it to the array passed to render
|
||||
str[_list]=list
|
||||
|
||||
render str "${cfg[namespace]}/templates/main.htm"
|
||||
```
|
||||
|
||||
And the template...
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{start _list}}
|
||||
{{.item}}<br>
|
||||
{{end _list}}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The result repeats the whole "subtemplate" between list start and end:
|
||||
|
||||

|
||||
|
||||
This is very useful for rendering data in tables:
|
||||
|
||||
```
|
||||
<table>
|
||||
<tr>
|
||||
<th>number</th>
|
||||
</tr>
|
||||
{{start _list}}
|
||||
<tr>
|
||||
<td>{{.item}}</td>
|
||||
<td>whatever...</td>
|
||||
</tr>
|
||||
{{end _list}}
|
||||
</table>
|
||||
```
|
||||
|
||||

|
||||
|
||||
### integration with notORM
|
||||
|
||||
notORM's `data_iter` function works great with nested_add; Body of a callback function can be
|
||||
treated as equal to a for loop:
|
||||
|
||||
```
|
||||
declare -A elem
|
||||
nested_declare list
|
||||
x() {
|
||||
elem[ns]="${data[2]}"
|
||||
elem[domain]="${data[1]}"
|
||||
nested_add list elem
|
||||
}
|
||||
data_iter storage/zones.dat "$username" x
|
||||
|
||||
str[title]="SERVFAIL :: zone list"
|
||||
str[_list]=list
|
||||
```
|
||||
|
||||
## date pretty-printing
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
Current time is {{+time}}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
declare -A str
|
||||
str[title]="time pretty-print"
|
||||
str[+time]="$EPOCHSECONDS"
|
||||
|
||||
render str "${cfg[namespace]}/templates/main.htm"
|
||||
```
|
||||
|
||||

|
||||
|
||||
If you get quirky with the `<meta http-equiv="refresh" content="1">`, you can even make it
|
||||
auto update! (don't)
|
||||
|
||||

|
157
docs/template.md
Normal file
157
docs/template.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
# HTTP.sh: template engine
|
||||
|
||||
We have a basic template engine! It's somewhat limited in capabilities compared to engines you might
|
||||
have previously used, but we're working on making it better :3
|
||||
|
||||
Note: the `templates` subdirectory in the HTTPsh repo is entirely unrelated to the template engine,
|
||||
and it will be removed in a future release. Please ignore it.
|
||||
|
||||
For practical examples, see the [template examples](template-examples.md) page.
|
||||
|
||||
## Tag schema
|
||||
|
||||
- Tags always start with `{{` and end with `}}`.
|
||||
- Tags can't include whitespace, outside of special iter/boolean tags defined below
|
||||
- Tag identifiers can contain letters, numbers, dashes and underscores (`[a-zA-Z0-9_-]`).
|
||||
Other characters may work but are NOT RECOMMENDED.
|
||||
- Tag identifiers are always prefixed by the tag type. This is also reflected in the code, outside
|
||||
of simple replaces which MUST skip the dot in the array assignment.
|
||||
- Identifiers are represented by `<name>` later in this document.
|
||||
|
||||
## API
|
||||
|
||||
`render <assoc_array> <file> [recurse]`
|
||||
|
||||
The first param points to an associative array containing the replacement data. Second one points
|
||||
to a file containing the template itself. Third is optional, and controls whether `render` will
|
||||
recurse or not. This is mostly used internally, you likely won't ever need to set it.
|
||||
|
||||
## Simple replace
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{.<name>}}` |
|
||||
| In the code | `array[<name>]="<value>"` |
|
||||
| Notes | For your convenience, code representation skips the dot. |
|
||||
|
||||
**Important**: to simplify your life (and protect your application), simple replaces ALWAYS use
|
||||
html_encode behind the scenes. This means that you're safe to assign any value to them without
|
||||
prior sanitization.
|
||||
|
||||
## Raw replace
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{@<name>}}` |
|
||||
| In the code | `array[@<name>]="<value>"` |
|
||||
|
||||
Same as a simple replace, but doesn't do html_encode. Useful if you want to guarantee unmangled
|
||||
output (for filling out hidden form values, etc.)
|
||||
|
||||
## Template includes
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{#<path>}}` |
|
||||
| In the code | n/a |
|
||||
|
||||
Template includes are special, in that you don't have to define them in the array.
|
||||
They get processed first to "glue together" one singular template.
|
||||
|
||||
Currently, the path starts at the root of HTTPsh's directory. We don't support expanding variables
|
||||
inside the include tag, so for now you'll need to hardcode `{{#app/templates/...}}`. This will
|
||||
likely get changed in a future release, starting the path in your namespace.
|
||||
|
||||
**Warning**: No recursion is supported within included templates; This means that you can't have
|
||||
an "include chain". Furthermore, some interactions between included templates and loops/ifs are
|
||||
a bit wonky; This will get ironed out at some point (sorry!)
|
||||
|
||||
## Boolean if statements
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{start ?<name>}} ... {{end ?<name>}}` |
|
||||
| In the template (alt.) | `{{start ?<name>}} ... {{else ?<name>}} ... {{end ?<name>}}` |
|
||||
| In the code | `array[?<name>]=_` |
|
||||
| Notes | Can be used both inline and not. See [examples page](template-examples.md) for more details. |
|
||||
|
||||
**Important**: Currently, you can't have two checks for the same variable. If needed, set a second
|
||||
variable in the code and check for that. Fix TBD.
|
||||
|
||||
This is a *boolean operator*. The only supported mode of operation is checking whether
|
||||
a variable is set or not.
|
||||
|
||||
## Loops
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{start _<name>}} ... {{end _<name>}}` |
|
||||
| In the code | `array[_name]="<reference>"` |
|
||||
|
||||
Each loop extracts the area between start/end markers, and executes another `render` internally.
|
||||
You have to provide it with an "array of arrays", essentially an intermediate holding references.
|
||||
This is usually done through `nested_declare <array>` and `nested_add <array> <temporary>`.
|
||||
|
||||
Essentially, this boils down to:
|
||||
|
||||
```
|
||||
nested_declare list # "array of arrays"
|
||||
declare -A elem # temporary element
|
||||
for i in {1..32}; do
|
||||
elem[item]="$i" # assign $i to the temporary element
|
||||
nested_add list elem # add elem to list; this creates a copy you can't modify
|
||||
done
|
||||
# once we have a full list of elements, assign it to the array passed to render
|
||||
str[_list]=list
|
||||
```
|
||||
|
||||
A more detailed usage description is available on the [template examples](template-examples.md) page.
|
||||
|
||||
### Leaky temporary array
|
||||
|
||||
You should excercise caution when handling the temporary arrays; Calling `unset elem` on the end
|
||||
of each loop may be a good idea if you can't guarantee that all of your elements will always have
|
||||
values. Otherwise, values from previous iterations may leak to the current one, potentially causing confusion.
|
||||
|
||||
## Loop indexes
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{-index}}` |
|
||||
| In the code | n/a |
|
||||
| Notes | Doesn't resolve at all outside loops. Counter starts at 0 and gets incremented with every element. |
|
||||
|
||||
## Date pretty-printing
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{+<name>}}` |
|
||||
| In the code | `array[+<name>]="<timestamp>"` |
|
||||
|
||||
This saves you from a few messy calls to `date`. Input is a UNIX timestamp.
|
||||
|
||||
The date format can be overriden by changing a config variable. Default is
|
||||
`cfg[template_date_format]='%Y-%m-%d %H:%M:%S'`.
|
||||
|
||||
## URI slices
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| In the template | `{{-uri-<level>}}` |
|
||||
| In the code | n/a |
|
||||
| Notes | Level must be a number. URI is always terminated with a slash, even if your last object is a file. |
|
||||
|
||||
Takes the current URI path, and slices it using `/` as a delimeter, going from the left.
|
||||
|
||||
Given an URL `http://localhost:1337/hello/world/asdf`...
|
||||
|
||||
- `{{-uri-0}}` -> `/`
|
||||
- `{{-uri-1}}` -> `/hello/`
|
||||
- `{{-uri-2}}` -> `/hello/world/`
|
||||
- `{{-uri-3}}` -> `/hello/world/asdf/`
|
||||
- `{{-uri-4}}` -> none (higher values are always empty)
|
||||
|
||||
This is very useful when creating menus; Instead of relying on hardcoded values, if the page is always
|
||||
on *the same URI level*, one can create links such as `<a href="{{-uri-2}}meow">(...)</a>`, which will always
|
||||
resolve to the same file; This eliminates a whole class of bugs where trailing slashes would break some
|
||||
poorly-written relative URLs.
|
|
@ -252,11 +252,13 @@ if [[ "${r[post]}" == true ]] && [[ "${r[status]}" == 200 || "${r[status]}" ==
|
|||
if [[ "${r[content_length]}" ]]; then
|
||||
read -r -N "${r[content_length]}" data
|
||||
else
|
||||
data=
|
||||
while read -r line; do
|
||||
data+="$line"
|
||||
done
|
||||
unset line
|
||||
if read -t0; then
|
||||
data=
|
||||
while read -r line; do
|
||||
data+="$line"
|
||||
done
|
||||
unset line
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${r[payload_type]}" == "urlencoded" ]]; then
|
||||
|
|
|
@ -13,13 +13,43 @@ function render() {
|
|||
local template="$(tr -d "${_tpl_ctrl}" < "$2" | sed -E 's/\\/\\\\/g')"
|
||||
fi
|
||||
local buf=
|
||||
local garbage=
|
||||
local -n ref=$1
|
||||
|
||||
# process file includes;
|
||||
# recursion is currently unsupported here, i feel like it may break things?
|
||||
if [[ "$template" == *'{{#'* && "$3" != true ]]; then
|
||||
local subtemplate=
|
||||
while read key; do
|
||||
# below check prevents the loop loading itself as a template.
|
||||
# this is possibly not enough to prevent all recursions, but
|
||||
# i see it as a last-ditch measure. so it'll do here.
|
||||
if [[ "$file" == "$2" ]]; then
|
||||
subtemplate+="s${_tpl_ctrl}\{\{\#$key\}\}${_tpl_ctrl}I cowardly refuse to endlessly recurse\!${_tpl_ctrl}g;"
|
||||
elif [[ -f "$key" ]]; then
|
||||
local input="$(tr -d "${_tpl_ctrl}${_tpl_newline}" < "$key" | sed 's/\&/<2F>UwU<77>/g')"
|
||||
garbage+="$input"$'\n'
|
||||
input="$(tr $'\n' "${_tpl_newline}" <<< "$input")" # for another hack
|
||||
subtemplate+="s${_tpl_ctrl}\{\{\#$key\}\}${_tpl_ctrl}${input}${_tpl_ctrl};"
|
||||
_template_find_special_uri "$(cat "$key")"
|
||||
fi
|
||||
done <<< "$(grep -Poh '{{#\K(.*?)(?=}})' <<< "$template")"
|
||||
|
||||
buf+="${subtemplate}"
|
||||
fi
|
||||
|
||||
local key
|
||||
IFS=$'\n'
|
||||
for key in ${!ref[@]}; do
|
||||
if [[ "$key" == "_"* ]]; then # iter mode
|
||||
local subtemplate="$(grep "{{start $key}}" -A99999 <<< "$template" | grep "{{end $key}}" -B99999 | tr '\n' "${_tpl_newline}")"
|
||||
# THE MOST EVIL OF ALL HACKS:
|
||||
# we're scraping a subtemplate from our main template.
|
||||
# HOWEVER: this fails on included templates, because they're not real.
|
||||
# this means that iterators can't work on included templates
|
||||
#
|
||||
# workaround? collect all includes, concatenate them all together and just.
|
||||
# use that pile of garbage here along with the real template. it works!
|
||||
local subtemplate="$(grep "{{start $key}}" -A99999 <<< "$template"$'\n'"$garbage" | grep "{{end $key}}" -B99999 | tr '\n' "${_tpl_newline}")"
|
||||
local -n asdf=${ref["$key"]}
|
||||
local j
|
||||
local value=''
|
||||
|
@ -31,7 +61,7 @@ function render() {
|
|||
(( _index++ ))
|
||||
done
|
||||
|
||||
buf+="s${_tpl_ctrl}\{\{start $key\}\}.*\{\{end $key\}\}${_tpl_ctrl}\{\{$key\}\}${_tpl_ctrl};s${_tpl_ctrl}\{\{$key\}\}${_tpl_ctrl}$(tr -d "${_tpl_ctrl}" <<< "$value" | sed -E "s${_tpl_ctrl}"'\{\{start '"$key"'\}\}'"${_tpl_ctrl}${_tpl_ctrl};s${_tpl_ctrl}"'\{\{end '"$key"'\}\}'"${_tpl_ctrl}${_tpl_ctrl}")${_tpl_ctrl};"
|
||||
buf+="s${_tpl_ctrl}\{\{start $key\}\}.*\{\{end $key\}\}${_tpl_ctrl}\{\{$key\}\}${_tpl_ctrl};s${_tpl_ctrl}\{\{$key\}\}${_tpl_ctrl}$(tr -d "${_tpl_ctrl}" <<< "$value" | sed "s${_tpl_ctrl}{{start $key}}${_tpl_ctrl}${_tpl_ctrl};s${_tpl_ctrl}{{end $key}}${_tpl_ctrl}${_tpl_ctrl}")${_tpl_ctrl};"
|
||||
unset "$subtemplate"
|
||||
elif [[ "$key" == "@"* && "${ref["$key"]}" != '' ]]; then
|
||||
local value="$(tr -d "${_tpl_ctrl}${_tpl_newline}" <<< "${ref["$key"]}" | sed -E 's/\&/<2F>UwU<77>/g')"
|
||||
|
@ -51,12 +81,7 @@ function render() {
|
|||
elif [[ "$key" == '?'* ]]; then
|
||||
local _key="\\?${key/?/}"
|
||||
|
||||
# TODO: check if this is needed?
|
||||
# the code below makes sure to resolve the conditional blocks
|
||||
# *before* anything else. I can't think of *why* this is needed
|
||||
# right now, but I definitely had a reason in this. Question is, what reason.
|
||||
|
||||
buf+="s${_tpl_ctrl}"'\{\{start '"$_key"'\}\}((.*)\{\{else '"$_key"'\}\}.*\{\{end '"$_key"'\}\}|(.*)\{\{end '"$_key"'\}\})'"${_tpl_ctrl}"'\2\3'"${_tpl_ctrl};${buf}" # MAYBE_SLOW
|
||||
buf+="s${_tpl_ctrl}"'\{\{start '"$_key"'\}\}((.*)\{\{else '"$_key"'\}\}.*\{\{end '"$_key"'\}\}|(.*)\{\{end '"$_key"'\}\})'"${_tpl_ctrl}"'\2\3'"${_tpl_ctrl};"
|
||||
|
||||
elif [[ "${ref["$key"]}" != "" ]]; then
|
||||
if [[ "$3" != true ]]; then
|
||||
|
@ -71,32 +96,12 @@ function render() {
|
|||
done
|
||||
unset IFS
|
||||
|
||||
# process file includes;
|
||||
# achtung: even though this is *after* the main loop, it actually executes sed reaplces *before* it;
|
||||
# recursion is currently unsupported here, i feel like it may break things?
|
||||
if [[ "$template" == *'{{#'* && "$3" != true ]]; then
|
||||
local subtemplate=
|
||||
while read key; do
|
||||
# below check prevents the loop loading itself as a template.
|
||||
# this is possibly not enough to prevent all recursions, but
|
||||
# i see it as a last-ditch measure. so it'll do here.
|
||||
if [[ "$file" == "$2" ]]; then
|
||||
subtemplate+="s${_tpl_ctrl}\{\{\#$key\}\}${_tpl_ctrl}I cowardly refuse to endlessly recurse\!${_tpl_ctrl}g;"
|
||||
elif [[ -f "$key" ]]; then
|
||||
subtemplate+="s${_tpl_ctrl}\{\{\#$key\}\}${_tpl_ctrl}$(tr -d "${_tpl_ctrl}${_tpl_newline}" < "$key" | tr $'\n' "${_tpl_newline}" | sed 's/\&/<2F>UwU<77>/g')${_tpl_ctrl};"
|
||||
_template_find_special_uri "$(cat "$key")"
|
||||
fi
|
||||
done <<< "$(grep -Poh '{{#.*?}}' <<< "$template" | sed 's/{{#//;s/}}$//')"
|
||||
|
||||
buf="${subtemplate}$buf"
|
||||
fi
|
||||
|
||||
_template_find_special_uri "$template"
|
||||
buf+="$(_template_gen_special_uri)"
|
||||
|
||||
if [[ "$3" != true ]]; then # are we recursing?
|
||||
tr '\n' ${_tpl_newline} <<< "$template" | sed -E -f <(
|
||||
tr '\n' "${_tpl_newline}" <<< "$buf" | sed -E $'s/\02;\01/\02;/g;s/\02g;\01/\02g;/g' # i'm sorry what is this sed replace??
|
||||
tr '\n' "${_tpl_newline}" <<< "$template" | sed -E -f <(
|
||||
tr '\n' "${_tpl_newline}" <<< "$buf" | sed $'s/\02;\01/\02;/g;s/\02g;\01/\02g;/g' # i'm sorry what is this sed replace??
|
||||
echo -n 's/\{\{start \?([a-zA-Z0-9_-]*[^}])\}\}(.*\{\{else \?\1\}\}(.*)\{\{end \?\1\}\}|.*\{\{end \?\1\}\})/\3/g'
|
||||
) | tr "${_tpl_newline}" '\n' | sed -E 's/<2F>UwU<77>/\&/g'
|
||||
else
|
||||
|
|
|
@ -158,7 +158,7 @@ server_req_header_dup() {
|
|||
server_req_header_invalid() {
|
||||
tst() {
|
||||
# we have to trick curl into sending an invalid header for us
|
||||
curl -s "localhost:1337/meow.shs" -H $'a:\nasdf asdf asdf asdf' -H "meow: asdf"
|
||||
curl -s "localhost:1337/meow.shs" -H $'meow:\nasdf asdf asdf asdf' -H "a: aaaa"
|
||||
}
|
||||
|
||||
match_not="asdf"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue