1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * gsf-outfile-zip.c: zip archive output.
5 * Copyright (C) 2002-2006 Jon K Hellan (hellan@acm.org)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2.1 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Outc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 #include <gsf-config.h>
23 #include <gsf/gsf-outfile-impl.h>
24 #include <gsf/gsf-outfile-zip.h>
25 #include <gsf/gsf-impl-utils.h>
26 #include <gsf/gsf-utils.h>
27 #include <gsf/gsf-zip-impl.h>
34 #define G_LOG_DOMAIN "libgsf:zip"
40 PROP_COMPRESSION_LEVEL
43 static GObjectClass *parent_class;
45 struct _GsfOutfileZip {
54 GPtrArray *root_order; /* only valid for the root */
57 GsfZipCompressionMethod compression_method;
66 GsfOutfileClass parent_class;
69 #define GSF_OUTFILE_ZIP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GSF_OUTFILE_ZIP_TYPE, GsfOutfileZipClass))
70 #define GSF_IS_OUTFILE_ZIP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSF_OUTFILE_ZIP_TYPE))
73 disconnect_children (GsfOutfileZip *zip)
80 for (i = 0 ; i < zip->root_order->len ; i++) {
81 GsfOutfileZip *child =
82 g_ptr_array_index (zip->root_order, i);
84 g_object_unref (child);
86 g_ptr_array_free (zip->root_order, TRUE);
87 zip->root_order = NULL;
91 gsf_outfile_zip_finalize (GObject *obj)
93 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (obj);
95 /* If the closing failed, we might have stuff here. */
96 disconnect_children (zip);
98 if (zip->sink != NULL) {
99 g_object_unref (zip->sink);
103 g_free (zip->entry_name);
106 (void) deflateEnd (zip->stream);
107 g_free (zip->stream);
110 if (zip == zip->root)
111 gsf_vdir_free (zip->vdir, TRUE); /* Frees vdirs recursively */
113 parent_class->finalize (obj);
117 gsf_outfile_zip_constructor (GType type,
118 guint n_construct_properties,
119 GObjectConstructParam *construct_params)
121 GsfOutfileZip *zip =(GsfOutfileZip *)
122 (parent_class->constructor (type,
123 n_construct_properties,
126 if (!zip->entry_name) {
127 zip->vdir = gsf_vdir_new ("", TRUE, NULL);
128 zip->root_order = g_ptr_array_new ();
131 /* The names are the same */
132 gsf_output_set_name (GSF_OUTPUT (zip), gsf_output_name (zip->sink));
133 gsf_output_set_container (GSF_OUTPUT (zip), NULL);
136 return (GObject *)zip;
140 gsf_outfile_zip_seek (G_GNUC_UNUSED GsfOutput *output,
141 G_GNUC_UNUSED gsf_off_t offset,
142 G_GNUC_UNUSED GSeekType whence)
148 zip_dirent_write (GsfOutput *sink, GsfZipDirent *dirent)
150 static guint8 const dirent_signature[] =
151 { 'P', 'K', 0x01, 0x02 };
152 guint8 buf[ZIP_DIRENT_SIZE];
153 int nlen = strlen (dirent->name);
156 memset (buf, 0, sizeof buf);
157 memcpy (buf, dirent_signature, sizeof dirent_signature);
158 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_ENCODER, 0x317); /* Unix */
159 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_EXTRACT, 0x14);
160 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_FLAGS, dirent->flags);
161 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_COMPR_METHOD,
162 dirent->compr_method);
163 GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_DOSTIME, dirent->dostime);
164 GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_CRC32, dirent->crc32);
165 GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_CSIZE, dirent->csize);
166 GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_USIZE, dirent->usize);
167 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_NAME_SIZE, nlen);
168 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_EXTRAS_SIZE, 0);
169 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_COMMENT_SIZE, 0);
170 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_DISKSTART, 0);
171 GSF_LE_SET_GUINT16 (buf + ZIP_DIRENT_FILE_TYPE, 0);
172 /* Hardcode file mode 644 */
173 GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_FILE_MODE, 0644 << 16);
174 GSF_LE_SET_GUINT32 (buf + ZIP_DIRENT_OFFSET, dirent->offset);
176 ret = gsf_output_write (sink, sizeof buf, buf);
178 ret = gsf_output_write (sink, nlen, dirent->name);
184 zip_trailer_write (GsfOutfileZip *zip, unsigned entries, gsf_off_t dirpos)
186 static guint8 const trailer_signature[] =
187 { 'P', 'K', 0x05, 0x06 };
188 guint8 buf[ZIP_TRAILER_SIZE];
189 gsf_off_t pos = gsf_output_tell (zip->sink);
191 memset (buf, 0, sizeof buf);
192 memcpy (buf, trailer_signature, sizeof trailer_signature);
193 GSF_LE_SET_GUINT16 (buf + ZIP_TRAILER_ENTRIES, entries);
194 GSF_LE_SET_GUINT16 (buf + ZIP_TRAILER_TOTAL_ENTRIES, entries);
195 GSF_LE_SET_GUINT32 (buf + ZIP_TRAILER_DIR_SIZE, pos - dirpos);
196 GSF_LE_SET_GUINT32 (buf + ZIP_TRAILER_DIR_POS, dirpos);
198 return gsf_output_write (zip->sink, sizeof buf, buf);
202 zip_close_root (GsfOutput *output)
204 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
205 GsfOutfileZip *child;
206 gsf_off_t dirpos = gsf_output_tell (zip->sink);
207 GPtrArray *elem = zip->root_order;
208 unsigned entries = elem->len;
211 /* Check that children are closed */
212 for (i = 0 ; i < elem->len ; i++) {
213 child = g_ptr_array_index (elem, i);
214 if (!gsf_output_is_closed (GSF_OUTPUT (child))) {
215 g_warning ("Child still open");
220 /* Write directory */
221 for (i = 0 ; i < entries ; i++) {
222 child = g_ptr_array_index (elem, i);
223 if (!zip_dirent_write (zip->sink, child->vdir->dirent))
227 disconnect_children (zip);
229 return zip_trailer_write (zip, entries, dirpos);
233 stream_name_write_to_buf (GsfOutfileZip *zip, GString *res)
235 GsfOutput *output = GSF_OUTPUT (zip);
236 GsfOutfile *container;
238 if (zip == zip->root)
241 container = gsf_output_container (output);
243 stream_name_write_to_buf (GSF_OUTFILE_ZIP (container), res);
245 /* Forward slash is specified by the format. */
246 g_string_append_c (res, '/');
251 g_string_append (res, zip->entry_name);
255 stream_name_build (GsfOutfileZip *zip)
257 GString *str = g_string_sized_new (80);
258 stream_name_write_to_buf (zip, str);
259 return g_string_free (str, FALSE);
263 zip_time_make (time_t t)
265 struct tm *localnow = localtime (&t);
268 ztime = (localnow->tm_year - 80) & 0x7f;
269 ztime = (ztime << 4) | ((localnow->tm_mon + 1) & 0x0f);
270 ztime = (ztime << 5) | (localnow->tm_mday & 0x1f);
271 ztime = (ztime << 5) | (localnow->tm_hour & 0x1f);
272 ztime = (ztime << 6) | (localnow->tm_min & 0x3f);
273 ztime = (ztime << 5) | ((localnow->tm_sec / 2) & 0x1f);
279 zip_dirent_update_flags (GsfZipDirent *dirent)
281 if (dirent->compr_method == GSF_ZIP_STORED)
288 zip_dirent_new_out (GsfOutfileZip *zip)
290 GsfZipDirent *dirent = gsf_zip_dirent_new ();
291 dirent->name = stream_name_build (zip);
292 dirent->compr_method = zip->compression_method;
293 dirent->dostime = zip_time_make (time (NULL));
294 zip_dirent_update_flags (dirent);
299 zip_header_write (GsfOutfileZip *zip)
301 static guint8 const header_signature[] =
302 { 'P', 'K', 0x03, 0x04 };
303 guint8 hbuf[ZIP_HEADER_SIZE];
304 GsfZipDirent *dirent = zip->vdir->dirent;
305 char *name = dirent->name;
306 int nlen = strlen (name);
309 memset (hbuf, 0, sizeof hbuf);
310 memcpy (hbuf, header_signature, sizeof header_signature);
311 GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_VERSION, 0x14);
312 GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_FLAGS, dirent->flags);
313 GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_COMP_METHOD,
314 dirent->compr_method);
315 GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_TIME, dirent->dostime);
316 GSF_LE_SET_GUINT16 (hbuf + ZIP_HEADER_NAME_LEN, nlen);
317 ret = gsf_output_write (zip->sink, sizeof hbuf, hbuf);
319 ret = gsf_output_write (zip->sink, nlen, name);
325 zip_init_write (GsfOutput *output)
327 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
328 GsfZipDirent *dirent;
331 if (zip->root->writing) {
332 g_warning ("Already writing to another stream in archive");
336 if (!gsf_output_wrap (G_OBJECT (output), zip->sink))
339 dirent = zip_dirent_new_out (zip);
340 dirent->offset = gsf_output_tell (zip->sink);
341 if (zip->vdir->dirent)
344 zip->vdir->dirent = dirent;
345 zip_header_write (zip);
347 zip->root->writing = TRUE;
348 dirent->crc32 = crc32 (0L, Z_NULL, 0);
349 if (zip->compression_method == GSF_ZIP_DEFLATED) {
351 zip->stream = g_new0 (z_stream, 1);
353 ret = deflateInit2 (zip->stream, Z_DEFAULT_COMPRESSION,
354 Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL,
359 zip->buf_size = ZIP_BUF_SIZE;
360 zip->buf = g_new (guint8, zip->buf_size);
362 zip->stream->next_out = zip->buf;
363 zip->stream->avail_out = zip->buf_size;
370 zip_output_block (GsfOutfileZip *zip)
372 size_t num_bytes = zip->buf_size - zip->stream->avail_out;
373 GsfZipDirent *dirent = zip->vdir->dirent;
375 if (!gsf_output_write (zip->sink, num_bytes, zip->buf)) {
378 dirent->csize += num_bytes;
379 zip->stream->next_out = zip->buf;
380 zip->stream->avail_out = zip->buf_size;
386 zip_flush (GsfOutfileZip *zip)
391 zret = deflate (zip->stream, Z_FINISH);
392 if (zret == Z_OK || (zret == Z_BUF_ERROR && zip->stream->avail_out == 0)) {
393 /* In this case Z_OK or Z_BUF_ERROR means more buffer
395 if (!zip_output_block (zip))
398 } while (zret == Z_OK || zret == Z_BUF_ERROR);
399 if (zret != Z_STREAM_END)
401 if (!zip_output_block (zip))
407 /* Write the per stream data descriptor */
409 zip_ddesc_write (GsfOutfileZip *zip)
411 static guint8 const ddesc_signature[] =
412 { 'P', 'K', 0x07, 0x08 };
414 GsfZipDirent *dirent = zip->vdir->dirent;
416 memcpy (buf, ddesc_signature, sizeof ddesc_signature);
417 GSF_LE_SET_GUINT32 (buf + 4, dirent->crc32);
418 GSF_LE_SET_GUINT32 (buf + 8, dirent->csize);
419 GSF_LE_SET_GUINT32 (buf + 12, dirent->usize);
420 if (!gsf_output_write (zip->sink, sizeof buf, buf)) {
428 zip_header_write_sizes (GsfOutfileZip *zip)
430 guint8 hbuf[ZIP_HEADER_SIZE];
431 GsfZipDirent *dirent = zip->vdir->dirent;
432 gsf_off_t pos = gsf_output_tell (zip->sink);
434 if (!gsf_output_seek (zip->sink, dirent->offset + ZIP_HEADER_CRC,
438 GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_CRC, dirent->crc32);
439 GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_COMP_SIZE, dirent->csize);
440 GSF_LE_SET_GUINT32 (hbuf + ZIP_HEADER_UNCOMP_SIZE, dirent->usize);
441 if (!gsf_output_write (zip->sink, 12, hbuf + ZIP_HEADER_CRC))
443 if (!gsf_output_seek (zip->sink, pos, G_SEEK_SET))
450 zip_close_stream (GsfOutput *output)
452 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
456 if (!zip_init_write (output))
459 if (zip->compression_method == GSF_ZIP_DEFLATED) {
460 if (!zip_flush (zip))
463 if (!zip_ddesc_write (zip)) /* Write data descriptor */
466 if (!zip_header_write_sizes (zip)) /* Write crc, sizes */
469 zip->root->writing = FALSE;
471 result = gsf_output_unwrap (G_OBJECT (output), zip->sink);
473 /* Free unneeded memory */
475 (void) deflateEnd (zip->stream);
476 g_free (zip->stream);
486 gsf_outfile_zip_close (GsfOutput *output)
488 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
492 if (zip == zip->root)
493 ret = zip_close_root (output);
494 else if (zip->vdir->is_directory)
495 /* Directories: Do nothing. Should change this to actually
496 * write dirs which don't have children. */
499 ret = zip_close_stream (output);
505 gsf_outfile_zip_write (GsfOutput *output,
506 size_t num_bytes, guint8 const *data)
508 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (output);
509 GsfZipDirent *dirent;
512 g_return_val_if_fail (zip && zip->vdir, FALSE);
513 g_return_val_if_fail (!zip->vdir->is_directory, FALSE);
514 g_return_val_if_fail (data, FALSE);
517 if (!zip_init_write (output))
520 dirent = zip->vdir->dirent;
521 if (zip->compression_method == GSF_ZIP_DEFLATED) {
522 zip->stream->next_in = (unsigned char *) data;
523 zip->stream->avail_in = num_bytes;
525 while (zip->stream->avail_in > 0) {
526 if (zip->stream->avail_out == 0) {
527 if (!zip_output_block (zip))
530 ret = deflate (zip->stream, Z_NO_FLUSH);
535 if (!gsf_output_write (zip->sink, num_bytes, data))
537 dirent->csize += num_bytes;
539 dirent->crc32 = crc32 (dirent->crc32, data, num_bytes);
540 dirent->usize += num_bytes;
546 root_register_child (GsfOutfileZip *root, GsfOutfileZip *child)
549 if (!child->vdir->is_directory) {
550 g_object_ref (child);
551 g_ptr_array_add (root->root_order, child);
556 gsf_outfile_zip_set_sink (GsfOutfileZip *zip, GsfOutput *sink)
561 g_object_unref (zip->sink);
566 gsf_outfile_zip_new_child (GsfOutfile *parent,
567 char const *name, gboolean is_dir,
568 char const *first_property_name, va_list args)
570 GsfOutfileZip *zip_parent = (GsfOutfileZip *)parent;
571 GsfOutfileZip *child;
573 GParameter *params = NULL;
576 g_return_val_if_fail (zip_parent != NULL, NULL);
577 g_return_val_if_fail (zip_parent->vdir, NULL);
578 g_return_val_if_fail (zip_parent->vdir->is_directory, NULL);
579 g_return_val_if_fail (name && *name, NULL);
581 gsf_property_settings_collect (GSF_OUTFILE_ZIP_TYPE,
583 "sink", zip_parent->sink,
586 gsf_property_settings_collect_valist (GSF_OUTFILE_ZIP_TYPE,
590 child = (GsfOutfileZip *)g_object_newv (GSF_OUTFILE_ZIP_TYPE,
593 gsf_property_settings_free (params, n_params);
595 child->vdir = gsf_vdir_new (name, is_dir, NULL);
597 /* FIXME: It isn't clear what encoding name is in. */
598 display_name = g_filename_display_name (name);
599 gsf_output_set_name (GSF_OUTPUT (child), display_name);
600 g_free (display_name);
602 gsf_output_set_container (GSF_OUTPUT (child), parent);
603 gsf_vdir_add_child (zip_parent->vdir, child->vdir);
604 root_register_child (zip_parent->root, child);
606 return GSF_OUTPUT (child);
610 gsf_outfile_zip_init (GObject *obj)
612 GsfOutfileZip *zip = GSF_OUTFILE_ZIP (obj);
616 zip->entry_name = NULL;
618 zip->root_order = NULL;
620 zip->compression_method = GSF_ZIP_DEFLATED;
621 zip->writing = FALSE;
627 gsf_outfile_zip_get_property (GObject *object,
632 GsfOutfileZip *zip = (GsfOutfileZip *)object;
634 switch (property_id) {
636 g_value_set_object (value, zip->sink);
638 case PROP_ENTRY_NAME:
639 g_value_set_string (value, zip->entry_name);
641 case PROP_COMPRESSION_LEVEL:
642 g_value_set_int (value,
644 ? zip->vdir->dirent->compr_method
648 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
654 gsf_outfile_zip_set_property (GObject *object,
659 GsfOutfileZip *zip = (GsfOutfileZip *)object;
661 switch (property_id) {
663 gsf_outfile_zip_set_sink (zip, g_value_get_object (value));
665 case PROP_ENTRY_NAME:
666 zip->entry_name = g_strdup (g_value_get_string (value));
668 case PROP_COMPRESSION_LEVEL: {
669 int level = g_value_get_int (value);
672 case GSF_ZIP_DEFLATED:
673 zip->compression_method = level;
676 g_warning ("Unsupported compression level %d", level);
681 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
687 gsf_outfile_zip_class_init (GObjectClass *gobject_class)
689 GsfOutputClass *output_class = GSF_OUTPUT_CLASS (gobject_class);
690 GsfOutfileClass *outfile_class = GSF_OUTFILE_CLASS (gobject_class);
692 gobject_class->constructor = gsf_outfile_zip_constructor;
693 gobject_class->finalize = gsf_outfile_zip_finalize;
694 gobject_class->get_property = gsf_outfile_zip_get_property;
695 gobject_class->set_property = gsf_outfile_zip_set_property;
697 output_class->Write = gsf_outfile_zip_write;
698 output_class->Seek = gsf_outfile_zip_seek;
699 output_class->Close = gsf_outfile_zip_close;
700 outfile_class->new_child = gsf_outfile_zip_new_child;
702 parent_class = g_type_class_peek_parent (gobject_class);
704 g_object_class_install_property
707 g_param_spec_object ("sink", "Sink",
708 "Where the archive is written.",
712 G_PARAM_CONSTRUCT_ONLY));
713 g_object_class_install_property
716 g_param_spec_string ("entry-name", "Entry Name",
717 "The filename of this member in the archive without path.",
721 G_PARAM_CONSTRUCT_ONLY));
722 g_object_class_install_property
724 PROP_COMPRESSION_LEVEL,
725 g_param_spec_int ("compression-level",
727 "The level of compression used, zero meaning none.",
732 G_PARAM_CONSTRUCT_ONLY));
735 GSF_CLASS (GsfOutfileZip, gsf_outfile_zip,
736 gsf_outfile_zip_class_init, gsf_outfile_zip_init,
740 * gsf_outfile_zip_new :
741 * @sink: a #GsfOutput to hold the ZIP file
742 * @err: Location to store error, or %NULL; currently unused.
744 * Creates the root directory of a Zip file and manages the addition of
747 * <note>This adds a reference to @sink.</note>
749 * Returns: the new zip file handler
752 gsf_outfile_zip_new (GsfOutput *sink, G_GNUC_UNUSED GError **err)
754 g_return_val_if_fail (GSF_IS_OUTPUT (sink), NULL);
756 return (GsfOutfile *)g_object_new (GSF_OUTFILE_ZIP_TYPE,
761 /* deprecated has no effect */
763 gsf_outfile_zip_set_compression_method (G_GNUC_UNUSED GsfOutfileZip *zip,
764 G_GNUC_UNUSED GsfZipCompressionMethod method)