#!/usr/bin/env zsh # zws -- Z Shell-based Web server # Copyright (C) 2024 Sergey Matveev # Serves static files, directory listings and MIME types # Usage: tcpserver -DHR -l 0 ::0 80 ./zws setopt ERR_EXIT PIPE_FAIL while read l ; do local cols=(${=l}) [[ ${#cols} -ge 2 ]] || break [[ $cols && ${cols[1]} = GET ]] || continue REQPATH=${cols[2]} done doreply() { local ct=$1 [[ -n $ct ]] local code=${2:-200 OK} print "HTTP/1.1 $code\r" print "Server: zws/0.1.0\r" print "Connection: close\r" print "Cache-Control: no-cache\r" print "Content-Type: $ct\r" print "\r" } notfound() { doreply text/plain "404 NOT FOUND" exit 0 } REQPATH=(${(s/?/)REQPATH}) REQQUERY=${REQPATH[2]} REQPATH=${REQPATH[1]} [[ $REQPATH =~ "/\.\./" ]] && notfound REQPATH=${REQPATH#/} if [[ (-z $REQPATH) || ($REQPATH =~ "/$") ]] ; then [[ (-n $REQPATH) && (! -d $REQPATH) ]] && notfound || : doreply text/html cat < $REQPATH EOF [[ -z $REQPATH ]] || cd $REQPATH ctr=0 zmodload -F zsh/stat b:zstat zmodload -F zsh/datetime b:strftime for i (*(onN)) { zstat -A mtimeSec +mtime $i strftime -s mtime "%Y-%m-%d %H:%M:%S" $mtimeSec zstat -A size +size $i [[ ! -d $i ]] || i="$i/" print "" print "" ctr=$(( $ctr + 1 )) } print "
 namemtimesize
$ctr$i$mtime$size
" exit 0 fi [[ -r $REQPATH ]] || notfound ct=application/octet-stream case $REQPATH:e in txt) ct=text/plain ;; html) ct=text/html ;; esac doreply $ct _cat=cat [[ $REQQUERY =~ "tai64nlocal=1" ]] && _cat=tai64nlocal $_cat < $REQPATH