]> git.plutz.net Git - rigidfind/blobdiff - cgilite/file.sh
Merge commit '9ffde1819707b7a9b5ef18f15a9a5819c876f253' as 'cgilite'
[rigidfind] / cgilite / file.sh
diff --git a/cgilite/file.sh b/cgilite/file.sh
new file mode 100755 (executable)
index 0000000..c66b17d
--- /dev/null
@@ -0,0 +1,144 @@
+#!/bin/sh
+
+# Copyright 2016 - 2024 Paul Hänsch
+# 
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+# 
+# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+[ -n "$include_fileserve" ] && return 0
+include_fileserve="$0"
+
+file_type(){
+  case ${1##*.} in
+    css)       printf 'text/css';;
+    gif)       printf 'image/gif';;
+    html|html) printf 'text/html';;
+    jpg|jpeg)  printf 'image/jpeg';;
+    js)        printf 'text/javascript';;
+    m3u8)      printf 'application/x-mpegURL';;
+    m4a)       printf 'audio/mp4';;
+    m4s)       printf 'video/iso.segment';;
+    m4v|mp4)   printf 'video/mp4';;
+    mpd)       printf 'application/dash+xml';;
+    ogg)       printf 'audio/ogg';;
+    pdf)       printf 'application/pdf';;
+    png)       printf 'image/png';;
+    sh)        printf 'text/x-shellscript';;
+    svg)       printf 'image/svg+xml';;
+    tex)       printf 'text/x-tex';;
+    txt)       printf 'text/plain';;
+    short)     printf 'text/prs.shorthand';;
+    ts)        printf 'video/MP2T';;
+    webm)      printf 'video/webm';;
+    xml)       printf 'application/xml';;
+    *)         printf 'application/octet-stream';;
+  esac
+}
+
+FILE(){
+  local file="$1" mime="$2"
+  local file_size file_date http_date cachedate range
+
+  if ! [ -f "$file" ]; then
+    printf 'Content-Length: 0\r\nStatus: 404 Not Found\r\n\r\n'
+    return 0
+  elif ! [ -r "$file" ]; then
+    printf 'Content-Length: 0\r\nStatus: 403 Forbidden\r\n\r\n'
+    return 0
+  fi
+
+  read file_size file_date <<-EOF
+       $(stat -Lc "%s  %Y" "$file")
+       EOF
+  http_date="$(date -ud "@$file_date" +"%a, %d %b %Y %T GMT")"
+
+  [ ! "$HTTP_IF_MODIFIED_SINCE" -a "$cgilite_headers" ] \
+  &&    HTTP_IF_MODIFIED_SINCE="$(HEADER If-Modified-Since)"
+  [ ! "$HTTP_RANGE"             -a "$cgilite_headers" ] \
+  &&    HTTP_RANGE="$(HEADER Range)"
+
+  cachedate="$(
+    # Parse the allowable date formats from Section 3.3.1 of
+    # https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
+    # HEADER If-Modified-Since \
+    printf %s "$HTTP_IF_MODIFIED_SINCE" \
+    | sed -E 's;^[^ ]+, ([0-9]{2}) (...) ([0-9]{4}) (..:..:..) GMT$;\3-\2-\1 \4;;
+              s;^[^ ]+, ([0-9]{2})-(...)-([789][0-9]) (..:..:..) GMT$;19\3-\2-\1 \4;;
+              s;^[^ ]+, ([0-9]{2})-(...)-([0-6][0-9]) (..:..:..) GMT$;20\3-\2-\1 \4;;
+              s;^[^ ]+ (...) ([0-9]{2}) (..:..:..) ([0-9]{4})$;\4-\1-\2 \3;;
+              s;^[^ ]+ (...)  ([0-9]) (..:..:..) ([0-9]{4})$;\4-\1-\2 \3;;
+              s;Jan;01;; s;Feb;02;; s;Mar;03;; s;Apr;04;; s;May;05;; s;Jun;06;;
+              s;Jul;07;; s;Aug;08;; s;Sep;09;; s;Oct;10;; s;Nov;11;; s;Dec;12;;' \
+    | xargs -r0 date +%s -ud 2>&-
+  )"
+
+  range="${HTTP_RANGE#bytes=}"
+  case "$range" in
+    *[!0-9]*-*|*-*[!0-9]*)
+      range=""
+      ;;
+    *-)
+      range="${range}$((file_size - 1))"
+      ;;
+    -*)
+      [ ${range#-} -le $file_size ] \
+      && range="$((file_size - ${range#-}))-$((file_size - 1))" \
+      || range="0-$((file_size - 1))"
+      ;;
+    *-*)
+      [ ${range#*-} -ge $file_size ] \
+      && range="${range%-*}-$((file_size - 1))"
+      ;;
+    *) range=""
+      ;;
+  esac
+
+  if [ "$file_date" -lt "$cachedate" ] 2>&-; then
+    printf '%s: %s\r\n' \
+      Status '304 Not Modified' \
+      Content-Length 0 \
+      Last-Modified "$http_date"
+    printf '\r\n'
+  
+  elif [ -z "$range" ]; then
+    printf '%s: %s\r\n' \
+      Status "200 OK" \
+      Accept-Ranges bytes \
+      Last-Modified "$http_date" \
+      Content-Type "${mime:-$(file_type "$file")}" \
+      Content-Length $file_size
+    printf '\r\n'
+  
+    [ "$REQUEST_METHOD" != HEAD ] && cat "$file"
+
+  elif [ "${range%-*}" -le "${range#*-}" ]; then
+    printf '%s: %s\r\n' \
+      Status "206 Partial Content" \
+      Accept-Ranges bytes \
+      Last-Modified "$http_date" \
+      Content-Type "${mime:-$(file_type "$file")}" \
+      Content-Range "bytes ${range}/${file_size}" \
+      Content-Length "$((${range#*-} - ${range%-*} + 1))"
+    printf '\r\n'
+  
+    [ "$REQUEST_METHOD" != HEAD ] \
+    && tail -c+$((${range%-*} + 1)) "$file" \
+     | head -c "$((${range#*-} - ${range%-*} + 1))"
+
+  elif [ "${range%-*}" -gt "${range#*-}" ]; then
+    printf '%s: %s\r\n' \
+      Status "216 Range Not Satisfiable" \
+      Content-Length 0 \
+      Content-Range \*/${file_size}
+    printf '\r\n'
+  fi
+}