]> Sergey Matveev's repositories - zws.git/blob - zws
456169dff3f04bda9653bc3ad5586f77b84d35ca2360ff781ee28bf408e31346
[zws.git] / zws
1 #!/usr/bin/env zsh
2 # zws -- Z Shell-based Web server
3 # Copyright (C) 2024 Sergey Matveev <stargrave@stargrave.org>
4 # Serves static files, directory listings and MIME types
5 # Usage: tcpserver -DHR -l 0 ::0 80 ./zws
6
7 setopt ERR_EXIT PIPE_FAIL
8
9 while read l ; do
10     local cols=(${=l})
11     [[ ${#cols} -ge 2 ]] || break
12     [[ $cols && ${cols[1]} = GET ]] || continue
13     REQPATH=${cols[2]}
14 done
15
16 doreply() {
17     local ct=$1
18     [[ -n $ct ]]
19     local code=${2:-200 OK}
20     print "HTTP/1.1 $code\r"
21     print "Server: zws/0.1.0\r"
22     print "Connection: close\r"
23     print "Cache-Control: no-cache\r"
24     print "Content-Type: $ct\r"
25     print "\r"
26 }
27
28 notfound() {
29     doreply text/plain "404 NOT FOUND"
30     exit 0
31 }
32
33 REQPATH=(${(s/?/)REQPATH})
34 REQQUERY=${REQPATH[2]}
35 REQPATH=${REQPATH[1]}
36 [[ $REQPATH =~ "/\.\./" ]] && notfound
37
38 REQPATH=${REQPATH#/}
39 if [[ (-z $REQPATH) || ($REQPATH =~ "/$") ]] ; then
40     [[ (-n $REQPATH) && (! -d $REQPATH) ]] && notfound || :
41     doreply text/html
42     cat <<EOF
43 <!DOCTYPE html>
44 <html><head>
45     <title>$REQPATH</title>
46     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
47 </head><body>
48 <table border=1>
49 <tr><th>&nbsp;</th><th>name</th><th>mtime</th><th>size</th></tr>
50 EOF
51     [[ -z $REQPATH ]] || cd $REQPATH
52     ctr=0
53     zmodload -F zsh/stat b:zstat
54     zmodload -F zsh/datetime b:strftime
55     for i (*(onN)) {
56         zstat -A mtimeSec +mtime $i
57         strftime -s mtime "%Y-%m-%d %H:%M:%S" $mtimeSec
58         zstat -A size +size $i
59         [[ ! -d $i ]] || i="$i/"
60         print "<tr><td>$ctr</td><td><a href=\"$i\">$i</a></td>"
61         print "<td>$mtime</td><td>$size</td></tr>"
62         ctr=$(( $ctr + 1 ))
63     }
64     print "</table></body></html>"
65     exit 0
66 fi
67
68 [[ -r $REQPATH ]] || notfound
69
70 ct=application/octet-stream
71 case $REQPATH:e in
72 txt) ct=text/plain ;;
73 html) ct=text/html ;;
74 esac
75
76 doreply $ct
77 _cat=cat
78 [[ $REQQUERY =~ "tai64nlocal=1" ]] && _cat=tai64nlocal
79 $_cat < $REQPATH