Skip to content
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

Content Range support added with unit tests #7

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
48 changes: 47 additions & 1 deletion shellweb
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,24 @@ senderr() {
$sent && return 0

local text
local v=1.0
case $1 in
400) text="Bad request";;
401) text="Unauthorized";;
403) text="Not allowed";;
404) text="Not found";;
416) text="Range Not Satisfiable"; v=1.1;;
*) text="Server error"; set -- 500;;
esac

sent=true
printf "HTTP/1.0 %s %s\r\n" "$1" "$text"
printf "HTTP/%s %s %s\r\n" "$v" "$1" "$text"
printf "Date: %s\r\n" "$(env LC_ALL=en_US.UTF-8 date)"
printf "Connection: close\r\n"
if [[ $1 == 401 ]]; then
printf "WWW-Authenticate: Basic realm=ShellWeb\r\n"
elif [[ $1 == 416 ]]; then
printf "Content-Range: bytes */%u\r\n" "$2"
fi

printf "\r\n"
Expand Down Expand Up @@ -119,6 +123,8 @@ validate_and_send() {
index "$1"
elif [[ "$1" == *.cgi* ]]; then
cgi "$1"
elif [[ $portion == true ]]; then
send_portion "$1"
else
sendfile "$1"
fi
Expand Down Expand Up @@ -186,6 +192,39 @@ sendfile() {
exec 3<&-
}

send_portion() {
$sent && return 0
test -d "$1" && return
local ct=$(file -bi "$root/$1") || { senderr 500; return 0; }
local sz=$(ls -l "$root/$1" | awk '{print $5}') || { senderr 500; return 0; }
local range_from=${range%%-*}
local range_to=${range##*-}
if [ -n "$range_from" ]; then
if [ -z "$range_to" ]; then
range_to=$(($sz - 1))
elif [ "$range_to" -ge "$sz" ]; then
range_to=$(($sz - 1))
fi
else
if [ -z "$range_to" ]; then
senderr 400
return 0
else
range_from=$(($sz - $range_to))
range_to=$(($sz - 1))
fi
fi
test "$range_from" -gt "$range_to" && { senderr 416 "$sz"; return 0; }
sent=true
local length=$(( $range_to - $range_from + 1))
printf "HTTP/1.1 206 Partial Content\r\n"
printf "Content-Range: bytes %s\r\n" "${range_from}-${range_to}/$sz"
printf "Content-Length: %u\r\n" "$length"
printf "Content-Type: %s\r\n" "$ct"
printf "\r\n"
dd skip="$range_from" count="$length" if="$root/$1" bs=1 status=none
}

html_escape() {
echo "$*" | sed -E \
-e 's/&/\&amp;/g;' \
Expand Down Expand Up @@ -332,6 +371,8 @@ while true; do
$proxy_mode && proxy
auth_passed=false
keep_conn=false
portion=false
range=
while ! $proxy_mode && ! $sent && read -p v1 v2 v3; do
header=$(echo "$v1" | tr A-Z a-z)
if [ -z "$file" ]; then
Expand Down Expand Up @@ -365,9 +406,14 @@ while true; do
fi
file=
auth_passed=false
portion=false
if $keep_conn; then
sent=false
fi
elif [[ $header == "range:" ]]; then
$verbose && echo "Range $v2 from $file requested" >&2
range=$(echo "${v2##bytes=}" | tr -d '\r')
portion=true
# else ignore all headers
fi
done >&p
Expand Down
22 changes: 22 additions & 0 deletions tests/test-416
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/ksh
#check for 416 response

. ${0%/*}/common

link="./tests/home.html"
bytes_request="25-14"
while getopts "l:r:" OPT; do case $OPT in
l) link=$OPTARG;;
r) byte_request=$OPTARG;;
esac; done
shift $((OPTIND - 1))
run_shellweb
request_file=$(mktemp -p $www)
cp "$link" "$request_file"
response=$(
{ printf "GET ${request_file#$www} HTTP/1.1\r\n"
printf "Range: bytes="$bytes_request"\r\n\r\n"
} | nc -w 1 127.0.0.1 $port | head -n 1 | tr -d '\r'
)
check="HTTP/1.1 416 Range Not Satisfiable"
test x"$response" = x"$check" || fail "$response"
59 changes: 59 additions & 0 deletions tests/test-part
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/ksh

. ${0%/*}/common
check_portion() {
local first_byte="${2%%-*}"
local last_byte="${2##*-}"
local sz=$(ls -l "$1" | awk '{print $5}')
local length

if [ -n "$first_byte" ]; then
if [ -z "$last_byte" ]; then
last_byte=$(($sz - 1))
elif [ "$last_byte" -ge "$sz" ]; then
last_byte=$(($sz - 1))
fi
else
if [ -z "$last_byte" ]; then
echo "Incorrect request"
return 0
else
first_byte=$(($sz - $last_byte))
last_byte=$(($sz - 1))
fi
fi
length=$(($last_byte - $first_byte + 1))

local request_file=$(mktemp -p $www)
cp "$1" "$request_file"
local part_file=$(mktemp -p $tmpdir) || { fail "mktemp fail"; return 0; }
dd skip="$first_byte" count="$length" if="$1" bs=1 status=none > "$part_file" || { fail "incorrect range"; return 0; }
local response=$(mktemp -p $tmpdir)
{ printf "GET ${request_file#$www} HTTP/1.1\r\n"
printf "Range: bytes=$2\r\n\r\n"
} | nc -w 1 127.0.0.1 $port | tr -d '\r' >> "$response"
local first_header=$(head -n 1 $response)
local check="HTTP/1.1 206 Partial Content"
test x"$first_header" = x"$check" || { fail "incorrect response header"; return 0; }
response_range=$(awk '/Content-Range:/{print $NF}' "$response")
test "${response_range%%/*}" = "${first_byte}-${last_byte}" || { fail "incorrect range"; return 0; }
sed -i '1,/^$/ d' "$response"
cmp -s "$part_file" "$response" || { fail "files don't match"; return 0; }
echo "Requested $2 bytes of $file: success"
}


file="./tests/home.html"
byte_request="25-30 26- -35"

while getopts "f:r:" OPT; do case $OPT in
f) file=$OPTARG;;
r) byte_request="$OPTARG";;
esac; done
shift $((OPTIND - 1))

run_shellweb
for t in $byte_request; do
echo "Bytes from $file requested: $t"
check_portion "$file" $t || fail
done