Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b76bcc4

Browse files
Issue #14099: Backout changeset e5bb3044402b (except adapted tests).
1 parent b6c0c5b commit b76bcc4

4 files changed

Lines changed: 102 additions & 132 deletions

File tree

Doc/library/zipfile.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,14 @@ ZipFile Objects
219219

220220
.. note::
221221

222-
Objects returned by :meth:`.open` can operate independently of the
223-
ZipFile.
222+
If the ZipFile was created by passing in a file-like object as the first
223+
argument to the constructor, then the object returned by :meth:`.open` shares the
224+
ZipFile's file pointer. Under these circumstances, the object returned by
225+
:meth:`.open` should not be used after any additional operations are performed
226+
on the ZipFile object. If the ZipFile was created by passing in a string (the
227+
filename) as the first argument to the constructor, then :meth:`.open` will
228+
create a new file object that will be held by the ZipExtFile, allowing it to
229+
operate independently of the ZipFile.
224230

225231
.. note::
226232

Lib/test/test_zipfile.py

Lines changed: 55 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,85 +1653,79 @@ def make_test_archive(self, f):
16531653
def test_same_file(self):
16541654
# Verify that (when the ZipFile is in control of creating file objects)
16551655
# multiple open() calls can be made without interfering with each other.
1656-
for f in get_files(self):
1657-
self.make_test_archive(f)
1658-
with zipfile.ZipFile(f, mode="r") as zipf:
1659-
with zipf.open('ones') as zopen1, zipf.open('ones') as zopen2:
1660-
data1 = zopen1.read(500)
1661-
data2 = zopen2.read(500)
1662-
data1 += zopen1.read()
1663-
data2 += zopen2.read()
1664-
self.assertEqual(data1, data2)
1665-
self.assertEqual(data1, self.data1)
1656+
self.make_test_archive(TESTFN2)
1657+
with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
1658+
with zipf.open('ones') as zopen1, zipf.open('ones') as zopen2:
1659+
data1 = zopen1.read(500)
1660+
data2 = zopen2.read(500)
1661+
data1 += zopen1.read()
1662+
data2 += zopen2.read()
1663+
self.assertEqual(data1, data2)
1664+
self.assertEqual(data1, self.data1)
16661665

16671666
def test_different_file(self):
16681667
# Verify that (when the ZipFile is in control of creating file objects)
16691668
# multiple open() calls can be made without interfering with each other.
1670-
for f in get_files(self):
1671-
self.make_test_archive(f)
1672-
with zipfile.ZipFile(f, mode="r") as zipf:
1673-
with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2:
1674-
data1 = zopen1.read(500)
1675-
data2 = zopen2.read(500)
1676-
data1 += zopen1.read()
1677-
data2 += zopen2.read()
1678-
self.assertEqual(data1, self.data1)
1679-
self.assertEqual(data2, self.data2)
1669+
self.make_test_archive(TESTFN2)
1670+
with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
1671+
with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2:
1672+
data1 = zopen1.read(500)
1673+
data2 = zopen2.read(500)
1674+
data1 += zopen1.read()
1675+
data2 += zopen2.read()
1676+
self.assertEqual(data1, self.data1)
1677+
self.assertEqual(data2, self.data2)
16801678

16811679
def test_interleaved(self):
16821680
# Verify that (when the ZipFile is in control of creating file objects)
16831681
# multiple open() calls can be made without interfering with each other.
1684-
for f in get_files(self):
1685-
self.make_test_archive(f)
1686-
with zipfile.ZipFile(f, mode="r") as zipf:
1687-
with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2:
1688-
data1 = zopen1.read(500)
1689-
data2 = zopen2.read(500)
1690-
data1 += zopen1.read()
1691-
data2 += zopen2.read()
1692-
self.assertEqual(data1, self.data1)
1693-
self.assertEqual(data2, self.data2)
1694-
1695-
def test_read_after_close(self):
1696-
for f in get_files(self):
1697-
self.make_test_archive(f)
1698-
with contextlib.ExitStack() as stack:
1699-
with zipfile.ZipFile(f, 'r') as zipf:
1700-
zopen1 = stack.enter_context(zipf.open('ones'))
1701-
zopen2 = stack.enter_context(zipf.open('twos'))
1682+
self.make_test_archive(TESTFN2)
1683+
with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
1684+
with zipf.open('ones') as zopen1, zipf.open('twos') as zopen2:
17021685
data1 = zopen1.read(500)
17031686
data2 = zopen2.read(500)
17041687
data1 += zopen1.read()
17051688
data2 += zopen2.read()
17061689
self.assertEqual(data1, self.data1)
17071690
self.assertEqual(data2, self.data2)
17081691

1692+
def test_read_after_close(self):
1693+
self.make_test_archive(TESTFN2)
1694+
with contextlib.ExitStack() as stack:
1695+
with zipfile.ZipFile(TESTFN2, 'r') as zipf:
1696+
zopen1 = stack.enter_context(zipf.open('ones'))
1697+
zopen2 = stack.enter_context(zipf.open('twos'))
1698+
data1 = zopen1.read(500)
1699+
data2 = zopen2.read(500)
1700+
data1 += zopen1.read()
1701+
data2 += zopen2.read()
1702+
self.assertEqual(data1, self.data1)
1703+
self.assertEqual(data2, self.data2)
1704+
17091705
def test_read_after_write(self):
1710-
for f in get_files(self):
1711-
with zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED) as zipf:
1712-
zipf.writestr('ones', self.data1)
1713-
zipf.writestr('twos', self.data2)
1714-
with zipf.open('ones') as zopen1:
1715-
data1 = zopen1.read(500)
1716-
self.assertEqual(data1, self.data1[:500])
1717-
with zipfile.ZipFile(f, 'r') as zipf:
1718-
data1 = zipf.read('ones')
1719-
data2 = zipf.read('twos')
1720-
self.assertEqual(data1, self.data1)
1721-
self.assertEqual(data2, self.data2)
1706+
with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_DEFLATED) as zipf:
1707+
zipf.writestr('ones', self.data1)
1708+
zipf.writestr('twos', self.data2)
1709+
with zipf.open('ones') as zopen1:
1710+
data1 = zopen1.read(500)
1711+
self.assertEqual(data1, self.data1[:500])
1712+
with zipfile.ZipFile(TESTFN2, 'r') as zipf:
1713+
data1 = zipf.read('ones')
1714+
data2 = zipf.read('twos')
1715+
self.assertEqual(data1, self.data1)
1716+
self.assertEqual(data2, self.data2)
17221717

17231718
def test_write_after_read(self):
1724-
for f in get_files(self):
1725-
with zipfile.ZipFile(f, "w", zipfile.ZIP_DEFLATED) as zipf:
1726-
zipf.writestr('ones', self.data1)
1727-
with zipf.open('ones') as zopen1:
1728-
zopen1.read(500)
1729-
zipf.writestr('twos', self.data2)
1730-
with zipfile.ZipFile(f, 'r') as zipf:
1731-
data1 = zipf.read('ones')
1732-
data2 = zipf.read('twos')
1733-
self.assertEqual(data1, self.data1)
1734-
self.assertEqual(data2, self.data2)
1719+
with zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_DEFLATED) as zipf:
1720+
zipf.writestr('ones', self.data1)
1721+
with zipf.open('ones') as zopen1:
1722+
zopen1.read(500)
1723+
zipf.writestr('twos', self.data2)
1724+
with zipfile.ZipFile(TESTFN2, 'r') as zipf:
1725+
data1 = zipf.read('ones')
1726+
data2 = zipf.read('twos')
1727+
self.assertEqual(data1, self.data1)
1728+
self.assertEqual(data2, self.data2)
17351729

17361730
def test_many_opens(self):
17371731
# Verify that read() and open() promptly close the file descriptor,

Lib/zipfile.py

Lines changed: 39 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -624,25 +624,6 @@ def _get_decompressor(compress_type):
624624
raise NotImplementedError("compression type %d" % (compress_type,))
625625

626626

627-
class _SharedFile:
628-
def __init__(self, file, pos, close):
629-
self._file = file
630-
self._pos = pos
631-
self._close = close
632-
633-
def read(self, n=-1):
634-
self._file.seek(self._pos)
635-
data = self._file.read(n)
636-
self._pos = self._file.tell()
637-
return data
638-
639-
def close(self):
640-
if self._file is not None:
641-
fileobj = self._file
642-
self._file = None
643-
self._close(fileobj)
644-
645-
646627
class ZipExtFile(io.BufferedIOBase):
647628
"""File-like object for reading an archive member.
648629
Is returned by ZipFile.open().
@@ -928,7 +909,7 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
928909
self.NameToInfo = {} # Find file info given name
929910
self.filelist = [] # List of ZipInfo instances for archive
930911
self.compression = compression # Method of compression
931-
self.mode = mode
912+
self.mode = key = mode.replace('b', '')[0]
932913
self.pwd = None
933914
self._comment = b''
934915

@@ -937,33 +918,28 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
937918
# No, it's a filename
938919
self._filePassed = 0
939920
self.filename = file
940-
modeDict = {'r' : 'rb', 'w': 'w+b', 'a' : 'r+b',
941-
'r+b': 'w+b', 'w+b': 'wb'}
942-
filemode = modeDict[mode]
943-
while True:
944-
try:
945-
self.fp = io.open(file, filemode)
946-
except OSError:
947-
if filemode in modeDict:
948-
filemode = modeDict[filemode]
949-
continue
921+
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
922+
try:
923+
self.fp = io.open(file, modeDict[mode])
924+
except OSError:
925+
if mode == 'a':
926+
mode = key = 'w'
927+
self.fp = io.open(file, modeDict[mode])
928+
else:
950929
raise
951-
break
952930
else:
953931
self._filePassed = 1
954932
self.fp = file
955933
self.filename = getattr(file, 'name', None)
956-
self._fileRefCnt = 1
957934

958935
try:
959-
if mode == 'r':
936+
if key == 'r':
960937
self._RealGetContents()
961-
elif mode == 'w':
938+
elif key == 'w':
962939
# set the modified flag so central directory gets written
963940
# even if no files are added to the archive
964941
self._didModify = True
965-
self.start_dir = 0
966-
elif mode == 'a':
942+
elif key == 'a':
967943
try:
968944
# See if file is a zip file
969945
self._RealGetContents()
@@ -976,13 +952,13 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
976952
# set the modified flag so central directory gets written
977953
# even if no files are added to the archive
978954
self._didModify = True
979-
self.start_dir = self.fp.tell()
980955
else:
981956
raise RuntimeError('Mode must be "r", "w" or "a"')
982957
except:
983958
fp = self.fp
984959
self.fp = None
985-
self._fpclose(fp)
960+
if not self._filePassed:
961+
fp.close()
986962
raise
987963

988964
def __enter__(self):
@@ -1155,17 +1131,23 @@ def open(self, name, mode="r", pwd=None):
11551131
raise RuntimeError(
11561132
"Attempt to read ZIP archive that was already closed")
11571133

1158-
# Make sure we have an info object
1159-
if isinstance(name, ZipInfo):
1160-
# 'name' is already an info object
1161-
zinfo = name
1134+
# Only open a new file for instances where we were not
1135+
# given a file object in the constructor
1136+
if self._filePassed:
1137+
zef_file = self.fp
11621138
else:
1163-
# Get info object for name
1164-
zinfo = self.getinfo(name)
1139+
zef_file = io.open(self.filename, 'rb')
11651140

1166-
self._fileRefCnt += 1
1167-
zef_file = _SharedFile(self.fp, zinfo.header_offset, self._fpclose)
11681141
try:
1142+
# Make sure we have an info object
1143+
if isinstance(name, ZipInfo):
1144+
# 'name' is already an info object
1145+
zinfo = name
1146+
else:
1147+
# Get info object for name
1148+
zinfo = self.getinfo(name)
1149+
zef_file.seek(zinfo.header_offset, 0)
1150+
11691151
# Skip the file header:
11701152
fheader = zef_file.read(sizeFileHeader)
11711153
if len(fheader) != sizeFileHeader:
@@ -1224,9 +1206,11 @@ def open(self, name, mode="r", pwd=None):
12241206
if h[11] != check_byte:
12251207
raise RuntimeError("Bad password for file", name)
12261208

1227-
return ZipExtFile(zef_file, mode, zinfo, zd, True)
1209+
return ZipExtFile(zef_file, mode, zinfo, zd,
1210+
close_fileobj=not self._filePassed)
12281211
except:
1229-
zef_file.close()
1212+
if not self._filePassed:
1213+
zef_file.close()
12301214
raise
12311215

12321216
def extract(self, member, path=None, pwd=None):
@@ -1360,7 +1344,6 @@ def write(self, filename, arcname=None, compress_type=None):
13601344

13611345
zinfo.file_size = st.st_size
13621346
zinfo.flag_bits = 0x00
1363-
self.fp.seek(self.start_dir, 0)
13641347
zinfo.header_offset = self.fp.tell() # Start of header bytes
13651348
if zinfo.compress_type == ZIP_LZMA:
13661349
# Compressed data includes an end-of-stream (EOS) marker
@@ -1377,7 +1360,6 @@ def write(self, filename, arcname=None, compress_type=None):
13771360
self.filelist.append(zinfo)
13781361
self.NameToInfo[zinfo.filename] = zinfo
13791362
self.fp.write(zinfo.FileHeader(False))
1380-
self.start_dir = self.fp.tell()
13811363
return
13821364

13831365
cmpr = _get_compressor(zinfo.compress_type)
@@ -1416,10 +1398,10 @@ def write(self, filename, arcname=None, compress_type=None):
14161398
raise RuntimeError('Compressed size larger than uncompressed size')
14171399
# Seek backwards and write file header (which will now include
14181400
# correct CRC and file sizes)
1419-
self.start_dir = self.fp.tell() # Preserve current position in file
1401+
position = self.fp.tell() # Preserve current position in file
14201402
self.fp.seek(zinfo.header_offset, 0)
14211403
self.fp.write(zinfo.FileHeader(zip64))
1422-
self.fp.seek(self.start_dir, 0)
1404+
self.fp.seek(position, 0)
14231405
self.filelist.append(zinfo)
14241406
self.NameToInfo[zinfo.filename] = zinfo
14251407

@@ -1448,7 +1430,6 @@ def writestr(self, zinfo_or_arcname, data, compress_type=None):
14481430
"Attempt to write to ZIP archive that was already closed")
14491431

14501432
zinfo.file_size = len(data) # Uncompressed size
1451-
self.fp.seek(self.start_dir, 0)
14521433
zinfo.header_offset = self.fp.tell() # Start of header data
14531434
if compress_type is not None:
14541435
zinfo.compress_type = compress_type
@@ -1477,7 +1458,6 @@ def writestr(self, zinfo_or_arcname, data, compress_type=None):
14771458
self.fp.write(struct.pack(fmt, zinfo.CRC, zinfo.compress_size,
14781459
zinfo.file_size))
14791460
self.fp.flush()
1480-
self.start_dir = self.fp.tell()
14811461
self.filelist.append(zinfo)
14821462
self.NameToInfo[zinfo.filename] = zinfo
14831463

@@ -1493,7 +1473,7 @@ def close(self):
14931473

14941474
try:
14951475
if self.mode in ("w", "a") and self._didModify: # write ending records
1496-
self.fp.seek(self.start_dir, 0)
1476+
pos1 = self.fp.tell()
14971477
for zinfo in self.filelist: # write central directory
14981478
dt = zinfo.date_time
14991479
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
@@ -1559,8 +1539,8 @@ def close(self):
15591539
pos2 = self.fp.tell()
15601540
# Write end-of-zip-archive record
15611541
centDirCount = len(self.filelist)
1562-
centDirSize = pos2 - self.start_dir
1563-
centDirOffset = self.start_dir
1542+
centDirSize = pos2 - pos1
1543+
centDirOffset = pos1
15641544
requires_zip64 = None
15651545
if centDirCount > ZIP_FILECOUNT_LIMIT:
15661546
requires_zip64 = "Files count"
@@ -1596,13 +1576,8 @@ def close(self):
15961576
finally:
15971577
fp = self.fp
15981578
self.fp = None
1599-
self._fpclose(fp)
1600-
1601-
def _fpclose(self, fp):
1602-
assert self._fileRefCnt > 0
1603-
self._fileRefCnt -= 1
1604-
if not self._fileRefCnt and not self._filePassed:
1605-
fp.close()
1579+
if not self._filePassed:
1580+
fp.close()
16061581

16071582

16081583
class PyZipFile(ZipFile):

Misc/NEWS

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,6 @@ Library
132132
- Issue #16043: Add a default limit for the amount of data xmlrpclib.gzip_decode
133133
will return. This resolves CVE-2013-1753.
134134

135-
- Issue #14099: ZipFile.open() no longer reopen the underlying file. Objects
136-
returned by ZipFile.open() can now operate independently of the ZipFile even
137-
if the ZipFile was created by passing in a file-like object as the first
138-
argument to the constructor.
139-
140135
- Issue #22966: Fix __pycache__ pyc file name clobber when pyc_compile is
141136
asked to compile a source file containing multiple dots in the source file
142137
name.

0 commit comments

Comments
 (0)