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
6 changes: 6 additions & 0 deletions src/pip/_internal/utils/unpacking.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ def _tar_member_paths(tar):
paths = set() # type: Set[str]
for member in tar.getmembers():
name = posixpath.normpath(member.name.replace("\\", "/"))
if name == ".":
# An explicit "." / "./" member is the archive root. Record it so a
# symlink resolving to the root (e.g. "./sub/root -> ..") is found,
# as tar.getmember(".") did. The root is never added implicitly.
paths.add(name)
continue
while name not in ("", ".", "/"):
paths.add(name)
parent = posixpath.dirname(name)
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/test_utils_unpacking.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pip._internal.exceptions import InstallationError
from pip._internal.utils.unpacking import (
_tar_member_paths,
is_symlink_target_in_tar,
is_within_directory,
unpack_file,
untar_file,
Expand Down Expand Up @@ -424,6 +425,7 @@ def test_unpack_polyglot_ambiguous_rejected(tmpdir):
with pytest.raises(InstallationError):
unpack_file(archive, os.path.join(str(tmpdir), 'out'))


def test_tar_member_paths_terminates_on_doubled_slash():
"""posixpath.dirname("//") == "//"; collecting parents must not loop."""
buf = io.BytesIO()
Expand All @@ -440,3 +442,46 @@ def test_tar_member_paths_terminates_on_doubled_slash():
tar.close()
assert "//abs/f.txt" in paths
assert "//abs" in paths


def _root_link_tar(explicit_root):
# type: (bool) -> tarfile.TarFile
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w") as tar:
names = (["./"] if explicit_root else []) + ["./sub/"]
for name in names:
info = tarfile.TarInfo(name)
info.type = tarfile.DIRTYPE
tar.addfile(info)
link = tarfile.TarInfo("./sub/root")
link.type = tarfile.SYMTYPE
link.linkname = ".."
tar.addfile(link)
buf.seek(0)
return tarfile.open(fileobj=buf)


def test_symlink_to_explicit_archive_root_is_in_tar():
"""A "tar -C dir ." archive with "./sub/root -> .." keeps unpacking."""
tar = _root_link_tar(explicit_root=True)
try:
paths = _tar_member_paths(tar)
assert "." in paths
assert is_symlink_target_in_tar(
tar, tar.getmember("./sub/root"), member_paths=paths
)
finally:
tar.close()


def test_symlink_to_implicit_archive_root_is_not_in_tar():
"""Without an explicit "." member the root is not added (as upstream)."""
tar = _root_link_tar(explicit_root=False)
try:
paths = _tar_member_paths(tar)
assert "." not in paths
assert not is_symlink_target_in_tar(
tar, tar.getmember("./sub/root"), member_paths=paths
)
finally:
tar.close()
Loading