/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* gsf-outfile-zip.c: zip archive output.
*
* Copyright (C) 2002-2006 Jon K Hellan (hellan@acm.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2.1 of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Outc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include <gsf-config.h>
#include <gsf/gsf-outfile-impl.h>
#include <gsf/gsf-outfile-zip.h>
#include <gsf/gsf-impl-utils.h>
#include <gsf/gsf-utils.h>
#include <gsf/gsf-zip-impl.h>
#include <string.h>
#include <time.h>
#include <zlib.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "libgsf:zip"
enum {
PROP_0,
PROP_SINK,
PROP_ENTRY_NAME,
PROP_COMPRESSION_LEVEL
};
static GObjectClass *parent_class;
struct _GsfOutfileZip {
GsfOutfile parent;
GsfOutput *sink;
GsfOutfileZip *root;
char *entry_name;
GsfZipVDir *vdir;
GPtrArray *root_order; /* only valid for the root */
z_stream *stream;
GsfZipCompressionMethod compression_method;
gboolean writing;
guint8 *buf;
size_t buf_size;
};
typedef struct {
GsfOutfileClass parent_class;
} GsfOutfileZipClass;
#define GSF_OUTFILE_ZIP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GSF_OUTFILE_ZIP_TYPE, GsfOutfileZipClass))
#define GSF_IS_OUTFILE_ZIP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSF_OUTFILE_ZIP_TYPE))
static void
disconnect_children (GsfOutfileZip *zip)
{
unsigned i;
if (!zip->root_order)
return;
for (i = 0 ; i < zip->root_order->len ; i++) {
GsfOutfileZip *child =
g_ptr_array_index (zip->root_order, i);
if (child)
g_object_unref (child);
}
g_ptr_array_free (zip->root_order, TRUE);
zip->root_order = NULL;
}
static void
gsf_outfile_zip_finalize (GObject *obj)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (obj);
/* If the closing failed, we might have stuff here. */
disconnect_children (zip);
if (zip->sink != NULL) {
g_object_unref (zip->sink);
zip->sink = NULL;
}
g_free (zip->entry_name);
if (zip->stream)
(void) deflateEnd (zip->stream);
g_free (zip->stream);
g_free (zip->buf);
if (zip == zip->root)
gsf_vdir_free (zip->vdir, TRUE); /* Frees vdirs recursively */
parent_class->finalize (obj);
}
static GObject *
gsf_outfile_zip_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_params)
{
GsfOutfileZip *zip =(GsfOutfileZip *)
(parent_class->constructor (type,
n_construct_properties,
construct_params));
if (!zip->entry_name) {
zip->vdir = gsf_vdir_new ("", TRUE, NULL);
zip->root_order = g_ptr_array_new ();
zip->root = zip;
/* The names are the same */
gsf_output_set_name (GSF_OUTPUT (zip), gsf_output_name (zip->sink));
gsf_output_set_container (GSF_OUTPUT (zip), NULL);
}
return (GObject *)zip;
}
static gboolean
gsf_outfile_zip_seek (G_GNUC_UNUSED GsfOutput *output,
G_GNUC_UNUSED gsf_off_t offset,
G_GNUC_UNUSED GSeekType whence)
{
return FALSE;
}
static gboolean
zip_dirent_write (GsfOutput *sink, GsfZipDirent *dirent)
{
static guint8 const dirent_signature[] =
{ 'P', 'K', 0x01, 0x02 };
guint8 buf[ZIP_DIRENT_SIZE];
int nlen = strlen (dirent->name);
gboolean ret;
memset (buf, 0, sizeof buf);
memcpy (buf, dirent_signature, sizeof dirent_signature);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_ENCODER, 0x317); /* Unix */
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_EXTRACT, 0x14);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_FLAGS, 0x08);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_COMPR_METHOD,
dirent->compr_method);
GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_DOSTIME, dirent->dostime);
GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_CRC32, dirent->crc32);
GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_CSIZE, dirent->csize);
GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_USIZE, dirent->usize);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_NAME_SIZE, nlen);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_EXTRAS_SIZE, 0);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_COMMENT_SIZE, 0);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_DISKSTART, 0);
GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_FILE_TYPE, 0);
/* Hardcode file mode 644 */
GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_FILE_MODE, 0644 << 16);
GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_OFFSET, dirent->offset);
ret = gsf_output_write (sink, sizeof buf, buf);
if (ret)
ret = gsf_output_write (sink, nlen, dirent->name);
return ret;
}
static gboolean
zip_trailer_write (GsfOutfileZip *zip, unsigned entries, gsf_off_t dirpos)
{
static guint8 const trailer_signature[] =
{ 'P', 'K', 0x05, 0x06 };
guint8 buf[ZIP_TRAILER_SIZE];
gsf_off_t pos = gsf_output_tell (zip->sink);
memset (buf, 0, sizeof buf);
memcpy (buf, trailer_signature, sizeof trailer_signature);
GSF_LE_SET_GUINT16 (buf + ZIP_TRAILER_ENTRIES, entries);
GSF_LE_SET_GUINT16 (buf + ZIP_TRAILER_TOTAL_ENTRIES, entries);
GSF_LE_SET_GUINT32 (buf + ZIP_TRAILER_DIR_SIZE, pos - dirpos);
GSF_LE_SET_GUINT32 (buf + ZIP_TRAILER_DIR_POS, dirpos);
return gsf_output_write (zip->sink, sizeof buf, buf);
}
static gboolean
zip_close_root (GsfOutput *output)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
GsfOutfileZip *child;
gsf_off_t dirpos = gsf_output_tell (zip->sink);
GPtrArray *elem = zip->root_order;
unsigned entries = elem->len;
unsigned i;
/* Check that children are closed */
for (i = 0 ; i < elem->len ; i++) {
child = g_ptr_array_index (elem, i);
if (!gsf_output_is_closed (GSF_OUTPUT (child))) {
g_warning ("Child still open");
return FALSE;
}
}
/* Write directory */
for (i = 0 ; i < entries ; i++) {
child = g_ptr_array_index (elem, i);
if (!zip_dirent_write (zip->sink, child->vdir->dirent))
return FALSE;
}
disconnect_children (zip);
return zip_trailer_write (zip, entries, dirpos);
}
static void
stream_name_write_to_buf (GsfOutfileZip *zip, GString *res)
{
GsfOutput *output = GSF_OUTPUT (zip);
GsfOutfile *container;
if (zip == zip->root)
return;
container = gsf_output_container (output);
if (container) {
stream_name_write_to_buf (GSF_OUTFILE_ZIP (container), res);
if (res->len) {
/* Forward slash is specified by the format. */
g_string_append_c (res, '/');
}
}
if (zip->entry_name)
g_string_append (res, zip->entry_name);
}
static char *
stream_name_build (GsfOutfileZip *zip)
{
GString *str = g_string_sized_new (80);
stream_name_write_to_buf (zip, str);
return g_string_free (str, FALSE);
}
static guint32
zip_time_make (time_t t)
{
struct tm *localnow = localtime (&t);
guint32 ztime;
ztime = (localnow->tm_year - 80) & 0x7f;
ztime = (ztime << 4) | ((localnow->tm_mon + 1) & 0x0f);
ztime = (ztime << 5) | (localnow->tm_mday & 0x1f);
ztime = (ztime << 5) | (localnow->tm_hour & 0x1f);
ztime = (ztime << 6) | (localnow->tm_min & 0x3f);
ztime = (ztime << 5) | ((localnow->tm_sec / 2) & 0x1f);
return ztime;
}
static GsfZipDirent*
zip_dirent_new_out (GsfOutfileZip *zip)
{
GsfZipDirent *dirent = gsf_zip_dirent_new ();
dirent->name = stream_name_build (zip);
dirent->compr_method = zip->compression_method;
dirent->dostime = zip_time_make (time (NULL));
return dirent;
}
static gboolean
zip_header_write (GsfOutfileZip *zip)
{
static guint8 const header_signature[] =
{ 'P', 'K', 0x03, 0x04 };
guint8 hbuf[ZIP_HEADER_SIZE];
GsfZipDirent *dirent = zip->vdir->dirent;
guint16 flags = 0;
char *name = dirent->name;
int nlen = strlen (name);
gboolean ret;
memset (hbuf, 0, sizeof hbuf);
memcpy (hbuf, header_signature, sizeof header_signature);
GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_VERSION, 0x14);
if (dirent->compr_method == GSF_ZIP_DEFLATED)
flags = 0x08;
GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_FLAGS, flags);
GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_COMP_METHOD,
dirent->compr_method);
GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_TIME, dirent->dostime);
GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_NAME_LEN, nlen);
ret = gsf_output_write (zip->sink, sizeof hbuf, hbuf);
if (ret)
ret = gsf_output_write (zip->sink, nlen, name);
return ret;
}
static gboolean
zip_init_write (GsfOutput *output)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
GsfZipDirent *dirent;
int ret;
if (zip->root->writing) {
g_warning ("Already writing to another stream in archive");
return FALSE;
}
if (!gsf_output_wrap (G_OBJECT (output), zip->sink))
return FALSE;
dirent = zip_dirent_new_out (zip);
dirent->offset = gsf_output_tell (zip->sink);
if (zip->vdir->dirent)
g_warning ("Leak.");
zip->vdir->dirent = dirent;
zip_header_write (zip);
zip->writing = TRUE;
zip->root->writing = TRUE;
dirent->crc32 = crc32 (0L, Z_NULL, 0);
if (zip->compression_method == GSF_ZIP_DEFLATED) {
if (!zip->stream) {
zip->stream = g_new0 (z_stream, 1);
}
ret = deflateInit2 (zip->stream, Z_DEFAULT_COMPRESSION,
Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL,
Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
return FALSE;
if (!zip->buf) {
zip->buf_size = ZIP_BUF_SIZE;
zip->buf = g_new (guint8, zip->buf_size);
}
zip->stream->next_out = zip->buf;
zip->stream->avail_out = zip->buf_size;
}
return TRUE;
}
static gboolean
zip_output_block (GsfOutfileZip *zip)
{
size_t num_bytes = zip->buf_size - zip->stream->avail_out;
GsfZipDirent *dirent = zip->vdir->dirent;
if (!gsf_output_write (zip->sink, num_bytes, zip->buf)) {
return FALSE;
}
dirent->csize += num_bytes;
zip->stream->next_out = zip->buf;
zip->stream->avail_out = zip->buf_size;
return TRUE;
}
static gboolean
zip_flush (GsfOutfileZip *zip)
{
int zret;
do {
zret = deflate (zip->stream, Z_FINISH);
if (zret == Z_OK || (zret == Z_BUF_ERROR && zip->stream->avail_out == 0)) {
/* In this case Z_OK or Z_BUF_ERROR means more buffer
space is needed */
if (!zip_output_block (zip))
return FALSE;
}
} while (zret == Z_OK || zret == Z_BUF_ERROR);
if (zret != Z_STREAM_END)
return FALSE;
if (!zip_output_block (zip))
return FALSE;
return TRUE;
}
/* Write the per stream data descriptor */
static gboolean
zip_ddesc_write (GsfOutfileZip *zip)
{
static guint8 const ddesc_signature[] =
{ 'P', 'K', 0x07, 0x08 };
guint8 buf[16];
GsfZipDirent *dirent = zip->vdir->dirent;
memcpy (buf, ddesc_signature, sizeof ddesc_signature);
GSF_LE_SET_GUINT32 (buf + 4, dirent->crc32);
GSF_LE_SET_GUINT32 (buf + 8, dirent->csize);
GSF_LE_SET_GUINT32 (buf + 12, dirent->usize);
if (!gsf_output_write (zip->sink, sizeof buf, buf)) {
return FALSE;
}
return TRUE;
}
static gboolean
zip_header_write_sizes (GsfOutfileZip *zip)
{
guint8 hbuf[ZIP_HEADER_SIZE];
GsfZipDirent *dirent = zip->vdir->dirent;
gsf_off_t pos = gsf_output_tell (zip->sink);
if (!gsf_output_seek (zip->sink, dirent->offset + ZIP_HEADER_CRC,
G_SEEK_SET))
return FALSE;
GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_CRC, dirent->crc32);
GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_COMP_SIZE, dirent->csize);
GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_UNCOMP_SIZE, dirent->usize);
if (!gsf_output_write (zip->sink, 12, hbuf + ZIP_HEADER_CRC))
return FALSE;
if (!gsf_output_seek (zip->sink, pos, G_SEEK_SET))
return FALSE;
return TRUE;
}
static gboolean
zip_close_stream (GsfOutput *output)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
gboolean result;
if (!zip->writing)
if (!zip_init_write (output))
return FALSE;
if (zip->compression_method == GSF_ZIP_DEFLATED) {
if (!zip_flush (zip))
return FALSE;
if (!zip_ddesc_write (zip)) /* Write data descriptor */
return FALSE;
} else {
if (!zip_header_write_sizes (zip)) /* Write crc, sizes */
return FALSE;
}
zip->root->writing = FALSE;
result = gsf_output_unwrap (G_OBJECT (output), zip->sink);
/* Free unneeded memory */
if (zip->stream) {
(void) deflateEnd (zip->stream);
g_free (zip->stream);
zip->stream = NULL;
g_free (zip->buf);
zip->buf = NULL;
}
return result;
}
static gboolean
gsf_outfile_zip_close (GsfOutput *output)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
gboolean ret;
/* The root dir */
if (zip == zip->root)
ret = zip_close_root (output);
else if (zip->vdir->is_directory)
/* Directories: Do nothing. Should change this to actually
* write dirs which don't have children. */
ret = TRUE;
else
ret = zip_close_stream (output);
return ret;
}
static gboolean
gsf_outfile_zip_write (GsfOutput *output,
size_t num_bytes, guint8 const *data)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
GsfZipDirent *dirent;
int ret;
g_return_val_if_fail (zip && zip->vdir, FALSE);
g_return_val_if_fail (!zip->vdir->is_directory, FALSE);
g_return_val_if_fail (data, FALSE);
if (!zip->writing)
if (!zip_init_write (output))
return FALSE;
dirent = zip->vdir->dirent;
if (zip->compression_method == GSF_ZIP_DEFLATED) {
zip->stream->next_in = (unsigned char *) data;
zip->stream->avail_in = num_bytes;
while (zip->stream->avail_in > 0) {
if (zip->stream->avail_out == 0) {
if (!zip_output_block (zip))
return FALSE;
}
ret = deflate (zip->stream, Z_NO_FLUSH);
if (ret != Z_OK)
return FALSE;
}
} else {
if (!gsf_output_write (zip->sink, num_bytes, data))
return FALSE;
dirent->csize += num_bytes;
}
dirent->crc32 = crc32 (dirent->crc32, data, num_bytes);
dirent->usize += num_bytes;
return TRUE;
}
static void
root_register_child (GsfOutfileZip *root, GsfOutfileZip *child)
{
child->root = root;
if (!child->vdir->is_directory) {
g_object_ref (child);
g_ptr_array_add (root->root_order, child);
}
}
static void
gsf_outfile_zip_set_sink (GsfOutfileZip *zip, GsfOutput *sink)
{
if (sink)
g_object_ref (sink);
if (zip->sink)
g_object_unref (zip->sink);
zip->sink = sink;
}
static GsfOutput *
gsf_outfile_zip_new_child (GsfOutfile *parent,
char const *name, gboolean is_dir,
char const *first_property_name, va_list args)
{
GsfOutfileZip *zip_parent = (GsfOutfileZip *)parent;
GsfOutfileZip *child;
size_t n_params = 0;
GParameter *params = NULL;
char *display_name;
g_return_val_if_fail (zip_parent != NULL, NULL);
g_return_val_if_fail (zip_parent->vdir, NULL);
g_return_val_if_fail (zip_parent->vdir->is_directory, NULL);
g_return_val_if_fail (name && *name, NULL);
gsf_property_settings_collect (GSF_OUTFILE_ZIP_TYPE,
¶ms, &n_params,
"sink", zip_parent->sink,
"entry-name", name,
NULL);
gsf_property_settings_collect_valist (GSF_OUTFILE_ZIP_TYPE,
¶ms, &n_params,
first_property_name,
args);
child = (GsfOutfileZip *)g_object_newv (GSF_OUTFILE_ZIP_TYPE,
n_params,
params);
gsf_property_settings_free (params, n_params);
child->vdir = gsf_vdir_new (name, is_dir, NULL);
/* FIXME: It isn't clear what encoding name is in. */
display_name = g_filename_display_name (name);
gsf_output_set_name (GSF_OUTPUT (child), display_name);
g_free (display_name);
gsf_output_set_container (GSF_OUTPUT (child), parent);
gsf_vdir_add_child (zip_parent->vdir, child->vdir);
root_register_child (zip_parent->root, child);
return GSF_OUTPUT (child);
}
static void
gsf_outfile_zip_init (GObject *obj)
{
GsfOutfileZip *zip = GSF_OUTFILE_ZIP (obj);
zip->sink = NULL;
zip->root = NULL;
zip->entry_name = NULL;
zip->vdir = NULL;
zip->root_order = NULL;
zip->stream = NULL;
zip->compression_method = GSF_ZIP_DEFLATED;
zip->writing = FALSE;
zip->buf = NULL;
zip->buf_size = 0;
}
static void
gsf_outfile_zip_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GsfOutfileZip *zip = (GsfOutfileZip *)object;
switch (property_id) {
case PROP_SINK:
g_value_set_object (value, zip->sink);
break;
case PROP_ENTRY_NAME:
g_value_set_string (value, zip->entry_name);
break;
case PROP_COMPRESSION_LEVEL:
g_value_set_int (value,
zip->vdir->dirent
? zip->vdir->dirent->compr_method
: 0);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gsf_outfile_zip_set_property (GObject *object,
guint property_id,
GValue const *value,
GParamSpec *pspec)
{
GsfOutfileZip *zip = (GsfOutfileZip *)object;
switch (property_id) {
case PROP_SINK:
gsf_outfile_zip_set_sink (zip, g_value_get_object (value));
break;
case PROP_ENTRY_NAME:
zip->entry_name = g_strdup (g_value_get_string (value));
break;
case PROP_COMPRESSION_LEVEL: {
int level = g_value_get_int (value);
switch (level) {
case GSF_ZIP_STORED:
case GSF_ZIP_DEFLATED:
zip->compression_method = level;
break;
default:
g_warning ("Unsupported compression level %d", level);
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gsf_outfile_zip_class_init (GObjectClass *gobject_class)
{
GsfOutputClass *output_class = GSF_OUTPUT_CLASS (gobject_class);
GsfOutfileClass *outfile_class = GSF_OUTFILE_CLASS (gobject_class);
gobject_class->constructor = gsf_outfile_zip_constructor;
gobject_class->finalize = gsf_outfile_zip_finalize;
gobject_class->get_property = gsf_outfile_zip_get_property;
gobject_class->set_property = gsf_outfile_zip_set_property;
output_class->Write = gsf_outfile_zip_write;
output_class->Seek = gsf_outfile_zip_seek;
output_class->Close = gsf_outfile_zip_close;
outfile_class->new_child = gsf_outfile_zip_new_child;
parent_class = g_type_class_peek_parent (gobject_class);
g_object_class_install_property
(gobject_class,
PROP_SINK,
g_param_spec_object ("sink", "Sink",
"Where the archive is written.",
GSF_OUTPUT_TYPE,
GSF_PARAM_STATIC |
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(gobject_class,
PROP_ENTRY_NAME,
g_param_spec_string ("entry-name", "Entry Name",
"The filename of this member in the archive without path.",
NULL,
GSF_PARAM_STATIC |
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(gobject_class,
PROP_COMPRESSION_LEVEL,
g_param_spec_int ("compression-level",
"Compression Level",
"The level of compression used, zero meaning none.",
0, 10,
GSF_ZIP_DEFLATED,
GSF_PARAM_STATIC |
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
}
GSF_CLASS (GsfOutfileZip, gsf_outfile_zip,
gsf_outfile_zip_class_init, gsf_outfile_zip_init,
GSF_OUTFILE_TYPE)
/**
* gsf_outfile_zip_new :
* @sink: a #GsfOutput to hold the ZIP file
* @err: Location to store error, or %NULL; currently unused.
*
* Creates the root directory of a Zip file and manages the addition of
* children.
*
* <note>This adds a reference to @sink.</note>
*
* Returns: the new zip file handler
**/
GsfOutfile *
gsf_outfile_zip_new (GsfOutput *sink, G_GNUC_UNUSED GError **err)
{
g_return_val_if_fail (GSF_IS_OUTPUT (sink), NULL);
return (GsfOutfile *)g_object_new (GSF_OUTFILE_ZIP_TYPE,
"sink", sink,
NULL);
}
/* deprecated has no effect */
gboolean
gsf_outfile_zip_set_compression_method (G_GNUC_UNUSED GsfOutfileZip *zip,
G_GNUC_UNUSED GsfZipCompressionMethod method)
{
return TRUE;
}
syntax highlighted by Code2HTML, v. 0.9.1