]> git.plutz.net Git - serve0/commitdiff
Squashed 'cgilite/' changes from f0383ee..5b013b6
authorPaul Hänsch <paul@plutz.net>
Wed, 3 Jun 2020 17:52:42 +0000 (19:52 +0200)
committerPaul Hänsch <paul@plutz.net>
Wed, 3 Jun 2020 17:52:42 +0000 (19:52 +0200)
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

git-subtree-dir: cgilite
git-subtree-split: 5b013b64b7bbc9a62775ed861e9c8b34ffb3dfaa

cgilite.sh

index 333334c460f5cae9bc763f0079584a41132f70c5..86ad28073018c9a2626877e74426c0b2d4a95914 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,19 @@ 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
+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 +72,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 +135,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 +163,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 \
@@ -180,19 +218,10 @@ URL(){
   | 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;'
-}
-
-
 SET_COOKIE(){
+  # Param: session | +seconds | [date]
+  # Param: name=value
+  # Param: Path= | Domain= | Secure
   local expire cookie
   case "$1" in
     ''|0|session) expire='';;
@@ -201,7 +230,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'