#!/bin/sh WORK="$PWD/nvm_wget_same_origin-work.$$" TEST_BIN="$WORK/bin" CALL_LOG="$WORK/calls.log" cleanup() { unset -f die cleanup run expect_calls unset CALL_LOG rm -rf "$WORK" export PATH="$OLDPATH" } die () { echo "$@" ; cleanup ; exit 1; } \. ../../../nvm.sh \. ../../common.sh OLDPATH="$PATH" mkdir -p "$TEST_BIN" # `command wget` bypasses shell functions, so the fake must be an executable. # It logs each call as one line of s, and answers like a mirror: a path # that redirects exits with 8 under `--max-redirect=0` (and shows its Location # given `-S`), and fails the test otherwise, since wget must never follow one. { echo '#!/bin/sh' echo 'url=""; show=""; stop=""' echo 'for a in "$@"; do case "$a" in http://* | https://* | ftp://*) url="$a" ;; -S) show=1 ;; --max-redirect=0) stop=1 ;; esac; done' echo 'printf "<%s>" "$@" >> "$CALL_LOG"; echo >> "$CALL_LOG"' echo 'redirect() { [ -n "$stop" ] || exit 97; [ -z "$show" ] || printf " HTTP/1.1 302 Found\n Location: %s\n" "$1" >&2; exit 8; }' echo 'case "$url" in' echo ' ftp://*) exit 8 ;;' echo ' */dist/target | */dist/ok) exit 0 ;;' echo ' */dist/unreachable) exit 4 ;;' echo ' */dist/missing | */dist/failure) [ -z "$show" ] || printf " HTTP/1.1 404 Not Found\n" >&2; exit 8 ;;' echo ' */dist/absolute) redirect http://mirror.example/dist/target ;;' echo ' https://mirror.example/dist/https-absolute) redirect https://mirror.example/dist/target ;;' echo ' */dist/path-absolute) redirect /dist/target ;;' echo ' */dist/scheme-relative) redirect //mirror.example/dist/target ;;' echo ' */dist/relative) redirect target ;;' echo ' */dist/crlf) [ -n "$stop" ] || exit 97; [ -z "$show" ] || printf " LOCATION: /dist/target\r\n" >&2; exit 8 ;;' echo ' */dist/two-locations) [ -n "$stop" ] || exit 97; [ -z "$show" ] || printf " HTTP/1.1 302 Found\n Location: /dist/target\n Location: http://elsewhere.example/dist/target\n" >&2; exit 8 ;;' echo ' */dist/hop1) redirect /dist/hop2 ;;' echo ' */dist/hop2) redirect http://mirror.example/dist/target ;;' echo ' */dist/to-failure) redirect /dist/failure ;;' echo ' */dist/other-host) redirect http://elsewhere.example/dist/target ;;' echo ' */dist/scheme-relative-other-host) redirect //elsewhere.example/dist/target ;;' echo ' */dist/other-port) redirect http://mirror.example:8080/dist/target ;;' echo ' https://mirror.example/dist/downgrade) redirect http://mirror.example/dist/target ;;' echo ' */dist/lookalike) redirect http://mirror.example.elsewhere.example/dist/target ;;' echo ' */dist/userinfo) redirect http://mirror.example@elsewhere.example/dist/target ;;' echo ' */dist/hop-then-other-host) redirect /dist/other-host ;;' echo ' */dist/loop) redirect /dist/loop ;;' echo 'esac' echo 'exit 98' } > "$TEST_BIN/wget" chmod +x "$TEST_BIN/wget" export CALL_LOG export PATH="$TEST_BIN:$OLDPATH" # the Authorization header goes along with every request HEADER='<--header>' run() { : > "$CALL_LOG" try_err nvm_wget_same_origin 'Bearer test-token' "$@" } # args: description, expected calls (one per argument) expect_calls() { EXPECTED_DESCRIPTION="${1}" shift EXPECTED_CALLS="$(printf '%s\n' "$@")" ACTUAL_CALLS="$(cat "$CALL_LOG")" [ "${ACTUAL_CALLS}" = "${EXPECTED_CALLS}" ] || die "${EXPECTED_DESCRIPTION}: expected calls ${EXPECTED_CALLS} got ${ACTUAL_CALLS}" } # a download that does not redirect is made once, with wget stopped at redirects run -q -O - 'http://mirror.example/dist/ok' [ "${CAPTURED_EXIT_CODE}" = 0 ] || die "a successful download: expected exit code 0, got ${CAPTURED_EXIT_CODE}" [ -z "${CAPTURED_STDERR}" ] || die "a successful download: expected no error, got >${CAPTURED_STDERR}<" expect_calls 'a successful download' \ "<--max-redirect=0><-q><-O><->${HEADER}" # any failure but an HTTP error is returned as is, with no Location lookup run -q -O - 'http://mirror.example/dist/unreachable' [ "${CAPTURED_EXIT_CODE}" = 4 ] || die "a network failure: expected exit code 4, got ${CAPTURED_EXIT_CODE}" [ -z "${CAPTURED_STDERR}" ] || die "a network failure: expected no error, got >${CAPTURED_STDERR}<" expect_calls 'a network failure' \ "<--max-redirect=0><-q><-O><->${HEADER}" # an HTTP error with no Location is returned as is run -q -O - 'http://mirror.example/dist/missing' [ "${CAPTURED_EXIT_CODE}" = 8 ] || die "an HTTP error: expected exit code 8, got ${CAPTURED_EXIT_CODE}" [ -z "${CAPTURED_STDERR}" ] || die "an HTTP error: expected no error, got >${CAPTURED_STDERR}<" expect_calls 'an HTTP error' \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" # with no http(s) URL in the arguments, there is nothing to look up run -q -O - 'ftp://mirror.example/dist/missing' [ "${CAPTURED_EXIT_CODE}" = 8 ] || die "no http(s) URL: expected exit code 8, got ${CAPTURED_EXIT_CODE}" [ -z "${CAPTURED_STDERR}" ] || die "no http(s) URL: expected no error, got >${CAPTURED_STDERR}<" expect_calls 'no http(s) URL' \ "<--max-redirect=0><-q><-O><->${HEADER}" # a same-origin redirect is followed, with the URL replaced in place and every other argument kept for LOCATION_KIND in absolute path-absolute scheme-relative relative crlf; do run -q -O - 'http://mirror.example/dist/'"${LOCATION_KIND}" --tries=1 [ "${CAPTURED_EXIT_CODE}" = 0 ] || die "a same-origin ${LOCATION_KIND} redirect: expected exit code 0, got ${CAPTURED_EXIT_CODE}; stderr >${CAPTURED_STDERR}<" [ -z "${CAPTURED_STDERR}" ] || die "a same-origin ${LOCATION_KIND} redirect: expected no error, got >${CAPTURED_STDERR}<" expect_calls "a same-origin ${LOCATION_KIND} redirect" \ "<--max-redirect=0><-q><-O><-><--tries=1>${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><-><--tries=1>${HEADER}" done # a doubled slash in the path does not change where a path-absolute or scheme-relative Location resolves for LOCATION_KIND in path-absolute scheme-relative; do run -q -O - 'http://mirror.example//dist/'"${LOCATION_KIND}" [ "${CAPTURED_EXIT_CODE}" = 0 ] || die "a same-origin ${LOCATION_KIND} redirect from a path with a doubled slash: expected exit code 0, got ${CAPTURED_EXIT_CODE}; stderr >${CAPTURED_STDERR}<" [ -z "${CAPTURED_STDERR}" ] || die "a same-origin ${LOCATION_KIND} redirect from a path with a doubled slash: expected no error, got >${CAPTURED_STDERR}<" expect_calls "a same-origin ${LOCATION_KIND} redirect from a path with a doubled slash" \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" done # only the first Location header is used run -q -O - 'http://mirror.example/dist/two-locations' [ "${CAPTURED_EXIT_CODE}" = 0 ] || die "a redirect with two Location headers: expected exit code 0, got ${CAPTURED_EXIT_CODE}; stderr >${CAPTURED_STDERR}<" [ -z "${CAPTURED_STDERR}" ] || die "a redirect with two Location headers: expected no error, got >${CAPTURED_STDERR}<" expect_calls 'a redirect with two Location headers' \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" # https stays https run -q -O - 'https://mirror.example/dist/https-absolute' [ "${CAPTURED_EXIT_CODE}" = 0 ] || die "a same-origin https redirect: expected exit code 0, got ${CAPTURED_EXIT_CODE}; stderr >${CAPTURED_STDERR}<" expect_calls 'a same-origin https redirect' \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" # a chain of same-origin redirects is followed, each Location looked up at the current URL run -q -O - 'http://mirror.example/dist/hop1' [ "${CAPTURED_EXIT_CODE}" = 0 ] || die "a chain of same-origin redirects: expected exit code 0, got ${CAPTURED_EXIT_CODE}; stderr >${CAPTURED_STDERR}<" expect_calls 'a chain of same-origin redirects' \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" # the redirect target's own HTTP error is the result run -q -O - 'http://mirror.example/dist/to-failure' [ "${CAPTURED_EXIT_CODE}" = 8 ] || die "a redirect to an HTTP error: expected exit code 8, got ${CAPTURED_EXIT_CODE}" [ -z "${CAPTURED_STDERR}" ] || die "a redirect to an HTTP error: expected no error, got >${CAPTURED_STDERR}<" expect_calls 'a redirect to an HTTP error' \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" # a redirect to another origin is not followed, so the header never reaches it for REDIRECT in \ 'http://mirror.example/dist/other-host' \ 'http://mirror.example/dist/scheme-relative-other-host' \ 'http://mirror.example/dist/other-port' \ 'https://mirror.example/dist/downgrade' \ 'http://mirror.example/dist/lookalike' \ 'http://mirror.example/dist/userinfo' \ ; do run -q -O - "${REDIRECT}" [ "${CAPTURED_EXIT_CODE}" = 8 ] || die "a cross-origin redirect from ${REDIRECT}: expected exit code 8, got ${CAPTURED_EXIT_CODE}" [ "${CAPTURED_STDERR}" = "${REDIRECT} redirects to another origin; wget would send \$NVM_AUTH_HEADER there too, so it is not followed. Install curl to download from this mirror." ] \ || die "a cross-origin redirect from ${REDIRECT}: expected an error naming it, got >${CAPTURED_STDERR}<" expect_calls "a cross-origin redirect from ${REDIRECT}" \ "<--max-redirect=0><-q><-O><-><${REDIRECT}>${HEADER}" \ "<--max-redirect=0><-q><-S><-O><${REDIRECT}>${HEADER}" done # a cross-origin redirect after a same-origin one names the URL that was requested run -q -O - 'http://mirror.example/dist/hop-then-other-host' [ "${CAPTURED_EXIT_CODE}" = 8 ] || die "a cross-origin redirect after a same-origin one: expected exit code 8, got ${CAPTURED_EXIT_CODE}" [ "${CAPTURED_STDERR}" = "http://mirror.example/dist/hop-then-other-host redirects to another origin; wget would send \$NVM_AUTH_HEADER there too, so it is not followed. Install curl to download from this mirror." ] \ || die "a cross-origin redirect after a same-origin one: expected an error naming the requested URL, got >${CAPTURED_STDERR}<" expect_calls 'a cross-origin redirect after a same-origin one' \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" \ "<--max-redirect=0><-q><-O><->${HEADER}" \ "<--max-redirect=0><-q><-S><-O>${HEADER}" # at most 20 redirects are followed run -q -O - 'http://mirror.example/dist/loop' [ "${CAPTURED_EXIT_CODE}" = 8 ] || die "a redirect loop: expected exit code 8, got ${CAPTURED_EXIT_CODE}" [ "${CAPTURED_STDERR}" = 'http://mirror.example/dist/loop: too many redirects' ] || die "a redirect loop: expected a too many redirects error, got >${CAPTURED_STDERR}<" DOWNLOADS="$(command grep -c -e '^<--max-redirect=0><-q><-O><->' "$CALL_LOG")" [ "${DOWNLOADS}" = 21 ] || die "a redirect loop: expected the request and 20 redirects, got ${DOWNLOADS} requests" LOOKUPS="$(command grep -c -e '^<--max-redirect=0><-q><-S><-O>' "$CALL_LOG")" [ "${LOOKUPS}" = 21 ] || die "a redirect loop: expected 21 Location lookups, got ${LOOKUPS}" cleanup