overhaul pre- and post-crypto sync
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Fri, 24 Mar 2017 13:46:59 +0000 (14:46 +0100)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Fri, 24 Mar 2017 13:47:31 +0000 (14:47 +0100)
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
deltatar/tarfile.py

index 9c4823c..7a5b3a7 100755 (executable)
@@ -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):
index 61fcc9f..d9c077a 100644 (file)
@@ -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)