From d74439a961c32ed4ac219da241ba9e92c5b0d7f6 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Fri, 24 Mar 2017 14:46:59 +0100 Subject: [PATCH] overhaul pre- and post-crypto sync In order to handle ``_Stream.close()`` well later, we need to write the header of the last object before the tar info is injected. To allow the padding and zlib finalization in .close(), this cannot be performed when the actual file contents are written but has to be suspended until we are certain no data will be written to the current crypto object. --- deltatar/crypto.py | 2 +- deltatar/tarfile.py | 87 +++++++++++++++++++++++--------------------------- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/deltatar/crypto.py b/deltatar/crypto.py index 9c4823c..7a5b3a7 100755 --- a/deltatar/crypto.py +++ b/deltatar/crypto.py @@ -193,7 +193,7 @@ def hdr_make_dummy (s): of the object header. """ c = reduce (lambda a, c: a + ord(c), s, 0) % 0xFF - return bytearray (struct.pack ("B", c)) * I2N_HDR_SIZE + return bytes (bytearray (struct.pack ("B", c)) * I2N_HDR_SIZE) def hdr_make (hdr): diff --git a/deltatar/tarfile.py b/deltatar/tarfile.py index 61fcc9f..d9c077a 100644 --- a/deltatar/tarfile.py +++ b/deltatar/tarfile.py @@ -580,10 +580,9 @@ class _Stream: pos1 = self.fileobj.tell () dpos = pos1 - self.lasthdr assert dpos == crypto.I2N_HDR_SIZE - data, hdr = self.encryption.done (dummy) - self.fileobj.seek_set (self.lasthdr) - self.__write_to_file(hdr) self.fileobj.seek_set (pos0) + data, hdr = self.encryption.done (dummy) + self.__write_to_file(hdr, pos=self.lasthdr) self.__write_to_file(data) # append remainder of data self.lasthdr = -1 @@ -619,8 +618,7 @@ class _Stream: if self.comptype == "gz": self._new_gz_block(True) elif self.encryption is not None: - pass # XXX - #self._new_aes_block(True) + pass else: raise CompressionError("Concat compression only available for comptype 'gz'") @@ -640,7 +638,6 @@ class _Stream: # if aes, we encrypt after compression if self.encryption is not None: - #self._new_aes_block(set_last_block_offset) raise Exception ("XXX sorry, no can do") elif set_last_block_offset: self.last_block_offset = self.fileobj.tell() @@ -649,27 +646,6 @@ class _Stream: self.__write(b"\037\213\010\000" + timestamp + b"\002\377") - # !!! THIS DOES **NOT** HANDLE ACTUAL AES BLOCKS WHICH HAVE A FIXED - # !!! SIZE OF 16 BYTES - def _new_aes_block(self, set_last_block_offset=False): - # TODO do kill this off along with the entirety of aescrypto.py - # this basically checks if it comes from new_compression_block() call, - # in which case we have to call to close - if self.comptype == "tar": - self.close(close_fileobj=False) - self.closed = False - - if set_last_block_offset: # XXX does this belong before the header or after? - self.last_block_offset = self.fileobj.tell() - - hdr = self.encryption.next (self.name, - version=DELTATAR_HEADER_VERSION, - paramversion=self.encver, - nacl=self.encryption.nacl) - if hdr is None: - raise EncryptionError ("Crypto.next(): bad header") - self.__write_to_file(hdr) - def write(self, s): """Write string s to the stream. """ @@ -697,12 +673,23 @@ class _Stream: self.__enc_write(self.buf[:self.bufsize]) self.buf = self.buf[self.bufsize:] - def __write_to_file(self, s): + def __write_to_file(self, s, pos=None): ''' - Writes directly to the fileobj; updates self.bytes_written + Writes directly to the fileobj; updates self.bytes_written. If “pos” is + given, the streem will seek to that position first and back afterwards, + and the total of bytes written is not updated. ''' - self.bytes_written += len(s) + % (len (s), + ("" if pos is None else (" at 0x%x" % pos)))) + if pos is not None: + self.fileobj + p0 = self.fileobj.tell () + self.fileobj.seek_set (pos) self.fileobj.write(s) + if pos is None: + self.bytes_written += len(s) + else: + self.fileobj.seek_set (p0) def __enc_write(self, s): ''' @@ -2550,6 +2537,22 @@ class TarFile(object): else: self.last_block_offset = self.fileobj.tell() + # below attributes aren’t present with other compression methods + init_e = getattr (self.fileobj, "_init_write_encrypt", None) + init_c = getattr (self.fileobj, "_init_write_gz" , None) + finalize_e = getattr (self.fileobj, "_finalize_write_encrypt", None) + finalize_c = getattr (self.fileobj, "_finalize_write_gz" , None) + + def new_item_hook (): # crypto is outer, compress is inner + if init_e is not None: init_e (tarinfo.name) + if init_c is not None: init_c () + + def end_item_hook (): # crypto is outer, compress is inner + if finalize_c is not None: finalize_c () + if finalize_e is not None: finalize_e () + + end_item_hook () # finalize current object + buf = tarinfo.tobuf(self.format, self.encoding, self.errors) self.fileobj.write(buf) self.offset += len(buf) @@ -2562,22 +2565,6 @@ class TarFile(object): else: _size_left = lambda: tarinfo.size - # below attributes aren’t present with other compression methods - init_e = getattr (self.fileobj, "_init_write_encrypt", None) - init_c = getattr (self.fileobj, "_init_write_gz" , None) - finalize_e = getattr (self.fileobj, "_finalize_write_encrypt", None) - finalize_c = getattr (self.fileobj, "_finalize_write_gz" , None) - - def new_item_hook (): # crypto is outer, compress is inner - # We cannot finalize symmetrically after encryption because - # tar(5) mandates a trailer of “two records consisting entirely - # of zero bytes” which the stream appends as part of the - # .close() operation. - if finalize_c is not None: finalize_c () - if finalize_e is not None: finalize_e () - if init_e is not None: init_e (tarinfo.name) - if init_c is not None: init_c () - # If there's no data to follow, finish if not fileobj: new_item_hook () @@ -2594,6 +2581,7 @@ class TarFile(object): if target_size_left < BLOCKSIZE: target_size_left = BLOCKSIZE + new_item_hook () # loop over multiple volumes while source_size_left > 0: @@ -2603,7 +2591,6 @@ class TarFile(object): size_can_write = min(target_size_left, source_size_left) while size_can_write > 0: - new_item_hook () copyfileobj(fileobj, self.fileobj, size_can_write) self.offset += size_can_write source_size_left -= size_can_write @@ -2614,6 +2601,10 @@ class TarFile(object): # if there is data left to write, we need to create a new volume if source_size_left > 0: + # Only finalize the crypto entry here if we’re continuing with + # another one; otherwise, the encryption must include the block + # padding below. + end_item_hook () tarinfo.type = GNUTYPE_MULTIVOL @@ -2637,6 +2628,8 @@ class TarFile(object): self.volume_tarinfo = None + new_item_hook () + # write new volume header buf = tarinfo.tobuf(self.format, self.encoding, self.errors) self.fileobj.write(buf) -- 1.7.1