1*cda5da8dSAndroid Build Coastguard Worker"""Internal classes used by the gzip, lzma and bz2 modules""" 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard Workerimport io 4*cda5da8dSAndroid Build Coastguard Workerimport sys 5*cda5da8dSAndroid Build Coastguard Worker 6*cda5da8dSAndroid Build Coastguard WorkerBUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size 7*cda5da8dSAndroid Build Coastguard Worker 8*cda5da8dSAndroid Build Coastguard Worker 9*cda5da8dSAndroid Build Coastguard Workerclass BaseStream(io.BufferedIOBase): 10*cda5da8dSAndroid Build Coastguard Worker """Mode-checking helper functions.""" 11*cda5da8dSAndroid Build Coastguard Worker 12*cda5da8dSAndroid Build Coastguard Worker def _check_not_closed(self): 13*cda5da8dSAndroid Build Coastguard Worker if self.closed: 14*cda5da8dSAndroid Build Coastguard Worker raise ValueError("I/O operation on closed file") 15*cda5da8dSAndroid Build Coastguard Worker 16*cda5da8dSAndroid Build Coastguard Worker def _check_can_read(self): 17*cda5da8dSAndroid Build Coastguard Worker if not self.readable(): 18*cda5da8dSAndroid Build Coastguard Worker raise io.UnsupportedOperation("File not open for reading") 19*cda5da8dSAndroid Build Coastguard Worker 20*cda5da8dSAndroid Build Coastguard Worker def _check_can_write(self): 21*cda5da8dSAndroid Build Coastguard Worker if not self.writable(): 22*cda5da8dSAndroid Build Coastguard Worker raise io.UnsupportedOperation("File not open for writing") 23*cda5da8dSAndroid Build Coastguard Worker 24*cda5da8dSAndroid Build Coastguard Worker def _check_can_seek(self): 25*cda5da8dSAndroid Build Coastguard Worker if not self.readable(): 26*cda5da8dSAndroid Build Coastguard Worker raise io.UnsupportedOperation("Seeking is only supported " 27*cda5da8dSAndroid Build Coastguard Worker "on files open for reading") 28*cda5da8dSAndroid Build Coastguard Worker if not self.seekable(): 29*cda5da8dSAndroid Build Coastguard Worker raise io.UnsupportedOperation("The underlying file object " 30*cda5da8dSAndroid Build Coastguard Worker "does not support seeking") 31*cda5da8dSAndroid Build Coastguard Worker 32*cda5da8dSAndroid Build Coastguard Worker 33*cda5da8dSAndroid Build Coastguard Workerclass DecompressReader(io.RawIOBase): 34*cda5da8dSAndroid Build Coastguard Worker """Adapts the decompressor API to a RawIOBase reader API""" 35*cda5da8dSAndroid Build Coastguard Worker 36*cda5da8dSAndroid Build Coastguard Worker def readable(self): 37*cda5da8dSAndroid Build Coastguard Worker return True 38*cda5da8dSAndroid Build Coastguard Worker 39*cda5da8dSAndroid Build Coastguard Worker def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args): 40*cda5da8dSAndroid Build Coastguard Worker self._fp = fp 41*cda5da8dSAndroid Build Coastguard Worker self._eof = False 42*cda5da8dSAndroid Build Coastguard Worker self._pos = 0 # Current offset in decompressed stream 43*cda5da8dSAndroid Build Coastguard Worker 44*cda5da8dSAndroid Build Coastguard Worker # Set to size of decompressed stream once it is known, for SEEK_END 45*cda5da8dSAndroid Build Coastguard Worker self._size = -1 46*cda5da8dSAndroid Build Coastguard Worker 47*cda5da8dSAndroid Build Coastguard Worker # Save the decompressor factory and arguments. 48*cda5da8dSAndroid Build Coastguard Worker # If the file contains multiple compressed streams, each 49*cda5da8dSAndroid Build Coastguard Worker # stream will need a separate decompressor object. A new decompressor 50*cda5da8dSAndroid Build Coastguard Worker # object is also needed when implementing a backwards seek(). 51*cda5da8dSAndroid Build Coastguard Worker self._decomp_factory = decomp_factory 52*cda5da8dSAndroid Build Coastguard Worker self._decomp_args = decomp_args 53*cda5da8dSAndroid Build Coastguard Worker self._decompressor = self._decomp_factory(**self._decomp_args) 54*cda5da8dSAndroid Build Coastguard Worker 55*cda5da8dSAndroid Build Coastguard Worker # Exception class to catch from decompressor signifying invalid 56*cda5da8dSAndroid Build Coastguard Worker # trailing data to ignore 57*cda5da8dSAndroid Build Coastguard Worker self._trailing_error = trailing_error 58*cda5da8dSAndroid Build Coastguard Worker 59*cda5da8dSAndroid Build Coastguard Worker def close(self): 60*cda5da8dSAndroid Build Coastguard Worker self._decompressor = None 61*cda5da8dSAndroid Build Coastguard Worker return super().close() 62*cda5da8dSAndroid Build Coastguard Worker 63*cda5da8dSAndroid Build Coastguard Worker def seekable(self): 64*cda5da8dSAndroid Build Coastguard Worker return self._fp.seekable() 65*cda5da8dSAndroid Build Coastguard Worker 66*cda5da8dSAndroid Build Coastguard Worker def readinto(self, b): 67*cda5da8dSAndroid Build Coastguard Worker with memoryview(b) as view, view.cast("B") as byte_view: 68*cda5da8dSAndroid Build Coastguard Worker data = self.read(len(byte_view)) 69*cda5da8dSAndroid Build Coastguard Worker byte_view[:len(data)] = data 70*cda5da8dSAndroid Build Coastguard Worker return len(data) 71*cda5da8dSAndroid Build Coastguard Worker 72*cda5da8dSAndroid Build Coastguard Worker def read(self, size=-1): 73*cda5da8dSAndroid Build Coastguard Worker if size < 0: 74*cda5da8dSAndroid Build Coastguard Worker return self.readall() 75*cda5da8dSAndroid Build Coastguard Worker 76*cda5da8dSAndroid Build Coastguard Worker if not size or self._eof: 77*cda5da8dSAndroid Build Coastguard Worker return b"" 78*cda5da8dSAndroid Build Coastguard Worker data = None # Default if EOF is encountered 79*cda5da8dSAndroid Build Coastguard Worker # Depending on the input data, our call to the decompressor may not 80*cda5da8dSAndroid Build Coastguard Worker # return any data. In this case, try again after reading another block. 81*cda5da8dSAndroid Build Coastguard Worker while True: 82*cda5da8dSAndroid Build Coastguard Worker if self._decompressor.eof: 83*cda5da8dSAndroid Build Coastguard Worker rawblock = (self._decompressor.unused_data or 84*cda5da8dSAndroid Build Coastguard Worker self._fp.read(BUFFER_SIZE)) 85*cda5da8dSAndroid Build Coastguard Worker if not rawblock: 86*cda5da8dSAndroid Build Coastguard Worker break 87*cda5da8dSAndroid Build Coastguard Worker # Continue to next stream. 88*cda5da8dSAndroid Build Coastguard Worker self._decompressor = self._decomp_factory( 89*cda5da8dSAndroid Build Coastguard Worker **self._decomp_args) 90*cda5da8dSAndroid Build Coastguard Worker try: 91*cda5da8dSAndroid Build Coastguard Worker data = self._decompressor.decompress(rawblock, size) 92*cda5da8dSAndroid Build Coastguard Worker except self._trailing_error: 93*cda5da8dSAndroid Build Coastguard Worker # Trailing data isn't a valid compressed stream; ignore it. 94*cda5da8dSAndroid Build Coastguard Worker break 95*cda5da8dSAndroid Build Coastguard Worker else: 96*cda5da8dSAndroid Build Coastguard Worker if self._decompressor.needs_input: 97*cda5da8dSAndroid Build Coastguard Worker rawblock = self._fp.read(BUFFER_SIZE) 98*cda5da8dSAndroid Build Coastguard Worker if not rawblock: 99*cda5da8dSAndroid Build Coastguard Worker raise EOFError("Compressed file ended before the " 100*cda5da8dSAndroid Build Coastguard Worker "end-of-stream marker was reached") 101*cda5da8dSAndroid Build Coastguard Worker else: 102*cda5da8dSAndroid Build Coastguard Worker rawblock = b"" 103*cda5da8dSAndroid Build Coastguard Worker data = self._decompressor.decompress(rawblock, size) 104*cda5da8dSAndroid Build Coastguard Worker if data: 105*cda5da8dSAndroid Build Coastguard Worker break 106*cda5da8dSAndroid Build Coastguard Worker if not data: 107*cda5da8dSAndroid Build Coastguard Worker self._eof = True 108*cda5da8dSAndroid Build Coastguard Worker self._size = self._pos 109*cda5da8dSAndroid Build Coastguard Worker return b"" 110*cda5da8dSAndroid Build Coastguard Worker self._pos += len(data) 111*cda5da8dSAndroid Build Coastguard Worker return data 112*cda5da8dSAndroid Build Coastguard Worker 113*cda5da8dSAndroid Build Coastguard Worker def readall(self): 114*cda5da8dSAndroid Build Coastguard Worker chunks = [] 115*cda5da8dSAndroid Build Coastguard Worker # sys.maxsize means the max length of output buffer is unlimited, 116*cda5da8dSAndroid Build Coastguard Worker # so that the whole input buffer can be decompressed within one 117*cda5da8dSAndroid Build Coastguard Worker # .decompress() call. 118*cda5da8dSAndroid Build Coastguard Worker while data := self.read(sys.maxsize): 119*cda5da8dSAndroid Build Coastguard Worker chunks.append(data) 120*cda5da8dSAndroid Build Coastguard Worker 121*cda5da8dSAndroid Build Coastguard Worker return b"".join(chunks) 122*cda5da8dSAndroid Build Coastguard Worker 123*cda5da8dSAndroid Build Coastguard Worker # Rewind the file to the beginning of the data stream. 124*cda5da8dSAndroid Build Coastguard Worker def _rewind(self): 125*cda5da8dSAndroid Build Coastguard Worker self._fp.seek(0) 126*cda5da8dSAndroid Build Coastguard Worker self._eof = False 127*cda5da8dSAndroid Build Coastguard Worker self._pos = 0 128*cda5da8dSAndroid Build Coastguard Worker self._decompressor = self._decomp_factory(**self._decomp_args) 129*cda5da8dSAndroid Build Coastguard Worker 130*cda5da8dSAndroid Build Coastguard Worker def seek(self, offset, whence=io.SEEK_SET): 131*cda5da8dSAndroid Build Coastguard Worker # Recalculate offset as an absolute file position. 132*cda5da8dSAndroid Build Coastguard Worker if whence == io.SEEK_SET: 133*cda5da8dSAndroid Build Coastguard Worker pass 134*cda5da8dSAndroid Build Coastguard Worker elif whence == io.SEEK_CUR: 135*cda5da8dSAndroid Build Coastguard Worker offset = self._pos + offset 136*cda5da8dSAndroid Build Coastguard Worker elif whence == io.SEEK_END: 137*cda5da8dSAndroid Build Coastguard Worker # Seeking relative to EOF - we need to know the file's size. 138*cda5da8dSAndroid Build Coastguard Worker if self._size < 0: 139*cda5da8dSAndroid Build Coastguard Worker while self.read(io.DEFAULT_BUFFER_SIZE): 140*cda5da8dSAndroid Build Coastguard Worker pass 141*cda5da8dSAndroid Build Coastguard Worker offset = self._size + offset 142*cda5da8dSAndroid Build Coastguard Worker else: 143*cda5da8dSAndroid Build Coastguard Worker raise ValueError("Invalid value for whence: {}".format(whence)) 144*cda5da8dSAndroid Build Coastguard Worker 145*cda5da8dSAndroid Build Coastguard Worker # Make it so that offset is the number of bytes to skip forward. 146*cda5da8dSAndroid Build Coastguard Worker if offset < self._pos: 147*cda5da8dSAndroid Build Coastguard Worker self._rewind() 148*cda5da8dSAndroid Build Coastguard Worker else: 149*cda5da8dSAndroid Build Coastguard Worker offset -= self._pos 150*cda5da8dSAndroid Build Coastguard Worker 151*cda5da8dSAndroid Build Coastguard Worker # Read and discard data until we reach the desired position. 152*cda5da8dSAndroid Build Coastguard Worker while offset > 0: 153*cda5da8dSAndroid Build Coastguard Worker data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset)) 154*cda5da8dSAndroid Build Coastguard Worker if not data: 155*cda5da8dSAndroid Build Coastguard Worker break 156*cda5da8dSAndroid Build Coastguard Worker offset -= len(data) 157*cda5da8dSAndroid Build Coastguard Worker 158*cda5da8dSAndroid Build Coastguard Worker return self._pos 159*cda5da8dSAndroid Build Coastguard Worker 160*cda5da8dSAndroid Build Coastguard Worker def tell(self): 161*cda5da8dSAndroid Build Coastguard Worker """Return the current file position.""" 162*cda5da8dSAndroid Build Coastguard Worker return self._pos 163