Index: Lib/zipfile.py =================================================================== --- Lib/zipfile.py (révision 83908) +++ Lib/zipfile.py (copie de travail) @@ -492,6 +492,17 @@ self.mode = mode self.name = zipinfo.filename + if hasattr(zipinfo, 'CRC'): + self._expected_crc = zipinfo.CRC + else: + self._expected_crc = None + self._file_size = zipinfo.file_size + self._running_crc = crc32(b'') & 0xffffffff + # Current offset for running CRC calculation inside _readbuffer + self._crc_offset = 0 + # Number of bytes already fed to CRC calculation + self._crc_count = 0 + def readline(self, limit=-1): """Read and return a line from the stream. @@ -569,6 +580,26 @@ return buf + def _update_crc(self): + # Update the CRC until the current offset in _readbuffer. + if self._expected_crc is None: + # No need to compute the CRC if we don't have a reference value + return + n = self._offset - self._crc_offset + assert n >= 0 + if n > 0: + self._running_crc = crc32( + self._readbuffer[self._crc_offset:self._offset], + self._running_crc) & 0xffffffff + self._crc_count += n + self._crc_offset = self._offset + + # Check the CRC if we're at the end of the file + assert self._crc_count <= self._file_size + if (self._crc_count == self._file_size and + self._running_crc != self._expected_crc): + raise BadZipfile("Bad CRC-32 for file %r" % self.name) + def read1(self, n): """Read up to n bytes with at most one read() system call.""" @@ -592,8 +623,9 @@ data = bytes(map(self._decrypter, data)) if self._compress_type == ZIP_STORED: + self._update_crc() self._readbuffer = self._readbuffer[self._offset:] + data - self._offset = 0 + self._crc_offset = self._offset = 0 else: # Prepare deflated bytes for decompression. self._unconsumed += data @@ -610,12 +642,15 @@ if len(self._unconsumed) == 0 and self._compress_left == 0: data += self._decompressor.flush() + self._update_crc() self._readbuffer = self._readbuffer[self._offset:] + data - self._offset = 0 + self._crc_offset = self._offset = 0 # Read from buffer. data = self._readbuffer[self._offset: self._offset + n] self._offset += len(data) + # Raise an error if bad CRC at end of file + self._update_crc() return data @@ -1380,7 +1415,9 @@ print(USAGE) sys.exit(1) zf = ZipFile(args[1], 'r') - zf.testzip() + badfile = zf.testzip() + if badfile: + print("The following enclosed file is corrupted: {!r}".format(badfile)) print("Done testing") elif args[0] == '-e': Index: Lib/test/test_zipfile.py =================================================================== --- Lib/test/test_zipfile.py (révision 83908) +++ Lib/test/test_zipfile.py (copie de travail) @@ -662,6 +662,16 @@ class OtherTests(unittest.TestCase): + zip_with_bad_crc = ( + b'PK\003\004\024\0\0\0\0\0 \213\212;:r' + b'\253\377\f\0\0\0\f\0\0\0\005\0\0\000af' + b'ilehello,AworldP' + b'K\001\002\024\003\024\0\0\0\0\0 \213\212;:' + b'r\253\377\f\0\0\0\f\0\0\0\005\0\0\0\0' + b'\0\0\0\0\0\0\0\200\001\0\0\0\000afi' + b'lePK\005\006\0\0\0\0\001\0\001\0003\000' + b'\0\0/\0\0\0\0\0') + def test_unicode_filenames(self): with zipfile.ZipFile(TESTFN, "w") as zf: zf.writestr("foo.txt", "Test for unicode filename") @@ -875,6 +885,32 @@ with zipfile.ZipFile(TESTFN, mode="r") as zipfr: self.assertEqual(zipfr.comment, comment2) + def test_testzip_with_bad_crc(self): + """Tests that files with bad CRCs return their name from testzip.""" + with open(TESTFN, 'wb') as fp: + fp.write(self.zip_with_bad_crc) + + with zipfile.ZipFile(TESTFN, mode="r") as zipf: + # testzip returns the name of the first corrupt file, or None + self.assertEqual('afile', zipf.testzip()) + + def test_read_with_bad_crc(self): + """Tests that files with bad CRCs raise a BadZipfile exception when read.""" + with open(TESTFN, 'wb') as fp: + fp.write(self.zip_with_bad_crc) + + with zipfile.ZipFile(TESTFN, mode="r") as zipf: + with zipf.open('afile', 'r') as corrupt_file: + self.assertRaises(zipfile.BadZipfile, corrupt_file.read) + + # Same with small reads (in order to exercise the buffering logic) + with zipfile.ZipFile(TESTFN, mode="r") as zipf: + with zipf.open('afile', 'r') as corrupt_file: + corrupt_file.MIN_READ_SIZE = 2 + with self.assertRaises(zipfile.BadZipfile): + while corrupt_file.read(2): + pass + def tearDown(self): unlink(TESTFN) unlink(TESTFN2) @@ -974,6 +1010,11 @@ for f in (TESTFN2, TemporaryFile(), io.BytesIO()): self.zip_test(f, zipfile.ZIP_STORED) + @skipUnless(zlib, "requires zlib") + def test_deflated(self): + for f in (TESTFN2, TemporaryFile(), io.BytesIO()): + self.zip_test(f, zipfile.ZIP_DEFLATED) + def zip_open_test(self, f, compression): self.make_test_archive(f, compression) @@ -1007,6 +1048,11 @@ for f in (TESTFN2, TemporaryFile(), io.BytesIO()): self.zip_open_test(f, zipfile.ZIP_STORED) + @skipUnless(zlib, "requires zlib") + def test_open_deflated(self): + for f in (TESTFN2, TemporaryFile(), io.BytesIO()): + self.zip_open_test(f, zipfile.ZIP_DEFLATED) + def zip_random_open_test(self, f, compression): self.make_test_archive(f, compression) @@ -1028,7 +1074,12 @@ for f in (TESTFN2, TemporaryFile(), io.BytesIO()): self.zip_random_open_test(f, zipfile.ZIP_STORED) + @skipUnless(zlib, "requires zlib") + def test_random_open_deflated(self): + for f in (TESTFN2, TemporaryFile(), io.BytesIO()): + self.zip_random_open_test(f, zipfile.ZIP_DEFLATED) + @skipUnless(zlib, "requires zlib") class TestsWithMultipleOpens(unittest.TestCase): def setUp(self):