[gnome-autoar] extractor: Add support for password protected archives



commit 58ac8fc571518c9830a6d0892b29215ce41cad81
Author: Felipe Borges <felipeborges gnome org>
Date:   Wed Dec 25 16:40:00 2019 +0100

    extractor: Add support for password protected archives
    
    AutoarExtractor used to error out when libarchived reported an
    archive entry to be encrypted.
    
    We now emit a "passphrase-requested" signal that clients can
    connect to return a single passphrase string which libarchive
    will attempt to use to decrypt the files. For an empty or invalid
    password, AutoarExtrator will emit its "error" signal and cleanup
    the destinatination directory.
    
    Although libarchive supports different password for each item,
    this implementation assumes the same password for all entries
    in a given archive. Single password per archive seems to be the
    most common use-case for regular desktop end users.
    
    See https://gitlab.gnome.org/GNOME/nautilus/-/issues/327
    
    Fixes https://gitlab.gnome.org/GNOME/gnome-autoar/-/issues/1

 gnome-autoar/autoar-extractor.c | 124 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 111 insertions(+), 13 deletions(-)
---
diff --git a/gnome-autoar/autoar-extractor.c b/gnome-autoar/autoar-extractor.c
index 55b9957..a7003c1 100644
--- a/gnome-autoar/autoar-extractor.c
+++ b/gnome-autoar/autoar-extractor.c
@@ -97,6 +97,7 @@ G_DEFINE_QUARK (autoar-extractor, autoar_extractor)
 #define BUFFER_SIZE (64 * 1024)
 #define NOT_AN_ARCHIVE_ERRNO 2013
 #define EMPTY_ARCHIVE_ERRNO 2014
+#define INCORRECT_PASSPHRASE_ERRNO 2015
 
 typedef struct _GFileAndInfo GFileAndInfo;
 
@@ -145,6 +146,9 @@ struct _AutoarExtractor
 
   int in_thread         : 1;
   int use_raw_format    : 1;
+
+  gchar *passphrase;
+  gboolean passphrase_requested;
 };
 
 G_DEFINE_TYPE (AutoarExtractor, autoar_extractor, G_TYPE_OBJECT)
@@ -163,6 +167,7 @@ enum
   CONFLICT,
   CANCELLED,
   COMPLETED,
+  REQUEST_PASSPHRASE,
   AR_ERROR,
   LAST_SIGNAL
 };
@@ -511,6 +516,10 @@ autoar_extractor_dispose (GObject *object)
     self->extracted_dir_list = NULL;
   }
 
+  if (self->passphrase != NULL) {
+    g_free (self->passphrase);
+  }
+
   G_OBJECT_CLASS (autoar_extractor_parent_class)->dispose (object);
 }
 
@@ -684,6 +693,18 @@ libarchive_read_skip_cb (struct archive *ar_read,
   return 0;
 }
 
+static inline gchar *
+autoar_extractor_request_passphrase (AutoarExtractor *self)
+{
+  if (!self->passphrase_requested) {
+    autoar_common_g_signal_emit (self, self->in_thread,
+                                 autoar_extractor_signals[REQUEST_PASSPHRASE], 0, &self->passphrase);
+    self->passphrase_requested = TRUE;
+  }
+
+  return self->passphrase;
+}
+
 static int
 libarchive_create_read_object (gboolean          use_raw_format,
                                AutoarExtractor  *self,
@@ -702,6 +723,10 @@ libarchive_create_read_object (gboolean          use_raw_format,
   archive_read_set_skip_callback (*a, libarchive_read_skip_cb);
   archive_read_set_callback_data (*a, self);
 
+  if (self->passphrase != NULL) {
+    archive_read_add_passphrase (*a, self->passphrase);
+  }
+
   return archive_read_open1 (*a);
 }
 
@@ -964,6 +989,57 @@ autoar_extractor_check_file_conflict (GFile  *file,
   return conflict;
 }
 
+static gboolean
+autoar_extractor_delete_file_recursively (AutoarExtractor *self,
+                                          GFile *file)
+{
+  gboolean success;
+  g_autoptr (GError) error = NULL;
+
+  do {
+    g_autoptr (GFileEnumerator) enumerator = NULL;
+
+    success = g_file_delete (file, self->cancellable, &error);
+    if (success || !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) {
+      break;
+    }
+
+    g_clear_error (&error);
+    enumerator = g_file_enumerate_children (file,
+                                            G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                            G_FILE_QUERY_INFO_NONE,
+                                            self->cancellable, &error);
+    if (enumerator) {
+      GFileInfo *info;
+
+      success = TRUE;
+
+      info = g_file_enumerator_next_file (enumerator,
+                                          self->cancellable,
+                                          &error);
+      while (info != NULL) {
+        g_autoptr (GFile) child = NULL;
+
+        child = g_file_enumerator_get_child (enumerator, info);
+
+        success = success && autoar_extractor_delete_file_recursively (self, child);
+        g_object_unref (info);
+
+        info = g_file_enumerator_next_file (enumerator,
+                                            self->cancellable,
+                                            &error);
+      }
+    }
+
+    if (error != NULL) {
+      success = FALSE;
+    }
+
+  } while (success);
+
+  return success;
+}
+
 static void
 autoar_extractor_do_write_entry (AutoarExtractor      *self,
                                  struct archive       *a,
@@ -1134,7 +1210,7 @@ autoar_extractor_do_write_entry (AutoarExtractor      *self,
         if (ostream != NULL) {
           /* Archive entry size may be zero if we use raw format. */
           if (archive_entry_size(entry) > 0 || self->use_raw_format) {
-            while (archive_read_data_block (a, &buffer, &size, &offset) == ARCHIVE_OK) {
+            while ((r = archive_read_data_block (a, &buffer, &size, &offset)) == ARCHIVE_OK) {
               /* buffer == NULL occurs in some zip archives when an entry is
                * completely read. We just skip this situation to prevent GIO
                * warnings. */
@@ -1161,6 +1237,20 @@ autoar_extractor_do_write_entry (AutoarExtractor      *self,
               self->completed_size += written;
               autoar_extractor_signal_progress (self);
             }
+
+            if (r == ARCHIVE_FAILED) {
+              autoar_extractor_delete_file_recursively (self, self->destination_dir);
+              if (self->error == NULL) {
+                self->error = g_error_new (AUTOAR_EXTRACTOR_ERROR,
+                                           INCORRECT_PASSPHRASE_ERRNO,
+                                           "%s",
+                                           archive_error_string (a));
+              }
+              g_output_stream_close (ostream, self->cancellable, NULL);
+              g_object_unref (ostream);
+
+              return;
+            }
           }
           g_output_stream_close (ostream, self->cancellable, NULL);
           g_object_unref (ostream);
@@ -1500,6 +1590,22 @@ autoar_extractor_class_init (AutoarExtractorClass *klass)
                   G_TYPE_NONE,
                   0);
 
+/**
+ * AutoarExtractor::request-passphrase:
+ * @self: the #AutoarExtractor
+ *
+ * This signal is emitted when the archive extracting job needs a
+ * passphrase.
+ **/
+  autoar_extractor_signals[REQUEST_PASSPHRASE] =
+    g_signal_new ("request-passphrase",
+                  type,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_generic,
+                  G_TYPE_STRING,
+                  0);
+
 /**
  * AutoarExtractor::error:
  * @self: the #AutoarExtractor
@@ -1553,6 +1659,9 @@ autoar_extractor_init (AutoarExtractor *self)
 
   self->in_thread = FALSE;
   self->use_raw_format = FALSE;
+
+  self->passphrase = NULL;
+  self->passphrase_requested = FALSE;
 }
 
 /**
@@ -1634,7 +1743,7 @@ autoar_extractor_step_scan_toplevel (AutoarExtractor *self)
     }
 
     if (archive_entry_is_encrypted (entry)) {
-      break;
+      autoar_extractor_request_passphrase (self);
     }
 
     if (self->use_raw_format) {
@@ -1659,17 +1768,6 @@ autoar_extractor_step_scan_toplevel (AutoarExtractor *self)
     archive_read_data_skip (a);
   }
 
-  if (entry && archive_entry_is_encrypted (entry)) {
-    g_debug ("autoar_extractor_step_scan_toplevel: encrypted entry");
-    if (self->error == NULL) {
-      self->error = g_error_new (G_IO_ERROR,
-                                 G_IO_ERROR_NOT_SUPPORTED,
-                                 "Encrypted archives are not supported.");
-    }
-    archive_read_free (a);
-    return;
-  }
-
   if (self->files_list == NULL) {
     if (self->error == NULL) {
       self->error = g_error_new (AUTOAR_EXTRACTOR_ERROR,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]