Skip to content

Commit 5dcdd8e

Browse files
committed
untar_file: remove common leading directory before unpacking
Fixes: pypa#12781
1 parent 300ed75 commit 5dcdd8e

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

news/12781.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix finding hardlink targets in tar files with an ignored top-level directory.

src/pip/_internal/utils/unpacking.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,19 @@ def untar_file(filename: str, location: str) -> None:
190190
else:
191191
default_mode_plus_executable = _get_default_mode_plus_executable()
192192

193-
def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo:
194-
if leading:
193+
if leading:
194+
# Strip the leading directory from all files in the archive,
195+
# including hardlink targets (which are relative to the
196+
# unpack location).
197+
for member in tar.getmembers():
198+
name_lead, name_rest = split_leading_dir(member.name)
195199
member.name = split_leading_dir(member.name)[1]
200+
if member.islnk():
201+
lnk_lead, lnk_rest = split_leading_dir(member.linkname)
202+
if lnk_lead == name_lead:
203+
member.linkname = lnk_rest
204+
205+
def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo:
196206
orig_mode = member.mode
197207
try:
198208
try:

tests/unit/test_utils_unpacking.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,49 @@ def test_unpack_tar_filter(self) -> None:
197197

198198
assert "is outside the destination" in str(e.value)
199199

200+
@pytest.mark.parametrize(
201+
("input_prefix", "unpack_prefix"),
202+
[
203+
("", ""),
204+
("dir/", ""), # pip ignores a common leading directory
205+
("dir/sub/", "sub/"), # pip ignores *one* common leading directory
206+
],
207+
)
208+
def test_unpack_tar_links(self, input_prefix: str, unpack_prefix: str) -> None:
209+
"""
210+
Test unpacking a *.tar with file containing hard & soft links
211+
"""
212+
test_tar = os.path.join(self.tempdir, "test_tar_links.tar")
213+
content = b"file content"
214+
with tarfile.open(test_tar, "w") as mytar:
215+
file_tarinfo = tarfile.TarInfo(input_prefix + "regular_file.txt")
216+
file_tarinfo.size = len(content)
217+
mytar.addfile(file_tarinfo, io.BytesIO(content))
218+
219+
hardlink_tarinfo = tarfile.TarInfo(input_prefix + "hardlink.txt")
220+
hardlink_tarinfo.type = tarfile.LNKTYPE
221+
hardlink_tarinfo.linkname = input_prefix + "regular_file.txt"
222+
mytar.addfile(hardlink_tarinfo)
223+
224+
symlink_tarinfo = tarfile.TarInfo(input_prefix + "symlink.txt")
225+
symlink_tarinfo.type = tarfile.SYMTYPE
226+
symlink_tarinfo.linkname = "regular_file.txt"
227+
mytar.addfile(symlink_tarinfo)
228+
229+
untar_file(test_tar, self.tempdir)
230+
231+
os.system(f"ls -alR {self.tempdir}")
232+
233+
unpack_dir = os.path.join(self.tempdir, unpack_prefix)
234+
with open(os.path.join(unpack_dir, "regular_file.txt"), "rb") as f:
235+
assert f.read() == content
236+
237+
with open(os.path.join(unpack_dir, "hardlink.txt"), "rb") as f:
238+
assert f.read() == content
239+
240+
with open(os.path.join(unpack_dir, "symlink.txt"), "rb") as f:
241+
assert f.read() == content
242+
200243

201244
def test_unpack_tar_unicode(tmpdir: Path) -> None:
202245
test_tar = tmpdir / "test.tar"

0 commit comments

Comments
 (0)