Skip to content

Commit d3e0ca6

Browse files
committed
[Fix] nvm_sanitize_path: match $NVM_DIR/$HOME literally; address review
- `nvm_sanitize_path` built a sed expression from `$NVM_DIR`/`$HOME`, so a `#` broke the substitution and regex characters could mis-match. Replace literally via a small awk helper (values passed through ENVIRON, so no escape processing). - `nvm_change_path`: compute the escaped `NVM_DIR_RE` only after the empty-path early return, to avoid extra forks on the common path. - Tests: add `\`, `$`, `|` dirs, the system-path-first branch, a MANPATH call, and a sanitize_path test.
1 parent dbffd4a commit d3e0ca6

3 files changed

Lines changed: 67 additions & 8 deletions

File tree

‎nvm.sh‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,15 +1251,17 @@ nvm_strip_path() {
12511251
}
12521252

12531253
nvm_change_path() {
1254-
# `${NVM_DIR}` is matched literally, so escape characters that are special in regexes
1255-
local NVM_DIR_RE
1256-
NVM_DIR_RE="$(nvm_echo "${NVM_DIR}" | command sed 's/[][\.*^$+?(){}|]/\\&/g')"
12571254
# if there’s no initial path, just return the supplementary path
12581255
if [ -z "${1-}" ]; then
12591256
nvm_echo "${3-}${2-}"
1257+
return
1258+
fi
1259+
# `${NVM_DIR}` is matched literally, so escape characters that are special in regexes
1260+
local NVM_DIR_RE
1261+
NVM_DIR_RE="$(nvm_echo "${NVM_DIR}" | command sed 's/[][\.*^$+?(){}|]/\\&/g')"
12601262
# if the initial path doesn’t contain an nvm path, prepend the supplementary
12611263
# path
1262-
elif ! nvm_echo "${1-}" | nvm_grep -Eq "${NVM_DIR_RE}/[^/]*${2-}" \
1264+
if ! nvm_echo "${1-}" | nvm_grep -Eq "${NVM_DIR_RE}/[^/]*${2-}" \
12631265
&& ! nvm_echo "${1-}" | nvm_grep -Eq "${NVM_DIR_RE}/versions/[^/]*/[^/]*${2-}"; then
12641266
nvm_echo "${3-}${2-}:${1-}"
12651267
# if the initial path contains BOTH an nvm path (checked for above) and
@@ -3645,14 +3647,29 @@ nvm_has_solaris_binary() {
36453647
fi
36463648
}
36473649

3650+
# replaces every literal occurrence of $2 in $1 with $3; values go through the
3651+
# environment so awk does not interpret backslash escapes or regex characters
3652+
nvm_replace_literal() {
3653+
nvm_echo "${1-}" | NVM_FROM="${2-}" NVM_TO="${3-}" command awk '
3654+
BEGIN { from = ENVIRON["NVM_FROM"]; to = ENVIRON["NVM_TO"]; n = length(from) }
3655+
{
3656+
out = ""; s = $0
3657+
while (n > 0 && (i = index(s, from)) > 0) {
3658+
out = out substr(s, 1, i - 1) to
3659+
s = substr(s, i + n)
3660+
}
3661+
print out s
3662+
}'
3663+
}
3664+
36483665
nvm_sanitize_path() {
36493666
local SANITIZED_PATH
36503667
SANITIZED_PATH="${1-}"
36513668
if [ "_${SANITIZED_PATH}" != "_${NVM_DIR}" ]; then
3652-
SANITIZED_PATH="$(nvm_echo "${SANITIZED_PATH}" | command sed -e "s#${NVM_DIR}#\${NVM_DIR}#g")"
3669+
SANITIZED_PATH="$(nvm_replace_literal "${SANITIZED_PATH}" "${NVM_DIR}" '${NVM_DIR}')"
36533670
fi
36543671
if [ "_${SANITIZED_PATH}" != "_${HOME}" ]; then
3655-
SANITIZED_PATH="$(nvm_echo "${SANITIZED_PATH}" | command sed -e "s#${HOME}#\${HOME}#g")"
3672+
SANITIZED_PATH="$(nvm_replace_literal "${SANITIZED_PATH}" "${HOME}" '${HOME}')"
36563673
fi
36573674
nvm_echo "${SANITIZED_PATH}"
36583675
}
@@ -5433,7 +5450,7 @@ nvm() {
54335450
nvm_install_lock_name nvm_acquire_install_lock nvm_release_install_lock \
54345451
nvm_list_aliases nvm_make_alias nvm_print_alias_file nvm_print_alias_path \
54355452
nvm_print_default_alias nvm_print_formatted_alias nvm_resolve_local_alias \
5436-
nvm_sanitize_path nvm_has_colors nvm_has_italics nvm_process_parameters \
5453+
nvm_replace_literal nvm_sanitize_path nvm_has_colors nvm_has_italics nvm_process_parameters \
54375454
nvm_node_version_has_solaris_binary nvm_iojs_version_has_solaris_binary \
54385455
nvm_curl_libz_support nvm_command_info nvm_is_zsh nvm_stdout_is_terminal \
54395456
nvm_npmrc_bad_news_bears nvm_sanitize_auth_header \

‎test/fast/Unit tests/nvm_change_path special characters‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,26 @@ die () { echo "$@" ; exit 1; }
55
: nvm.sh
66
\. ../../../nvm.sh
77

8-
for DIR in '/tmp/a&b/.nvm' '/tmp/a#b/.nvm' '/tmp/a[1]/.nvm' '/tmp/a*b/.nvm' '/tmp/a+(b)/.nvm' '/tmp/a.b/.nvm'; do
8+
for DIR in '/tmp/a&b/.nvm' '/tmp/a#b/.nvm' '/tmp/a[1]/.nvm' '/tmp/a*b/.nvm' '/tmp/a+(b)/.nvm' '/tmp/a.b/.nvm' '/tmp/a\b/.nvm' '/tmp/a$b/.nvm' '/tmp/a|b/.nvm'; do
99
NVM_DIR="${DIR}"
1010
TEST_PATH="/opt/bin:${NVM_DIR}/versions/node/v4.5.0/bin:/usr/bin"
1111

12+
# replace branch
1213
NEW_PATH="$(nvm_change_path "${TEST_PATH}" "/bin" "${NVM_DIR}/versions/node/v7.1.0")"
1314
EXPECTED="/opt/bin:${NVM_DIR}/versions/node/v7.1.0/bin:/usr/bin"
1415
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR}: expected >${EXPECTED}<, got >${NEW_PATH}<"
16+
17+
# system path precedes the nvm path: prepend instead of replacing
18+
SYSTEM_FIRST_PATH="/usr/bin:${NVM_DIR}/versions/node/v4.5.0/bin"
19+
NEW_PATH="$(nvm_change_path "${SYSTEM_FIRST_PATH}" "/bin" "${NVM_DIR}/versions/node/v7.1.0")"
20+
EXPECTED="${NVM_DIR}/versions/node/v7.1.0/bin:${SYSTEM_FIRST_PATH}"
21+
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR} (system path first): expected >${EXPECTED}<, got >${NEW_PATH}<"
22+
23+
# MANPATH call
24+
TEST_MANPATH="/opt/share/man:${NVM_DIR}/versions/node/v4.5.0/share/man:/usr/share/man"
25+
NEW_PATH="$(nvm_change_path "${TEST_MANPATH}" "/share/man" "${NVM_DIR}/versions/node/v7.1.0")"
26+
EXPECTED="/opt/share/man:${NVM_DIR}/versions/node/v7.1.0/share/man:/usr/share/man"
27+
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR} (MANPATH): expected >${EXPECTED}<, got >${NEW_PATH}<"
1528
done
1629

1730
# `.` in NVM_DIR must be escaped so it matches itself only, not "any character":
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/sh
2+
3+
die () { echo "$@" ; exit 1; }
4+
5+
: nvm.sh
6+
\. ../../../nvm.sh
7+
8+
HOME='/home/user'
9+
10+
for DIR in '/tmp/a&b/.nvm' '/tmp/a#b/.nvm' '/tmp/a[1]/.nvm' '/tmp/a*b/.nvm' '/tmp/a+(b)/.nvm' '/tmp/a.b/.nvm' '/tmp/a\b/.nvm' '/tmp/a$b/.nvm' '/tmp/a|b/.nvm'; do
11+
NVM_DIR="${DIR}"
12+
13+
OUTPUT="$(nvm_sanitize_path "${NVM_DIR}/versions/node/v4.5.0")"
14+
EXPECTED='${NVM_DIR}/versions/node/v4.5.0'
15+
[ "${OUTPUT}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR}: expected >${EXPECTED}<, got >${OUTPUT}<"
16+
17+
OUTPUT="$(nvm_sanitize_path "${NVM_DIR}")"
18+
[ "${OUTPUT}" = "${NVM_DIR}" ] || die "NVM_DIR=${DIR}: path equal to NVM_DIR should be left alone, got >${OUTPUT}<"
19+
done
20+
21+
# `.` in NVM_DIR must match itself only, not any character
22+
NVM_DIR='/tmp/a.b/.nvm'
23+
OUTPUT="$(nvm_sanitize_path '/tmp/aXb/.nvm/foo')"
24+
[ "${OUTPUT}" = '/tmp/aXb/.nvm/foo' ] || die "unrelated path was rewritten: >${OUTPUT}<"
25+
26+
# HOME is replaced too
27+
NVM_DIR='/tmp/a#b/.nvm'
28+
OUTPUT="$(nvm_sanitize_path "${HOME}/.npmrc")"
29+
[ "${OUTPUT}" = '${HOME}/.npmrc' ] || die "expected >\${HOME}/.npmrc<, got >${OUTPUT}<"

0 commit comments

Comments
 (0)