[gcab] Add extract support for MSZIP



commit 6caabad5b3dda23607a5e7eaf2faed920f2c2cc8
Author: Marc-Andrà Lureau <marcandre lureau gmail com>
Date:   Mon Jan 14 01:22:30 2013 +0100

    Add extract support for MSZIP

 gcab.c                 |   31 +++++++++---
 libgcab/cabinet.c      |   68 +++++++++++++++++++++++++--
 libgcab/cabinet.h      |    6 ++-
 libgcab/gcab-cabinet.c |   52 ++++++++++++++++++++-
 libgcab/gcab-cabinet.h |   10 ++++-
 libgcab/gcab-folder.c  |  122 ++++++++++++++++++++++++++++++++++++++++++++++--
 libgcab/gcab-priv.h    |   12 ++++-
 7 files changed, 281 insertions(+), 20 deletions(-)
---
diff --git a/gcab.c b/gcab.c
index 821a509..2d82b4f 100644
--- a/gcab.c
+++ b/gcab.c
@@ -74,14 +74,18 @@ main (int argc, char *argv[])
     int i;
 
     gchar **args = NULL;
+    gchar *change = NULL;
     int nopath = 0;
     int compress = 0;
     int list = 0;
     int create = 0;
+    int extract = 0;
     GOptionEntry entries[] = {
         { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, N_("Be verbose"), NULL },
+        { "change", 'C', 0, G_OPTION_ARG_FILENAME, &change, N_("Change to directory"), NULL },
         { "list", 't', 0, G_OPTION_ARG_NONE, &list, N_("List content"), NULL },
         { "create", 'c', 0, G_OPTION_ARG_NONE, &create, N_("Create archive"), NULL },
+        { "extract", 'x', 0, G_OPTION_ARG_NONE, &extract, N_("Extract all files"), NULL },
         { "zip", 'z', 0, G_OPTION_ARG_NONE, &compress, N_("Use zip compression"), NULL },
         { "nopath", 'n', 0, G_OPTION_ARG_NONE, &nopath, N_("Do not include path"), NULL },
         { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("FILE INPUT_FILES...") },
@@ -109,7 +113,7 @@ individual files from the archive.\
         gcab_error (_("option parsing failed: %s\n"), error->message);
     g_option_context_free(context);
 
-    if ((list + create) != 1)
+    if ((list + extract + create) != 1)
         gcab_error (_("Please specify a single operation."));
 
     if (!args || args[0] == NULL)
@@ -118,7 +122,7 @@ individual files from the archive.\
     GCancellable *cancellable = g_cancellable_new ();
     GCabCabinet *cabinet = gcab_cabinet_new ();
 
-    if (list) {
+    if (list || extract) {
         GFile *file = g_file_new_for_commandline_arg (args[0]);
         GInputStream *in = G_INPUT_STREAM (g_file_read (file, cancellable, &error));
 
@@ -127,13 +131,24 @@ individual files from the archive.\
         if (!gcab_cabinet_load (cabinet, in, cancellable, &error))
             gcab_error (_("error reading %s: %s\n"), args[0], error->message);
 
-        GPtrArray *folders = gcab_cabinet_get_folders (cabinet);
-        for (i = 0; i < folders->len; i++) {
-            GSList *l, *list = gcab_folder_get_files (g_ptr_array_index (folders, i));
-            for (l = list; l != NULL; l = l->next)
-                g_print ("%s\n", gcab_file_get_name (GCAB_FILE (l->data)));
-            g_slist_free (list);
+        if (list) {
+            GPtrArray *folders = gcab_cabinet_get_folders (cabinet);
+            for (i = 0; i < folders->len; i++) {
+                GSList *l, *list = gcab_folder_get_files (g_ptr_array_index (folders, i));
+                for (l = list; l != NULL; l = l->next)
+                    g_print ("%s\n", gcab_file_get_name (GCAB_FILE (l->data)));
+                g_slist_free (list);
+            }
+        } else if (extract) {
+            g_object_unref (file);
+            if (change == NULL)
+                change = g_get_current_dir ();
+            file = g_file_new_for_path (change);
+
+            if (!gcab_cabinet_extract (cabinet, file, file_callback, NULL, NULL, cancellable, &error))
+                gcab_error (_("error during extraction: %s"), error->message);
         }
+
         g_object_unref (in);
         g_object_unref (file);
         goto end;
diff --git a/libgcab/cabinet.c b/libgcab/cabinet.c
index 1bd17a8..b38c0b9 100644
--- a/libgcab/cabinet.c
+++ b/libgcab/cabinet.c
@@ -1,4 +1,3 @@
-#include <zlib.h>
 #include "gcab-priv.h"
 
 static voidpf
@@ -136,6 +135,8 @@ hexdump (guchar *p, gsize s)
     g_debug ("%15s: %s", #field, p->field)
 #define PN(p, field, size) \
     g_debug ("%15s:", #field), hexdump (p->field, size)
+#define PND(p, field, size) \
+    g_debug ("%15s:", #field), hexdump (field, size)
 
 #define W1(val) \
     g_data_output_stream_put_byte (out, val, cancellable, error)
@@ -400,21 +401,29 @@ cdata_write (cdata_t *cd, GDataOutputStream *out, int type,
     return TRUE;
 }
 
+G_GNUC_INTERNAL void
+cdata_finish (cdata_t *cd)
+{
+
+}
+
 G_GNUC_INTERNAL gboolean
 cdata_read (cdata_t *cd, u1 res_data, GCabCompression compression,
             GDataInputStream *in, GCancellable *cancellable, GError **error)
 
 {
     gboolean success = FALSE;
+    int zret = Z_OK;
+    gchar *data = compression == GCAB_COMPRESSION_NONE ? cd->out : cd->data;
 
     R4 (cd->checksum);
     R2 (cd->ncbytes);
     R2 (cd->nubytes);
     cd->reserved = g_malloc (res_data);
     RN (cd->reserved, res_data);
-    RN (cd->data, cd->ncbytes);
+    RN (data, cd->ncbytes);
 
-    CHECKSUM datacsum = compute_checksum(cd->data, cd->ncbytes, 0);
+    CHECKSUM datacsum = compute_checksum(data, cd->ncbytes, 0);
     g_return_val_if_fail (cd->checksum == compute_checksum ((guint8*)&cd->ncbytes, 4, datacsum), FALSE);
 
     if (g_getenv ("GCAB_DEBUG")) {
@@ -424,11 +433,62 @@ cdata_read (cdata_t *cd, u1 res_data, GCabCompression compression,
         P2 (cd, nubytes);
         if (res_data)
             PN (cd, reserved, res_data);
-        PN (cd, data, 64);
+        PND (cd, data, 64);
+    }
+
+    if (compression == GCAB_COMPRESSION_MSZIP) {
+        z_stream *z = &cd->z;
+
+        if (cd->data[0] != 'C' || cd->data[1] != 'K')
+            goto end;
+
+        z->avail_in = cd->ncbytes - 2;
+        z->next_in = cd->data + 2;
+        z->avail_out = cd->nubytes;
+        z->next_out = cd->out;
+
+        if (!z->opaque) {
+            z->zalloc = zalloc;
+            z->zfree = zfree;
+            z->opaque = cd;
+
+            zret = inflateInit2 (z, -15);
+            if (zret != Z_OK)
+                goto end;
+        }
+
+        while (1) {
+            zret = inflate (z, Z_BLOCK);
+            if (zret != Z_OK)
+                break;
+        }
+
+        if (zret != Z_STREAM_END)
+            goto end;
+
+        g_warn_if_fail (z->avail_in == 0);
+        g_warn_if_fail (z->avail_out == 0);
+        if (z->avail_in != 0 || z->avail_out != 0)
+            goto end;
+
+        zret = inflateReset (z);
+        if (zret != Z_OK)
+            goto end;
+
+        zret = inflateSetDictionary (z, cd->out, cd->nubytes);
+        if (zret != Z_OK)
+            goto end;
     }
 
     success = TRUE;
 
 end:
+    if (zret != Z_OK)
+        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
+                     "zlib failed: %s", zError(zret));
+    if (!*error && !success)
+        g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
+                     "Invalid cabint chunk");
+
     return success;
 }
diff --git a/libgcab/cabinet.h b/libgcab/cabinet.h
index 07d1418..c7ea983 100644
--- a/libgcab/cabinet.h
+++ b/libgcab/cabinet.h
@@ -12,6 +12,7 @@
 #include <dirent.h>
 #include <unistd.h>
 #include <time.h>
+#include <zlib.h>
 #include "gcab-folder.h"
 
 /* based on the spec
@@ -93,7 +94,9 @@ struct cdata
     u2 ncbytes;
     u2 nubytes;
     guint8 *reserved;
-    guint8 data[DATABLOCKSIZE*2];
+    guint8 data[DATABLOCKSIZE];
+    guint8 out[DATABLOCKSIZE];
+    z_stream z;
 };
 
 gboolean     cheader_write                      (cheader_t *ch,
@@ -135,5 +138,6 @@ gboolean     cdata_read                         (cdata_t *cd,
                                                  GDataInputStream *in,
                                                  GCancellable *cancellable,
                                                  GError **error);
+void         cdata_finish                       (cdata_t *cd);
 
 #endif /* CABINET_H */
diff --git a/libgcab/gcab-cabinet.c b/libgcab/gcab-cabinet.c
index 12fad3a..104b53d 100644
--- a/libgcab/gcab-cabinet.c
+++ b/libgcab/gcab-cabinet.c
@@ -11,6 +11,7 @@ struct _GCabCabinet
 
     GPtrArray *folders;
     GByteArray *reserved;
+    cheader_t cheader;
 };
 
 enum {
@@ -303,7 +304,9 @@ gcab_cabinet_load (GCabCabinet *self,
     cheader_t cheader;
     int i;
     GDataInputStream *in = g_data_input_stream_new (stream);
+    g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (in), FALSE);
     g_data_input_stream_set_byte_order (in, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
+
     GPtrArray *folders = self->folders;
 
     if (!cheader_read (&cheader, in, cancellable, error))
@@ -319,7 +322,7 @@ gcab_cabinet_load (GCabCabinet *self,
         if (!cfolder_read (&cfolder, cheader.res_folder, in, cancellable, error))
             goto end;
 
-        GCabFolder *folder = gcab_folder_new_with_cfolder (&cfolder);
+        GCabFolder *folder = gcab_folder_new_with_cfolder (&cfolder, stream);
         if (cfolder.reserved)
             g_object_set (folder, "reserved",
                           g_byte_array_new_take (cfolder.reserved, cheader.res_folder),
@@ -348,3 +351,50 @@ end:
         g_object_unref (in);
     return success;
 }
+
+/**
+ * gcab_cabinet_extract:
+ * @cabinet: a #GCabCabinet
+ * @path: the path to extract files
+ * @file_callback: (allow-none) (scope call): an optionnal #GCabFile callback,
+ *     return %FALSE to filter out or skip files.
+ * @progress_callback: (allow-none) (scope call): a progress callback
+ * @callback_data: callback data
+ * @cancellable: (allow-none): optional #GCancellable object,
+ *     %NULL to ignore
+ * @error: (allow-none): #GError to set on error, or %NULL
+ *
+ * Extract files to given path.
+ *
+ * Returns: %TRUE on success.
+ **/
+gboolean
+gcab_cabinet_extract (GCabCabinet *self,
+                      GFile *path,
+                      GCabFileCallback file_callback,
+                      GFileProgressCallback progress_callback,
+                      gpointer callback_data,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+    gboolean success = TRUE;
+    int i;
+
+    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 (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+    g_return_val_if_fail (!error || *error == NULL, FALSE);
+
+    for (i = 0; i < self->folders->len; ++i) {
+        GCabFolder *folder = g_ptr_array_index (self->folders, i);
+        if (!gcab_folder_extract (folder, path, self->cheader.res_data,
+                                  file_callback, progress_callback, callback_data,
+                                  cancellable, error)) {
+            success = FALSE;
+            break;
+        }
+    }
+
+end:
+    return success;
+}
diff --git a/libgcab/gcab-cabinet.h b/libgcab/gcab-cabinet.h
index e32f347..e6208d1 100644
--- a/libgcab/gcab-cabinet.h
+++ b/libgcab/gcab-cabinet.h
@@ -40,7 +40,8 @@ GQuark gcab_error_quark (void);
 
 typedef enum GCabError
 {
-    GCAB_ERROR_FORMAT
+    GCAB_ERROR_FORMAT,
+    GCAB_ERROR_FAILED,
 } GCabError;
 
 typedef struct _GCabCabinetClass GCabCabinetClass;
@@ -66,6 +67,13 @@ gboolean           gcab_cabinet_write         (GCabCabinet *cabinet,
                                                gpointer callback_data,
                                                GCancellable *cancellable,
                                                GError **error);
+gboolean           gcab_cabinet_extract       (GCabCabinet *cabinet,
+                                               GFile *path,
+                                               GCabFileCallback file_callback,
+                                               GFileProgressCallback progress_callback,
+                                               gpointer callback_data,
+                                               GCancellable *cancellable,
+                                               GError **error);
 
 G_END_DECLS
 
diff --git a/libgcab/gcab-folder.c b/libgcab/gcab-folder.c
index 07f0df1..b642557 100644
--- a/libgcab/gcab-folder.c
+++ b/libgcab/gcab-folder.c
@@ -30,6 +30,8 @@ gcab_folder_finalize (GObject *object)
     g_hash_table_unref (self->hash);
     if (self->reserved)
         g_byte_array_unref (self->reserved);
+    if (self->stream)
+        g_object_unref (self->stream);
 
     G_OBJECT_CLASS (gcab_folder_parent_class)->finalize (object);
 }
@@ -247,11 +249,15 @@ gcab_folder_new (GCabCompression compression)
 }
 
 G_GNUC_INTERNAL GCabFolder *
-gcab_folder_new_with_cfolder (const cfolder_t *folder)
+gcab_folder_new_with_cfolder (const cfolder_t *folder, GInputStream *stream)
 {
-    return g_object_new (GCAB_TYPE_FOLDER,
-                         "compression", folder->typecomp,
-                         NULL);
+    GCabFolder *self = g_object_new (GCAB_TYPE_FOLDER,
+                                     "compression", folder->typecomp,
+                                     NULL);
+    self->stream = g_object_ref (stream);
+    self->cfolder = *folder;
+
+    return self;
 }
 
 /**
@@ -267,3 +273,111 @@ gcab_folder_get_files (GCabFolder *self)
 
     return g_slist_reverse (g_slist_copy (self->files));
 }
+
+static gint
+sort_by_offset (GCabFile *a, GCabFile *b)
+{
+    g_return_val_if_fail (a != NULL, 0);
+    g_return_val_if_fail (b != NULL, 0);
+
+    return (gint64)a->cfile.uoffset - (gint64)b->cfile.uoffset;
+}
+
+G_GNUC_INTERNAL gboolean
+gcab_folder_extract (GCabFolder *self,
+                     GFile *path,
+                     u1 res_data,
+                     GCabFileCallback file_callback,
+                     GFileProgressCallback progress_callback,
+                     gpointer callback_data,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+    GError *my_error = NULL;
+    gboolean success = FALSE;
+    GDataInputStream *data = NULL;
+    GOutputStream *out = NULL;
+    GSList *f, *files = NULL;
+    cdata_t cdata = { 0, };
+
+    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))
+        goto end;
+
+    files = g_slist_sort (g_slist_copy (self->files), (GCompareFunc)sort_by_offset);
+
+    u4 nubytes = 0;
+    for (f = files; f != NULL; f = f->next) {
+        GCabFile *file = f->data;
+
+        if (file_callback && !file_callback (file, callback_data))
+            continue;
+
+        int i = 0, len = strlen (file->name);
+        gchar *fname = g_strdup (file->name);
+        for (i = 0; i < len; i++)
+            if (fname[i] == '\\')
+                fname[i] = '/';
+
+        GFileOutputStream *out;
+        GFile *gfile = g_file_resolve_relative_path (path, fname);
+        GFile *parent = g_file_get_parent (gfile);
+        g_free (fname);
+
+        if (!g_file_make_directory_with_parents (parent, cancellable, &my_error)) {
+            if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+                g_clear_error (&my_error);
+            else {
+                g_object_unref (gfile);
+                g_object_unref (parent);
+                g_propagate_error (error, my_error);
+                goto end;
+            }
+        }
+        g_object_unref (parent);
+
+        out = g_file_replace (gfile, NULL, FALSE, 0, cancellable, error);
+        if (!out) {
+            g_object_unref (gfile);
+            goto end;
+        }
+
+        u4 usize = file->cfile.usize;
+        u4 uoffset = file->cfile.uoffset;
+        do {
+            if ((nubytes + cdata.nubytes) <= uoffset) {
+                nubytes += cdata.nubytes;
+                if (!cdata_read (&cdata, res_data, self->compression,
+                                 data, cancellable, error))
+                    goto end;
+                continue;
+            } else {
+                gsize offset = file->cfile.uoffset > nubytes ?
+                    file->cfile.uoffset - nubytes : 0;
+                const void *p = &cdata.out[offset];
+                gsize count = MIN (usize, cdata.nubytes - offset);
+                if (!g_output_stream_write_all (G_OUTPUT_STREAM (out), p, count,
+                                                NULL, cancellable, error))
+                    goto end;
+                usize -= count;
+                uoffset += count;
+            }
+        } while (usize > 0);
+    }
+
+    success = TRUE;
+
+end:
+    if (files)
+        g_slist_free (files);
+    if (data)
+        g_object_unref (data);
+    if (out)
+        g_object_unref (out);
+    cdata_finish (&cdata);
+
+    return success;
+}
diff --git a/libgcab/gcab-priv.h b/libgcab/gcab-priv.h
index af2784f..82ce81f 100644
--- a/libgcab/gcab-priv.h
+++ b/libgcab/gcab-priv.h
@@ -27,14 +27,24 @@ struct _GCabFolder
     GHashTable *hash;
     GCabCompression compression;
     GByteArray *reserved;
+    cfolder_t cfolder;
+    GInputStream *stream;
 };
 
-GCabFolder *     gcab_folder_new_with_cfolder        (const cfolder_t *folder);
+GCabFolder *     gcab_folder_new_with_cfolder        (const cfolder_t *folder, GInputStream *stream);
 GCabFile *       gcab_file_new_with_cfile            (const cfile_t *file);
 
 gboolean         gcab_file_update_info               (GCabFile *file, GFileInfo *info);
 gboolean         gcab_file_set_uoffset               (GCabFile *file, u4 uoffset);
 
 gsize            gcab_folder_get_ndatablocks         (GCabFolder *folder);
+gboolean         gcab_folder_extract                 (GCabFolder *self,
+                                                      GFile *path,
+                                                      u1 res_data,
+                                                      GCabFileCallback file_callback,
+                                                      GFileProgressCallback progress_callback,
+                                                      gpointer callback_data,
+                                                      GCancellable *cancellable,
+                                                      GError **error);
 
 #endif /* GCAB_PRIV_H */



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