diff --git a/install.sh b/install.sh index 799c075a..c16bc6aa 100755 --- a/install.sh +++ b/install.sh @@ -119,7 +119,7 @@ nvm_download() { -e 's/-o /-O /' \ -e 's/-C - /-c /') # shellcheck disable=SC2086 - eval wget $ARGS + eval command wget $ARGS fi } diff --git a/nvm.sh b/nvm.sh index 05406df6..c410569a 100755 --- a/nvm.sh +++ b/nvm.sh @@ -87,7 +87,7 @@ nvm_has_colors() { } nvm_curl_libz_support() { - curl -V 2>/dev/null | nvm_grep "^Features:" | nvm_grep -q "libz" + command curl -V 2>/dev/null | nvm_grep "^Features:" | nvm_grep -q "libz" } nvm_curl_use_compression() { @@ -103,7 +103,7 @@ nvm_get_latest() { fi NVM_LATEST_URL="$(curl ${CURL_COMPRESSED_FLAG:-} -q -w "%{url_effective}\\n" -L -s -S https://latest.nvm.sh -o /dev/null)" elif nvm_has "wget"; then - NVM_LATEST_URL="$(wget -q https://latest.nvm.sh --server-response -O /dev/null 2>&1 | command awk '/^ Location: /{DEST=$2} END{ print DEST }')" + NVM_LATEST_URL="$(command wget -q https://latest.nvm.sh --server-response -O /dev/null 2>&1 | command awk '/^ Location: /{DEST=$2} END{ print DEST }')" else nvm_err 'nvm needs curl or wget to proceed.' return 1 @@ -170,7 +170,7 @@ nvm_download() { set -- "$@" --header "Authorization: ${sanitized_header}" fi - "${NVM_DOWNLOADER}" "$@" + command "${NVM_DOWNLOADER}" "$@" } nvm_sanitize_auth_header() { @@ -649,7 +649,7 @@ nvm_clang_version() { } nvm_curl_version() { - curl -V | command awk '{ if ($1 == "curl") print $2 }' | command sed 's/-.*$//g' + command curl -V | command awk '{ if ($1 == "curl") print $2 }' | command sed 's/-.*$//g' } nvm_version_greater() { diff --git a/test/common.sh b/test/common.sh index 586a7f34..adb0633b 100644 --- a/test/common.sh +++ b/test/common.sh @@ -50,6 +50,39 @@ make_echo() { chmod a+x "$1" } +# `command curl` bypasses shell functions and aliases, so tests that mock curl +# must provide an actual executable on the PATH: this creates one in the given +# directory. Invoked as `curl -V`, it prints ${VERSION_MESSAGE} (exported here +# on the mock's behalf); any other invocation runs the body given as the +# second argument, or fails. +make_fake_curl() { + local FAKE_BIN_DIR + FAKE_BIN_DIR="${1-}" + [ -n "${FAKE_BIN_DIR}" ] || return 1 + + local FAKE_CURL_BODY + FAKE_CURL_BODY="${2-}" + if [ -z "${FAKE_CURL_BODY}" ]; then + FAKE_CURL_BODY='echo >&2 "This fake curl only takes one parameter, -V" +exit 1' + fi + + mkdir -p "${FAKE_BIN_DIR}" || return 2 + + { + echo '#!/bin/sh' + echo 'if [ "$#" -eq 1 ] && [ "$1" = "-V" ]; then' + echo ' echo "${VERSION_MESSAGE}"' + echo ' exit 0' + echo 'fi' + printf '%s\n' "${FAKE_CURL_BODY}" + } > "${FAKE_BIN_DIR}/curl" + chmod +x "${FAKE_BIN_DIR}/curl" + + # the fake curl reads VERSION_MESSAGE from the environment + export VERSION_MESSAGE +} + make_fake_node() { local VERSION VERSION="${1-}" diff --git a/test/fast/Unit tests/nvm_curl_libz_support b/test/fast/Unit tests/nvm_curl_libz_support index acab56fb..72d9fd79 100755 --- a/test/fast/Unit tests/nvm_curl_libz_support +++ b/test/fast/Unit tests/nvm_curl_libz_support @@ -1,7 +1,11 @@ #!/bin/sh +WORK="$PWD/nvm_curl_libz_support-work.$$" +TEST_BIN="$WORK/bin" + cleanup() { - unset -f curl + rm -rf "$WORK" + export PATH="$OLDPATH" } die() { cleanup; echo "$@" ; exit 1; } @@ -9,32 +13,28 @@ die() { cleanup; echo "$@" ; exit 1; } : nvm.sh \. ../../../nvm.sh -curl() { - # curl with libz feature - if [ $# -ne 1 ] || [ "$1" != "-V" ]; then - die "This fake curl only takes one parameter -V" - fi - echo " +\. ../../common.sh + +OLDPATH="$PATH" + +make_fake_curl "$TEST_BIN" + +export PATH="$TEST_BIN:$OLDPATH" + +# curl with libz feature +VERSION_MESSAGE=" curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets" -} nvm_curl_libz_support || die "nvm_curl_libz_support should return 0" -unset -f curl - -curl() { - # curl without libz feature - if [ "$#" -ne 1 ] || [ "$1" != "-V" ]; then - die "This fake curl only takes one parameter -V" - fi - echo " +# curl without libz feature +VERSION_MESSAGE=" curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.32 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL TLS-SRP UnixSockets" -} ! nvm_curl_libz_support || die "nvm_curl_libz_support should return 1" -unset -f curl +cleanup diff --git a/test/fast/Unit tests/nvm_curl_use_compression b/test/fast/Unit tests/nvm_curl_use_compression index e2638641..19aca513 100755 --- a/test/fast/Unit tests/nvm_curl_use_compression +++ b/test/fast/Unit tests/nvm_curl_use_compression @@ -1,18 +1,25 @@ #!/bin/sh +WORK="$PWD/nvm_curl_use_compression-work.$$" +TEST_BIN="$WORK/bin" + cleanup () { unset -f die + rm -rf "$WORK" + export PATH="$OLDPATH" } die () { echo -e "$@" ; cleanup ; exit 1; } NVM_ENV=testing \. ../../../nvm.sh -curl() { - if [ "$1" = "-V" ]; then - echo "${VERSION_MESSAGE}" - fi -} +\. ../../common.sh + +OLDPATH="$PATH" + +make_fake_curl "$TEST_BIN" + +export PATH="$TEST_BIN:$OLDPATH" CURL_VERSION_ON_ARCHLINUX_WITH_LIBZ="curl 7.54.0 (x86_64-pc-linux-gnu) libcurl/7.54.0 OpenSSL/1.1.0f zlib/1.2.11 libpsl/0.17.0 (+libicu/59.1) libssh2/1.8.0 nghttp2/1.22.0 Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp diff --git a/test/fast/Unit tests/nvm_curl_version b/test/fast/Unit tests/nvm_curl_version index d61014ee..3bbb5071 100755 --- a/test/fast/Unit tests/nvm_curl_version +++ b/test/fast/Unit tests/nvm_curl_version @@ -1,19 +1,25 @@ #!/bin/sh +WORK="$PWD/nvm_curl_version-work.$$" +TEST_BIN="$WORK/bin" + cleanup () { - unset -f die - unset -f curl + unset -f die cleanup assert_version_is + rm -rf "$WORK" + export PATH="$OLDPATH" } die () { echo -e "$@" ; cleanup ; exit 1; } NVM_ENV=testing \. ../../../nvm.sh -curl() { - if [ "$1" = "-V" ]; then - echo "${VERSION_MESSAGE}" - fi -} +\. ../../common.sh + +OLDPATH="$PATH" + +make_fake_curl "$TEST_BIN" + +export PATH="$TEST_BIN:$OLDPATH" assert_version_is() { if [ "${1}" != "${2}" ]; then diff --git a/test/fast/Unit tests/nvm_download and curl helpers bypass shadowed curl b/test/fast/Unit tests/nvm_download and curl helpers bypass shadowed curl new file mode 100755 index 00000000..d6746fe8 --- /dev/null +++ b/test/fast/Unit tests/nvm_download and curl helpers bypass shadowed curl @@ -0,0 +1,48 @@ +#!/bin/sh + +WORK="$PWD/shadowed-curl-work.$$" +TEST_BIN="$WORK/bin" +ARGV_LOG="$WORK/argv.log" + +cleanup () { + unset -f die cleanup curl wget + rm -rf "$WORK" + export PATH="$OLDPATH" +} + +die () { echo "$@" ; cleanup ; exit 1; } + +NVM_ENV=testing \. ../../../nvm.sh + +\. ../../common.sh + +OLDPATH="$PATH" + +# real-looking curl on PATH; calling the shadowing shell function below instead is a failure +make_fake_curl "$TEST_BIN" ': > "$ARGV_LOG" +for a in "$@"; do printf "%s\n" "$a" >> "$ARGV_LOG"; done' + +VERSION_MESSAGE="curl 7.99.0 (fake) +Features: libz" + +export ARGV_LOG +export PATH="$TEST_BIN:$OLDPATH" + +# shadowing shell functions, like aliases baked into a user's shell, must be +# bypassed in favor of the executables on PATH (https://github.com/nvm-sh/nvm/issues/2923) +curl() { + echo 'curl 0.0.0-shadowed' + return 1 +} +wget() { + return 1 +} + +[ "$(nvm_curl_version)" = '7.99.0' ] || die "nvm_curl_version used the shadowing curl function; got $(nvm_curl_version)" + +nvm_curl_libz_support || die 'nvm_curl_libz_support used the shadowing curl function' + +nvm_download -s 'http://example.com/' -o /dev/null || die 'nvm_download used the shadowing curl function and failed' +grep -Fxq 'http://example.com/' "$ARGV_LOG" || die "nvm_download did not invoke the curl executable; got: $(cat "$ARGV_LOG")" + +cleanup diff --git a/test/slow/nvm_get_latest/nvm_get_latest b/test/slow/nvm_get_latest/nvm_get_latest index 870c6c21..41830b72 100755 --- a/test/slow/nvm_get_latest/nvm_get_latest +++ b/test/slow/nvm_get_latest/nvm_get_latest @@ -2,37 +2,45 @@ die () { echo "$@" ; cleanup ; exit 1; } +WORK="$PWD/nvm_get_latest-work.$$" +TEST_BIN="$WORK/bin" + cleanup() { - unset -f curl wget nvm_has + rm -rf "$WORK" + export PATH="$OLDPATH" } \. ../../../nvm.sh +\. ../../common.sh + +OLDPATH="$PATH" + EXPECTED_VERSION="v12.3.456" URL="https://github.com/nvm-sh/nvm/releases/tag/$EXPECTED_VERSION" EXPECTED_CURL_ARGS="--compressed -q -w %{url_effective}\n -L -s -S https://latest.nvm.sh -o /dev/null" EXPECTED_WGET_ARGS="-q https://latest.nvm.sh --server-response -O /dev/null" +export URL EXPECTED_CURL_ARGS EXPECTED_WGET_ARGS -curl() { - if [ $# -eq 1 ] && [ "$1" = "-V" ]; then - echo " +VERSION_MESSAGE=" curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets" - elif [ "_$*" != "_$EXPECTED_CURL_ARGS" ]; then - echo >&2 "expected args ($EXPECTED_CURL_ARGS), got ($*)" - return 1 - else - echo $URL - fi -} -wget() { - if [ "_$*" != "_$EXPECTED_WGET_ARGS" ]; then - echo >&2 "expected args ($EXPECTED_WGET_ARGS), got ($*)" - return 1 - else - local WGET_CONTENTS - WGET_CONTENTS=" + +# fake curl/wget: `command curl` bypasses shell functions, so the mocks must be executables on PATH +make_fake_curl "$TEST_BIN" 'if [ "_$*" != "_$EXPECTED_CURL_ARGS" ]; then + echo >&2 "expected args ($EXPECTED_CURL_ARGS), got ($*)" + exit 1 +fi +echo "$URL"' + +cat > "$TEST_BIN/wget" <<'OUTER' +#!/bin/sh +if [ "_$*" != "_$EXPECTED_WGET_ARGS" ]; then + echo >&2 "expected args ($EXPECTED_WGET_ARGS), got ($*)" + exit 1 +fi +cat >&2 <&2 echo "$line" - done - fi -} +CONTENTS +OUTER +chmod +x "$TEST_BIN/wget" -\. ../../common.sh +export PATH="$TEST_BIN:$OLDPATH" try nvm_get_latest [ "_$CAPTURED_STDOUT" = "_$EXPECTED_VERSION" ] \ diff --git a/test/slow/nvm_get_latest/nvm_get_latest failed redirect b/test/slow/nvm_get_latest/nvm_get_latest failed redirect index f03cf6fa..658d5e22 100755 --- a/test/slow/nvm_get_latest/nvm_get_latest failed redirect +++ b/test/slow/nvm_get_latest/nvm_get_latest failed redirect @@ -2,20 +2,26 @@ die () { echo "$@" ; cleanup ; exit 1; } +WORK="$PWD/nvm_get_latest_failed_redirect-work.$$" +TEST_BIN="$WORK/bin" + cleanup() { - unset -f curl wget + rm -rf "$WORK" + export PATH="$OLDPATH" } \. ../../../nvm.sh \. ../../common.sh -curl() { - return 1 -} -wget() { - return 1 -} +OLDPATH="$PATH" + +# fake curl/wget: `command curl` bypasses shell functions, so the mocks must be executables on PATH +make_fake_curl "$TEST_BIN" 'exit 1' +printf '#!/bin/sh\nexit 1\n' > "$TEST_BIN/wget" +chmod +x "$TEST_BIN/wget" + +export PATH="$TEST_BIN:$OLDPATH" try_err nvm_get_latest [ "_$CAPTURED_STDERR" = "_https://latest.nvm.sh did not redirect to the latest release on GitHub" ] \