From 13c763fd654c75b86b9d57c54c56a357cffc6d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9A=D0=B8=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 26 Mar 2020 21:00:59 +0100 Subject: [PATCH 1/5] partial requests support added --- shellweb | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/shellweb b/shellweb index 379bbd2..6f121ca 100755 --- a/shellweb +++ b/shellweb @@ -26,13 +26,13 @@ while getopts "a:d:p:v" OPT; do case $OPT in *) usage;; esac; done shift $((OPTIND - 1)) - +if [ -n "$authfile" ]; then authfile=$(readlink -f -- "$authfile") if ! [ -r "$authfile" -a -f "$authfile" ]; then echo "could not read file '$authfile'" >&2 exit 1 fi - +fi $verbose && echo "root directory is $root" >&2 decode_base64() { @@ -61,20 +61,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" @@ -110,6 +114,8 @@ validate_and_send() { index "$1" elif [[ "$1" == *.cgi* ]]; then cgi "$1" + elif [[ $portion == true ]]; then + send_portion "$1" else sendfile "$1" fi @@ -177,6 +183,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/&/\&/g;' \ @@ -304,6 +343,8 @@ while true; do ncpid=$! auth_passed=false keep_conn=false + portion=false + range= while ! $sent && read -p v1 v2 v3; do header=$(echo "$v1" | tr A-Z a-z) if [ -z "$file" ]; then @@ -340,6 +381,10 @@ while true; do 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 From f55ca377c5175f8238e4cf7a3d0b3184aecae416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9A=D0=B8=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 26 Mar 2020 21:10:10 +0100 Subject: [PATCH 2/5] portion=false fixed --- shellweb | 1 + 1 file changed, 1 insertion(+) diff --git a/shellweb b/shellweb index 6f121ca..fc4bf65 100755 --- a/shellweb +++ b/shellweb @@ -380,6 +380,7 @@ while true; do auth_passed=false if $keep_conn; then sent=false + portion=false fi elif [[ $header == "range:" ]]; then $verbose && echo "Range $v2 from $file requested" >&2 From da151915ddc7ff28153db3bcf4afb7cc65db749c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9A=D0=B8=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 26 Mar 2020 21:21:27 +0100 Subject: [PATCH 3/5] auth fixed --- shellweb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shellweb b/shellweb index fc4bf65..c882663 100755 --- a/shellweb +++ b/shellweb @@ -26,13 +26,13 @@ while getopts "a:d:p:v" OPT; do case $OPT in *) usage;; esac; done shift $((OPTIND - 1)) -if [ -n "$authfile" ]; then + authfile=$(readlink -f -- "$authfile") if ! [ -r "$authfile" -a -f "$authfile" ]; then echo "could not read file '$authfile'" >&2 exit 1 fi -fi + $verbose && echo "root directory is $root" >&2 decode_base64() { From 6f48cc487d1f2191f6b146fd4e8eb06dc5deec94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9A=D0=B8=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 3 Apr 2020 20:08:50 +0200 Subject: [PATCH 4/5] tests added --- shellweb | 3 +-- tests/common | 9 ++++++-- tests/test-416 | 22 ++++++++++++++++++ tests/test-part | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 tests/test-416 create mode 100644 tests/test-part diff --git a/shellweb b/shellweb index c882663..b2085ed 100755 --- a/shellweb +++ b/shellweb @@ -26,7 +26,6 @@ while getopts "a:d:p:v" OPT; do case $OPT in *) usage;; esac; done shift $((OPTIND - 1)) - authfile=$(readlink -f -- "$authfile") if ! [ -r "$authfile" -a -f "$authfile" ]; then echo "could not read file '$authfile'" >&2 @@ -378,9 +377,9 @@ while true; do fi file= auth_passed=false + portion=false if $keep_conn; then sent=false - portion=false fi elif [[ $header == "range:" ]]; then $verbose && echo "Range $v2 from $file requested" >&2 diff --git a/tests/common b/tests/common index 49a275a..e202510 100644 --- a/tests/common +++ b/tests/common @@ -6,7 +6,8 @@ workdir="$PWD" tmpdir=$(mktemp -d -p $workdir) port=13245 -www=$(mkdir -d -p $tmpdir) +mkdir -p ${tmpdir}/www +www="${tmpdir}/www" shellweb_cmd="${workdir}/shellweb -d $www -p $port" test_name=${0##*/test-} @@ -37,7 +38,11 @@ fail() { finish() { kill_shellweb || true cleanup || true - test $failed -gt 0 || echo "$test_name ==> OK" + if [ "$failed" -gt 0 ]; then + echo "$test_name ==> FAIL" + else + echo "$test_name ==> OK" + fi } trap 'finish' EXIT diff --git a/tests/test-416 b/tests/test-416 new file mode 100644 index 0000000..820d397 --- /dev/null +++ b/tests/test-416 @@ -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 + f) file=$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" diff --git a/tests/test-part b/tests/test-part new file mode 100644 index 0000000..286a017 --- /dev/null +++ b/tests/test-part @@ -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" + 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 From 7df04ef3dc2591832006e79fbc2604b6bf62c20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9A=D0=B8=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 3 Apr 2020 20:28:48 +0200 Subject: [PATCH 5/5] 416 error fixed --- tests/test-416 | 2 +- tests/test-part | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-416 b/tests/test-416 index 820d397..ddfa232 100644 --- a/tests/test-416 +++ b/tests/test-416 @@ -6,7 +6,7 @@ link="./tests/home.html" bytes_request="25-14" while getopts "l:r:" OPT; do case $OPT in - f) file=$OPTARG;; + l) link=$OPTARG;; r) byte_request=$OPTARG;; esac; done shift $((OPTIND - 1)) diff --git a/tests/test-part b/tests/test-part index 286a017..6d94f35 100644 --- a/tests/test-part +++ b/tests/test-part @@ -26,7 +26,7 @@ check_portion() { local request_file=$(mktemp -p $www) cp "$1" "$request_file" - local part_file=$(mktemp -p $tmpdir) || fail "mktemp fail" + 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"