Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion nodeenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,8 @@ def download_node_src(node_url, src_dir, args):
for member in members(archive)
if re.match(rexp_string, member_name(member)) is None
]
if sys.version_info >= (3, 12):
# filter= is a tarfile-only argument, zipfile has no such option
if sys.version_info >= (3, 12) and not (is_WIN or is_CYGWIN):
archive.extractall(src_dir, extract_list, filter="data")
else:
archive.extractall(src_dir, extract_list)
Expand Down
65 changes: 61 additions & 4 deletions tests/nodeenv_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from pipes import quote as _quote
else:
from shlex import quote as _quote
import io
import os.path
import subprocess
import sys
import sysconfig
import platform
import zipfile

try:
from unittest import mock
Expand All @@ -33,10 +35,17 @@ def test_smoke(tmpdir):
'-m', 'nodeenv', '--prebuilt', nenv_path,
])
assert os.path.exists(nenv_path)
activate = _quote(os.path.join(nenv_path, 'bin', 'activate'))
subprocess.check_call([
'sh', '-c', '. {} && node --version'.format(activate),
])
if sys.platform == 'win32':
# on Windows nodeenv installs into Scripts/ and provides
# activate.bat/Activate.ps1, there is no posix activate script
subprocess.check_call([
os.path.join(nenv_path, 'Scripts', 'node.exe'), '--version',
])
else:
activate = _quote(os.path.join(nenv_path, 'bin', 'activate'))
subprocess.check_call([
'sh', '-c', '. {} && node --version'.format(activate),
])


@pytest.mark.integration
Expand Down Expand Up @@ -201,6 +210,54 @@ def test__download_node_file():
assert m_urlopen.call_count == 5


def _zip_with_node(node_version):
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr('node-v%s-win-x64/README.md' % node_version, 'readme')
zf.writestr('node-v%s-win-x64/node.exe' % node_version, 'binary')
return io.BytesIO(buf.getvalue())


def test_download_node_src_zip(tmpdir):
"""On Windows the archive is a zip, which has no extractall(filter=...)"""
class args:
node = '22.14.0'

with mock.patch.object(nodeenv, 'is_WIN', True), \
mock.patch.object(nodeenv, '_download_node_file',
return_value=_zip_with_node(args.node)):
nodeenv.download_node_src('https://dummy/node.zip',
tmpdir.strpath, args)

node_dir = os.path.join(tmpdir.strpath, 'node-v22.14.0-win-x64')
assert os.path.exists(os.path.join(node_dir, 'node.exe'))
# docs are excluded from the extract list
assert not os.path.exists(os.path.join(node_dir, 'README.md'))


def test_download_node_src_tar_keeps_data_filter(tmpdir):
"""The tar path must keep filter='data' (CVE-2007-4559 protection)"""
class args:
node = '22.14.0'

archive = mock.MagicMock()
archive.__enter__.return_value = archive
archive.getmembers.return_value = []
with mock.patch.object(nodeenv, 'is_WIN', False), \
mock.patch.object(nodeenv, 'is_CYGWIN', False), \
mock.patch.object(nodeenv, 'tarfile_open', return_value=archive), \
mock.patch.object(nodeenv, '_download_node_file',
return_value=io.BytesIO(b'')):
nodeenv.download_node_src('https://dummy/node.tar.gz',
tmpdir.strpath, args)

if sys.version_info >= (3, 12):
archive.extractall.assert_called_once_with(
tmpdir.strpath, [], filter="data")
else:
archive.extractall.assert_called_once_with(tmpdir.strpath, [])


def test_parse_version():
assert nodeenv.parse_version("v21.7") == (21, 7)
assert nodeenv.parse_version("v21.7.3") == (21, 7, 3)
Expand Down
Loading