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
4 changes: 3 additions & 1 deletion Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1616,6 +1616,8 @@ def _proc_pax(self, tarfile):
except HeaderError as e:
raise SubsequentHeaderError(str(e)) from None

data_offset = next.offset_data

# Process GNU sparse information.
if "GNU.sparse.map" in pax_headers:
# GNU extended sparse format version 0.1.
Expand All @@ -1637,7 +1639,7 @@ def _proc_pax(self, tarfile):
# If the extended header replaces the size field,
# we need to recalculate the offset where the next
# header starts.
offset = next.offset + BLOCKSIZE
offset = data_offset
if next.isreg() or next.type not in SUPPORTED_TYPES:
try:
size = PAX_NUMBER_FIELDS["size"](pax_headers["size"])
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,34 @@ def test_sparse_file_10_pax_size(self):
self.assertEqual(tar.getnames(), ["sparse", "last"])
self.assertEqual(tar.extractfile("last").read(), data)

def test_pax_size_with_gnu_longname(self):
# gh-159435: a GNU long-name header between a pax header and its
# member must not make the member header get read a second time.
long_name = "long-name-" * 12 + "file.txt"

def header(name, type, size):
info = tarfile.TarInfo(name)
info.type = type
info.size = size
return info.tobuf(tarfile.USTAR_FORMAT)

def padded(data):
return data + b"\0" * (-len(data) % tarfile.BLOCKSIZE)

pax_record = b"9 size=5\n"
buf = header("././@PaxHeader", tarfile.XHDTYPE, len(pax_record))
buf += padded(pax_record)
longname = long_name.encode() + b"\0"
buf += header("././@LongLink", tarfile.GNUTYPE_LONGNAME, len(longname))
buf += padded(longname)
buf += header("member-short", tarfile.REGTYPE, 5) + padded(b"hello")
buf += header("next.txt", tarfile.REGTYPE, 3) + padded(b"bye")
buf += b"\0" * (tarfile.BLOCKSIZE * 2)

with tarfile.open(fileobj=io.BytesIO(buf)) as tar:
self.assertEqual(tar.getnames(), [long_name, "next.txt"])
self.assertEqual(tar.extractfile("next.txt").read(), b"bye")

@staticmethod
def _fs_supports_holes():
# Return True if the platform knows the st_blocks stat attribute and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix an offset calculation in :mod:`tarfile` when a PAX header with a ``size``
record is followed by a GNU long-name header. Such archives could duplicate or
skip members while being read.
Loading