Merge #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.
This commit is contained in:
parent
6125e232e2
commit
f50b38a11f
|
@ -970,6 +970,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:
|
||||||
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:
|
||||||
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:
|
||||||
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:
|
||||||
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:
|
||||||
|
|
|
@ -19,6 +19,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.
|
||||||
|
|
||||||
- trace.CoverageResults.is_ignored_filename() now ignores any name that starts
|
- trace.CoverageResults.is_ignored_filename() now ignores any name that starts
|
||||||
with "<" and ends with ">" instead of special-casing "<string>" and
|
with "<" and ends with ">" instead of special-casing "<string>" and
|
||||||
"<doctest ".
|
"<doctest ".
|
||||||
|
|
Loading…
Reference in New Issue