]> git.plutz.net Git - lobster/commitdiff
Squashed 'cgilite/' changes from fa0f919..238f0f8
authorPaul Hänsch <paul@plutz.net>
Wed, 5 Aug 2020 14:17:37 +0000 (16:17 +0200)
committerPaul Hänsch <paul@plutz.net>
Wed, 5 Aug 2020 14:17:37 +0000 (16:17 +0200)
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

cgilite.sh
storage.sh

index 333334c460f5cae9bc763f0079584a41132f70c5..2e538324c0a8b5ec45f6fb2235f254fcf359db22 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# 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.
@@ -27,15 +27,21 @@ BR='
 '
 cgilite_timeout=2
 
-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%/}"
 }
 
 HEX_DECODE='
@@ -68,41 +74,59 @@ if [ -z "$REQUEST_METHOD" ]; then
 
   (sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$!
   while read REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL; do
+    [ "${SERVER_PROTOCOL#HTTP/1.[01]${CR}}" ] && break
     kill $cgilite_watchdog
-    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 \
-           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.
 
-    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
-      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}}" \
-             "$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
+  kill $cgilite_watchdog
+  exit 0
 fi
 
-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")"
 fi
 
-[ -n "${DEBUG+x}" ] && env
+[ "${DEBUG+x}" ] && env >&2
 
 cgilite_count(){
   printf %s "&$1" \
@@ -113,7 +137,8 @@ cgilite_count(){
 cgilite_value(){
   local str="&$1" name="$2" cnt="${3:-1}"
   while [ $cnt -gt 0 ]; do
-    str=${str#*&${name}=}
+    [ "${str}" = "${str#*&${name}=}" ] && return 1
+    str="${str#*&${name}=}"
     cnt=$((cnt - 1))
   done
   printf -- "$(printf %s "${str%%&*}" |sed -E 's;\+; ;g;'"$HEX_DECODE")"
@@ -140,6 +165,21 @@ REF(){ cgilite_value "${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 \
@@ -149,50 +189,54 @@ COOKIE(){
 }
 
 HTML(){
-  # 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}&amp;";;
+      \<*) out="${out}&lt;";;
+      \>*) out="${out}&gt;";;
+      \"*) out="${out}&quot;";;
+      \'*) out="${out}&#x27;";;
+      \[*) out="${out}&#x5B;";;
+      \]*) out="${out}&#x5D;";;
+      "${BR}"*) out="${out}&#x0A;";;
+      *) out="${out}${str%"${str#?}"}";;
     esac
-  done \
-  | sed -E 's;.{7};&\n;g;' \
-  | while read n; do
-    printf '&#%d;' $((0$n))
+    str="${str#?}"
   done
+  printf %s "$out"
 }
 
 URL(){
-  # 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"
 }
 
-
 SET_COOKIE(){
+  # Param: session | +seconds | [date]
+  # Param: name=value
+  # Param: Path= | Domain= | Secure
   local expire cookie
   case "$1" in
     ''|0|session) expire='';;
@@ -201,7 +245,7 @@ SET_COOKIE(){
   esac
   cookie="$2"
 
-  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'
index 10a802950465c813001a3aa27f95f2f4a22dd1b7..7f70e6480d6c89c8a32d3a9ee3fb68a5bfcc35b6 100755 (executable)
 [ -n "$include_storage" ] && return 0
 include_storage="$0"
 
+CR="\r"
+BR='
+'
+
 LOCK(){
   local lock timeout block
   lock="${1}.lock"
@@ -72,11 +76,27 @@ STRING='
   s; ;+;g;
 '
 
-STRING(){
+STRING_OLD(){
   { [ $# -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;
@@ -87,7 +107,23 @@ UNSTRING='
   s;((^|[^\\])(\\\\)*)\\\+;\1+;g;
   s;\\\\;\\;g;
 '
-UNSTRING(){
+UNSTRING_OLD(){
   { [ $# -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"
+}
+