]> git.plutz.net Git - cgilite/blob - file.sh
independent macro extension
[cgilite] / file.sh
1 #!/bin/sh
2
3 # Copyright 2016 - 2023 Paul Hänsch
4
5 # Permission to use, copy, modify, and/or distribute this software for any
6 # purpose with or without fee is hereby granted, provided that the above
7 # copyright notice and this permission notice appear in all copies.
8
9 # THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 # SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15 # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 [ -n "$include_fileserve" ] && return 0
18 include_fileserve="$0"
19
20 file_type(){
21   case ${1##*.} in
22     css)       printf 'text/css';;
23     gif)       printf 'image/gif';;
24     html|html) printf 'text/html';;
25     jpg|jpeg)  printf 'image/jpeg';;
26     js)        printf 'text/javascript';;
27     m3u8)      printf 'application/x-mpegURL';;
28     m4a)       printf 'audio/mp4';;
29     m4s)       printf 'video/iso.segment';;
30     m4v|mp4)   printf 'video/mp4';;
31     mpd)       printf 'application/dash+xml';;
32     ogg)       printf 'audio/ogg';;
33     pdf)       printf 'application/pdf';;
34     png)       printf 'image/png';;
35     sh)        printf 'text/x-shellscript';;
36     svg)       printf 'image/svg+xml';;
37     tex)       printf 'text/x-tex';;
38     txt)       printf 'text/plain';;
39     short)     printf 'text/prs.shorthand';;
40     ts)        printf 'video/MP2T';;
41     webm)      printf 'video/webm';;
42     xml)       printf 'application/xml';;
43     *)         printf 'application/octet-stream';;
44   esac
45 }
46
47 FILE(){
48   local file="$1" mime="$2"
49   local file_size file_date http_date cachedate range
50
51   if ! [ -f "$file" ]; then
52     printf 'Content-Length: 0\r\nStatus: 404 Not Found\r\n\r\n'
53     return 0
54   elif ! [ -r "$file" ]; then
55     printf 'Content-Length: 0\r\nStatus: 403 Forbidden\r\n\r\n'
56     return 0
57   fi
58
59   read file_size file_date <<-EOF
60         $(stat -Lc "%s  %Y" "$file")
61         EOF
62   http_date="$(date -ud "@$file_date" +"%a, %d %b %Y %T GMT")"
63
64   [ ! "$HTTP_IF_MODIFIED_SINCE" -a "$cgilite_headers" ] \
65   &&    HTTP_IF_MODIFIED_SINCE="$(HEADER If-Modified-Since)"
66   [ ! "$HTTP_RANGE"             -a "$cgilite_headers" ] \
67   &&    HTTP_RANGE="$(HEADER Range)"
68
69   cachedate="$(
70     # Parse the allowable date formats from Section 3.3.1 of
71     # https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
72     # HEADER If-Modified-Since \
73     printf %s "$HTTP_IF_MODIFIED_SINCE" \
74     | sed -E 's;^[^ ]+, ([0-9]{2}) (...) ([0-9]{4}) (..:..:..) GMT$;\3-\2-\1 \4;;
75               s;^[^ ]+, ([0-9]{2})-(...)-([789][0-9]) (..:..:..) GMT$;19\3-\2-\1 \4;;
76               s;^[^ ]+, ([0-9]{2})-(...)-([0-6][0-9]) (..:..:..) GMT$;20\3-\2-\1 \4;;
77               s;^[^ ]+ (...) ([0-9]{2}) (..:..:..) ([0-9]{4})$;\4-\1-\2 \3;;
78               s;^[^ ]+ (...)  ([0-9]) (..:..:..) ([0-9]{4})$;\4-\1-\2 \3;;
79               s;Jan;01;; s;Feb;02;; s;Mar;03;; s;Apr;04;; s;May;05;; s;Jun;06;;
80               s;Jul;07;; s;Aug;08;; s;Sep;09;; s;Oct;10;; s;Nov;11;; s;Dec;12;;' \
81     | xargs -r0 date +%s -ud 2>&-
82   )"
83
84   range="${HTTP_RANGE#bytes=}"
85   case "$range" in
86     *[^0-9]*-*|*-*[^0-9]*)
87       range=""
88       ;;
89     *-)
90       range="${range}$((file_size - 1))"
91       ;;
92     -*)
93       [ ${range#-} -le $file_size ] \
94       && range="$((file_size - ${range#-}))-$((file_size - 1))" \
95       || range="0-$((file_size - 1))"
96       ;;
97     *-*)
98       [ ${range#*-} -ge $file_size ] \
99       && range="${range%-*}-$((file_size - 1))"
100       ;;
101     *) range=""
102       ;;
103   esac
104
105   if [ "$file_date" -lt "$cachedate" ] 2>&-; then
106     printf '%s: %s\r\n' \
107       Status '304 Not Modified' \
108       Content-Length 0 \
109       Last-Modified "$http_date"
110     printf '\r\n'
111   
112   elif [ -z "$range" ]; then
113     printf '%s: %s\r\n' \
114       Status "200 OK" \
115       Accept-Ranges bytes \
116       Last-Modified "$http_date" \
117       Content-Type "${mime:-$(file_type "$file")}" \
118       Content-Length $file_size
119     printf '\r\n'
120   
121     [ "$REQUEST_METHOD" != HEAD ] && cat "$file"
122
123   elif [ "${range%-*}" -le "${range#*-}" ]; then
124     printf '%s: %s\r\n' \
125       Status "206 Partial Content" \
126       Accept-Ranges bytes \
127       Last-Modified "$http_date" \
128       Content-Type "${mime:-$(file_type "$file")}" \
129       Content-Range "bytes ${range}/${file_size}" \
130       Content-Length "$((${range#*-} - ${range%-*} + 1))"
131     printf '\r\n'
132   
133     [ "$REQUEST_METHOD" != HEAD ] \
134     && tail -c+$((${range%-*} + 1)) "$file" \
135      | head -c "$((${range#*-} - ${range%-*} + 1))"
136
137   elif [ "${range%-*}" -gt "${range#*-}" ]; then
138     printf '%s: %s\r\n' \
139       Status "216 Range Not Satisfiable" \
140       Content-Length 0 \
141       Content-Range \*/${file_size}
142     printf '\r\n'
143   fi
144 }