[gcab] Add gcab_file_new_with_bytes()



commit c873b05f3c5f2bef8927f82f3d84ad228fd154ee
Author: Richard Hughes <richard hughsie com>
Date:   Fri Dec 15 10:26:38 2017 +0000

    Add gcab_file_new_with_bytes()
    
    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.
    
    This new API allows us to do two new things:
    
    * Create an archive without a GFile to read from
    * Decompress an archive to memory, without writing any files on disk
    
    Allowing GCab to decompress to a GBytes buffer means we can make the fwupd code
    a lot simpler and safer.

 libgcab/gcab-cabinet.c |    7 +++-
 libgcab/gcab-file.c    |   79 ++++++++++++++++++++++++++++++++++++++++
 libgcab/gcab-file.h    |    2 +
 libgcab/gcab-folder.c  |    8 ++++
 libgcab/gcab-priv.h    |    1 +
 libgcab/libgcab.syms   |    2 +
 tests/gcab-self-test.c |   95 ++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 192 insertions(+), 2 deletions(-)
---
diff --git a/libgcab/gcab-cabinet.c b/libgcab/gcab-cabinet.c
index f86c5bb..b5e2c04 100644
--- a/libgcab/gcab-cabinet.c
+++ b/libgcab/gcab-cabinet.c
@@ -478,7 +478,7 @@ gcab_cabinet_load (GCabCabinet *self,
 /**
  * gcab_cabinet_extract:
  * @cabinet: a #GCabCabinet
- * @path: the path to extract files
+ * @path: the path to extract files, or %NULL
  * @file_callback: (allow-none) (scope call) (closure user_data): an optional #GCabFile callback,
  *     return %FALSE to filter out or skip files.
  * @progress_callback: (allow-none) (scope call) (closure user_data): a progress callback
@@ -489,6 +489,9 @@ gcab_cabinet_load (GCabCabinet *self,
  *
  * Extract files to given path.
  *
+ * If @path is NULL then the files are decompressed to memory blobs stored on
+ * each #GCabFile.
+ *
  * Returns: %TRUE on success.
  **/
 gboolean
@@ -501,7 +504,7 @@ gcab_cabinet_extract (GCabCabinet *self,
                       GError **error)
 {
     g_return_val_if_fail (GCAB_IS_CABINET (self), FALSE);
-    g_return_val_if_fail (G_IS_FILE (path), FALSE);
+    g_return_val_if_fail (!path || G_IS_FILE (path), FALSE);
     g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
     g_return_val_if_fail (!error || *error == NULL, FALSE);
 
diff --git a/libgcab/gcab-file.c b/libgcab/gcab-file.c
index 118f1ba..cbf8a22 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,17 @@ 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);
+
+    /* this is embedded into the archive */
+    self->cfile->usize = g_bytes_get_size (bytes);
+}
+
 static void
 gcab_file_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
@@ -109,6 +124,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 +146,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 +173,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));
 }
 
 /**
@@ -358,6 +384,24 @@ gcab_file_get_file (GCabFile *self)
 }
 
 /**
+ * gcab_file_get_bytes:
+ * @file: a #GCabFile
+ *
+ * Get the #GFile associated with @file. This will only be non-%NULL if the
+ * #GCabFile has been created using gcab_file_new_with_bytes().
+ *
+ * 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
@@ -381,6 +425,33 @@ 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.
+ *
+ * If this file is to be added to an archive you should also probably use
+ * gcab_file_set_date() and gcab_file_set_attributes() to set sensible values.
+ *
+ * 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 (bytes != NULL, NULL);
+
+    GCabFile *self = g_object_new (GCAB_TYPE_FILE, NULL);
+    self->cfile = g_new0 (cfile_t, 1);
+    gcab_file_set_bytes (self, bytes);
+    gcab_file_set_name (self, name);
+    return self;
+}
+
 G_GNUC_INTERNAL GCabFile *
 gcab_file_new_steal_cfile (cfile_t **cfile)
 {
@@ -399,6 +470,10 @@ gcab_file_get_input_stream (GCabFile *self, GCancellable *cancellable, GError **
     if (self->file != NULL)
         return G_INPUT_STREAM (g_file_read (self->file, cancellable, error));
 
+    /* backed by a GBytes */
+    if (self->bytes != NULL)
+        return g_memory_input_stream_new_from_bytes (self->bytes);
+
     /* nothing to do */
     g_set_error (error, GCAB_ERROR, GCAB_ERROR_FORMAT,
                  "No GFile for %s", gcab_file_get_name (self));
@@ -411,6 +486,10 @@ gcab_file_get_output_stream (GCabFile *self,
                              GCancellable *cancellable,
                              GError **error)
 {
+    /* not writing to a GFile */
+    if (path_extract == NULL)
+        return g_memory_output_stream_new_resizable ();
+
     /* make path have UNIX directory slashes */
     g_autofree gchar *fname = g_strdup (gcab_file_get_extract_name (self));
     g_strdelimit (fname, "\\", '/');
diff --git a/libgcab/gcab-file.h b/libgcab/gcab-file.h
index ab3f012..bb03bf0 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 1a12371..d9917bd 100644
--- a/libgcab/gcab-folder.c
+++ b/libgcab/gcab-folder.c
@@ -461,6 +461,14 @@ gcab_folder_extract (GCabFolder *self,
 
         if (!g_output_stream_close (out, cancellable, error))
             return FALSE;
+
+        /* no backing GFile */
+        if (path_extract == NULL) {
+            g_autoptr(GBytes) blob = NULL;
+            blob = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out));
+            gcab_file_set_bytes (file, blob);
+        }
+
     }
 
     return TRUE;
diff --git a/libgcab/gcab-priv.h b/libgcab/gcab-priv.h
index 6d200e5..a3894e8 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,
diff --git a/libgcab/libgcab.syms b/libgcab/libgcab.syms
index 80f59c5..5bff171 100644
--- a/libgcab/libgcab.syms
+++ b/libgcab/libgcab.syms
@@ -43,6 +43,8 @@ LIBGCAB1_0.6 {
 } LIBGCAB1_0.5;
 
 LIBGCAB1_1.0 {
+        gcab_file_get_bytes;
+        gcab_file_new_with_bytes;
         gcab_file_set_attributes;
         gcab_file_set_date;
         gcab_folder_get_comptype;
diff --git a/tests/gcab-self-test.c b/tests/gcab-self-test.c
index defd278..085ca2b 100644
--- a/tests/gcab-self-test.c
+++ b/tests/gcab-self-test.c
@@ -22,6 +22,7 @@
 
 #include <limits.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <libgcab.h>
 
@@ -254,6 +255,76 @@ _compute_checksum_for_file (GFile *file, GError **error)
 }
 
 static void
+gcab_test_cabinet_blob_func (void)
+{
+    gboolean ret;
+    g_autoptr(GError) error = NULL;
+    g_autoptr(GCabCabinet) cabinet = gcab_cabinet_new ();
+
+    /* create folder and add to cabinet */
+    g_autoptr(GCabFolder) folder = gcab_folder_new (GCAB_COMPRESSION_NONE);
+    ret = gcab_cabinet_add_folder (cabinet, folder, &error);
+    g_assert_no_error (error);
+    g_assert (ret);
+
+    /* add the two files */
+    struct {
+        const gchar *fn;
+        const gchar *contents;
+    } files[] = {
+        { "test.sh",            "echo ola\n" },
+        { "test.txt",           "Ola!\n" },
+        { NULL,                 NULL }
+    };
+    for (guint i = 0; files[i].fn != NULL; i++) {
+        g_autoptr(GBytes) bytes_tmp = g_bytes_new_static (files[i].contents,
+                                                          strlen (files[i].contents));
+        g_autoptr(GCabFile) cabfile = gcab_file_new_with_bytes (files[i].fn, bytes_tmp);
+
+        /* set the time and attributes */
+        g_autoptr(GDateTime) dt = dt = g_date_time_new_utc (2017, 9, 15, 0, 0, 0.f);
+        GTimeVal tv;
+        ret = g_date_time_to_timeval (dt, &tv);
+        g_assert (ret);
+        gcab_file_set_date (cabfile, &tv);
+        gcab_file_set_attributes (cabfile, GCAB_FILE_ATTRIBUTE_ARCH);
+
+        /* add file to folder */
+        ret = gcab_folder_add_file (folder, cabfile, FALSE, NULL, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+    }
+
+    /* write to a blob */
+    g_autoptr(GOutputStream) op = g_memory_output_stream_new_resizable ();
+    ret = gcab_cabinet_write_simple (cabinet, op, NULL, NULL, NULL, &error);
+    g_assert_no_error (error);
+    g_assert (ret);
+
+    /* get what the checksum is supposed to be */
+    g_autofree gchar *fn = gcab_test_get_filename ("test-none.cab");
+    g_assert (fn != NULL);
+    g_autoptr(GFile) file = g_file_new_for_path (fn);
+    g_assert (file != NULL);
+    g_autofree gchar *checksum = _compute_checksum_for_file (file, &error);
+    g_assert_no_error (error);
+    g_assert (checksum != NULL);
+
+    /* write for debugging */
+    ret = g_output_stream_close (op, NULL, &error);
+    g_assert_no_error (error);
+    g_assert (ret);
+    g_autoptr(GBytes) blob = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (op));
+    ret = g_file_set_contents ("/tmp/test-none.cab", g_bytes_get_data (blob, NULL), g_bytes_get_size (blob), 
&error);
+    g_assert_no_error (error);
+    g_assert (ret);
+
+    /* verify the checksum */
+    g_autofree gchar *csum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob);
+    g_assert_cmpstr (csum, ==, checksum);
+}
+
+static void
 gcab_test_cabinet_load_func (void)
 {
     struct {
@@ -304,6 +375,14 @@ gcab_test_cabinet_load_func (void)
         cabfolder_tmp = g_ptr_array_index (cabfolders, 0);
         g_assert_cmpint (gcab_folder_get_comptype (cabfolder_tmp), ==, tests[i].comptype);
 
+        g_autoptr(GSList) cabfiles = gcab_folder_get_files (cabfolder_tmp);
+        for (GSList *l = cabfiles; l != NULL; l = l->next) {
+            GCabFile *cabfile = GCAB_FILE (l->data);
+            g_assert_null (gcab_file_get_file (cabfile));
+            g_assert_null (gcab_file_get_bytes (cabfile));
+            g_assert_cmpint (gcab_file_get_attributes (cabfile), ==, GCAB_FILE_ATTRIBUTE_ARCH);
+        }
+
         file_tmpdir = g_file_new_for_path ("/tmp");
         ret = gcab_cabinet_extract_simple (cabinet, file_tmpdir, NULL, NULL, NULL, &error);
         g_assert_no_error (error);
@@ -318,6 +397,21 @@ gcab_test_cabinet_load_func (void)
             g_assert (csum != NULL);
             g_assert_cmpstr (csum, ==, files[j].checksum);
         }
+
+        /* extract again to a memory blob on each GCabFile */
+        ret = gcab_cabinet_extract_simple (cabinet, NULL, NULL, NULL, NULL, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+
+        /* check each blob checksum */
+        for (guint j = 0; files[j].fn != NULL; j++) {
+            GCabFile *cabfile = gcab_folder_get_file_by_name (cabfolder_tmp, files[j].fn);
+            g_assert_nonnull (cabfile);
+            GBytes *blob = gcab_file_get_bytes (cabfile);
+            g_assert_nonnull (blob);
+            g_autofree gchar *csum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob);
+            g_assert_cmpstr (csum, ==, files[j].checksum);
+        }
     }
 }
 
@@ -495,6 +589,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{blob}", gcab_test_cabinet_blob_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]