From 667e02f48d1f633f3213143d76bbf593198e8102 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Thu, 24 May 2018 12:29:49 +0200 Subject: [PATCH] Fix error wrapping stdout when redirecting In some cases (e.g. redirecting stdout to shell), the automatic determination in ZipFile constructor whether its file argument can seek() and tell() fails. stdout seems to support both but returns bogus values for tell(), resulting in negative sizes when writing the archive's end record. Provide a fix with additional arg force_wrap Also ensure that CRCs are always correct as suggested in python docs and stackoverflow --- src/zip_stream.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 47 insertions(+), 1 deletions(-) diff --git a/src/zip_stream.py b/src/zip_stream.py index 0bda371..d4adf4a 100644 --- a/src/zip_stream.py +++ b/src/zip_stream.py @@ -78,9 +78,55 @@ def _get_compressor(compress_type): return None +class BytesTellWrapper: + """ + Wrapper around a write-only stream that supports tell but not seek + + copy of zipfile._Tellable + """ + def __init__(self, fp): + self.fp = fp + self.offset = 0 + + def write(self, data): + n = self.fp.write(data) + self.offset += n + return n + + def tell(self): + return self.offset + + def flush(self): + self.fp.flush() + + def close(self): + self.fp.close() + + class ZipStream(ZipFile): """Subclass of ZipFile that supports non-seekable input and output""" + def __init__(self, file, *args, **kwargs): + """ + Create ZipStream instance which is like a ZipFile plus functions + create_zipinfo and write_stream. + + ZipFile determines whether output stream can seek() and tell(). + Unfortunately some streams (like sys.stdout.buffer when redirecting + output) seem to support these methods but only return 0 from tell. + + :param bool force_wrap: force wrapping of output stream with + BytesTellWrapper. + """ + if 'force_wrap' in kwargs and kwargs['force_wrap']: + if isstr(file): + raise ValueError('force_wrap only makes sense for streams') + del kwargs['force_wrap'] + super(ZipStream, self).__init__(BytesTellWrapper(file), *args, + **kwargs) + else: + super(ZipStream, self).__init__(file, *args, **kwargs) + def create_zipinfo(self, filename, arcname=None): """ Create ZipInfo for given file @@ -168,7 +214,7 @@ class ZipStream(ZipFile): if not buf: break file_size = file_size + len(buf) - CRC = crc32(buf, CRC) + CRC = crc32(buf, CRC) & 0xffffffff if cmpr: buf = cmpr.compress(buf) compress_size = compress_size + len(buf) -- 1.7.1