Commit 51804e97 authored by R David Murray's avatar R David Murray

#14399: zipfile now correctly handles comments added to empty zipfiles.

Patch by Serhiy Storchaka.

This also moves the TypeError that results from trying to use a unicode
comment from the 'close' step to the point at which the comment is added to
the zipfile.
parent d46d69c2
...@@ -972,6 +972,28 @@ class OtherTests(unittest.TestCase): ...@@ -972,6 +972,28 @@ class OtherTests(unittest.TestCase):
with zipfile.ZipFile(TESTFN, mode="r") as zipfr: with zipfile.ZipFile(TESTFN, mode="r") as zipfr:
self.assertEqual(zipfr.comment, comment2) self.assertEqual(zipfr.comment, comment2)
def test_unicode_comment(self):
with zipfile.ZipFile(TESTFN, "w", zipfile.ZIP_STORED) as zipf:
zipf.writestr("foo.txt", "O, for a Muse of Fire!")
with self.assertRaises(TypeError):
zipf.comment = "this is an error"
def test_change_comment_in_empty_archive(self):
with zipfile.ZipFile(TESTFN, "a", zipfile.ZIP_STORED) as zipf:
self.assertFalse(zipf.filelist)
zipf.comment = b"this is a comment"
with zipfile.ZipFile(TESTFN, "r") as zipf:
self.assertEqual(zipf.comment, b"this is a comment")
def test_change_comment_in_nonempty_archive(self):
with zipfile.ZipFile(TESTFN, "w", zipfile.ZIP_STORED) as zipf:
zipf.writestr("foo.txt", "O, for a Muse of Fire!")
with zipfile.ZipFile(TESTFN, "a", zipfile.ZIP_STORED) as zipf:
self.assertTrue(zipf.filelist)
zipf.comment = b"this is a comment"
with zipfile.ZipFile(TESTFN, "r") as zipf:
self.assertEqual(zipf.comment, b"this is a comment")
def check_testzip_with_bad_crc(self, compression): def check_testzip_with_bad_crc(self, compression):
"""Tests that files with bad CRCs return their name from testzip.""" """Tests that files with bad CRCs return their name from testzip."""
zipdata = self.zips_with_bad_crc[compression] zipdata = self.zips_with_bad_crc[compression]
......
...@@ -698,7 +698,7 @@ class ZipFile: ...@@ -698,7 +698,7 @@ class ZipFile:
self.compression = compression # Method of compression self.compression = compression # Method of compression
self.mode = key = mode.replace('b', '')[0] self.mode = key = mode.replace('b', '')[0]
self.pwd = None self.pwd = None
self.comment = b'' self._comment = b''
# Check if we were passed a file-like object # Check if we were passed a file-like object
if isinstance(file, str): if isinstance(file, str):
...@@ -774,7 +774,7 @@ class ZipFile: ...@@ -774,7 +774,7 @@ class ZipFile:
print(endrec) print(endrec)
size_cd = endrec[_ECD_SIZE] # bytes in central directory size_cd = endrec[_ECD_SIZE] # bytes in central directory
offset_cd = endrec[_ECD_OFFSET] # offset of central directory offset_cd = endrec[_ECD_OFFSET] # offset of central directory
self.comment = endrec[_ECD_COMMENT] # archive comment self._comment = endrec[_ECD_COMMENT] # archive comment
# "concat" is zero, unless zip was concatenated to another file # "concat" is zero, unless zip was concatenated to another file
concat = endrec[_ECD_LOCATION] - size_cd - offset_cd concat = endrec[_ECD_LOCATION] - size_cd - offset_cd
...@@ -886,6 +886,24 @@ class ZipFile: ...@@ -886,6 +886,24 @@ class ZipFile:
else: else:
self.pwd = None self.pwd = None
@property
def comment(self):
"""The comment text associated with the ZIP file."""
return self._comment
@comment.setter
def comment(self, comment):
if not isinstance(comment, bytes):
raise TypeError("comment: expected bytes, got %s" % type(comment))
# check for valid comment length
if len(comment) >= ZIP_MAX_COMMENT:
if self.debug:
print('Archive comment is too long; truncating to %d bytes'
% ZIP_MAX_COMMENT)
comment = comment[:ZIP_MAX_COMMENT]
self._comment = comment
self._didModify = True
def read(self, name, pwd=None): def read(self, name, pwd=None):
"""Return file bytes (as a string) for name.""" """Return file bytes (as a string) for name."""
with self.open(name, "r", pwd) as fp: with self.open(name, "r", pwd) as fp:
...@@ -1287,18 +1305,11 @@ class ZipFile: ...@@ -1287,18 +1305,11 @@ class ZipFile:
centDirSize = min(centDirSize, 0xFFFFFFFF) centDirSize = min(centDirSize, 0xFFFFFFFF)
centDirOffset = min(centDirOffset, 0xFFFFFFFF) centDirOffset = min(centDirOffset, 0xFFFFFFFF)
# check for valid comment length
if len(self.comment) >= ZIP_MAX_COMMENT:
if self.debug > 0:
msg = 'Archive comment is too long; truncating to %d bytes' \
% ZIP_MAX_COMMENT
self.comment = self.comment[:ZIP_MAX_COMMENT]
endrec = struct.pack(structEndArchive, stringEndArchive, endrec = struct.pack(structEndArchive, stringEndArchive,
0, 0, centDirCount, centDirCount, 0, 0, centDirCount, centDirCount,
centDirSize, centDirOffset, len(self.comment)) centDirSize, centDirOffset, len(self._comment))
self.fp.write(endrec) self.fp.write(endrec)
self.fp.write(self.comment) self.fp.write(self._comment)
self.fp.flush() self.fp.flush()
if not self._filePassed: if not self._filePassed:
......
...@@ -887,6 +887,7 @@ Richard Stoakley ...@@ -887,6 +887,7 @@ Richard Stoakley
Peter Stoehr Peter Stoehr
Casper Stoel Casper Stoel
Michael Stone Michael Stone
Serhiy Storchaka
Ken Stox Ken Stox
Dan Stromberg Dan Stromberg
Daniel Stutzbach Daniel Stutzbach
......
...@@ -39,6 +39,11 @@ Core and Builtins ...@@ -39,6 +39,11 @@ Core and Builtins
Library Library
------- -------
- Issue #14399: zipfile now correctly adds a comment even when the zipfile
being created is otherwise empty. In addition, the TypeError that results
from trying to set a non-binary value as a comment is now now raised at the
time the comment is set rather than at the time the zipfile is written.
- Issue #7978: socketserver now restarts the select() call when EINTR is - Issue #7978: socketserver now restarts the select() call when EINTR is
returned. This avoids crashing the server loop when a signal is received. returned. This avoids crashing the server loop when a signal is received.
Patch by Jerzy Kozera. Patch by Jerzy Kozera.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment