Skip to content

Fixed support for binary files #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 135 additions & 89 deletions bashttpd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
# Original author: Avleen Vig, 2012
# Reworked by: Josh Cartwright, 2012

warn() { echo "WARNING: $@" >&2; }
warn() {
echo "WARNING: $@" >&2
}

[ -r bashttpd.conf ] || {
cat >bashttpd.conf <<'EOF'
Expand Down Expand Up @@ -71,6 +73,10 @@ warn() { echo "WARNING: $@" >&2; }
# When a client's requested URI has the word 'root' in it, serve up
# a directory listing of /
#
# on_uri_match '^/downloads+/*(.*)' serve_dir_or_file_from "/path/to/Downloads"
#
# When requesting /downloads show tree view of dir, when selecting file delivering it
#
# 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
Expand Down Expand Up @@ -100,140 +106,179 @@ unconditionally serve_static_string 'Hello, world! You can configure bashttpd b
# 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

warn "Created bashttpd.conf using defaults. Please review it/configure before running bashttpd again."
exit 1
}

recv() { echo "< $@" >&2; }
send() { echo "> $@" >&2;
printf '%s\r\n' "$*"; }
recv() {
echo "< $@" >&2
}

send() {
echo "> $@" >&2
printf '%s\r\n' "$*"
}

[[ $UID = 0 ]] && warn "It is not recommended to run bashttpd as root."

DATE=$(date +"%a, %d %b %Y %H:%M:%S %Z")

declare -a RESPONSE_HEADERS=(
"Date: $DATE"
"Expires: $DATE"
"Server: Slash Bin Slash Bash"
"Date: $DATE"
"Expires: $DATE"
"Server: Slash Bin Slash Bash"
)

add_response_header() {
RESPONSE_HEADERS+=("$1: $2")
RESPONSE_HEADERS+=("$1: $2")
}

declare -a HTTP_RESPONSE=(
[200]="OK"
[400]="Bad Request"
[403]="Forbidden"
[404]="Not Found"
[405]="Method Not Allowed"
[500]="Internal Server Error"
[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
local code=$1
send "HTTP/1.1 $1 ${HTTP_RESPONSE[$1]}"
for i in "${RESPONSE_HEADERS[@]}"
do
send "$i"
done
send

while read -r line
do
send "$line"
done
}

send_response_ok_exit() {
send_response 200
exit 0
}

send_response_ok_exit() { send_response 200; exit 0; }
send_fileresponse() {
local code=$1
send "HTTP/1.1 $1 ${HTTP_RESPONSE[$1]}"
for i in "${RESPONSE_HEADERS[@]}"
do
send "$i"
done
send
}

send_fileresponse_ok_exit() {
send_fileresponse 200
cat "$1"
exit 0
}

fail_with() {
send_response "$1" <<< "$1 ${HTTP_RESPONSE[$1]}"
exit 1
send_response "$1" <<< "$1 ${HTTP_RESPONSE[$1]}"
exit 1
}

serve_file() {
local file=$1
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"
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"
send_fileresponse_ok_exit "$file"
}

serve_dir_with_tree()
{
local dir="$1" tree_vers tree_opts basehref x
serve_dir_with_tree() {
local dir="${1%/}"
local tree_vers=""
local tree_opts=""
local basehref="${2%/}"
local x=""

add_response_header "Content-Type" "text/html; charset=UTF-8"

add_response_header "Content-Type" "text/html"
# The --du option was added in 1.6.0.
read x tree_vers x < <(tree --version)
if [[ $tree_vers == v1.6* ]]
then
send_response_ok_exit < \
<(tree -C -H "$basehref" --du -L 1 -D "$dir")

# The --du option was added in 1.6.0.
read x tree_vers x < <(tree --version)
[[ $tree_vers == v1.6* ]] && tree_opts="--du"
else
send_response_ok_exit < \
<(tree -C -H "$basehref" -L 1 -D "$dir")

send_response_ok_exit < \
<(tree -H "$2" -L 1 "$tree_opts" -D "$dir")
fi
}

serve_dir_with_ls()
{
local dir=$1
serve_dir_with_ls() {
local dir=$1

add_response_header "Content-Type" "text/plain"
add_response_header "Content-Type" "text/plain; charset=UTF-8"

send_response_ok_exit < \
<(ls -la "$dir")
send_response_ok_exit < \
<(ls -la "$dir")
}

serve_dir() {
local dir=$1
local dir=$1

# If `tree` is installed, use that for pretty output.
which tree &>/dev/null && \
serve_dir_with_tree "$@"
# If `tree` is installed, use that for pretty output.
which tree &>/dev/null && \
serve_dir_with_tree "$@"

serve_dir_with_ls "$@"
serve_dir_with_ls "$@"

fail_with 500
fail_with 500
}

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
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
}

serve_static_string() {
add_response_header "Content-Type" "text/plain"
send_response_ok_exit <<< "$1"
add_response_header "Content-Type" "text/plain; charset=UTF-8"
send_response_ok_exit <<< "$1"
}

on_uri_match() {
local regex=$1
shift
local regex=$1
shift

[[ $REQUEST_URI =~ $regex ]] && \
"$@" "${BASH_REMATCH[@]}"
[[ $REQUEST_URI =~ $regex ]] && \
"$@" "${BASH_REMATCH[@]}"
}

unconditionally() {
"$@" "$REQUEST_URI"
"$@" "$REQUEST_URI"
}

# Request-Line HTTP RFC 2616 $5.1
Expand All @@ -248,21 +293,22 @@ read -r REQUEST_METHOD REQUEST_URI REQUEST_HTTP_VERSION <<<"$line"
[ -n "$REQUEST_METHOD" ] && \
[ -n "$REQUEST_URI" ] && \
[ -n "$REQUEST_HTTP_VERSION" ] \
|| fail_with 400
|| fail_with 400

# Only GET is supported at this time
[ "$REQUEST_METHOD" = "GET" ] || fail_with 405

declare -a REQUEST_HEADERS

while read -r line; do
line=${line%%$'\r'}
recv "$line"
while read -r line
do
line=${line%%$'\r'}
recv "$line"

# If we've reached the end of the headers, break.
[ -z "$line" ] && break
# If we've reached the end of the headers, break.
[ -z "$line" ] && break

REQUEST_HEADERS+=("$line")
REQUEST_HEADERS+=("$line")
done

source bashttpd.conf
Expand Down