]> git.plutz.net Git - cgilite/blobdiff - cgilite.sh
bugfix: allow empty query string
[cgilite] / cgilite.sh
index 4c6de8ca6b784a5a361fb5cd8d70208c1a4d701d..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,26 +72,41 @@ 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%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_response:+${BR}}" "${cgilite_cl}"
            cat || kill $$
@@ -96,13 +115,16 @@ if [ -z "$REQUEST_METHOD" ]; then
     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'