[gcab/wip/hughsie/future: 32/32] Add gcab_cabinet_decompress()



commit 78b71b0c643023fa0e3f70cabd772469c4ed9858
Author: Richard Hughes <richard hughsie com>
Date:   Thu Dec 14 22:05:49 2017 +0000

    Add gcab_cabinet_decompress()
    
    This gives GCab to ability to decompress each file to a memory blob.
    
    At the moment fwupd decompresses each archive into a folder in /tmp, then reads
    back the files into memory so that it can parse them. This is fragile, and
    somewhat insecure if not done carefully.
    
    Allowing GCab to decompress to a GBytes buffer means we can make the fwupd code
    a lot simpler and safer.

 libgcab/gcab-cabinet.c |   44 +++++++++++++++++++++
 libgcab/gcab-cabinet.h |    5 ++
 libgcab/gcab-file.c    |   67 ++++++++++++++++++++++++++++++++
 libgcab/gcab-file.h    |    2 +
 libgcab/gcab-folder.c  |  101 +++++++++++++++++++++++++++++++++++++++++++-----
 libgcab/gcab-priv.h    |    7 +++
 libgcab/libgcab.syms   |    3 +
 tests/gcab-self-test.c |   57 +++++++++++++++++++++++++++
 8 files changed, 276 insertions(+), 10 deletions(-)
---
diff --git a/libgcab/gcab-cabinet.c b/libgcab/gcab-cabinet.c
index 346a70f..9ae09c8 100644
--- a/libgcab/gcab-cabinet.c
+++ b/libgcab/gcab-cabinet.c
@@ -532,6 +532,50 @@ gcab_cabinet_extract (GCabCabinet *self,
 }
 
 /**
+ * gcab_cabinet_decompress:
+ * @cabinet: a #GCabCabinet
+ * @file_callback: (allow-none) (scope call) (closure user_data): an optional #GCabFile callback,
+ *     return %FALSE to skip files.
+ * @user_data: (closure): callback data
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
+ * @error: (allow-none): #GError to set on error, or %NULL
+ *
+ * Decompresses files to memory. The data blob for each file can be retrieved
+ * using gcab_file_get_bytes().
+ *
+ * Returns: %TRUE on success.
+ **/
+gboolean
+gcab_cabinet_decompress (GCabCabinet *self,
+                         GCabFileCallback file_callback,
+                         gpointer user_data,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+    g_return_val_if_fail (GCAB_IS_CABINET (self), FALSE);
+    g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+    g_return_val_if_fail (!error || *error == NULL, FALSE);
+
+    /* never loaded from a stream */
+    if (self->cheader == NULL) {
+        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
+                     "Cabinet has not been loaded");
+        return FALSE;
+    }
+
+    for (guint i = 0; i < self->folders->len; ++i) {
+        GCabFolder *folder = g_ptr_array_index (self->folders, i);
+        if (!gcab_folder_decompress (folder, self->cheader->res_data,
+                                     file_callback, user_data,
+                                     cancellable, error)) {
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+/**
  * gcab_cabinet_extract_simple:
  * @cabinet: a #GCabCabinet
  * @path: the path to extract files
diff --git a/libgcab/gcab-cabinet.h b/libgcab/gcab-cabinet.h
index 57893ee..f0dcb03 100644
--- a/libgcab/gcab-cabinet.h
+++ b/libgcab/gcab-cabinet.h
@@ -87,6 +87,11 @@ gboolean           gcab_cabinet_write_simple  (GCabCabinet *cabinet,
                                                gpointer user_data,
                                                GCancellable *cancellable,
                                                GError **error);
+gboolean           gcab_cabinet_decompress    (GCabCabinet *cabinet,
+                                               GCabFileCallback file_callback,
+                                               gpointer user_data,
+                                               GCancellable *cancellable,
+                                               GError **error);
 gboolean           gcab_cabinet_extract       (GCabCabinet *cabinet,
                                                GFile *path,
                                                GCabFileCallback file_callback,
diff --git a/libgcab/gcab-file.c b/libgcab/gcab-file.c
index 6ea46f2..39ae57c 100644
--- a/libgcab/gcab-file.c
+++ b/libgcab/gcab-file.c
@@ -45,6 +45,7 @@ struct _GCabFile
 
     gchar *extract_name;
     GFile *file;
+    GBytes *bytes;
     cfile_t *cfile;
 };
 
@@ -53,6 +54,7 @@ enum {
 
     PROP_NAME,
     PROP_FILE,
+    PROP_BYTES,
 };
 
 G_DEFINE_TYPE (GCabFile, gcab_file, G_TYPE_OBJECT);
@@ -69,6 +71,8 @@ gcab_file_finalize (GObject *object)
 
     if (self->file != NULL)
         g_object_unref (self->file);
+    if (self->bytes != NULL)
+        g_bytes_unref (self->bytes);
     cfile_free (self->cfile);
     g_free (self->extract_name);
 
@@ -96,6 +100,14 @@ gcab_file_set_name (GCabFile *self, const gchar *name)
     self->cfile->name = fname;
 }
 
+G_GNUC_INTERNAL void
+gcab_file_set_bytes (GCabFile *self, GBytes *bytes)
+{
+    if (self->bytes != NULL)
+        g_bytes_unref (self->bytes);
+    self->bytes = g_bytes_ref (bytes);
+}
+
 static void
 gcab_file_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
@@ -109,6 +121,9 @@ gcab_file_set_property (GObject *object, guint prop_id, const GValue *value, GPa
     case PROP_FILE:
         self->file = g_value_dup_object (value);
         break;
+    case PROP_BYTES:
+        gcab_file_set_bytes (self, g_value_get_boxed (value));
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -128,6 +143,9 @@ gcab_file_get_property (GObject *object, guint prop_id, GValue *value, GParamSpe
     case PROP_FILE:
         g_value_set_object (value, self->file);
         break;
+    case PROP_BYTES:
+        g_value_set_boxed (value, self->bytes);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -152,6 +170,11 @@ gcab_file_class_init (GCabFileClass *klass)
         g_param_spec_object ("file", "file", "file", G_TYPE_FILE,
                              G_PARAM_READWRITE |
                              G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property (object_class, PROP_BYTES,
+        g_param_spec_boxed ("bytes", "bytes", "bytes", G_TYPE_BYTES,
+                            G_PARAM_READWRITE |
+                            G_PARAM_STATIC_STRINGS));
 }
 
 /**
@@ -342,6 +365,26 @@ gcab_file_get_file (GCabFile *self)
 }
 
 /**
+ * gcab_file_get_bytes:
+ * @file: a #GCabFile
+ *
+ * Get the #GFile associated with @file.
+ *
+ * If @file is from an existing cabinet, the fuction will return
+ * %NULL.
+ *
+ * Returns: (transfer none): the associated #GBytes or %NULL
+ *
+ * Since: 1.0
+ **/
+GBytes *
+gcab_file_get_bytes (GCabFile *self)
+{
+    g_return_val_if_fail (GCAB_IS_FILE (self), NULL);
+    return self->bytes;
+}
+
+/**
  * gcab_file_new_with_file:
  * @name: name of the file within the cabinet
  * @file: a #GFile to be added to the cabinet
@@ -365,6 +408,30 @@ gcab_file_new_with_file (const gchar *name, GFile *file)
     return self;
 }
 
+/**
+ * gcab_file_new_with_bytes:
+ * @name: name of the file within the cabinet
+ * @bytes: a #GBytes to be added to the cabinet
+ *
+ * Create a #GCabFile from a given #GBytes.
+ *
+ * Returns: a new #GCabFile
+ *
+ * Since: 1.0
+ **/
+GCabFile *
+gcab_file_new_with_bytes (const gchar *name, GBytes *bytes)
+{
+    g_return_val_if_fail (name != NULL, NULL);
+    g_return_val_if_fail (G_IS_FILE (bytes), NULL);
+
+    GCabFile *self = g_object_new (GCAB_TYPE_FILE, NULL);
+    self->cfile = g_new0 (cfile_t, 1);
+    self->bytes = g_bytes_ref (bytes);
+    gcab_file_set_name (self, name);
+    return self;
+}
+
 G_GNUC_INTERNAL GCabFile *
 gcab_file_new_steal_cfile (cfile_t **cfile)
 {
diff --git a/libgcab/gcab-file.h b/libgcab/gcab-file.h
index 9a3a134..a62d762 100644
--- a/libgcab/gcab-file.h
+++ b/libgcab/gcab-file.h
@@ -66,7 +66,9 @@ typedef enum
 typedef gboolean (*GCabFileCallback) (GCabFile *file, gpointer user_data);
 
 GCabFile *      gcab_file_new_with_file             (const gchar *name, GFile *file);
+GCabFile *      gcab_file_new_with_bytes            (const gchar *name, GBytes *bytes);
 GFile *         gcab_file_get_file                  (GCabFile *file);
+GBytes *        gcab_file_get_bytes                 (GCabFile *file);
 const gchar *   gcab_file_get_name                  (GCabFile *file);
 guint32         gcab_file_get_size                  (GCabFile *file);
 guint32         gcab_file_get_attributes            (GCabFile *file);
diff --git a/libgcab/gcab-folder.c b/libgcab/gcab-folder.c
index 174a5c6..dd7fdba 100644
--- a/libgcab/gcab-folder.c
+++ b/libgcab/gcab-folder.c
@@ -54,7 +54,7 @@ struct _GCabFolder
     gint comptype;
     GByteArray *reserved;
     cfolder_t *cfolder;
-    GInputStream *stream;
+    GDataInputStream *stream;
 };
 
 enum {
@@ -349,7 +349,12 @@ gcab_folder_new_steal_cfolder (cfolder_t **cfolder, GInputStream *stream)
     GCabFolder *self = g_object_new (GCAB_TYPE_FOLDER,
                                      "comptype", (*cfolder)->typecomp,
                                      NULL);
-    self->stream = g_object_ref (stream);
+
+    /* create LE-adjusted stream */
+    self->stream = g_data_input_stream_new (stream);
+    g_data_input_stream_set_byte_order (self->stream, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
+    g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (self->stream), FALSE);
+
     self->cfolder = g_steal_pointer (cfolder);
 
     return self;
@@ -392,7 +397,6 @@ gcab_folder_extract (GCabFolder *self,
 {
     GError *my_error = NULL;
     gboolean success = FALSE;
-    g_autoptr(GDataInputStream) data = NULL;
     g_autoptr(GFileOutputStream) out = NULL;
     GSList *f = NULL;
     g_autoptr(GSList) files = NULL;
@@ -402,11 +406,7 @@ gcab_folder_extract (GCabFolder *self,
     /* never loaded from a stream */
     g_assert (self->cfolder != NULL);
 
-    data = g_data_input_stream_new (self->stream);
-    g_data_input_stream_set_byte_order (data, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
-    g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (data), FALSE);
-
-    if (!g_seekable_seek (G_SEEKABLE (data), self->cfolder->offsetdata, G_SEEK_SET, cancellable, error))
+    if (!g_seekable_seek (G_SEEKABLE (self->stream), self->cfolder->offsetdata, G_SEEK_SET, cancellable, 
error))
         goto end;
 
     files = g_slist_sort (g_slist_copy (self->files), (GCompareFunc)sort_by_offset);
@@ -459,7 +459,7 @@ gcab_folder_extract (GCabFolder *self,
 
         /* let's rewind if need be */
         if (uoffset < nubytes) {
-            if (!g_seekable_seek (G_SEEKABLE (data), self->cfolder->offsetdata,
+            if (!g_seekable_seek (G_SEEKABLE (self->stream), self->cfolder->offsetdata,
                                   G_SEEK_SET, cancellable, error))
                 goto end;
             bzero(&cdata, sizeof(cdata));
@@ -470,7 +470,7 @@ gcab_folder_extract (GCabFolder *self,
             if ((nubytes + cdata.nubytes) <= uoffset) {
                 nubytes += cdata.nubytes;
                 if (!cdata_read (&cdata, res_data, self->comptype,
-                                 data, cancellable, error))
+                                 self->stream, cancellable, error))
                     goto end;
                 continue;
             } else {
@@ -494,3 +494,84 @@ end:
 
     return success;
 }
+
+G_GNUC_INTERNAL gboolean
+gcab_folder_decompress (GCabFolder *self,
+                        guint8 res_data,
+                        GCabFileCallback file_callback,
+                        gpointer callback_data,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+    gboolean success = FALSE;
+    g_autoptr(GFileOutputStream) out = NULL;
+    GSList *f = NULL;
+    g_autoptr(GSList) files = NULL;
+    cdata_t cdata = { 0, };
+    guint32 nubytes = 0;
+
+    /* never loaded from a stream */
+    g_assert (self->cfolder != NULL);
+
+    if (!g_seekable_seek (G_SEEKABLE (self->stream), self->cfolder->offsetdata, G_SEEK_SET, cancellable, 
error))
+        goto end;
+
+    files = g_slist_sort (g_slist_copy (self->files), (GCompareFunc)sort_by_offset);
+
+    for (f = files; f != NULL; f = f->next) {
+        GCabFile *file = f->data;
+
+        if (file_callback && !file_callback (file, callback_data))
+            continue;
+
+        g_autoptr(GMemoryOutputStream) out2 = NULL;
+        out2 = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new_resizable ());
+        if (!out2)
+            goto end;
+
+        guint32 usize = gcab_file_get_usize (file);
+        guint32 uoffset = gcab_file_get_uoffset (file);
+
+        /* let's rewind if need be */
+        if (uoffset < nubytes) {
+            if (!g_seekable_seek (G_SEEKABLE (self->stream), self->cfolder->offsetdata,
+                                  G_SEEK_SET, cancellable, error))
+                goto end;
+            bzero(&cdata, sizeof(cdata));
+            nubytes = 0;
+        }
+
+        while (usize > 0) {
+            if ((nubytes + cdata.nubytes) <= uoffset) {
+                nubytes += cdata.nubytes;
+                if (!cdata_read (&cdata, res_data, self->comptype,
+                                 self->stream, cancellable, error))
+                    goto end;
+                continue;
+            } else {
+                gsize offset = gcab_file_get_uoffset (file) > nubytes ?
+                    gcab_file_get_uoffset (file) - nubytes : 0;
+                const void *p = &cdata.out[offset];
+                gsize count = MIN (usize, cdata.nubytes - offset);
+                if (!g_output_stream_write_all (G_OUTPUT_STREAM (out2), p, count,
+                                                NULL, cancellable, error))
+                    goto end;
+                usize -= count;
+                uoffset += count;
+            }
+        }
+
+        /* steal as a blob */
+        if (!g_output_stream_close (G_OUTPUT_STREAM (out2), cancellable, error))
+            goto end;
+        g_autoptr(GBytes) bytes_tmp = g_memory_output_stream_steal_as_bytes (out2);
+        gcab_file_set_bytes (file, bytes_tmp);
+    }
+
+    success = TRUE;
+
+end:
+    cdata_finish (&cdata, NULL);
+
+    return success;
+}
diff --git a/libgcab/gcab-priv.h b/libgcab/gcab-priv.h
index 0cde13d..5e3f1eb 100644
--- a/libgcab/gcab-priv.h
+++ b/libgcab/gcab-priv.h
@@ -50,6 +50,7 @@ guint32          gcab_file_get_usize                 (GCabFile *file);
 GFile           *gcab_file_get_gfile                 (GCabFile *file);
 cfile_t         *gcab_file_get_cfile                 (GCabFile *file);
 void             gcab_file_add_attribute             (GCabFile *file, guint32 attribute);
+void             gcab_file_set_bytes                 (GCabFile *file, GBytes *bytes);
 
 gsize            gcab_folder_get_ndatablocks         (GCabFolder *folder);
 gboolean         gcab_folder_extract                 (GCabFolder *self,
@@ -60,5 +61,11 @@ gboolean         gcab_folder_extract                 (GCabFolder *self,
                                                       gpointer callback_data,
                                                       GCancellable *cancellable,
                                                       GError **error);
+gboolean         gcab_folder_decompress              (GCabFolder *self,
+                                                      guint8 res_data,
+                                                      GCabFileCallback file_callback,
+                                                      gpointer callback_data,
+                                                      GCancellable *cancellable,
+                                                      GError **error);
 
 #endif /* GCAB_PRIV_H */
diff --git a/libgcab/libgcab.syms b/libgcab/libgcab.syms
index 76bda2b..6693a78 100644
--- a/libgcab/libgcab.syms
+++ b/libgcab/libgcab.syms
@@ -43,6 +43,9 @@ LIBGCAB1_0.6 {
 } LIBGCAB1_0.5;
 
 LIBGCAB1_1.0 {
+        gcab_cabinet_decompress;
+        gcab_file_get_bytes;
+        gcab_file_new_with_bytes;
         gcab_file_set_date;
         gcab_folder_get_comptype;
 } LIBGCAB1_0.6;
diff --git a/tests/gcab-self-test.c b/tests/gcab-self-test.c
index b44a800..c0ff0b2 100644
--- a/tests/gcab-self-test.c
+++ b/tests/gcab-self-test.c
@@ -248,6 +248,62 @@ _compute_checksum_for_file (GFile *file, GError **error)
 }
 
 static void
+gcab_test_cabinet_decompress_func (void)
+{
+    gboolean ret;
+    g_autofree gchar *fn = NULL;
+    g_autoptr(GCabCabinet) cabinet = NULL;
+    g_autoptr(GError) error = NULL;
+    g_autoptr(GFile) file = NULL;
+    g_autoptr(GInputStream) in = NULL;
+
+    /* decompress to memory */
+    fn = gcab_test_get_filename ("test-none.cab");
+    g_assert (fn != NULL);
+    file = g_file_new_for_path (fn);
+    in = G_INPUT_STREAM (g_file_read (file, NULL, &error));
+    g_assert_no_error (error);
+    g_assert (in != NULL);
+    cabinet = gcab_cabinet_new ();
+    ret = gcab_cabinet_load (cabinet, in, NULL, &error);
+    g_assert_no_error (error);
+    g_assert (ret);
+    ret = gcab_cabinet_decompress (cabinet, NULL, NULL, NULL, &error);
+    g_assert_no_error (error);
+    g_assert (ret);
+
+    GPtrArray *folders = gcab_cabinet_get_folders (cabinet);
+    g_assert_cmpint (folders->len, ==, 1);
+    GCabFolder *folder = g_ptr_array_index (folders, 0);
+    g_autoptr(GSList) cabfiles = gcab_folder_get_files (folder);
+    for (GSList *l = cabfiles; l != NULL; l = l->next) {
+        GCabFile *cabfile = GCAB_FILE (l->data);
+        GBytes *bytes = NULL;
+        const gchar *checksum = NULL;
+        struct {
+            const gchar *fn;
+            const gchar *checksum;
+        } files[] = {
+            { "test.sh",            "82b4415cf30efc9b5877e366475d652f263c0ced" },
+            { "test.txt",           "decc67ff4a11acd93430cbb18c7bbddd00abf4fa" },
+            { NULL,                 NULL }
+        };
+        for (guint i = 0; files[i].fn != NULL; i++) {
+            if (g_strcmp0 (gcab_file_get_name (cabfile), files[i].fn) == 0) {
+                checksum = files[i].checksum;
+                break;
+            }
+        }
+        if (checksum == NULL)
+            g_error ("failed to find hash for %s", gcab_file_get_name (cabfile));
+        bytes = gcab_file_get_bytes (cabfile);
+        g_assert (bytes != NULL);
+        g_autofree gchar *csum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, bytes);
+        g_assert_cmpstr (csum, ==, checksum);
+    }
+}
+
+static void
 gcab_test_cabinet_load_func (void)
 {
     struct {
@@ -489,6 +545,7 @@ main (int argc, char **argv)
     g_test_add_func ("/GCab/cabinet{error-cves}", gcab_test_cabinet_error_cves_func);
     g_test_add_func ("/GCab/cabinet{load}", gcab_test_cabinet_load_func);
     g_test_add_func ("/GCab/cabinet{write}", gcab_test_cabinet_write_func);
+    g_test_add_func ("/GCab/cabinet{decompress}", gcab_test_cabinet_decompress_func);
     g_test_add_func ("/GCab/cabinet{signature}", gcab_test_cabinet_signature_func);
     return g_test_run ();
 }


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