summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2017-12-14 22:05:49 +0000
committerRichard Hughes <richard@hughsie.com>2017-12-14 22:07:35 +0000
commit78b71b0c643023fa0e3f70cabd772469c4ed9858 (patch)
treeff6216f71a7d975d0220d05269c2d964249eadcf
parentde0855d414c1913d29545383e53549cd6c1199f3 (diff)
downloadgcab-wip/hughsie/future.tar.gz
Add gcab_cabinet_decompress()wip/hughsie/future
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.
-rw-r--r--libgcab/gcab-cabinet.c44
-rw-r--r--libgcab/gcab-cabinet.h5
-rw-r--r--libgcab/gcab-file.c67
-rw-r--r--libgcab/gcab-file.h2
-rw-r--r--libgcab/gcab-folder.c101
-rw-r--r--libgcab/gcab-priv.h7
-rw-r--r--libgcab/libgcab.syms3
-rw-r--r--tests/gcab-self-test.c57
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 ();
}