Commit fd7d8855 authored by Serhiy Storchaka's avatar Serhiy Storchaka

Issue #14099: Backout changeset c2c4cde55f6f (except adapted tests).

parent 5274a1f6
...@@ -186,8 +186,14 @@ ZipFile Objects ...@@ -186,8 +186,14 @@ ZipFile Objects
.. note:: .. note::
Objects returned by :meth:`.open` can operate independently of the If the ZipFile was created by passing in a file-like object as the first
ZipFile. argument to the constructor, then the object returned by :meth:`.open` shares the
ZipFile's file pointer. Under these circumstances, the object returned by
:meth:`.open` should not be used after any additional operations are performed
on the ZipFile object. If the ZipFile was created by passing in a string (the
filename) as the first argument to the constructor, then :meth:`.open` will
create a new file object that will be held by the ZipExtFile, allowing it to
operate independently of the ZipFile.
.. note:: .. note::
......
...@@ -35,21 +35,9 @@ SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'), ...@@ -35,21 +35,9 @@ SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'),
('ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), ('ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'),
('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')] ('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')]
def getrandbytes(size):
return getrandbits(8 * size).to_bytes(size, 'little')
def getrandbytes(size): def getrandbytes(size):
return bytes(bytearray.fromhex('%0*x' % (2 * size, getrandbits(8 * size)))) return bytes(bytearray.fromhex('%0*x' % (2 * size, getrandbits(8 * size))))
def get_files(test):
yield TESTFN2
with TemporaryFile() as f:
yield f
test.assertFalse(f.closed)
with io.BytesIO() as f:
yield f
test.assertFalse(f.closed)
class TestsWithSourceFile(unittest.TestCase): class TestsWithSourceFile(unittest.TestCase):
def setUp(self): def setUp(self):
self.line_gen = ["Zipfile test line %d. random float: %f" % (i, random()) self.line_gen = ["Zipfile test line %d. random float: %f" % (i, random())
...@@ -1427,9 +1415,8 @@ class TestsWithMultipleOpens(unittest.TestCase): ...@@ -1427,9 +1415,8 @@ class TestsWithMultipleOpens(unittest.TestCase):
def test_same_file(self): def test_same_file(self):
# Verify that (when the ZipFile is in control of creating file objects) # Verify that (when the ZipFile is in control of creating file objects)
# multiple open() calls can be made without interfering with each other. # multiple open() calls can be made without interfering with each other.
for f in get_files(self): self.make_test_archive(TESTFN2)
self.make_test_archive(f) with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
with zipfile.ZipFile(f, mode="r") as zipf:
with zipf.open('ones') as zopen1, zipf.open('ones') as zopen2: with zipf.open('ones') as zopen1, zipf.open('ones') as zopen2:
data1 = zopen1.read(500) data1 = zopen1.read(500)
data2 = zopen2.read(500) data2 = zopen2.read(500)
...@@ -1441,9 +1428,8 @@ class TestsWithMultipleOpens(unittest.TestCase): ...@@ -1441,9 +1428,8 @@ class TestsWithMultipleOpens(unittest.TestCase):
def test_different_file(self): def test_different_file(self):
# Verify that (when the ZipFile is in control of creating file objects) # Verify that (when the ZipFile is in control of creating file objects)
# multiple open() calls can be made without interfering with each other. # multiple open() calls can be made without interfering with each other.
for f in get_files(self): self.make_test_archive(TESTFN2)
self.make_test_archive(f) with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
with zipfile.ZipFile(f, mode="r") as zipf:
with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2: with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2:
data1 = zopen1.read(500) data1 = zopen1.read(500)
data2 = zopen2.read(500) data2 = zopen2.read(500)
...@@ -1455,9 +1441,8 @@ class TestsWithMultipleOpens(unittest.TestCase): ...@@ -1455,9 +1441,8 @@ class TestsWithMultipleOpens(unittest.TestCase):
def test_interleaved(self): def test_interleaved(self):
# Verify that (when the ZipFile is in control of creating file objects) # Verify that (when the ZipFile is in control of creating file objects)
# multiple open() calls can be made without interfering with each other. # multiple open() calls can be made without interfering with each other.
for f in get_files(self): self.make_test_archive(TESTFN2)
self.make_test_archive(f) with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
with zipfile.ZipFile(f, mode="r") as zipf:
with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2: with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2:
data1 = zopen1.read(500) data1 = zopen1.read(500)
data2 = zopen2.read(500) data2 = zopen2.read(500)
...@@ -1467,11 +1452,10 @@ class TestsWithMultipleOpens(unittest.TestCase): ...@@ -1467,11 +1452,10 @@ class TestsWithMultipleOpens(unittest.TestCase):
self.assertEqual(data2, self.data2) self.assertEqual(data2, self.data2)
def test_read_after_close(self): def test_read_after_close(self):
for f in get_files(self): self.make_test_archive(TESTFN2)
self.make_test_archive(f)
zopen1 = zopen2 = None zopen1 = zopen2 = None
try: try:
with zipfile.ZipFile(f, 'r') as zipf: with zipfile.ZipFile(TESTFN2, 'r') as zipf:
zopen1 = zipf.open('ones') zopen1 = zipf.open('ones')
zopen2 = zipf.open('twos') zopen2 = zipf.open('twos')
data1 = zopen1.read(500) data1 = zopen1.read(500)
...@@ -1487,27 +1471,25 @@ class TestsWithMultipleOpens(unittest.TestCase): ...@@ -1487,27 +1471,25 @@ class TestsWithMultipleOpens(unittest.TestCase):
self.assertEqual(data2, self.data2) self.assertEqual(data2, self.data2)
def test_read_after_write(self): def test_read_after_write(self):
for f in get_files(self): with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_DEFLATED) as zipf:
with zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.writestr('ones', self.data1) zipf.writestr('ones', self.data1)
zipf.writestr('twos', self.data2) zipf.writestr('twos', self.data2)
with zipf.open('ones') as zopen1: with zipf.open('ones') as zopen1:
data1 = zopen1.read(500) data1 = zopen1.read(500)
self.assertEqual(data1, self.data1[:500]) self.assertEqual(data1, self.data1[:500])
with zipfile.ZipFile(f, 'r') as zipf: with zipfile.ZipFile(TESTFN2, 'r') as zipf:
data1 = zipf.read('ones') data1 = zipf.read('ones')
data2 = zipf.read('twos') data2 = zipf.read('twos')
self.assertEqual(data1, self.data1) self.assertEqual(data1, self.data1)
self.assertEqual(data2, self.data2) self.assertEqual(data2, self.data2)
def test_write_after_read(self): def test_write_after_read(self):
for f in get_files(self): with zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_DEFLATED) as zipf:
with zipfile.ZipFile(f, "w", zipfile.ZIP_DEFLATED) as zipf:
zipf.writestr('ones', self.data1) zipf.writestr('ones', self.data1)
with zipf.open('ones') as zopen1: with zipf.open('ones') as zopen1:
zopen1.read(500) zopen1.read(500)
zipf.writestr('twos', self.data2) zipf.writestr('twos', self.data2)
with zipfile.ZipFile(f, 'r') as zipf: with zipfile.ZipFile(TESTFN2, 'r') as zipf:
data1 = zipf.read('ones') data1 = zipf.read('ones')
data2 = zipf.read('twos') data2 = zipf.read('twos')
self.assertEqual(data1, self.data1) self.assertEqual(data1, self.data1)
......
...@@ -498,25 +498,6 @@ compressor_names = { ...@@ -498,25 +498,6 @@ compressor_names = {
} }
class _SharedFile:
def __init__(self, file, pos, close):
self._file = file
self._pos = pos
self._close = close
def read(self, n=-1):
self._file.seek(self._pos)
data = self._file.read(n)
self._pos = self._file.tell()
return data
def close(self):
if self._file is not None:
fileobj = self._file
self._file = None
self._close(fileobj)
class ZipExtFile(io.BufferedIOBase): class ZipExtFile(io.BufferedIOBase):
"""File-like object for reading an archive member. """File-like object for reading an archive member.
Is returned by ZipFile.open(). Is returned by ZipFile.open().
...@@ -762,7 +743,7 @@ class ZipFile(object): ...@@ -762,7 +743,7 @@ class ZipFile(object):
self.NameToInfo = {} # Find file info given name self.NameToInfo = {} # Find file info given name
self.filelist = [] # List of ZipInfo instances for archive self.filelist = [] # List of ZipInfo instances for archive
self.compression = compression # Method of compression self.compression = compression # Method of compression
self.mode = mode self.mode = key = mode.replace('b', '')[0]
self.pwd = None self.pwd = None
self._comment = '' self._comment = ''
...@@ -770,33 +751,28 @@ class ZipFile(object): ...@@ -770,33 +751,28 @@ class ZipFile(object):
if isinstance(file, basestring): if isinstance(file, basestring):
self._filePassed = 0 self._filePassed = 0
self.filename = file self.filename = file
modeDict = {'r' : 'rb', 'w': 'w+b', 'a' : 'r+b', modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
'r+b': 'w+b', 'w+b': 'wb'}
filemode = modeDict[mode]
while True:
try: try:
self.fp = io.open(file, filemode) self.fp = open(file, modeDict[mode])
except IOError: except IOError:
if filemode in modeDict: if mode == 'a':
filemode = modeDict[filemode] mode = key = 'w'
continue self.fp = open(file, modeDict[mode])
else:
raise raise
break
else: else:
self._filePassed = 1 self._filePassed = 1
self.fp = file self.fp = file
self.filename = getattr(file, 'name', None) self.filename = getattr(file, 'name', None)
self._fileRefCnt = 1
try: try:
if mode == 'r': if key == 'r':
self._RealGetContents() self._RealGetContents()
elif mode == 'w': elif key == 'w':
# set the modified flag so central directory gets written # set the modified flag so central directory gets written
# even if no files are added to the archive # even if no files are added to the archive
self._didModify = True self._didModify = True
self.start_dir = 0 elif key == 'a':
elif mode == 'a':
try: try:
# See if file is a zip file # See if file is a zip file
self._RealGetContents() self._RealGetContents()
...@@ -809,13 +785,13 @@ class ZipFile(object): ...@@ -809,13 +785,13 @@ class ZipFile(object):
# set the modified flag so central directory gets written # set the modified flag so central directory gets written
# even if no files are added to the archive # even if no files are added to the archive
self._didModify = True self._didModify = True
self.start_dir = self.fp.tell()
else: else:
raise RuntimeError('Mode must be "r", "w" or "a"') raise RuntimeError('Mode must be "r", "w" or "a"')
except: except:
fp = self.fp fp = self.fp
self.fp = None self.fp = None
self._fpclose(fp) if not self._filePassed:
fp.close()
raise raise
def __enter__(self): def __enter__(self):
...@@ -966,6 +942,16 @@ class ZipFile(object): ...@@ -966,6 +942,16 @@ class ZipFile(object):
raise RuntimeError, \ raise RuntimeError, \
"Attempt to read ZIP archive that was already closed" "Attempt to read ZIP archive that was already closed"
# Only open a new file for instances where we were not
# given a file object in the constructor
if self._filePassed:
zef_file = self.fp
should_close = False
else:
zef_file = open(self.filename, 'rb')
should_close = True
try:
# Make sure we have an info object # Make sure we have an info object
if isinstance(name, ZipInfo): if isinstance(name, ZipInfo):
# 'name' is already an info object # 'name' is already an info object
...@@ -974,9 +960,8 @@ class ZipFile(object): ...@@ -974,9 +960,8 @@ class ZipFile(object):
# Get info object for name # Get info object for name
zinfo = self.getinfo(name) zinfo = self.getinfo(name)
self._fileRefCnt += 1 zef_file.seek(zinfo.header_offset, 0)
zef_file = _SharedFile(self.fp, zinfo.header_offset, self._fpclose)
try:
# Skip the file header: # Skip the file header:
fheader = zef_file.read(sizeFileHeader) fheader = zef_file.read(sizeFileHeader)
if len(fheader) != sizeFileHeader: if len(fheader) != sizeFileHeader:
...@@ -1021,8 +1006,10 @@ class ZipFile(object): ...@@ -1021,8 +1006,10 @@ class ZipFile(object):
if ord(h[11]) != check_byte: if ord(h[11]) != check_byte:
raise RuntimeError("Bad password for file", name) raise RuntimeError("Bad password for file", name)
return ZipExtFile(zef_file, mode, zinfo, zd, True) return ZipExtFile(zef_file, mode, zinfo, zd,
close_fileobj=should_close)
except: except:
if should_close:
zef_file.close() zef_file.close()
raise raise
...@@ -1154,7 +1141,6 @@ class ZipFile(object): ...@@ -1154,7 +1141,6 @@ class ZipFile(object):
zinfo.file_size = st.st_size zinfo.file_size = st.st_size
zinfo.flag_bits = 0x00 zinfo.flag_bits = 0x00
self.fp.seek(self.start_dir, 0)
zinfo.header_offset = self.fp.tell() # Start of header bytes zinfo.header_offset = self.fp.tell() # Start of header bytes
self._writecheck(zinfo) self._writecheck(zinfo)
...@@ -1168,7 +1154,6 @@ class ZipFile(object): ...@@ -1168,7 +1154,6 @@ class ZipFile(object):
self.filelist.append(zinfo) self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo self.NameToInfo[zinfo.filename] = zinfo
self.fp.write(zinfo.FileHeader(False)) self.fp.write(zinfo.FileHeader(False))
self.start_dir = self.fp.tell()
return return
with open(filename, "rb") as fp: with open(filename, "rb") as fp:
...@@ -1211,10 +1196,10 @@ class ZipFile(object): ...@@ -1211,10 +1196,10 @@ class ZipFile(object):
raise RuntimeError('Compressed size larger than uncompressed size') raise RuntimeError('Compressed size larger than uncompressed size')
# Seek backwards and write file header (which will now include # Seek backwards and write file header (which will now include
# correct CRC and file sizes) # correct CRC and file sizes)
self.start_dir = self.fp.tell() # Preserve current position in file position = self.fp.tell() # Preserve current position in file
self.fp.seek(zinfo.header_offset, 0) self.fp.seek(zinfo.header_offset, 0)
self.fp.write(zinfo.FileHeader(zip64)) self.fp.write(zinfo.FileHeader(zip64))
self.fp.seek(self.start_dir, 0) self.fp.seek(position, 0)
self.filelist.append(zinfo) self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo self.NameToInfo[zinfo.filename] = zinfo
...@@ -1243,7 +1228,6 @@ class ZipFile(object): ...@@ -1243,7 +1228,6 @@ class ZipFile(object):
zinfo.compress_type = compress_type zinfo.compress_type = compress_type
zinfo.file_size = len(bytes) # Uncompressed size zinfo.file_size = len(bytes) # Uncompressed size
self.fp.seek(self.start_dir, 0)
zinfo.header_offset = self.fp.tell() # Start of header bytes zinfo.header_offset = self.fp.tell() # Start of header bytes
self._writecheck(zinfo) self._writecheck(zinfo)
self._didModify = True self._didModify = True
...@@ -1267,7 +1251,6 @@ class ZipFile(object): ...@@ -1267,7 +1251,6 @@ class ZipFile(object):
self.fp.write(struct.pack(fmt, zinfo.CRC, zinfo.compress_size, self.fp.write(struct.pack(fmt, zinfo.CRC, zinfo.compress_size,
zinfo.file_size)) zinfo.file_size))
self.fp.flush() self.fp.flush()
self.start_dir = self.fp.tell()
self.filelist.append(zinfo) self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo self.NameToInfo[zinfo.filename] = zinfo
...@@ -1283,7 +1266,7 @@ class ZipFile(object): ...@@ -1283,7 +1266,7 @@ class ZipFile(object):
try: try:
if self.mode in ("w", "a") and self._didModify: # write ending records if self.mode in ("w", "a") and self._didModify: # write ending records
self.fp.seek(self.start_dir, 0) pos1 = self.fp.tell()
for zinfo in self.filelist: # write central directory for zinfo in self.filelist: # write central directory
dt = zinfo.date_time dt = zinfo.date_time
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
...@@ -1346,8 +1329,8 @@ class ZipFile(object): ...@@ -1346,8 +1329,8 @@ class ZipFile(object):
pos2 = self.fp.tell() pos2 = self.fp.tell()
# Write end-of-zip-archive record # Write end-of-zip-archive record
centDirCount = len(self.filelist) centDirCount = len(self.filelist)
centDirSize = pos2 - self.start_dir centDirSize = pos2 - pos1
centDirOffset = self.start_dir centDirOffset = pos1
requires_zip64 = None requires_zip64 = None
if centDirCount > ZIP_FILECOUNT_LIMIT: if centDirCount > ZIP_FILECOUNT_LIMIT:
requires_zip64 = "Files count" requires_zip64 = "Files count"
...@@ -1383,12 +1366,7 @@ class ZipFile(object): ...@@ -1383,12 +1366,7 @@ class ZipFile(object):
finally: finally:
fp = self.fp fp = self.fp
self.fp = None self.fp = None
self._fpclose(fp) if not self._filePassed:
def _fpclose(self, fp):
assert self._fileRefCnt > 0
self._fileRefCnt -= 1
if not self._fileRefCnt and not self._filePassed:
fp.close() fp.close()
......
...@@ -53,11 +53,6 @@ Library ...@@ -53,11 +53,6 @@ Library
- Issue #23016: A warning no longer produces an AttributeError when sys.stderr - Issue #23016: A warning no longer produces an AttributeError when sys.stderr
is None. is None.
- Issue #14099: ZipFile.open() no longer reopen the underlying file. Objects
returned by ZipFile.open() can now operate independently of the ZipFile even
if the ZipFile was created by passing in a file-like object as the first
argument to the constructor.
- Issue #21032. Fixed socket leak if HTTPConnection.getresponse() fails. - Issue #21032. Fixed socket leak if HTTPConnection.getresponse() fails.
Original patch by Martin Panter. Original patch by Martin Panter.
......
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