def extract(self, fd): """Extracts the archive file and writes the extracted file to the provided file object. Returns the checksum obtained from the archive. If blocks are uncompressed, the file is directly extracted. If the blocks are compressed, each block is added to a buffer, skipping the length field, and decompression is performed after the block marked as last. Expected length and compression header is obtained from the first block and checksum from the last block. :param fd: file-like object to write the extracted file to :type fd: file :return: checksum :rtype: int :raise DecompressError: If there's a decompression error :raise SAPCARInvalidFileException: If the file is invalid """ if self.file_length == 0: return 0 compressed = "" checksum = 0 exp_length = None remaining_length = self.file_length for block in self.blocks: # Process uncompressed block types if block.type in [ SAPCAR_BLOCK_TYPE_UNCOMPRESSED, SAPCAR_BLOCK_TYPE_UNCOMPRESSED_LAST ]: fd.write(block.compressed) remaining_length -= len(block.compressed) # Store compressed block types for later decompression elif block.type in [ SAPCAR_BLOCK_TYPE_COMPRESSED, SAPCAR_BLOCK_TYPE_COMPRESSED_LAST ]: # Add compressed block to a buffer, skipping the first 4 bytes of each block (uncompressed length) compressed += str(block.compressed)[4:] # If the expected length wasn't already set, do it if not exp_length: exp_length = block.compressed.uncompress_length else: raise SAPCARInvalidFileException("Invalid block type found") # Check last block, performing decompression if needed if sapcar_is_last_block(block): checksum = block.checksum # If there was at least one compressed block that set the expected length, decompress it if exp_length: (_, block_length, block_buffer) = decompress(str(compressed), exp_length) if block_length != exp_length or not block_buffer: raise DecompressError("Error decompressing block") fd.write(block_buffer) break return checksum
def extract(self, fd): """Extracts the archive file and writes the extracted file to the provided file object. Returns the checksum obtained from the archive. If blocks are uncompressed, the file is directly extracted. If the blocks are compressed, each block is decompressed independently. Checksum is obtained from the last block. :param fd: file-like object to write the extracted file to :type fd: file :return: checksum :rtype: int :raise DecompressError: If there's a decompression error :raise SAPCARInvalidFileException: If the file is invalid """ if self.file_length == 0: return 0 checksum = 0 remaining_length = self.file_length for block in self.blocks: # Process uncompressed block types if block.type in [ SAPCAR_BLOCK_TYPE_UNCOMPRESSED, SAPCAR_BLOCK_TYPE_UNCOMPRESSED_LAST ]: fd.write(block.compressed) remaining_length -= len(block.compressed) # Process compressed block types elif block.type in [ SAPCAR_BLOCK_TYPE_COMPRESSED, SAPCAR_BLOCK_TYPE_COMPRESSED_LAST ]: compressed = block.compressed exp_block_length = compressed.uncompress_length (_, block_length, block_buffer) = decompress( str(compressed)[4:], exp_block_length) if block_length != exp_block_length or not block_buffer: raise DecompressError("Error decompressing block") fd.write(block_buffer) remaining_length -= block_length else: raise SAPCARInvalidFileException("Invalid block type found") # Check last block if sapcar_is_last_block(block): checksum = block.checksum break return checksum
def open(self): """Opens the compressed file and returns a file-like object that can be used to access its uncompressed content. :return: file-like object with the uncompressed file content :rtype: file """ if not self._file_format.type == SAPCAR_TYPE_FILE: raise Exception("Invalid file type") out_buffer = "" if self._file_format.file_length != 0: compressed = self._file_format.compressed exp_length = self._file_format.file_length (_, out_length, out_buffer) = decompress(str(compressed)[4:], exp_length) if out_length != exp_length or not out_buffer: raise DecompressError("Decompression error") return StringIO(out_buffer)