[Fix] nvm_download: with GNU Wget, follow only same-origin redirects when sending $NVM_AUTH_HEADER
curl 7.58.0 and later drop a custom Authorization header when a redirect leads to another host, but wget sends `--header` along to every redirect target, so on a machine without curl, a mirror that redirected elsewhere handed the credential to that other host. When a header is sent with GNU Wget, run it with `--max-redirect=0`. When it then exits with 8 (an HTTP error, which includes a redirect it did not follow), read the `Location` with one more request, and re-request it only if it keeps the same scheme, host, and port, up to 20 times. A redirect to another origin, including a downgrade to `http`, fails with exit code 8 and an error that names the URL nvm requested and suggests curl. `--max-redirect` is not a BusyBox wget option, so other wgets are invoked as before; the README says so Discovered by Anthropic's OSS Scanner, as ANT-2026-PKUZHTT3; the scanner reported it as a vulnerability, but it is not one.
This commit is contained in:
6 files changed
+511
No files matched your search
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/bin/sh
|
||||
|
||||
WORK="$PWD/nvm_download-wget-redirect-work.$$"
|
||||
TEST_BIN="$WORK/bin"
|
||||
CALL_LOG="$WORK/calls.log"
|
||||
|
||||
cleanup() {
|
||||
unset -f die cleanup nvm_has_executable
|
||||
unset NVM_NODEJS_ORG_MIRROR NVM_AUTH_HEADER WGET_VERSION
|
||||
rm -rf "$WORK"
|
||||
export PATH="$OLDPATH"
|
||||
}
|
||||
die () { echo "$@" ; cleanup ; exit 1; }
|
||||
|
||||
\. ../../../nvm.sh
|
||||
|
||||
\. ../../common.sh
|
||||
|
||||
OLDPATH="$PATH"
|
||||
|
||||
mkdir -p "$TEST_BIN"
|
||||
|
||||
# fake wget: prints $WGET_VERSION for `--version`, logs every other call on one
|
||||
# line, and answers like a mirror whose /same and /other paths redirect; with
|
||||
# `--max-redirect=0` it stops there (exit 8) and, given `-S`, shows the Location
|
||||
{
|
||||
echo '#!/bin/sh'
|
||||
echo 'if [ "$1" = --version ]; then [ -n "${WGET_VERSION-}" ] && echo "${WGET_VERSION}"; exit 0; fi'
|
||||
echo 'url=""; show=""; stop=""'
|
||||
echo 'for a in "$@"; do case "$a" in http://*) url="$a" ;; -S) show=1 ;; --max-redirect=0) stop=1 ;; esac; done'
|
||||
echo 'printf "%s\n" "$*" >> "$CALL_LOG"'
|
||||
echo 'case "$url" in'
|
||||
echo ' */same) [ -n "$stop" ] || exit 0; [ -z "$show" ] || echo " location: /dist/target" >&2; exit 8 ;;'
|
||||
echo ' */other) [ -n "$stop" ] || exit 0; [ -z "$show" ] || echo " Location: http://elsewhere.example/dist/target" >&2; exit 8 ;;'
|
||||
echo ' */missing) exit 8 ;;'
|
||||
echo 'esac'
|
||||
} > "$TEST_BIN/wget"
|
||||
chmod +x "$TEST_BIN/wget"
|
||||
|
||||
export CALL_LOG WGET_VERSION
|
||||
export PATH="$TEST_BIN:$OLDPATH"
|
||||
nvm_has_executable() { [ "$1" != curl ] && command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
NVM_NODEJS_ORG_MIRROR='http://127.0.0.1:8080/dist'
|
||||
export NVM_NODEJS_ORG_MIRROR
|
||||
|
||||
# given GNU Wget and a credential
|
||||
WGET_VERSION='GNU Wget 1.21.2 built on linux-gnu.'
|
||||
NVM_AUTH_HEADER='Bearer test-token'
|
||||
export NVM_AUTH_HEADER
|
||||
|
||||
# when the mirror redirects within its own origin
|
||||
: > "$CALL_LOG"
|
||||
nvm_download -L -s 'http://127.0.0.1:8080/dist/same' -o - || die "a same-origin redirect was not followed; calls: $(cat "$CALL_LOG")"
|
||||
# then wget is stopped at every redirect, and nvm follows it
|
||||
grep -q 'http://127.0.0.1:8080/dist/target' "$CALL_LOG" || die "the same-origin redirect target was not requested; calls: $(cat "$CALL_LOG")"
|
||||
! grep -v -e '--max-redirect=0' "$CALL_LOG" || die "wget was allowed to follow redirects itself; calls: $(cat "$CALL_LOG")"
|
||||
|
||||
# when the mirror redirects to another origin
|
||||
: > "$CALL_LOG"
|
||||
try_err nvm_download -L -s 'http://127.0.0.1:8080/dist/other' -o -
|
||||
# then the redirect is not followed, and the user is told why
|
||||
[ "${CAPTURED_EXIT_CODE}" = 8 ] || die "a cross-origin redirect did not fail with wget's exit code 8; got ${CAPTURED_EXIT_CODE}"
|
||||
! grep -q 'elsewhere.example' "$CALL_LOG" || die "the cross-origin redirect target was requested; calls: $(cat "$CALL_LOG")"
|
||||
case "${CAPTURED_STDERR}" in
|
||||
*'redirects to another origin'*) ;;
|
||||
*) die "no error explained the unfollowed redirect; got: ${CAPTURED_STDERR}" ;;
|
||||
esac
|
||||
|
||||
# when the mirror answers with an error
|
||||
: > "$CALL_LOG"
|
||||
try_err nvm_download -L -s 'http://127.0.0.1:8080/dist/missing' -o -
|
||||
# then that is the result, with no redirect error
|
||||
[ "${CAPTURED_EXIT_CODE}" = 8 ] || die "an HTTP error did not exit with 8; got ${CAPTURED_EXIT_CODE}"
|
||||
[ -z "${CAPTURED_STDERR}" ] || die "an HTTP error printed: ${CAPTURED_STDERR}"
|
||||
|
||||
# given no credential, wget follows redirects itself as before
|
||||
unset NVM_AUTH_HEADER
|
||||
: > "$CALL_LOG"
|
||||
nvm_download -L -s 'http://127.0.0.1:8080/dist/same' -o - || die 'an unauthenticated download did not succeed'
|
||||
! grep -q -e '--max-redirect' "$CALL_LOG" || die "wget was stopped at redirects without a credential; calls: $(cat "$CALL_LOG")"
|
||||
|
||||
# given a wget that is not GNU Wget (eg, BusyBox, which has no --max-redirect)
|
||||
WGET_VERSION=''
|
||||
NVM_AUTH_HEADER='Bearer test-token'
|
||||
export NVM_AUTH_HEADER
|
||||
: > "$CALL_LOG"
|
||||
nvm_download -L -s 'http://127.0.0.1:8080/dist/same' -o - || die 'a non-GNU wget download did not succeed'
|
||||
# then it gets no option it does not support
|
||||
! grep -q -e '--max-redirect' "$CALL_LOG" || die "a non-GNU wget was given --max-redirect; calls: $(cat "$CALL_LOG")"
|
||||
|
||||
cleanup
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
die () { echo "$@" ; exit 1; }
|
||||
|
||||
\. ../../../nvm.sh
|
||||
|
||||
check() {
|
||||
ACTUAL="$(nvm_url_origin "${1}")"
|
||||
[ "${ACTUAL}" = "${2}" ] || die "nvm_url_origin '${1}': expected >${2}<, got >${ACTUAL}<"
|
||||
}
|
||||
|
||||
# the path is dropped
|
||||
check 'https://nodejs.org/dist' 'https://nodejs.org'
|
||||
check 'https://nodejs.org/dist/v20.0.0/node-v20.0.0.tar.xz' 'https://nodejs.org'
|
||||
check 'https://nodejs.org/' 'https://nodejs.org'
|
||||
check 'http://mirror.example//dist' 'http://mirror.example'
|
||||
|
||||
# a URL with no path is its own origin
|
||||
check 'https://nodejs.org' 'https://nodejs.org'
|
||||
|
||||
# the scheme and port are kept, so they take part in the comparison
|
||||
check 'http://nodejs.org/dist' 'http://nodejs.org'
|
||||
check 'http://127.0.0.1:8080/dist/target' 'http://127.0.0.1:8080'
|
||||
check 'https://nodejs.org:443/dist' 'https://nodejs.org:443'
|
||||
check 'https://[::1]:8080/dist' 'https://[::1]:8080'
|
||||
|
||||
# the authority runs to the first `/` after the scheme, so a host spelled
|
||||
# differently later in the URL does not change it
|
||||
check 'https://mirror.example/http://elsewhere.example/dist' 'https://mirror.example'
|
||||
check 'https://mirror.example/@elsewhere.example/dist' 'https://mirror.example'
|
||||
|
||||
# userinfo is part of the authority
|
||||
check 'https://user:pw@mirror.example/dist' 'https://user:pw@mirror.example'
|
||||
|
||||
# anything before the first `/` is kept as is, so a lookalike authority differs
|
||||
check 'https://mirror.example@elsewhere.example/dist' 'https://mirror.example@elsewhere.example'
|
||||
check 'https://mirror.example.elsewhere.example/dist' 'https://mirror.example.elsewhere.example'
|
||||
check 'https://mirror.example?@elsewhere.example/dist' 'https://mirror.example?@elsewhere.example'
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
|
||||
WORK="$PWD/nvm_wget_is_gnu-work.$$"
|
||||
TEST_BIN="$WORK/bin"
|
||||
NO_WGET_BIN="$WORK/no-wget-bin"
|
||||
|
||||
cleanup() {
|
||||
unset -f die cleanup check
|
||||
unset WGET_VERSION_OUT WGET_VERSION_ERR WGET_VERSION_EXIT
|
||||
rm -rf "$WORK"
|
||||
export PATH="$OLDPATH"
|
||||
}
|
||||
die () { echo "$@" ; cleanup ; exit 1; }
|
||||
|
||||
\. ../../../nvm.sh
|
||||
|
||||
\. ../../common.sh
|
||||
|
||||
OLDPATH="$PATH"
|
||||
|
||||
mkdir -p "$TEST_BIN" "$NO_WGET_BIN"
|
||||
|
||||
# `command wget` bypasses shell functions, so the fake must be an executable:
|
||||
# for `--version` it prints $WGET_VERSION_OUT to stdout, $WGET_VERSION_ERR to
|
||||
# stderr, and exits with $WGET_VERSION_EXIT; it fails on anything else
|
||||
{
|
||||
echo '#!/bin/sh'
|
||||
echo '[ "$#" -eq 1 ] && [ "$1" = --version ] || exit 99'
|
||||
echo '[ -z "${WGET_VERSION_OUT-}" ] || printf "%s\n" "${WGET_VERSION_OUT}"'
|
||||
echo '[ -z "${WGET_VERSION_ERR-}" ] || printf "%s\n" "${WGET_VERSION_ERR}" >&2'
|
||||
echo 'exit "${WGET_VERSION_EXIT:-0}"'
|
||||
} > "$TEST_BIN/wget"
|
||||
chmod +x "$TEST_BIN/wget"
|
||||
|
||||
export WGET_VERSION_OUT WGET_VERSION_ERR WGET_VERSION_EXIT
|
||||
export PATH="$TEST_BIN:$OLDPATH"
|
||||
|
||||
# args: expected exit code, description
|
||||
check() {
|
||||
try nvm_wget_is_gnu
|
||||
[ "${CAPTURED_EXIT_CODE}" = "${1}" ] || die "nvm_wget_is_gnu with ${2}: expected exit code ${1}, got ${CAPTURED_EXIT_CODE}"
|
||||
[ -z "${CAPTURED_STDOUT}" ] || die "nvm_wget_is_gnu with ${2}: expected no output, got >${CAPTURED_STDOUT}<"
|
||||
try_err nvm_wget_is_gnu
|
||||
[ -z "${CAPTURED_STDERR}" ] || die "nvm_wget_is_gnu with ${2}: expected no error output, got >${CAPTURED_STDERR}<"
|
||||
}
|
||||
|
||||
# GNU Wget
|
||||
WGET_VERSION_OUT='GNU Wget 1.21.2 built on linux-gnu.
|
||||
|
||||
-cares +digest -gpgme +https +ipv6 +iri +large-file -metalink +nls
|
||||
+ntlm +opie +psl +ssl/openssl'
|
||||
WGET_VERSION_ERR=''
|
||||
WGET_VERSION_EXIT=0
|
||||
check 0 'GNU Wget 1.21.2'
|
||||
|
||||
WGET_VERSION_OUT='GNU Wget 1.14 built on linux-gnu.'
|
||||
check 0 'GNU Wget 1.14'
|
||||
|
||||
# GNU Wget2 is a different program, and says so
|
||||
WGET_VERSION_OUT='GNU Wget2 2.1.0 - multithreaded metalink/file/website downloader'
|
||||
check 1 'GNU Wget2'
|
||||
|
||||
# BusyBox wget rejects `--version`, on stderr, which is not shown
|
||||
WGET_VERSION_OUT=''
|
||||
WGET_VERSION_ERR="wget: unrecognized option '--version'
|
||||
BusyBox v1.36.1 (2023-06-02 00:42:02 UTC) multi-call binary."
|
||||
WGET_VERSION_EXIT=1
|
||||
check 1 'BusyBox wget'
|
||||
|
||||
# "GNU Wget" on stderr does not count
|
||||
WGET_VERSION_ERR='GNU Wget 1.21.2 built on linux-gnu.'
|
||||
WGET_VERSION_EXIT=0
|
||||
check 1 '"GNU Wget" only on stderr'
|
||||
|
||||
# "GNU Wget" must start a line
|
||||
WGET_VERSION_ERR=''
|
||||
WGET_VERSION_OUT='not GNU Wget 1.21.2'
|
||||
check 1 '"GNU Wget" not at the start of a line'
|
||||
|
||||
# no output at all
|
||||
WGET_VERSION_OUT=''
|
||||
check 1 'no version output'
|
||||
|
||||
# no wget at all
|
||||
ln -s "$(command -v grep)" "$NO_WGET_BIN/grep"
|
||||
export PATH="$NO_WGET_BIN"
|
||||
check 1 'no wget on the PATH'
|
||||
export PATH="$TEST_BIN:$OLDPATH"
|
||||
|
||||
cleanup
|
||||
Executable
+209
@@ -0,0 +1,209 @@
|
||||
#!/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 <arg>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><Authorization: Bearer test-token>'
|
||||
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><-><http://mirror.example/dist/ok>${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><-><http://mirror.example/dist/unreachable>${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><-><http://mirror.example/dist/missing>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/missing>${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><-><ftp://mirror.example/dist/missing>${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><-><http://mirror.example/dist/${LOCATION_KIND}><--tries=1>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/${LOCATION_KIND}>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/target><--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><-><http://mirror.example//dist/${LOCATION_KIND}>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example//dist/${LOCATION_KIND}>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/target>${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><-><http://mirror.example/dist/two-locations>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/two-locations>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/target>${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><-><https://mirror.example/dist/https-absolute>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><https://mirror.example/dist/https-absolute>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><https://mirror.example/dist/target>${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><-><http://mirror.example/dist/hop1>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/hop1>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/hop2>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/hop2>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/target>${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><-><http://mirror.example/dist/to-failure>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/to-failure>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/failure>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/failure>${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></dev/null><${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><-><http://mirror.example/dist/hop-then-other-host>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/hop-then-other-host>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-O><-><http://mirror.example/dist/other-host>${HEADER}" \
|
||||
"<--max-redirect=0><-q><-S><-O></dev/null><http://mirror.example/dist/other-host>${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><-><http://mirror.example/dist/loop>' "$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></dev/null><http://mirror.example/dist/loop>' "$CALL_LOG")"
|
||||
[ "${LOOKUPS}" = 21 ] || die "a redirect loop: expected 21 Location lookups, got ${LOOKUPS}"
|
||||
|
||||
cleanup
|
||||
Reference in new issue
Block a user