Skip to content
Open
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
5 changes: 4 additions & 1 deletion Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,7 @@ def create_pax_header(self, info, encoding):
# be represented in ASCII encoding.
for name, hname, length in (
("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK),
("uname", "uname", 32), ("gname", "gname", 32)):
("uname", "uname", 31), ("gname", "gname", 31)):
Comment thread
ethanfurman marked this conversation as resolved.

if hname in pax_headers:
# The pax header has priority.
Expand Down Expand Up @@ -1177,6 +1177,9 @@ def create_pax_header(self, info, encoding):
else:
buf = b""

# The owner names in the base header must be NUL-terminated.
info["uname"] = info["uname"][:31]
info["gname"] = info["gname"][:31]
return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace")

@classmethod
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2494,6 +2494,43 @@ def test_dereference_hardlink(self):

class PaxWriteTest(GNUWriteTest):

def test_100_byte_names(self):
for field, offset in (("name", 0), ("linkname", 157)):
with self.subTest(field=field):
name = "a" * 100
tarinfo = tarfile.TarInfo("test")
if field == "linkname":
tarinfo.type = tarfile.SYMTYPE
setattr(tarinfo, field, name)
header = tarinfo.tobuf(format=tarfile.PAX_FORMAT)
self.assertEqual(len(header), tarfile.BLOCKSIZE)
self.assertEqual(header[offset:offset + 100],
name.encode("ascii"))
with tarfile.open(fileobj=io.BytesIO(header)) as tar:
member = tar.next()
self.assertEqual(getattr(member, field), name)
self.assertEqual(member.pax_headers, {})

def test_owner_names(self):
for field, offset in (("uname", 265), ("gname", 297)):
for length in (0, 31, 32, 33, 40):
with self.subTest(field=field, length=length):
name = "a" * length
tarinfo = tarfile.TarInfo("test")
setattr(tarinfo, field, name)
header = tarinfo.tobuf(format=tarfile.PAX_FORMAT)
# The base header must leave room for a NUL terminator.
base_header = header[-tarfile.BLOCKSIZE:]
self.assertEqual(base_header[offset:offset + 32],
name[:31].encode().ljust(32, b"\0"))
with tarfile.open(fileobj=io.BytesIO(header)) as tar:
member = tar.next()
self.assertEqual(getattr(member, field), name)
if length > 31:
self.assertEqual(member.pax_headers[field], name)
else:
self.assertNotIn(field, member.pax_headers)

def _test(self, name, link=None):
# See GNUWriteTest.
tarinfo = tarfile.TarInfo(name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :mod:`tarfile` to NUL-terminate user and group names in PAX archive
headers and use extended headers for names longer than 31 characters.
Loading