diff options
author | Richard Hughes <richard@hughsie.com> | 2017-12-14 22:05:49 +0000 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2017-12-14 22:07:35 +0000 |
commit | 78b71b0c643023fa0e3f70cabd772469c4ed9858 (patch) | |
tree | ff6216f71a7d975d0220d05269c2d964249eadcf | |
parent | de0855d414c1913d29545383e53549cd6c1199f3 (diff) | |
download | gcab-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.c | 44 | ||||
-rw-r--r-- | libgcab/gcab-cabinet.h | 5 | ||||
-rw-r--r-- | libgcab/gcab-file.c | 67 | ||||
-rw-r--r-- | libgcab/gcab-file.h | 2 | ||||
-rw-r--r-- | libgcab/gcab-folder.c | 101 | ||||
-rw-r--r-- | libgcab/gcab-priv.h | 7 | ||||
-rw-r--r-- | libgcab/libgcab.syms | 3 | ||||
-rw-r--r-- | 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 (); } |