#! /usr/bin/env python | |
import sys,os,subprocess | |
from stat import * | |
#!/usr/bin/env bash | |
# | |
# A simple, configurable HTTP server written in bash. | |
# | |
# See LICENSE for licensing information. | |
# | |
# Original author: Avleen Vig, 2012 | |
# Reworked by: Josh Cartwright, 2012 | |
warn() { echo "WARNING: $@" >&2; } | |
# | |
# A simple, configurable HTTP server written in bash. | |
# | |
# See LICENSE for licensing information. | |
# | |
# Original author: Avleen Vig, 2012 | |
# Reworked by: Josh Cartwright, 2012 | |
def warn () : print("WARNING: "+str('"'+"\" \"".join(sys.argv[1:])+'"')) | |
[ -r bashttpd.conf ] || { cat >bashttpd.conf <<'EOF' # # bashttpd.conf - configuration for bashttpd # # The behavior of bashttpd is dictated by the evaluation # of rules specified in this configuration file. Each rule # is evaluated until one is matched. If no rule is matched, # bashttpd will serve a 500 Internal Server Error. # # The format of the rules are: # on_uri_match REGEX command [args] # unconditionally command [args] # # on_uri_match: # On an incoming request, the URI is checked against the specified # (bash-supported extended) regular expression, and if encounters a match the # specified command is executed with the specified arguments. # # For additional flexibility, on_uri_match will also pass the results of the # regular expression match, ${BASH_REMATCH[@]} as additional arguments to the # command. # # unconditionally: # Always serve via the specified command. Useful for catchall rules. # # The following commands are available for use: # # serve_file FILE # Statically serves a single file. # # serve_dir_with_tree DIRECTORY # Statically serves the specified directory using 'tree'. It must be # installed and in the PATH. # # serve_dir_with_ls DIRECTORY # Statically serves the specified directory using 'ls -al'. # # serve_dir DIRECTORY # Statically serves a single directory listing. Will use 'tree' if it is # installed and in the PATH, otherwise, 'ls -al' # # serve_dir_or_file_from DIRECTORY # Serves either a directory listing (using serve_dir) or a file (using # serve_file). Constructs local path by appending the specified root # directory, and the URI portion of the client request. # # serve_static_string STRING # Serves the specified static string with Content-Type text/plain. # # Examples of rules: # # on_uri_match '^/issue$' serve_file "/etc/issue" # # When a client's requested URI matches the string '/issue', serve them the # contents of /etc/issue # # on_uri_match 'root' serve_dir / # # When a client's requested URI has the word 'root' in it, serve up # a directory listing of / # # DOCROOT=/var/www/html # on_uri_match '/(.*)' serve_dir_or_file_from "$DOCROOT" # When any URI request is made, attempt to serve a directory listing # or file content based on the request URI, by mapping URI's to local # paths relative to the specified "$DOCROOT" # unconditionally serve_static_string 'Hello, world! You can configure bashttpd by modifying bashttpd.conf.' # More about commands: # # It is possible to somewhat easily write your own commands. An example # may help. The following example will serve "Hello, $x!" whenever # a client sends a request with the URI /say_hello_to/$x: # # serve_hello() { # add_response_header "Content-Type" "text/plain" # send_response_ok_exit <<< "Hello, $2!" # } # on_uri_match '^/say_hello_to/(.*)$' serve_hello # # Like mentioned before, the contents of ${BASH_REMATCH[@]} are passed # to your command, so its possible to use regular expression groups # to pull out info. # # With this example, when the requested URI is /say_hello_to/Josh, serve_hello # is invoked with the arguments '/say_hello_to/Josh' 'Josh', # (${BASH_REMATCH[0]} is always the full match) EOF warn "Created bashttpd.conf using defaults. Please review it/configure before running bashttpd again." exit 1 } | |
"-r" "bashttpd.conf" or { _rc = subprocess.Popen("cat",shell=True,stdout=file('bashttpd.conf','wb'),stdin=subprocess.PIPE) _rc.communicate("""# # bashttpd.conf - configuration for bashttpd # # The behavior of bashttpd is dictated by the evaluation # of rules specified in this configuration file. Each rule # is evaluated until one is matched. If no rule is matched, # bashttpd will serve a 500 Internal Server Error. # # The format of the rules are: # on_uri_match REGEX command [args] # unconditionally command [args] # # on_uri_match: # On an incoming request, the URI is checked against the specified # (bash-supported extended) regular expression, and if encounters a match the # specified command is executed with the specified arguments. # # For additional flexibility, on_uri_match will also pass the results of the # regular expression match, ${BASH_REMATCH[@]} as additional arguments to the # command. # # unconditionally: # Always serve via the specified command. Useful for catchall rules. # # The following commands are available for use: # # serve_file FILE # Statically serves a single file. # # serve_dir_with_tree DIRECTORY # Statically serves the specified directory using 'tree'. It must be # installed and in the PATH. # # serve_dir_with_ls DIRECTORY # Statically serves the specified directory using 'ls -al'. # # serve_dir DIRECTORY # Statically serves a single directory listing. Will use 'tree' if it is # installed and in the PATH, otherwise, 'ls -al' # # serve_dir_or_file_from DIRECTORY # Serves either a directory listing (using serve_dir) or a file (using # serve_file). Constructs local path by appending the specified root # directory, and the URI portion of the client request. # # serve_static_string STRING # Serves the specified static string with Content-Type text/plain. # # Examples of rules: # # on_uri_match '^/issue$' serve_file "/etc/issue" # # When a client's requested URI matches the string '/issue', serve them the # contents of /etc/issue # # on_uri_match 'root' serve_dir / # # When a client's requested URI has the word 'root' in it, serve up # a directory listing of / # # DOCROOT=/var/www/html # on_uri_match '/(.*)' serve_dir_or_file_from "$DOCROOT" # When any URI request is made, attempt to serve a directory listing # or file content based on the request URI, by mapping URI's to local # paths relative to the specified "$DOCROOT" # unconditionally serve_static_string 'Hello, world! You can configure bashttpd by modifying bashttpd.conf.' # More about commands: # # It is possible to somewhat easily write your own commands. An example # may help. The following example will serve "Hello, $x!" whenever # a client sends a request with the URI /say_hello_to/$x: # # serve_hello() { # add_response_header "Content-Type" "text/plain" # send_response_ok_exit <<< "Hello, $2!" # } # on_uri_match '^/say_hello_to/(.*)$' serve_hello # # Like mentioned before, the contents of ${BASH_REMATCH[@]} are passed # to your command, so its possible to use regular expression groups # to pull out info. # # With this example, when the requested URI is /say_hello_to/Josh, serve_hello # is invoked with the arguments '/say_hello_to/Josh' 'Josh', # (${BASH_REMATCH[0]} is always the full match) """) warn() exit(1) } | |
recv() { echo "< $@" >&2; } | |
def recv () : print("< "+str('"'+"\" \"".join(sys.argv[1:])+'"')) | |
send() { echo "> $@" >&2; printf '%s\r\n' "$*"; } | |
def send () : print("> "+str('"'+"\" \"".join(sys.argv[1:])+'"')) print( r'%s\r\n' % (str(" ".join(sys.argv[1:]))) ) | |
[[ $UID = 0 ]] && warn "It is not recommended to run bashttpd as root." | |
UID == 0 and warn() | |
DATE=$(date +"%a, %d %b %Y %H:%M:%S %Z") | |
DATE=os.popen("date +\\\"%a, %d %b %Y %H:%M:%S %Z\\\"").read() | |
declare -a RESPONSE_HEADERS=( "Date: $DATE" "Expires: $DATE" "Server: Slash Bin Slash Bash" ) | |
_rc = subprocess.call(["declare",and,RESPONSE_HEADERS=("Date: "+str(DATE)+" Expires: "+str(DATE)+" Server: Slash Bin Slash Bash"+)]) | |
add_response_header() { RESPONSE_HEADERS+=("$1: $2") } | |
def add_response_header () : "RESPONSE_HEADERS+="+(str(sys.argv[1])+": "+str(sys.argv[2])+) | |
declare -a HTTP_RESPONSE=( [200]="OK" [400]="Bad Request" [403]="Forbidden" [404]="Not Found" [405]="Method Not Allowed" [500]="Internal Server Error" ) | |
_rc = subprocess.call(["declare",and,HTTP_RESPONSE=([200]="OK "+[400]="Bad Request "+[403]="Forbidden "+[404]="Not Found "+[405]="Method Not Allowed "+[500]="Internal Server Error"+)]) | |
send_response() { local code=$1 send "HTTP/1.0 $1 ${HTTP_RESPONSE[$1]}" for i in "${RESPONSE_HEADERS[@]}"; do send "$i" done send while read -r line; do send "$line" done } | |
def send_response () : global RESPONSE_HEADERS code=str(sys.argv[1]) send() for i in [RESPONSE_HEADERS[@]]: send() send() while (-r = raw_input()): send() | |
send_response_ok_exit() { send_response 200; exit 0; } | |
def send_response_ok_exit () : send_response() exit(0) | |
fail_with() { send_response "$1" <<< "$1 ${HTTP_RESPONSE[$1]}" exit 1 } | |
def fail_with () : send_response() exit(1) | |
serve_file() { local file=$1 read -r CONTENT_TYPE < <(file -b --mime-type "$file") && \ add_response_header "Content-Type" "$CONTENT_TYPE" read -r CONTENT_LENGTH < <(stat -c'%s' "$file") && \ add_response_header "Content-Length" "$CONTENT_LENGTH" send_response_ok_exit < "$file" } | |
def serve_file () : file=str(sys.argv[1]) -r = raw_input() and add_response_header() -r = raw_input() and add_response_header() send_response_ok_exit() | |
serve_dir_with_tree() { local dir="$1" tree_vers tree_opts basehref x add_response_header "Content-Type" "text/html" # The --du option was added in 1.6.0. read x tree_vers x < <(tree --version) [[ $tree_vers == v1.6* ]] && tree_opts="--du" send_response_ok_exit < \ <(tree -H "$2" -L 1 "$tree_opts" -D "$dir") } | |
def serve_dir_with_tree () : global tree_vers global tree_opts dir=str(sys.argv[1]) "tree_vers" "tree_opts" "basehref" "x" add_response_header() # The --du option was added in 1.6.0. | |
x = raw_input() tree_vers == "v1.6*" and tree_opts="--du" send_response_ok_exit() | |
serve_dir_with_ls() { local dir=$1 add_response_header "Content-Type" "text/plain" send_response_ok_exit < \ <(ls -la "$dir") } | |
def serve_dir_with_ls () : dir=str(sys.argv[1]) add_response_header() send_response_ok_exit() | |
serve_dir() { local dir=$1 # If `tree` is installed, use that for pretty output. which tree &>/dev/null && \ serve_dir_with_tree "$@" serve_dir_with_ls "$@" fail_with 500 } | |
def serve_dir () : dir=str(sys.argv[1]) # If `tree` is installed, use that for pretty output. | |
_rc = subprocess.Popen("which" + " " + "tree",shell=True,stderr=file('/dev/null','wb'),stdout=file('/dev/null','wb')) and serve_dir_with_tree() serve_dir_with_ls() fail_with() | |
serve_dir_or_file_from() { local URL_PATH=$1/$3 shift # sanitize URL_PATH URL_PATH=${URL_PATH//[^a-zA-Z0-9_~\-\.\/]/} [[ $URL_PATH == *..* ]] && fail_with 400 # Serve index file if exists in requested directory [[ -d $URL_PATH && -f $URL_PATH/index.html && -r $URL_PATH/index.html ]] && \ URL_PATH="$URL_PATH/index.html" if [[ -f $URL_PATH ]]; then [[ -r $URL_PATH ]] && \ serve_file "$URL_PATH" "$@" || fail_with 403 elif [[ -d $URL_PATH ]]; then [[ -x $URL_PATH ]] && \ serve_dir "$URL_PATH" "$@" || fail_with 403 fi fail_with 404 } | |
def serve_dir_or_file_from () : URL_PATH=str(sys.argv[1])+"/"+str(sys.argv[3]) _rc = subprocess.call(["shift"]) # sanitize URL_PATH | |
URL_PATH=str(URL_PATH//[^a-zA-Z0-9_~\-\.\/]/) URL_PATH == "*..*" and fail_with() # Serve index file if exists in requested directory | |
S_ISDIR(os.stat(URL_PATH).st_mode) and (os.path.isfile(URL_PATH+"/index.html") and S_ISREG(os.stat(URL_PATH+"/index.html").st_mode)) and -r URL_PATH+"/index.html" and URL_PATH=str(URL_PATH)+"/index.html" if ((os.path.isfile(URL_PATH) and S_ISREG(os.stat(URL_PATH).st_mode)) ): -r URL_PATH and serve_file() or fail_with() elif (S_ISDIR(os.stat(URL_PATH).st_mode) ): -x URL_PATH and serve_dir() or fail_with() fail_with() | |
serve_static_string() { add_response_header "Content-Type" "text/plain" send_response_ok_exit <<< "$1" } | |
def serve_static_string () : add_response_header() send_response_ok_exit() | |
on_uri_match() { local regex=$1 shift [[ $REQUEST_URI =~ $regex ]] && \ "$@" "${BASH_REMATCH[@]}" } | |
def on_uri_match () : global REQUEST_URI global BASH_REMATCH regex=str(sys.argv[1]) _rc = subprocess.call(["shift"]) REQUEST_URI =~ regex and _rc = subprocess.call([str('"'+"\" \"".join(sys.argv[1:])+'"'),str(BASH_REMATCH[@])]) | |
unconditionally() { "$@" "$REQUEST_URI" } | |
def unconditionally () : global REQUEST_URI _rc = subprocess.call([str('"'+"\" \"".join(sys.argv[1:])+'"'),str(REQUEST_URI)]) | |
# Request-Line HTTP RFC 2616 $5.1 | |
read -r line || fail_with 400 | |
# Request-Line HTTP RFC 2616 $5.1 | |
-r = raw_input() or fail_with() | |
# strip trailing CR if it exists | |
line=${line%%$'\r'} | |
# strip trailing CR if it exists | |
line=str(line%%' ') | |
recv "$line" | |
recv() | |
read -r REQUEST_METHOD REQUEST_URI REQUEST_HTTP_VERSION <<<"$line" | |
-r = raw_input() | |
[ -n "$REQUEST_METHOD" ] && \ [ -n "$REQUEST_URI" ] && \ [ -n "$REQUEST_HTTP_VERSION" ] \ || fail_with 400 | |
(str(REQUEST_METHOD) != '') and (str(REQUEST_URI) != '') and (str(REQUEST_HTTP_VERSION) != '') or fail_with() | |
# Only GET is supported at this time | |
[ "$REQUEST_METHOD" = "GET" ] || fail_with 405 | |
# Only GET is supported at this time | |
REQUEST_METHOD == "GET" or fail_with() | |
declare -a REQUEST_HEADERS | |
_rc = subprocess.call(["declare",and,"REQUEST_HEADERS"]) | |
while read -r line; do line=${line%%$'\r'} recv "$line" # If we've reached the end of the headers, break. [ -z "$line" ] && break REQUEST_HEADERS+=("$line") done | |
while (-r = raw_input()): line=str(line%%' ') recv() # If we've reached the end of the headers, break. | |
('"$line"' not in globals()) and break "REQUEST_HEADERS+="+(str(line)+) | |
source bashttpd.conf | |
_rc = subprocess.call(["source","bashttpd.conf"]) | |
fail_with 500 | |
fail_with() | |
ÿ |