Quick and dirty web serveur with bash

Did you ever want to share some files with someone, or with another device (a smartphone for instance…), in a situation where convenient means of achieving this (USB flash drive, network share…) were not available? Would it not have been nice at the time to have an instant-on web server that would have shared the current directory?

If these files are hosted on a Unix- or Linux-like machine, with both the bash shell and the socat command available, then a simple shell script a few lines long can do the job!

Cet article existe aussi en français.

In its simplest form, if you accept that you will have to enter all URLs in full, the minimal script below is enough:

#!/bin/bash
dec() { eval printf %s \$\'$(sed 's#'\''#%27#g;s#\\#\\\\#g;s#%#\\x#g')\'; }

if [ "$1" != '-' ]; then
  self="$(cd "$(dirname "$0")" &>/dev/null && pwd)/$(basename "$0")"
  cd "${1:-.}" && exec socat TCP-LISTEN:${2:-8080},reuseaddr,fork exec:"$self -"
fi

request=".$(sed -n '/^GET /{s/^GET \(.*\) HTTP.*/\1/p;q}' | dec)"
printf 'HTTP/1.0 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: %d\r\n\r\n' "$(stat -c %s "$request")"
cat "$request"

When launching this script, you may optionally give as parameters the path to share (current directory by default) and the port (8080 by default).

For example, in order to download the ~/.bash_profile file from another device, you could launch the above script without any parameter from the home directory, and then type this address on that other device: http://your_pc:8080/.bash_profile.

As far as I am concerned, since I will rather keep this script around for when it is needed, and only rarely type it from memory, I prefer the longer version, that allows me to browse the filesystem:

#!/bin/bash
dec() { eval printf %s \$\'$(sed 's#'\''#%27#g;s#\\#\\\\#g;s#%#\\x#g')\'; }
enc() { sed 's#&#\&amp;#g;s#<#\&lt;#g;s#>#\&gt;#g;s#\"#\&quot;#g' <<<"$1"; }

if [ "$1" != '-' ]; then
  self="$(cd "$(dirname "$0")" &>/dev/null && pwd)/$(basename "$0")"
  cd "${1:-.}" && exec socat TCP-LISTEN:${2:-8080},reuseaddr,fork exec:"$self -" || exit 1
fi

request=".$(sed -n '/^GET /{s/^GET \(.*\) HTTP.*/\1/p;q}' | dec)"
order=$(grep -o '\?[0-9][nd]$' <<<"$request")
order=${order:1}
request="${request%\?[0-9]?}"
if [ ! -e "$request" ]; then
  status='404 Not Found'
elif [ ! -r "$request" ]; then
  status='403 Forbidden'
elif [ -f "$request" ]; then
  status='200 OK'
  type=application/octet-stream
  size=$(stat -c %s "$request")
elif [ -d "$request" ]; then
  status='200 OK'
  type=text/html
  output=$(cat <<-END
    <!doctype html>
    <html><head>
     <meta charset="utf-8">
     <title>$(enc "${request:1}")</title>
    </head><body><table><thead>
     <th scope=col><a href="?1d">Type</a></th><th scope=col><a href="?2d">Mode</a></th>
     <th scope=col><a href="?3d">User</a></th><th scope=col><a href="?4d">Group</a></th>
     <th scope=col><a href="?5n">Bytes</a></th><th scope=col><a href="?6d">Last modification</a></th>
     <th scope=col><a href="?7d">Name</a></th>
    </thead><tbody>
    $(
      find "$PWD/${request:2}" -maxdepth 1 -printf '%Y\t%M\t%u\t%g\t%s\t%T+\t%P\n' \
      | sort -t$'\t' -k${order:-7d} | while IFS=$'\t' read t m u g s d n; do
        [ -n "$n" ] && l="${request%/}/$n" || { l="${request%/?*}/"; m=; u=; g=; s=; d=; }
        printf ' <tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td><a href="%s">%s</a></td></tr>\n' \
          "$t" "$m" "$(enc "$u")" "$(enc "$g")" "$s" "$d" "$(enc "${l:1}${order:+?$order}")" "$(enc "${n:-..}")"
      done
    )
    </tbody></table></body></html>
    END
  )
else
  status='403 Forbidden'
fi
type=${type:-text/plain}
[ -z "$size" ] && output=${output:-$status ${request:1}}
size=${size:-${#output}}
printf 'HTTP/1.0 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n%s' "$status" "$type" "$size" "$output"
[ -z "$output" ] && cat "$request"

This script’s usage is the same as the previous one’s. However, it allows you to see the details of a directory, browse the filesystem, and download files just by clicking on them. Besides, this script allows you to sort the displayed lines (on the name, or the size…). Finally, it handles error cases (forbidden access, file not found).

I am aware, that the coding style is a bit ugly (complex, with no explanations…). I wanted this script to be (too) short. If you have questions about its inner workings, then please write them in the comments!

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

La discussion continue ailleurs

URL de rétrolien : http://yalis.fr/cms/index.php/trackback/57

Fil des commentaires de ce billet