From: Paul Hänsch Date: Sun, 1 Jul 2018 21:46:16 +0000 (+0200) Subject: static file serving supporting byte-ranges and If-Modified-Since header X-Git-Url: http://git.plutz.net/?p=cgilite;a=commitdiff_plain;h=750ac074f123acdc39d7e316224c4416b7d85acc static file serving supporting byte-ranges and If-Modified-Since header --- diff --git a/file.sh b/file.sh new file mode 100755 index 0000000..aaa3723 --- /dev/null +++ b/file.sh @@ -0,0 +1,116 @@ +#!/bin/zsh + +# Copyright 2016 - 2018 Paul Hänsch +# +# This file is part of cgilite. +# +# cgilite is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cgilite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with cgilite. If not, see . + +file_type(){ + case ${1##*.} in + html|html) printf 'text/html';; + css) printf 'text/css';; + js) printf 'text/javascript';; + txt) printf 'text/plain';; + sh) printf 'text/shellscript';; + jpg|jpeg) printf 'image/jpeg';; + png) printf 'image/png';; + svg) printf 'image/svg+xml';; + gif) printf 'image/gif';; + webm) printf 'video/webm';; + mp4) printf 'video/mp4';; + ogg) printf 'audio/ogg';; + xml) printf 'application/xml';; + *) printf 'application/octet-stream';; + esac +} + +FILE(){ + unset range file_size file_date http_date cachedate + file="$1" + + if ! [ -f "$file" ]; then + printf 'Status: 404 Not Found\r\n\r\n' + exit 0 + elif ! [ -r "$file" ]; then + printf 'Status: 403 Forbidden\r\n\r\n' + exit 0 + fi + + file_size="$(stat -Lc %s "$file")" + file_date="$(stat -Lc %Y "$file")" + http_date="$(date -uRd @$file_date)" + http_date="${http_date%+0000}GMT" + 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 \ + | sed -r '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 -0 date +%s -ud 2>&- + )" + + range="$(HEADER Range |sed -nr 's;^bytes=([0-9]+-[0-9]*|-[0-9]+)$;\1;p;q;')" + case "$range" in + *-) range="${range}$((file_size - 1))";; + -*) [ ${range#-} -le $file_size ] \ + && range="$((file-size - ${rang#-}))-$((file_size - 1))" \ + || range="0-$((file_size - 1))";; + *-*) [ ${range#*-} -ge $file_size ] \ + && range="${range%-*}-$((file_size - 1))";; + esac + + if [ "$file_date" -lt "$cachedate" ] 2>&-; then + printf '%s: %s\r\n' \ + Status '304 Not Modified' \ + 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 $(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 $(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-Range \*/${file_size} + printf '\r\n' + fi +}