238f0f8 rudimentary debug function
b8cec22 more escapes for use in html-sh and HTTP headers
4a73c71 limit escaping to necessary characters, more readable output, much faster escaping functions
5b013b6 bugfix: allow empty query string
7e6d863 improved handling of Connection header
87f88f1 quicker path sanitizing
06edc60 sanitizing and security
73550e0 updated copyright line
5a99761 speed improvements
7335b73 allow empty script headers
f0383ee bugfix, STRING and UNSTRING input like "foo[bar]"
0d6196d faster STRING and UNSTRING functions
git-subtree-dir: cgilite
git-subtree-split:
238f0f8a2932b5ba66f7139c227eaaeb5dd7a013
-# Copyright 2017 - 2018 Paul Hänsch
+# Copyright 2017 - 2020 Paul Hänsch
#
# This is CGIlite.
# A collection of posix shell functions for writing CGI scripts.
#
# This is CGIlite.
# A collection of posix shell functions for writing CGI scripts.
-HEADER(){
- # Read value of header line. Use this instead of
- # referencing HTTP_* environment variables.
- if [ -n "${cgilite_headers+x}" ]; then
- printf %s "$cgilite_headers" \
- | sed -En 's;^'"${1}"': ([^\r]+)\r?$;\1;i; tX; d; :X;p;q;'
- else
- eval "printf %s \"\$HTTP_$(printf %s "${1}" |tr a-z A-Z |tr -c A-Z _)\""
- fi
+debug(){ [ $# -gt 0 ] && printf '%s\n' "$@" >&2 || tee -a /dev/stderr; }
+
+PATH(){
+ local str seg out
+ [ $# -eq 0 ] && str="$(cat)" || str="$*"
+ while [ "$str" ]; do
+ seg=${str%%/*}; str="${str#*/}"
+ case $seg in
+ ..) out="${out%/}"; out="${out%/*}/";;
+ .|'') out="${out%/}/";;
+ *) out="${out%/}/${seg}";;
+ esac;
+ [ "$seg" = "$str" ] && break
+ done
+ [ "${str}" -a "${out}" ] && printf %s "$out" || printf %s/ "${out%/}"
(sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$!
while read REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL; do
(sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$!
while read REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL; do
+ [ "${SERVER_PROTOCOL#HTTP/1.[01]${CR}}" ] && break
- PATH_INFO="$(HEX_DECODE "${REQUEST_URI%\?*}")"
- QUERY_STRING="${REQUEST_URI#*\?}"
- cgilite_headers="$(while read -r hl; do [ "${hl%${CR}}" ] && printf '%s\n' "$hl" || break; done )"
- HTTP_CONTENT_LENGTH="$(HEADER Content-Length |grep -xE '[0-9]+')"
+ SERVER_PROTOCOL="${SERVER_PROTOCOL%${CR}}"
+ PATH_INFO="$(HEX_DECODE "${REQUEST_URI%\?*}" |PATH)"
+ [ "${REQUEST_URI}" = "${REQUEST_URI#*\?}" ] \
+ && QUERY_STRING='' \
+ || QUERY_STRING="${REQUEST_URI#*\?}"
+ cgilite_headers=''; while read -r hl; do
+ hl="${hl%${CR}}"; [ "$hl" ] || break
+ case $hl in
+ 'Content-Length: '*) CONTENT_LENGTH="${hl#*: }";;
+ 'Content-Type: '*) CONTENT_TYPE="${hl#*: }";;
+ esac
+ cgilite_headers="${cgilite_headers}${hl}${BR}"
+ done
export REMOTE_ADDR SERVER_NAME SERVER_PORT REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL \
export REMOTE_ADDR SERVER_NAME SERVER_PORT REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL \
- PATH_INFO QUERY_STRING HTTP_CONTENT_LENGTH
+ PATH_INFO QUERY_STRING CONTENT_TYPE CONTENT_LENGTH
# Try to serve multiple requests, provided that script serves a
# Content-Length header.
# Without Content-Length header, connection will terminate after
# script.
# Try to serve multiple requests, provided that script serves a
# Content-Length header.
# Without Content-Length header, connection will terminate after
# script.
- cgilite_status='200 OK'; cgilite_response=''; cgilite_cl="Connection: close${CR}";
+ cgilite_status='200 OK'; cgilite_response=''; cgilite_cl="Connection: close${CR}${BR}";
. "$0" | while read -r l; do case $l in
. "$0" | while read -r l; do case $l in
- Status:*) cgilite_status="${l#Status: }";;
- Content-Length:*) cgilite_cl="${l}";;
- $CR) printf '%s %s\r\n%s\n%s\n\r\n' \
+ Status:*)
+ cgilite_status="${l#Status: }";;
+ Content-Length:*)
+ cgilite_cl=""
+ cgilite_response="${cgilite_response:+${cgilite_response}${BR}}${l}";;
+ Connection:*)
+ cgilite_cl="${l}${BR}";;
+ $CR) printf '%s %s\r\n%s%s\r\n' \
'HTTP/1.1' "${cgilite_status%${CR}}" \
'HTTP/1.1' "${cgilite_status%${CR}}" \
- "$cgilite_response" "${cgilite_cl}"
+ "${cgilite_response}${cgilite_response:+${BR}}" "${cgilite_cl}"
cat || kill $$
[ "${cgilite_cl#Connection}" = "${cgilite_cl}" ]; exit;;
*) cgilite_response="${cgilite_response:+${cgilite_response}${BR}}${l}";;
esac; done || exit 0;
(sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$!
done
cat || kill $$
[ "${cgilite_cl#Connection}" = "${cgilite_cl}" ]; exit;;
*) cgilite_response="${cgilite_response:+${cgilite_response}${BR}}${l}";;
esac; done || exit 0;
(sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$!
done
+ kill $cgilite_watchdog
+ exit 0
-if [ "$REQUEST_METHOD" = POST -a "${HTTP_CONTENT_LENGTH:=${CONTENT_LENGTH:=0}}" -gt 0 ]; then
- cgilite_post="$(head -c "$HTTP_CONTENT_LENGTH")"
+if [ "${REQUEST_METHOD}" = POST -a "${CONTENT_LENGTH:-0}" -gt 0 -a \
+ "${CONTENT_TYPE}" = "application/x-www-form-urlencoded" ]; then
+ cgilite_post="$(head -c "$CONTENT_LENGTH")"
-[ -n "${DEBUG+x}" ] && env
+[ "${DEBUG+x}" ] && env >&2
cgilite_count(){
printf %s "&$1" \
cgilite_count(){
printf %s "&$1" \
cgilite_value(){
local str="&$1" name="$2" cnt="${3:-1}"
while [ $cnt -gt 0 ]; do
cgilite_value(){
local str="&$1" name="$2" cnt="${3:-1}"
while [ $cnt -gt 0 ]; do
+ [ "${str}" = "${str#*&${name}=}" ] && return 1
+ str="${str#*&${name}=}"
cnt=$((cnt - 1))
done
printf -- "$(printf %s "${str%%&*}" |sed -E 's;\+; ;g;'"$HEX_DECODE")"
cnt=$((cnt - 1))
done
printf -- "$(printf %s "${str%%&*}" |sed -E 's;\+; ;g;'"$HEX_DECODE")"
REF_COUNT(){ cgilite_count "${HTTP_REFERER#*\?}" $1; }
REF_KEYS(){ cgilite_keys "${HTTP_REFERER#*\?}"; }
REF_COUNT(){ cgilite_count "${HTTP_REFERER#*\?}" $1; }
REF_KEYS(){ cgilite_keys "${HTTP_REFERER#*\?}"; }
+HEADER(){
+ # Read value of header line. Use this instead of
+ # referencing HTTP_* environment variables.
+ if [ -n "${cgilite_headers+x}" ]; then
+ local str="${BR}${cgilite_headers}"
+ [ "${str}" = "${str#*${BR}${1}: }" ] && return 1
+ str="${str#*${BR}${1}: }"
+ printf %s "${str%%${BR}*}"
+ else
+ local var="HTTP_$(printf %s "$1" |tr a-z- A-Z-)"
+ eval "[ \"\$$var\" ] && printf %s \"\$$var\" || return 1"
+ # eval "printf %s \"\$HTTP_$(printf %s "${1}" |tr a-z A-Z |tr -c A-Z _)\""
+ fi
+}
+
COOKIE(){
HEX_DECODE "$(
HEADER Cookie \
COOKIE(){
HEX_DECODE "$(
HEADER Cookie \
- # HTML Entity Coding
- # Prints UTF-8 string as decimal Unicode Code Points
- # Useful for escaping user input for use in HTML text and attributes
- { [ $# -eq 0 ] && cat || printf %s "$*"; } \
- | hexdump -ve '/1 "%03o\n"' \
- | while read n; do
- case $n in
- # bitbanging octal UTF-8 chains into singular 7 digit octal numbers
- [01]??) printf '0000%s' $n;; # 7 bit ASCII character, nothing to do
- 2??) printf '%s' ${n#2};; # tail fragment, append 6 bit
- 3[0123]?) printf '000%s' ${n#3};; # 2 octet (11 bit) chain start
- 34?) printf '00%s' ${n#34};; # 3 octet (16 bit) chain start
- 35?) printf '01%s' ${n#35};; # 3 octet chain start, high
- 36?) printf '%s' ${n#36};; # 4 octet (21 bit) chain start
+ # Escape HTML cahracters
+ # Also escape [, ], and \n for use in html-sh
+ local str out
+ [ $# -eq 0 ] && str="$(cat)" || str="$*"
+ while [ "$str" ]; do
+ case $str in
+ \&*) out="${out}&";;
+ \<*) out="${out}<";;
+ \>*) out="${out}>";;
+ \"*) out="${out}"";;
+ \'*) out="${out}'";;
+ \[*) out="${out}[";;
+ \]*) out="${out}]";;
+ "${BR}"*) out="${out}
";;
+ *) out="${out}${str%"${str#?}"}";;
- done \
- | sed -E 's;.{7};&\n;g;' \
- | while read n; do
- printf '&#%d;' $((0$n))
- # Code every character in URL escape hex format
- # except alphanumeric ascii
-
- { [ $# -eq 0 ] && cat || printf %s "$*"; } \
- | hexdump -v -e '/1 ",%02X"' \
- | sed 's;,;%;g; s;%2F;/;g;'
-}
-
-PATH(){
- { [ $# -eq 0 ] && cat || printf %s "$*"; } \
- | sed -E 's;^.*$;/&/;; s;/+;/;g;
- :X;
- s;^/\.\./;/;; s;/\./;/;g;
- tX;
- s;/[^/]+/\.\./;/;;
- tX;
- s;^(/.*)/$;\1;'
+ # Escape pathes, so they can be used in link tags and HTTP Headers
+ local str out
+ [ $# -eq 0 ] && str="$(cat)" || str="$*"
+ while [ "$str" ]; do
+ case $str in
+ \&*) out="${out}%26";;
+ \"*) out="${out}%22";;
+ \'*) out="${out}%27";;
+ \?*) out="${out}%3F";;
+ \#*) out="${out}%23";;
+ \[*) out="${out}%5B";;
+ \]*) out="${out}%5D";;
+ \ *) out="${out}%20";;
+ "${BR}"*) out="${out}%0A";;
+ %*) out="${out}%25";;
+ *) out="${out}${str%"${str#?}"}";;
+ esac
+ str="${str#?}"
+ done
+ printf %s "$out"
+ # Param: session | +seconds | [date]
+ # Param: name=value
+ # Param: Path= | Domain= | Secure
local expire cookie
case "$1" in
''|0|session) expire='';;
local expire cookie
case "$1" in
''|0|session) expire='';;
- printf 'Set-Cookie: %s' "$cookie"
+ printf 'Set-Cookie: %s; HttpOnly; SameSite=Lax' "$cookie"
[ -n "$expire" ] && printf '; Expires=%s' "${expire%+????}${expire:+GMT}"
[ $# -ge 3 ] && shift 2 && printf '; %s' "$@"
printf '\r\n'
[ -n "$expire" ] && printf '; Expires=%s' "${expire%+????}${expire:+GMT}"
[ $# -ge 3 ] && shift 2 && printf '; %s' "$@"
printf '\r\n'
[ -n "$include_storage" ] && return 0
include_storage="$0"
[ -n "$include_storage" ] && return 0
include_storage="$0"
LOCK(){
local lock timeout block
lock="${1}.lock"
LOCK(){
local lock timeout block
lock="${1}.lock"
{ [ $# -eq 0 ] && cat || printf %s "$*"; } \
| sed -E ':X; $!{N;bX;}'"$STRING"
}
{ [ $# -eq 0 ] && cat || printf %s "$*"; } \
| sed -E ':X; $!{N;bX;}'"$STRING"
}
+STRING(){
+ local in out=''
+ [ $# -gt 0 ] && in="$*" || in="$(cat)"
+ while [ "$in" ]; do case $in in
+ \\*) out="${out}\\\\"; in="${in#\\}" ;;
+ "$BR"*) out="${out}\\n"; in="${in#${BR}}" ;;
+ "$CR"*) out="${out}\\r"; in="${in#${CR}}" ;;
+ " "*) out="${out}\\t"; in="${in# }" ;;
+ +*) out="${out}\\+"; in="${in#+}" ;;
+ " "*) out="${out}+"; in="${in# }" ;;
+ *) out="${out}${in%%[\\${CR}${BR} + ]*}"; in="${in#"${in%%[\\${BR}${CR} + ]*}"}" ;;
+ esac; done
+ printf '%s' "$out"
+}
+
+
UNSTRING='
:UNSTRING_X
s;((^|[^\\])(\\\\)*)\\n;\1\n;g;
UNSTRING='
:UNSTRING_X
s;((^|[^\\])(\\\\)*)\\n;\1\n;g;
s;((^|[^\\])(\\\\)*)\\\+;\1+;g;
s;\\\\;\\;g;
'
s;((^|[^\\])(\\\\)*)\\\+;\1+;g;
s;\\\\;\\;g;
'
{ [ $# -eq 0 ] && cat || printf %s "$*"; } \
| sed -E "$UNSTRING"
}
{ [ $# -eq 0 ] && cat || printf %s "$*"; } \
| sed -E "$UNSTRING"
}
+UNSTRING(){
+ local in out=''
+ [ $# -gt 0 ] && in="$*" || in="$(cat)"
+ while [ "$in" ]; do case $in in
+ \\\\*) out="${out}\\"; in="${in#\\\\}" ;;
+ \\n*) out="${out}${BR}"; in="${in#\\n}" ;;
+ \\r*) out="${out}${CR}"; in="${in#\\r}" ;;
+ \\t*) out="${out} "; in="${in#\\t}" ;;
+ \\+) out="${out}+"; in="${in#\\+}" ;;
+ +*) out="${out} "; in="${in#+}" ;;
+ \\*) in="${in#\\}" ;;
+ *) out="${out}${in%%[\\+]*}"; in="${in#"${in%%[\\+]*}"}" ;;
+ esac; done
+ printf '%s' "$out"
+}
+