697 lines
19 KiB
Python
697 lines
19 KiB
Python
"""
|
|
Java zip standard library APIs implemented in Python.
|
|
java.util.zip package - ZipFile, ZipEntry, GZIP, CRC, Deflater, Inflater.
|
|
"""
|
|
|
|
from .._core import Stub
|
|
from .io import IOException, Closeable, Flushable
|
|
import zipfile as _zipfile
|
|
import gzip as _gzip
|
|
import zlib as _zlib
|
|
import io as _pyio
|
|
import os as _os
|
|
from datetime import datetime as _datetime
|
|
|
|
__all__ = [
|
|
"ZipFile", "ZipEntry", "ZipOutputStream", "ZipInputStream",
|
|
"GZIPInputStream", "GZIPOutputStream",
|
|
"CRC32", "CheckedInputStream", "CheckedOutputStream",
|
|
"Adler32",
|
|
"DataFormatException", "ZipException",
|
|
"Deflater", "Inflater",
|
|
"DeflaterOutputStream", "InflaterInputStream",
|
|
]
|
|
|
|
|
|
# ============================================================
|
|
# Exception classes
|
|
# ============================================================
|
|
|
|
class ZipException(IOException):
|
|
pass
|
|
|
|
class DataFormatException(Exception):
|
|
pass
|
|
|
|
|
|
# ============================================================
|
|
# CRC32
|
|
# ============================================================
|
|
|
|
class CRC32(Stub):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._value = 0
|
|
|
|
def getValue(self):
|
|
return self._value
|
|
|
|
def reset(self):
|
|
self._value = 0
|
|
|
|
def update(self, b, off=None, length=None):
|
|
if isinstance(b, int):
|
|
self._value = _zlib.crc32(bytes([b & 0xFF]), self._value) & 0xFFFFFFFF
|
|
elif isinstance(b, (bytes, bytearray)):
|
|
if off is not None and length is not None:
|
|
self._value = _zlib.crc32(bytes(b[off:off+length]), self._value) & 0xFFFFFFFF
|
|
else:
|
|
self._value = _zlib.crc32(bytes(b), self._value) & 0xFFFFFFFF
|
|
|
|
|
|
# ============================================================
|
|
# Adler32
|
|
# ============================================================
|
|
|
|
class Adler32(Stub):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._value = 1
|
|
|
|
def getValue(self):
|
|
return self._value
|
|
|
|
def reset(self):
|
|
self._value = 1
|
|
|
|
def update(self, b, off=None, length=None):
|
|
if isinstance(b, int):
|
|
self._value = _zlib.adler32(bytes([b & 0xFF]), self._value) & 0xFFFFFFFF
|
|
elif isinstance(b, (bytes, bytearray)):
|
|
if off is not None and length is not None:
|
|
self._value = _zlib.adler32(bytes(b[off:off+length]), self._value) & 0xFFFFFFFF
|
|
else:
|
|
self._value = _zlib.adler32(bytes(b), self._value) & 0xFFFFFFFF
|
|
|
|
|
|
# ============================================================
|
|
# CheckedInputStream / CheckedOutputStream
|
|
# ============================================================
|
|
|
|
class CheckedInputStream(Closeable):
|
|
def __init__(self, inp, cksum):
|
|
super().__init__()
|
|
self._in = inp
|
|
self._cksum = cksum
|
|
|
|
def read(self, b=None, off=None, length=None):
|
|
if b is None:
|
|
result = self._in.read()
|
|
if result >= 0:
|
|
self._cksum.update(result)
|
|
return result
|
|
if off is None:
|
|
off = 0
|
|
if length is None:
|
|
length = len(b)
|
|
result = self._in.read(b, off, length)
|
|
if result > 0:
|
|
self._cksum.update(b[off:off+result])
|
|
return result
|
|
|
|
def getChecksum(self):
|
|
return self._cksum
|
|
|
|
def close(self):
|
|
self._in.close()
|
|
|
|
def skip(self, n):
|
|
return self._in.skip(n)
|
|
|
|
|
|
class CheckedOutputStream(Closeable, Flushable):
|
|
def __init__(self, out, cksum):
|
|
super().__init__()
|
|
self._out = out
|
|
self._cksum = cksum
|
|
|
|
def write(self, b, off=None, length=None):
|
|
if isinstance(b, int):
|
|
self._out.write(b)
|
|
self._cksum.update(b)
|
|
else:
|
|
if off is not None and length is not None:
|
|
data = bytes(b[off:off+length])
|
|
else:
|
|
data = bytes(b)
|
|
self._out.write(data)
|
|
self._cksum.update(data)
|
|
|
|
def getChecksum(self):
|
|
return self._cksum
|
|
|
|
def flush(self):
|
|
self._out.flush()
|
|
|
|
def close(self):
|
|
self._out.close()
|
|
|
|
|
|
# ============================================================
|
|
# ZipEntry
|
|
# ============================================================
|
|
|
|
class ZipEntry(Stub):
|
|
STORED = 0
|
|
DEFLATED = 8
|
|
|
|
def __init__(self, name):
|
|
super().__init__()
|
|
self._name = name
|
|
self._method = ZipEntry.DEFLATED
|
|
self._size = 0
|
|
self._compressed_size = 0
|
|
self._crc = 0
|
|
self._comment = ""
|
|
self._time = 0
|
|
self._extra = b""
|
|
self._is_directory = name.endswith("/")
|
|
|
|
def getName(self):
|
|
return self._name
|
|
|
|
def getMethod(self):
|
|
return self._method
|
|
|
|
def setMethod(self, method):
|
|
self._method = method
|
|
|
|
def getSize(self):
|
|
return self._size
|
|
|
|
def setSize(self, size):
|
|
self._size = size
|
|
|
|
def getCompressedSize(self):
|
|
return self._compressed_size
|
|
|
|
def setCompressedSize(self, size):
|
|
self._compressed_size = size
|
|
|
|
def getCrc(self):
|
|
return self._crc
|
|
|
|
def setCrc(self, crc):
|
|
self._crc = crc
|
|
|
|
def getComment(self):
|
|
return self._comment
|
|
|
|
def setComment(self, comment):
|
|
self._comment = comment
|
|
|
|
def getTime(self):
|
|
return self._time
|
|
|
|
def setTime(self, time):
|
|
self._time = time
|
|
|
|
def getExtra(self):
|
|
return self._extra
|
|
|
|
def setExtra(self, extra):
|
|
self._extra = extra
|
|
|
|
def isDirectory(self):
|
|
return self._is_directory
|
|
|
|
def toString(self):
|
|
return self._name
|
|
|
|
def __str__(self):
|
|
return self._name
|
|
|
|
|
|
# ============================================================
|
|
# ZipFile
|
|
# ============================================================
|
|
|
|
class ZipFile(Closeable):
|
|
OPEN_READ = 1
|
|
OPEN_DELETE = 2
|
|
|
|
def __init__(self, file, mode="r"):
|
|
super().__init__()
|
|
if isinstance(file, str):
|
|
path = file
|
|
else:
|
|
path = file.getPath()
|
|
self._path = path
|
|
self._mode = mode
|
|
try:
|
|
self._zfile = _zipfile.ZipFile(path, mode)
|
|
except Exception as e:
|
|
raise ZipException(str(e))
|
|
|
|
def entries(self):
|
|
return iter(self._zfile.infolist())
|
|
|
|
def getEntry(self, name):
|
|
try:
|
|
info = self._zfile.getinfo(name)
|
|
entry = ZipEntry(info.filename)
|
|
entry.setMethod(info.compress_type)
|
|
entry.setSize(info.file_size)
|
|
entry.setCompressedSize(info.compress_size)
|
|
entry.setCrc(info.CRC)
|
|
entry.setTime(int(_datetime(*info.date_time).timestamp() * 1000) if info.date_time else 0)
|
|
return entry
|
|
except KeyError:
|
|
return None
|
|
|
|
def getInputStream(self, entry):
|
|
try:
|
|
data = self._zfile.read(entry.getName() if isinstance(entry, ZipEntry) else entry)
|
|
from ..io import ByteArrayInputStream
|
|
return ByteArrayInputStream(data)
|
|
except Exception as e:
|
|
raise ZipException(str(e))
|
|
|
|
def close(self):
|
|
self._zfile.close()
|
|
|
|
def size(self):
|
|
return len(self._zfile.namelist())
|
|
|
|
def getName(self):
|
|
return self._path
|
|
|
|
def getComment(self):
|
|
return ""
|
|
|
|
|
|
# ============================================================
|
|
# ZipOutputStream
|
|
# ============================================================
|
|
|
|
class ZipOutputStream(Closeable, Flushable):
|
|
STORED = 0
|
|
DEFLATED = 8
|
|
|
|
def __init__(self, out):
|
|
super().__init__()
|
|
self._out = out
|
|
self._zfile = _zipfile.ZipFile(out, "w", _zipfile.ZIP_DEFLATED)
|
|
self._method = ZipOutputStream.DEFLATED
|
|
self._level = -1
|
|
self._comment = ""
|
|
|
|
def putNextEntry(self, entry):
|
|
zi = _zipfile.ZipInfo(entry.getName() if isinstance(entry, ZipEntry) else str(entry))
|
|
if hasattr(entry, 'getTime') and entry.getTime():
|
|
dt = _datetime.fromtimestamp(entry.getTime() / 1000.0)
|
|
zi.date_time = dt.timetuple()[:6]
|
|
self._zfile.writestr(zi, b"")
|
|
|
|
def closeEntry(self):
|
|
pass
|
|
|
|
def write(self, b, off=None, length=None):
|
|
if off is not None and length is not None:
|
|
self._zfile._zout.write(bytes(b[off:off+length]))
|
|
else:
|
|
self._zfile._zout.write(bytes(b) if not isinstance(b, (bytes, bytearray)) else b)
|
|
|
|
def finish(self):
|
|
self._zfile.close()
|
|
|
|
def close(self):
|
|
self._zfile.close()
|
|
|
|
def setMethod(self, method):
|
|
self._method = method
|
|
|
|
def setLevel(self, level):
|
|
self._level = level
|
|
|
|
def setComment(self, comment):
|
|
self._comment = comment
|
|
|
|
def flush(self):
|
|
self._zfile._zout.flush() if hasattr(self._zfile._zout, 'flush') else None
|
|
|
|
|
|
# ============================================================
|
|
# ZipInputStream
|
|
# ============================================================
|
|
|
|
class ZipInputStream(Closeable):
|
|
def __init__(self, inp):
|
|
super().__init__()
|
|
self._inp = inp
|
|
self._zfile = _zipfile.ZipFile(inp)
|
|
self._entries = iter(self._zfile.infolist())
|
|
self._current_entry = None
|
|
self._current_data = b""
|
|
self._current_pos = 0
|
|
|
|
def getNextEntry(self):
|
|
try:
|
|
info = next(self._entries)
|
|
entry = ZipEntry(info.filename)
|
|
entry.setMethod(info.compress_type)
|
|
entry.setSize(info.file_size)
|
|
entry.setCompressedSize(info.compress_size)
|
|
entry.setCrc(info.CRC)
|
|
self._current_entry = info
|
|
self._current_data = self._zfile.read(info)
|
|
self._current_pos = 0
|
|
return entry
|
|
except StopIteration:
|
|
self._current_entry = None
|
|
self._current_data = b""
|
|
return None
|
|
|
|
def read(self, b=None, off=None, length=None):
|
|
if self._current_entry is None:
|
|
return -1
|
|
avail = len(self._current_data) - self._current_pos
|
|
if avail <= 0:
|
|
return -1
|
|
if b is None:
|
|
val = self._current_data[self._current_pos]
|
|
self._current_pos += 1
|
|
return val
|
|
if off is None:
|
|
off = 0
|
|
if length is None:
|
|
length = len(b)
|
|
n = min(length, avail)
|
|
if isinstance(b, bytearray):
|
|
b[off:off+n] = self._current_data[self._current_pos:self._current_pos+n]
|
|
self._current_pos += n
|
|
return n
|
|
|
|
def closeEntry(self):
|
|
self._current_entry = None
|
|
self._current_data = b""
|
|
|
|
def available(self):
|
|
if self._current_entry is None:
|
|
return 0
|
|
return len(self._current_data) - self._current_pos
|
|
|
|
def close(self):
|
|
self._zfile.close()
|
|
|
|
|
|
# ============================================================
|
|
# GZIPInputStream
|
|
# ============================================================
|
|
|
|
class GZIPInputStream(Closeable):
|
|
def __init__(self, inp, size=512):
|
|
super().__init__()
|
|
self._inp = inp
|
|
self._gzip_file = _gzip.GzipFile(fileobj=_pyio.BufferedReader(inp) if hasattr(inp, 'read') else inp)
|
|
|
|
def read(self, b=None, off=None, length=None):
|
|
if b is None:
|
|
data = self._gzip_file.read(1)
|
|
return data[0] if data else -1
|
|
if off is None:
|
|
off = 0
|
|
if length is None:
|
|
length = len(b)
|
|
data = self._gzip_file.read(length)
|
|
if not data:
|
|
return -1
|
|
n = len(data)
|
|
if isinstance(b, bytearray):
|
|
b[off:off+n] = data
|
|
else:
|
|
ba = bytearray(b)
|
|
ba[off:off+n] = data
|
|
b[:] = bytes(ba)
|
|
return n
|
|
|
|
def close(self):
|
|
self._gzip_file.close()
|
|
|
|
|
|
# ============================================================
|
|
# GZIPOutputStream
|
|
# ============================================================
|
|
|
|
class GZIPOutputStream(Closeable, Flushable):
|
|
def __init__(self, out, size=512):
|
|
super().__init__()
|
|
self._out = out
|
|
self._gzip_file = _gzip.GzipFile(fileobj=out, mode="wb")
|
|
|
|
def write(self, b, off=None, length=None):
|
|
if isinstance(b, int):
|
|
self._gzip_file.write(bytes([b & 0xFF]))
|
|
elif isinstance(b, (bytes, bytearray)):
|
|
if off is not None and length is not None:
|
|
self._gzip_file.write(bytes(b[off:off+length]))
|
|
else:
|
|
self._gzip_file.write(bytes(b))
|
|
|
|
def finish(self):
|
|
self._gzip_file.close()
|
|
|
|
def close(self):
|
|
self._gzip_file.close()
|
|
|
|
def flush(self):
|
|
self._gzip_file.flush()
|
|
|
|
|
|
# ============================================================
|
|
# Deflater
|
|
# ============================================================
|
|
|
|
class Deflater(Stub):
|
|
BEST_COMPRESSION = 9
|
|
BEST_SPEED = 1
|
|
DEFAULT_COMPRESSION = -1
|
|
DEFAULT_STRATEGY = 0
|
|
FILTERED = 1
|
|
HUFFMAN_ONLY = 2
|
|
NO_COMPRESSION = 0
|
|
NO_FLUSH = 0
|
|
SYNC_FLUSH = 2
|
|
FULL_FLUSH = 3
|
|
|
|
def __init__(self, level=-1, nowrap=False):
|
|
super().__init__()
|
|
self._level = level
|
|
self._nowrap = nowrap
|
|
self._compress = _zlib.compressobj(level=level if level >= 0 else 6,
|
|
method=_zlib.DEFLATED,
|
|
wbits=-_zlib.MAX_WBITS if nowrap else _zlib.MAX_WBITS)
|
|
self._input_buffer = b""
|
|
self._finished = False
|
|
|
|
def setInput(self, data, off=0, length=-1):
|
|
if length < 0:
|
|
length = len(data) - off
|
|
self._input_buffer = bytes(data[off:off+length])
|
|
|
|
def needsInput(self):
|
|
return len(self._input_buffer) == 0
|
|
|
|
def setDictionary(self, dictionary):
|
|
pass
|
|
|
|
def getTotalIn(self):
|
|
return len(self._input_buffer)
|
|
|
|
def getTotalOut(self):
|
|
return 0
|
|
|
|
def deflate(self, output, off=0, length=-1):
|
|
if length < 0:
|
|
length = len(output) - off
|
|
data = self._compress.compress(self._input_buffer)
|
|
self._input_buffer = b""
|
|
n = min(len(data), length)
|
|
if isinstance(output, bytearray):
|
|
output[off:off+n] = data[:n]
|
|
else:
|
|
ba = bytearray(output)
|
|
ba[off:off+n] = data[:n]
|
|
output[:] = bytes(ba)
|
|
return n
|
|
|
|
def finish(self):
|
|
self._finished = True
|
|
|
|
def finished(self):
|
|
return self._finished
|
|
|
|
def end(self):
|
|
pass
|
|
|
|
def getAdler(self):
|
|
return 1
|
|
|
|
def reset(self):
|
|
self._compress = _zlib.compressobj(level=self._level if self._level >= 0 else 6,
|
|
method=_zlib.DEFLATED,
|
|
wbits=-_zlib.MAX_WBITS if self._nowrap else _zlib.MAX_WBITS)
|
|
self._input_buffer = b""
|
|
self._finished = False
|
|
|
|
|
|
# ============================================================
|
|
# Inflater
|
|
# ============================================================
|
|
|
|
class Inflater(Stub):
|
|
def __init__(self, nowrap=False):
|
|
super().__init__()
|
|
self._nowrap = nowrap
|
|
wbits = -_zlib.MAX_WBITS if nowrap else _zlib.MAX_WBITS
|
|
self._decompress = _zlib.decompressobj(wbits)
|
|
self._input_buffer = b""
|
|
self._finished = False
|
|
|
|
def setInput(self, data, off=0, length=-1):
|
|
if length < 0:
|
|
length = len(data) - off
|
|
self._input_buffer = bytes(data[off:off+length])
|
|
|
|
def needsInput(self):
|
|
return len(self._input_buffer) == 0
|
|
|
|
def setDictionary(self, dictionary):
|
|
pass
|
|
|
|
def getTotalIn(self):
|
|
return len(self._input_buffer)
|
|
|
|
def getTotalOut(self):
|
|
return 0
|
|
|
|
def inflate(self, output, off=0, length=-1):
|
|
if length < 0:
|
|
length = len(output) - off
|
|
try:
|
|
data = self._decompress.decompress(self._input_buffer)
|
|
self._input_buffer = b""
|
|
n = min(len(data), length)
|
|
if isinstance(output, bytearray):
|
|
output[off:off+n] = data[:n]
|
|
else:
|
|
ba = bytearray(output)
|
|
ba[off:off+n] = data[:n]
|
|
output[:] = bytes(ba)
|
|
return n
|
|
except _zlib.error as e:
|
|
raise DataFormatException(str(e))
|
|
|
|
def finished(self):
|
|
return self._finished
|
|
|
|
def end(self):
|
|
pass
|
|
|
|
def getAdler(self):
|
|
return 1
|
|
|
|
def needsDictionary(self):
|
|
return False
|
|
|
|
def reset(self):
|
|
wbits = -_zlib.MAX_WBITS if self._nowrap else _zlib.MAX_WBITS
|
|
self._decompress = _zlib.decompressobj(wbits)
|
|
self._input_buffer = b""
|
|
self._finished = False
|
|
|
|
|
|
# ============================================================
|
|
# DeflaterOutputStream
|
|
# ============================================================
|
|
|
|
class DeflaterOutputStream(Closeable, Flushable):
|
|
def __init__(self, out, deflater=None, size=512):
|
|
super().__init__()
|
|
self._out = out
|
|
self._deflater = deflater or Deflater()
|
|
self._buffer = bytearray(size)
|
|
|
|
def write(self, b, off=None, length=None):
|
|
if isinstance(b, int):
|
|
self._out.write(bytes([b & 0xFF]))
|
|
else:
|
|
if off is not None and length is not None:
|
|
self._out.write(bytes(b[off:off+length]))
|
|
else:
|
|
self._deflater.setInput(bytes(b) if isinstance(b, (bytes, bytearray)) else bytes(b))
|
|
while not self._deflater.needsInput():
|
|
n = self._deflater.deflate(self._buffer)
|
|
if n > 0:
|
|
self._out.write(bytes(self._buffer[:n]))
|
|
|
|
def finish(self):
|
|
self._deflater.finish()
|
|
while not self._deflater.finished():
|
|
n = self._deflater.deflate(self._buffer)
|
|
if n > 0:
|
|
self._out.write(bytes(self._buffer[:n]))
|
|
|
|
def close(self):
|
|
self.finish()
|
|
self._deflater.end()
|
|
self._out.close()
|
|
|
|
def flush(self):
|
|
self._out.flush()
|
|
|
|
|
|
# ============================================================
|
|
# InflaterInputStream
|
|
# ============================================================
|
|
|
|
class InflaterInputStream(Closeable):
|
|
def __init__(self, inp, inflater=None, size=512):
|
|
super().__init__()
|
|
self._in = inp
|
|
self._inflater = inflater or Inflater()
|
|
self._buffer = bytearray(size)
|
|
|
|
def read(self, b=None, off=None, length=None):
|
|
if b is None:
|
|
buf = bytearray(1)
|
|
n = self._read_from_inflater(buf, 0, 1)
|
|
return buf[0] if n > 0 else -1
|
|
if off is None:
|
|
off = 0
|
|
if length is None:
|
|
length = len(b)
|
|
return self._read_from_inflater(b, off, length)
|
|
|
|
def _read_from_inflater(self, b, off, length):
|
|
while not self._inflater.finished():
|
|
if self._inflater.needsInput():
|
|
data = self._in.read(self._buffer)
|
|
if data is None or data == -1 or data == 0:
|
|
break
|
|
if isinstance(data, int):
|
|
break
|
|
self._inflater.setInput(data)
|
|
try:
|
|
n = self._inflater.inflate(b, off, length)
|
|
if n > 0:
|
|
return n
|
|
except DataFormatException:
|
|
break
|
|
return -1
|
|
|
|
def available(self):
|
|
return 0
|
|
|
|
def close(self):
|
|
self._inflater.end()
|
|
self._in.close()
|
|
|
|
def mark(self, readlimit):
|
|
pass
|
|
|
|
def reset(self):
|
|
raise IOException("mark/reset not supported")
|
|
|
|
def markSupported(self):
|
|
return False |