@@ -667,6 +667,26 @@ def close(self):
667667 self ._file = None
668668 self ._close (fileobj )
669669
670+ # Provide the tell method for unseekable stream
671+ class _Tellable :
672+ def __init__ (self , fp ):
673+ self .fp = fp
674+ self .offset = 0
675+
676+ def write (self , data ):
677+ n = self .fp .write (data )
678+ self .offset += n
679+ return n
680+
681+ def tell (self ):
682+ return self .offset
683+
684+ def flush (self ):
685+ self .fp .flush ()
686+
687+ def close (self ):
688+ self .fp .close ()
689+
670690
671691class ZipExtFile (io .BufferedIOBase ):
672692 """File-like object for reading an archive member.
@@ -994,6 +1014,7 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
9941014 self .filename = getattr (file , 'name' , None )
9951015 self ._fileRefCnt = 1
9961016 self ._lock = threading .RLock ()
1017+ self ._seekable = True
9971018
9981019 try :
9991020 if mode == 'r' :
@@ -1002,13 +1023,24 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
10021023 # set the modified flag so central directory gets written
10031024 # even if no files are added to the archive
10041025 self ._didModify = True
1005- self .start_dir = self .fp .tell ()
1026+ try :
1027+ self .start_dir = self .fp .tell ()
1028+ except (AttributeError , OSError ):
1029+ self .fp = _Tellable (self .fp )
1030+ self .start_dir = 0
1031+ self ._seekable = False
1032+ else :
1033+ # Some file-like objects can provide tell() but not seek()
1034+ try :
1035+ self .fp .seek (self .start_dir )
1036+ except (AttributeError , OSError ):
1037+ self ._seekable = False
10061038 elif mode == 'a' :
10071039 try :
10081040 # See if file is a zip file
10091041 self ._RealGetContents ()
10101042 # seek to start of directory and overwrite
1011- self .fp .seek (self .start_dir , 0 )
1043+ self .fp .seek (self .start_dir )
10121044 except BadZipFile :
10131045 # file is not a zip file, just append
10141046 self .fp .seek (0 , 2 )
@@ -1415,7 +1447,8 @@ def write(self, filename, arcname=None, compress_type=None):
14151447 zinfo .file_size = st .st_size
14161448 zinfo .flag_bits = 0x00
14171449 with self ._lock :
1418- self .fp .seek (self .start_dir , 0 )
1450+ if self ._seekable :
1451+ self .fp .seek (self .start_dir )
14191452 zinfo .header_offset = self .fp .tell () # Start of header bytes
14201453 if zinfo .compress_type == ZIP_LZMA :
14211454 # Compressed data includes an end-of-stream (EOS) marker
@@ -1436,6 +1469,8 @@ def write(self, filename, arcname=None, compress_type=None):
14361469 return
14371470
14381471 cmpr = _get_compressor (zinfo .compress_type )
1472+ if not self ._seekable :
1473+ zinfo .flag_bits |= 0x08
14391474 with open (filename , "rb" ) as fp :
14401475 # Must overwrite CRC and sizes with correct data later
14411476 zinfo .CRC = CRC = 0
@@ -1464,17 +1499,24 @@ def write(self, filename, arcname=None, compress_type=None):
14641499 zinfo .compress_size = file_size
14651500 zinfo .CRC = CRC
14661501 zinfo .file_size = file_size
1467- if not zip64 and self ._allowZip64 :
1468- if file_size > ZIP64_LIMIT :
1469- raise RuntimeError ('File size has increased during compressing' )
1470- if compress_size > ZIP64_LIMIT :
1471- raise RuntimeError ('Compressed size larger than uncompressed size' )
1472- # Seek backwards and write file header (which will now include
1473- # correct CRC and file sizes)
1474- self .start_dir = self .fp .tell () # Preserve current position in file
1475- self .fp .seek (zinfo .header_offset , 0 )
1476- self .fp .write (zinfo .FileHeader (zip64 ))
1477- self .fp .seek (self .start_dir , 0 )
1502+ if zinfo .flag_bits & 0x08 :
1503+ # Write CRC and file sizes after the file data
1504+ fmt = '<LQQ' if zip64 else '<LLL'
1505+ self .fp .write (struct .pack (fmt , zinfo .CRC , zinfo .compress_size ,
1506+ zinfo .file_size ))
1507+ self .start_dir = self .fp .tell ()
1508+ else :
1509+ if not zip64 and self ._allowZip64 :
1510+ if file_size > ZIP64_LIMIT :
1511+ raise RuntimeError ('File size has increased during compressing' )
1512+ if compress_size > ZIP64_LIMIT :
1513+ raise RuntimeError ('Compressed size larger than uncompressed size' )
1514+ # Seek backwards and write file header (which will now include
1515+ # correct CRC and file sizes)
1516+ self .start_dir = self .fp .tell () # Preserve current position in file
1517+ self .fp .seek (zinfo .header_offset )
1518+ self .fp .write (zinfo .FileHeader (zip64 ))
1519+ self .fp .seek (self .start_dir )
14781520 self .filelist .append (zinfo )
14791521 self .NameToInfo [zinfo .filename ] = zinfo
14801522
@@ -1504,11 +1546,8 @@ def writestr(self, zinfo_or_arcname, data, compress_type=None):
15041546
15051547 zinfo .file_size = len (data ) # Uncompressed size
15061548 with self ._lock :
1507- try :
1549+ if self . _seekable :
15081550 self .fp .seek (self .start_dir )
1509- except (AttributeError , io .UnsupportedOperation ):
1510- # Some file-like objects can provide tell() but not seek()
1511- pass
15121551 zinfo .header_offset = self .fp .tell () # Start of header data
15131552 if compress_type is not None :
15141553 zinfo .compress_type = compress_type
@@ -1557,11 +1596,8 @@ def close(self):
15571596 try :
15581597 if self .mode in ("w" , "a" ) and self ._didModify : # write ending records
15591598 with self ._lock :
1560- try :
1599+ if self . _seekable :
15611600 self .fp .seek (self .start_dir )
1562- except (AttributeError , io .UnsupportedOperation ):
1563- # Some file-like objects can provide tell() but not seek()
1564- pass
15651601 self ._write_end_record ()
15661602 finally :
15671603 fp = self .fp
0 commit comments