]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Anselm Strauss' unfinished zip writer and tests.
authorTim Kientzle <kientzle@gmail.com>
Mon, 13 Apr 2009 22:37:27 +0000 (18:37 -0400)
committerTim Kientzle <kientzle@gmail.com>
Mon, 13 Apr 2009 22:37:27 +0000 (18:37 -0400)
I'd like to get this finished for libarchive 2.8.

SVN-Revision: 986

libarchive/CMakeLists.txt
libarchive/Makefile
libarchive/archive.h
libarchive/archive_write_set_format_zip.c [new file with mode: 0644]
libarchive/test/CMakeLists.txt
libarchive/test/Makefile
libarchive/test/test_write_format_zip.c [new file with mode: 0644]
libarchive/test/test_write_format_zip_empty.c [new file with mode: 0644]
libarchive/test/test_write_format_zip_no_compression.c [new file with mode: 0644]

index 0c2eee18f44632d75ef27dd7f3bfe5f365474ce3..135a98a90928363c4aea018bd02dfa91ba8d1a2b 100644 (file)
@@ -77,6 +77,7 @@ SET(libarchive_SOURCES
   archive_write_set_format_pax.c
   archive_write_set_format_shar.c
   archive_write_set_format_ustar.c
+  archive_write_set_format_zip.c
   filter_fork.c
   filter_fork.h
 )
index 6b2902283a25183b0150fe9ffe84dfa508716e6a..c86d88191215fbc606faeffbaf4c4450c6f152a1 100644 (file)
@@ -74,6 +74,7 @@ SRCS= archive_check_magic.c                           \
        archive_write_set_format_pax.c                  \
        archive_write_set_format_shar.c                 \
        archive_write_set_format_ustar.c                \
+       archive_write_set_format_zip.c                  \
        filter_fork.c
 
 CLEANFILES += *.o *.lo *~ .dirstamp
index 0236d2e665aadd1f7108f2a2cf51848fb3091e75..572e19dbfeb16e1062ba2a9df3cee15b18a6725e 100644 (file)
@@ -527,6 +527,9 @@ __LA_DECL int                archive_write_set_format_pax_restricted(struct archive *);
 __LA_DECL int           archive_write_set_format_shar(struct archive *);
 __LA_DECL int           archive_write_set_format_shar_dump(struct archive *);
 __LA_DECL int           archive_write_set_format_ustar(struct archive *);
+__LA_DECL int           archive_write_set_format_zip(struct archive *);
+__LA_DECL int           archive_write_zip_set_deflate(struct archive *);
+__LA_DECL int           archive_write_zip_set_store(struct archive *);
 __LA_DECL int           archive_write_open(struct archive *, void *,
                     archive_open_callback *, archive_write_callback *,
                     archive_close_callback *);
diff --git a/libarchive/archive_write_set_format_zip.c b/libarchive/archive_write_set_format_zip.c
new file mode 100644 (file)
index 0000000..96df2b9
--- /dev/null
@@ -0,0 +1,603 @@
+/*-
+ * Copyright (c) 2008 Anselm Strauss
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Development supported by Google Summer of Code 2008.
+ */
+
+/*
+ * The current implementation is very limited:
+ *
+ *   - No compression support.
+ *   - No encryption support.
+ *   - No ZIP64 support.
+ *   - No support for splitting and spanning.
+ *   - Only supports regular file and folder entries.
+ *
+ * Note that generally data in ZIP files is little-endian encoded,
+ * with some exceptions.
+ *
+ * TODO: Since Libarchive is generally 64bit oriented, but this implementation
+ * does not yet support sizes exceeding 32bit, it is highly fragile for
+ * big archives. This should change when ZIP64 is finally implemented, otherwise
+ * some serious checking has to be done.
+ *
+ */
+
+#include "archive_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#include <zlib.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "archive_private.h"
+#include "archive_write_private.h"
+
+#define ZIP_SIGNATURE_LOCAL_FILE_HEADER 0x04034b50
+#define ZIP_SIGNATURE_DATA_DESCRIPTOR 0x08074b50
+#define ZIP_SIGNATURE_FILE_HEADER 0x02014b50
+#define ZIP_SIGNATURE_CENTRAL_DIRECTORY_END 0x06054b50
+#define ZIP_SIGNATURE_EXTRA_TIMESTAMP 0x5455
+#define ZIP_SIGNATURE_EXTRA_UNIX 0x7855
+#define ZIP_VERSION_EXTRACT 0x0014 /* ZIP version 2.0 is needed. */
+#define ZIP_VERSION_BY 0x0314 /* Made by UNIX, using ZIP version 2.0. */
+#define ZIP_FLAGS 0x08 /* Flagging bit 3 (count from 0) for using data descriptor. */
+
+enum compression {
+       COMPRESSION_STORE = 0,
+       COMPRESSION_DEFLATE = 6
+};
+
+static ssize_t archive_write_zip_data(struct archive_write *, const void *buff, size_t s);
+static int archive_write_zip_finish(struct archive_write *);
+static int archive_write_zip_destroy(struct archive_write *);
+static int archive_write_zip_finish_entry(struct archive_write *);
+static int archive_write_zip_header(struct archive_write *, struct archive_entry *);
+static void zip_encode(uint64_t, void *, size_t);
+static unsigned int dos_time(const time_t);
+static size_t path_length(struct archive_entry *);
+static int write_path(struct archive_entry *, struct archive_write *);
+
+struct zip_local_file_header {
+       char signature[4];
+       char version[2];
+       char flags[2];
+       char compression[2];
+       char timedate[4];
+       char crc32[4];
+       char compressed_size[4];
+       char uncompressed_size[4];
+       char filename_length[2];
+       char extra_length[2];
+};
+
+struct zip_file_header {
+       char signature[4];
+       char version_by[2];
+       char version_extract[2];
+       char flags[2];
+       char compression[2];
+       char timedate[4];
+       char crc32[4];
+       char compressed_size[4];
+       char uncompressed_size[4];
+       char filename_length[2];
+       char extra_length[2];
+       char comment_length[2];
+       char disk_number[2];
+       char attributes_internal[2];
+       char attributes_external[4];
+       char offset[4];
+};
+
+struct zip_data_descriptor {
+       char signature[4]; /* Not mandatory, but recommended by specification. */
+       char crc32[4];
+       char compressed_size[4];
+       char uncompressed_size[4];
+};
+
+struct zip_extra_data_local {
+       char time_id[2];
+       char time_size[2];
+       char time_flag[1];
+       char mtime[4];
+       char atime[4];
+       char ctime[4];
+       char unix_id[2];
+       char unix_size[2];
+       char unix_uid[2];
+       char unix_gid[2];
+};
+
+struct zip_extra_data_central {
+       char time_id[2];
+       char time_size[2];
+       char time_flag[1];
+       char mtime[4];
+       char unix_id[2];
+       char unix_size[2];
+};
+
+struct zip_file_header_link {
+       struct zip_file_header_link *next;
+       struct archive_entry *entry;
+       off_t offset;
+       uLong crc32;
+       enum compression compression;
+};
+
+struct zip {
+       struct zip_data_descriptor data_descriptor;
+       struct zip_file_header_link *central_directory;
+       struct zip_file_header_link *central_directory_end;
+       off_t offset;
+       size_t written_bytes;
+       size_t remaining_data_bytes;
+       enum compression compression;
+};
+
+struct zip_central_directory_end {
+       char signature[4];
+       char disk[2];
+       char start_disk[2];
+       char entries_disk[2];
+       char entries[2];
+       char size[4];
+       char offset[4];
+       char comment_length[2];
+};
+
+int
+archive_write_set_format_zip(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+       struct zip *zip;
+
+       /* If another format was already registered, unregister it. */
+       if (a->format_destroy != NULL)
+               (a->format_destroy)(a);
+
+       zip = (struct zip *) malloc(sizeof(*zip));
+       if (zip == NULL) {
+               archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data");
+               return (ARCHIVE_FATAL);
+       }
+       zip->central_directory = NULL;
+       zip->central_directory_end = NULL;
+       zip->offset = 0;
+       zip->written_bytes = 0;
+       zip->remaining_data_bytes = 0;
+       zip->compression = COMPRESSION_DEFLATE;
+       a->format_data = zip;
+
+       a->pad_uncompressed = 0; /* Actually not needed for now, since no compression support yet. */
+       a->format_write_header = archive_write_zip_header;
+       a->format_write_data = archive_write_zip_data;
+       a->format_finish_entry = archive_write_zip_finish_entry;
+       a->format_finish = archive_write_zip_finish;
+       a->format_destroy = archive_write_zip_destroy;
+       a->archive.archive_format = ARCHIVE_FORMAT_ZIP;
+       a->archive.archive_format_name = "ZIP";
+
+       zip_encode(
+               ZIP_SIGNATURE_DATA_DESCRIPTOR,
+               &zip->data_descriptor.signature,
+               sizeof(zip->data_descriptor.signature)
+       );
+
+       return (ARCHIVE_OK);
+}
+
+int
+archive_write_zip_set_store(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+       struct zip *zip = a->format_data;
+       zip->compression = COMPRESSION_STORE;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_write_zip_set_deflate(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+       struct zip *zip = a->format_data;
+       zip->compression = COMPRESSION_DEFLATE;
+       return (ARCHIVE_OK);
+}
+
+static int
+archive_write_zip_header(struct archive_write *a, struct archive_entry *entry)
+{
+       struct zip *zip;
+       struct zip_local_file_header h;
+       struct zip_extra_data_local e;
+       struct zip_data_descriptor *d;
+       struct zip_file_header_link *l;
+       int ret;
+       int64_t size;
+       mode_t type;
+
+       /* Entries other than a regular file or a folder are skipped. */
+       type = archive_entry_filetype(entry);
+       if ((type != AE_IFREG) & (type != AE_IFDIR)) {
+               archive_set_error(&a->archive, 0, "Filetype not supported");
+               return ARCHIVE_FAILED;
+       };
+
+       /* Directory entries should have a size of 0. */
+       if (type == AE_IFDIR)
+               archive_entry_set_size(entry, 0);
+
+       zip = a->format_data;
+       d = &zip->data_descriptor;
+       size = archive_entry_size(entry);
+       zip->remaining_data_bytes = size;
+
+       /* Append archive entry to the central directory data. */
+       l = (struct zip_file_header_link *) malloc(sizeof(*l));
+       if (l == NULL) {
+               archive_set_error(&a->archive, ENOMEM, "Can't allocate zip header data");
+               return (ARCHIVE_FATAL);
+       }
+       l->entry = archive_entry_clone(entry);
+       l->crc32 = crc32(0, NULL, 0);
+       l->compression = zip->compression;
+       l->next = NULL;
+       if (zip->central_directory == NULL) {
+               zip->central_directory = l;
+       } else {
+               zip->central_directory_end->next = l;
+       }
+       zip->central_directory_end = l;
+
+       /* Store the offset of this header for later use in central directory. */
+       l->offset = zip->written_bytes;
+
+       memset(&h, 0, sizeof(h));
+       zip_encode(ZIP_SIGNATURE_LOCAL_FILE_HEADER, &h.signature, sizeof(h.signature));
+       zip_encode(ZIP_VERSION_EXTRACT, &h.version, sizeof(h.version));
+       zip_encode(ZIP_FLAGS, &h.flags, sizeof(h.flags));
+       zip_encode(zip->compression, &h.compression, sizeof(h.compression));
+       zip_encode(dos_time(archive_entry_mtime(entry)), &h.timedate, sizeof(h.timedate));
+       zip_encode(path_length(entry), &h.filename_length, sizeof(h.filename_length));
+       zip_encode(sizeof(e), &h.extra_length, sizeof(h.extra_length));
+
+       if (zip->compression == COMPRESSION_STORE) {
+               /* Setting compressed and uncompressed sizes even when specification says
+                * to set to zero when using data descriptors. Otherwise the end of the
+                * data for an entry is rather difficult to find. */
+               zip_encode(size, &h.compressed_size, sizeof(h.compressed_size));
+               zip_encode(size, &h.uncompressed_size, sizeof(h.uncompressed_size));
+       }
+
+       /* Formatting extra data. */
+       zip_encode(sizeof(e), &h.extra_length, sizeof(h.extra_length));
+       zip_encode(ZIP_SIGNATURE_EXTRA_TIMESTAMP, &e.time_id, sizeof(e.time_id));
+       zip_encode(sizeof(e.atime) + sizeof(e.mtime) + sizeof(e.ctime) + sizeof(e.time_flag), &e.time_size, sizeof(e.time_size));
+       zip_encode(0x07, &e.time_flag, sizeof(e.time_flag));
+       zip_encode(archive_entry_mtime(entry), &e.mtime, sizeof(e.mtime));
+       zip_encode(archive_entry_atime(entry), &e.atime, sizeof(e.atime));
+       zip_encode(archive_entry_ctime(entry), &e.ctime, sizeof(e.ctime));
+       zip_encode(ZIP_SIGNATURE_EXTRA_UNIX, &e.unix_id, sizeof(e.unix_id));
+       zip_encode(sizeof(e.unix_uid) + sizeof(e.unix_gid), &e.unix_size, sizeof(e.unix_size));
+       zip_encode(archive_entry_uid(entry), &e.unix_uid, sizeof(e.unix_uid));
+       zip_encode(archive_entry_gid(entry), &e.unix_gid, sizeof(e.unix_gid));
+
+       zip_encode(size, &d->uncompressed_size, sizeof(d->uncompressed_size));
+
+       ret = (a->compressor.write)(a, &h, sizeof(h));
+       if (ret != ARCHIVE_OK)
+               return (ARCHIVE_FATAL);
+       zip->written_bytes += sizeof(h);
+
+       ret = write_path(entry, a);
+       if (ret <= ARCHIVE_OK)
+               return (ARCHIVE_FATAL);
+       zip->written_bytes += ret;
+
+       ret = (a->compressor.write)(a, &e, sizeof(e));
+       if (ret != ARCHIVE_OK)
+               return (ARCHIVE_FATAL);
+       zip->written_bytes += sizeof(e);
+
+       return (ARCHIVE_OK);
+}
+
+static ssize_t
+archive_write_zip_data(struct archive_write *a, const void *buff, size_t s)
+{
+       int ret;
+       struct zip *zip = a->format_data;
+       struct zip_file_header_link *l = zip->central_directory_end;
+       z_stream stream;
+       size_t buff_out_size = 32768;
+       unsigned char buff_out[buff_out_size];
+
+       if (s > zip->remaining_data_bytes)
+               s = zip->remaining_data_bytes;
+
+       if (s == 0) return 0;
+
+       switch (zip->compression) {
+               case COMPRESSION_STORE:
+                       ret = (a->compressor.write)(a, buff, s);
+                       if (ret < 0) return (ret);
+                       zip->written_bytes += s;
+                       zip->remaining_data_bytes -= s;
+                       l->crc32 = crc32(l->crc32, buff, s);
+                       return (ret);
+               case COMPRESSION_DEFLATE:
+                       stream.zalloc = Z_NULL;
+                       stream.zfree = Z_NULL;
+                       stream.opaque = Z_NULL;
+                       ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+                       if (ret != Z_OK) return (ARCHIVE_FATAL);
+                       stream.next_in = (unsigned char*)(uintptr_t)buff;
+                       stream.avail_in = s;
+                       do {
+                               stream.next_out = buff_out;
+                               stream.avail_out = buff_out_size;
+                               ret = deflate(&stream, Z_FINISH);
+                               if (ret == Z_STREAM_ERROR) {
+                                       deflateEnd(&stream);
+                                       return (ARCHIVE_FATAL);
+                               }
+                               ret = (a->compressor.write)(a, buff_out, stream.avail_out);
+                               if (ret < 0) {
+                                       deflateEnd(&stream);
+                                       return (ret);
+                               }
+                               zip->written_bytes += ret;
+                       } while (stream.avail_out == 0);
+                       zip->remaining_data_bytes -= s;
+                       l->crc32 = crc32(l->crc32, buff, s);
+                       deflateEnd(&stream);
+                       return (s);
+               default:
+
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "Invalid ZIP compression type");
+                       return ARCHIVE_FATAL;
+       }
+       /* TODO: set compressed size in data descriptor and local file header link */
+}
+
+static int
+archive_write_zip_finish_entry(struct archive_write *a)
+{
+       /* Write the data descripter after file data has been written. */
+       int ret;
+       struct zip *zip = a->format_data;
+       struct zip_data_descriptor *d = &zip->data_descriptor;
+       struct zip_file_header_link *l = zip->central_directory_end;
+
+       zip_encode(l->crc32, &d->crc32, sizeof(d->crc32));
+       ret = (a->compressor.write)(a, d, sizeof(*d));
+       if (ret != ARCHIVE_OK)
+               return (ARCHIVE_FATAL);
+       zip->written_bytes += sizeof(*d);
+       return (ARCHIVE_OK);
+}
+
+static int
+archive_write_zip_finish(struct archive_write *a)
+{
+       struct zip *zip;
+       struct zip_file_header_link *l;
+       struct zip_file_header h;
+       struct zip_central_directory_end end;
+       struct zip_extra_data_central e;
+       off_t offset_start, offset_end;
+       int entries;
+       int ret;
+       mode_t mode;
+
+       zip = a->format_data;
+       l = zip->central_directory;
+
+       /*
+        * Formatting central directory file header fields that are fixed for all entries.
+        * Fields not used (and therefor 0) are:
+        *
+        *   - comment_length
+        *   - disk_number
+        *   - attributes_internal
+        */
+       memset(&h, 0, sizeof(h));
+       zip_encode(ZIP_SIGNATURE_FILE_HEADER, &h.signature, sizeof(h.signature));
+       zip_encode(ZIP_VERSION_BY, &h.version_by, sizeof(h.version_by));
+       zip_encode(ZIP_VERSION_EXTRACT, &h.version_extract, sizeof(h.version_extract));
+       zip_encode(ZIP_FLAGS, &h.flags, sizeof(h.flags));
+
+       entries = 0;
+       offset_start = zip->written_bytes;
+
+       /* Formatting individual header fields per entry and
+        * writing each entry. */
+       while (l != NULL) {
+               zip_encode(l->compression, &h.compression, sizeof(h.compression));
+               zip_encode(dos_time(archive_entry_mtime(l->entry)), &h.timedate, sizeof(h.timedate));
+               zip_encode(l->crc32, &h.crc32, sizeof(h.crc32));
+               /* TODO: write compressed size */
+               zip_encode(archive_entry_size(l->entry), &h.uncompressed_size, sizeof(h.uncompressed_size));
+               zip_encode(path_length(l->entry), &h.filename_length, sizeof(h.filename_length));
+               zip_encode(sizeof(e), &h.extra_length, sizeof(h.extra_length));
+               mode = archive_entry_mode(l->entry);
+               zip_encode(mode, &h.attributes_external[2], sizeof(mode));
+               zip_encode(l->offset, &h.offset, sizeof(h.offset));
+
+               /* Formatting extra data. */
+               zip_encode(ZIP_SIGNATURE_EXTRA_TIMESTAMP, &e.time_id, sizeof(e.time_id));
+               zip_encode(sizeof(e.mtime) + sizeof(e.time_flag), &e.time_size, sizeof(e.time_size));
+               zip_encode(0x07, &e.time_flag, sizeof(e.time_flag));
+               zip_encode(archive_entry_mtime(l->entry), &e.mtime, sizeof(e.mtime));
+               zip_encode(ZIP_SIGNATURE_EXTRA_UNIX, &e.unix_id, sizeof(e.unix_id));
+               zip_encode(0x0000, &e.unix_size, sizeof(e.unix_size));
+
+               ret = (a->compressor.write)(a, &h, sizeof(h));
+               if (ret != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+               zip->written_bytes += sizeof(h);
+
+               ret = write_path(l->entry, a);
+               if (ret <= ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+               zip->written_bytes += ret;
+
+               ret = (a->compressor.write)(a, &e, sizeof(e));
+               if (ret != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+               zip->written_bytes += sizeof(e);
+
+               l = l->next;
+               entries++;
+       }
+       offset_end = zip->written_bytes;
+
+       /* Formatting end of central directory. */
+       memset(&end, 0, sizeof(end));
+       zip_encode(ZIP_SIGNATURE_CENTRAL_DIRECTORY_END, &end.signature, sizeof(end.signature));
+       zip_encode(entries, &end.entries, sizeof(end.entries));
+       zip_encode(entries, &end.entries_disk, sizeof(end.entries_disk));
+       zip_encode(offset_end - offset_start, &end.size, sizeof(end.size));
+       zip_encode(offset_start, &end.offset, sizeof(end.offset));
+
+       /* Writing end of central directory. */
+       ret = (a->compressor.write)(a, &end, sizeof(end));
+       if (ret != ARCHIVE_OK)
+               return (ARCHIVE_FATAL);
+       zip->written_bytes += sizeof(end);
+       return (ARCHIVE_OK);
+}
+
+static int
+archive_write_zip_destroy(struct archive_write *a)
+{
+       struct zip *zip;
+       struct zip_file_header_link *l;
+
+       zip = a->format_data;
+       while (zip->central_directory != NULL) {
+          l = zip->central_directory;
+          zip->central_directory = l->next;
+          archive_entry_free(l->entry);
+          free(l);
+       }
+       free(zip);
+       a->format_data = NULL;
+       return (ARCHIVE_OK);
+}
+
+/* Encode data in little-endian for writing it to a ZIP file. */
+static void
+zip_encode(uint64_t value, void *_p, size_t size)
+{
+       unsigned char *p = (unsigned char *) _p;
+       size_t i;
+       for (i = 0; i < size; i++) {
+               *p = value & 0xff;
+               value >>= 8;
+               p++;
+       }
+}
+
+/* Convert into MSDOS-style date/time. */
+static unsigned int
+dos_time(const time_t unix_time)
+{
+       struct tm *t;
+       unsigned int dt;
+
+       /* This will not preserve time when creating/extracting the archive
+        * on two systems with different time zones. */
+       t = localtime(&unix_time);
+
+       dt = 0;
+       dt += ((t->tm_year - 80) & 0x7f) << 9;
+       dt += ((t->tm_mon + 1) & 0x0f) << 5;
+       dt += (t->tm_mday & 0x1f);
+       dt <<= 16;
+       dt += (t->tm_hour & 0x1f) << 11;
+       dt += (t->tm_min & 0x3f) << 5;
+       dt += (t->tm_sec & 0x3e) >> 1; /* Only counting every 2 seconds. */
+       return dt;
+}
+
+static size_t
+path_length(struct archive_entry *entry)
+{
+       mode_t type;
+       const char *path;
+
+       type = archive_entry_filetype(entry);
+       path = archive_entry_pathname(entry);
+
+       if ((type == AE_IFDIR) & (path[strlen(path) - 1] != '/')) {
+               return strlen(path) + 1;
+       } else {
+               return strlen(path);
+       }
+}
+
+static int
+write_path(struct archive_entry *entry, struct archive_write *archive)
+{
+       int ret;
+       const char *path;
+       mode_t type;
+       size_t written_bytes;
+
+       path = archive_entry_pathname(entry);
+       type = archive_entry_filetype(entry);
+       written_bytes = 0;
+
+       ret = (archive->compressor.write)(archive, path, strlen(path));
+       if (ret != ARCHIVE_OK)
+               return (ARCHIVE_FATAL);
+       written_bytes += strlen(path);
+
+       /* Folders are recognized by a traling slash. */
+       if ((type == AE_IFDIR) & (path[strlen(path) - 1] != '/')) {
+               ret = (archive->compressor.write)(archive, "/", 1);
+               if (ret != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+               written_bytes += 1;
+       }
+
+       return written_bytes;
+}
index 2495eb9d8b43e1c1880b903cc7374a3e3bc0eadd..61f214a06f2cf1d470c2ce798edcbbdcf8556a32 100644 (file)
@@ -96,6 +96,9 @@ IF(ENABLE_TEST)
     test_write_format_tar.c
     test_write_format_tar_empty.c
     test_write_format_tar_ustar.c
+    test_write_format_zip.c
+    test_write_format_zip_empty.c
+    test_write_format_zip_no_compression.c
     test_write_open_memory.c
   )
 
index bf3dfaf2ab1c49a4aaf9ce2b58d00c4989ab067d..a04e1b47f651fd4922f7506316e6d292fd3691bc 100644 (file)
@@ -91,6 +91,9 @@ TESTS= \
        test_write_format_tar.c                 \
        test_write_format_tar_empty.c           \
        test_write_format_tar_ustar.c           \
+       test_write_format_zip.c                 \
+       test_write_format_zip_empty.c           \
+       test_write_format_zip_no_compression.c  \
        test_write_open_memory.c
 
 
diff --git a/libarchive/test/test_write_format_zip.c b/libarchive/test/test_write_format_zip.c
new file mode 100644 (file)
index 0000000..9f7965f
--- /dev/null
@@ -0,0 +1,172 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * Copyright (c) 2008 Anselm Strauss
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Development supported by Google Summer of Code 2008.
+ */
+
+/* TODO: reader does not yet restore permissions. */
+
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+DEFINE_TEST(test_write_format_zip)
+{
+       char filedata[64];
+       struct archive_entry *ae;
+       struct archive *a;
+       size_t used;
+       size_t buffsize = 1000000;
+       char *buff;
+
+       buff = malloc(buffsize);
+
+       /* Create a new archive in memory. */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_zip(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_compression_none(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_write_open_memory(a, buff, buffsize, &used));
+
+       /*
+        * Write a file to it.
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(ae, 1, 10);
+       assertEqualInt(1, archive_entry_mtime(ae));
+       assertEqualInt(10, archive_entry_mtime_nsec(ae));
+       archive_entry_copy_pathname(ae, "file");
+       assertEqualString("file", archive_entry_pathname(ae));
+       archive_entry_set_mode(ae, S_IFREG | 0755);
+       assertEqualInt((S_IFREG | 0755), archive_entry_mode(ae));
+       archive_entry_set_size(ae, 8);
+
+       assertEqualInt(0, archive_write_header(a, ae));
+       archive_entry_free(ae);
+       assertEqualInt(8, archive_write_data(a, "12345678", 9));
+       assertEqualInt(0, archive_write_data(a, "1", 1));
+
+       /*
+        * Write another file to it.
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(ae, 1, 10);
+       assertEqualInt(1, archive_entry_mtime(ae));
+       assertEqualInt(10, archive_entry_mtime_nsec(ae));
+       archive_entry_copy_pathname(ae, "file2");
+       assertEqualString("file2", archive_entry_pathname(ae));
+       archive_entry_set_mode(ae, S_IFREG | 0755);
+       assertEqualInt((S_IFREG | 0755), archive_entry_mode(ae));
+       archive_entry_set_size(ae, 4);
+
+       assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+       assertEqualInt(4, archive_write_data(a, "1234", 5));
+
+       /*
+        * Write a directory to it.
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(ae, 11, 110);
+       archive_entry_copy_pathname(ae, "dir");
+       archive_entry_set_mode(ae, S_IFDIR | 0755);
+       archive_entry_set_size(ae, 512);
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       failure("size should be zero so that applications know not to write");
+       assertEqualInt(0, archive_entry_size(ae));
+       archive_entry_free(ae);
+       assertEqualIntA(a, 0, archive_write_data(a, "12345678", 9));
+
+       /* Close out the archive. */
+       assertEqualInt(ARCHIVE_OK, archive_write_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_write_finish(a));
+
+       /*
+        * Now, read the data back.
+        */
+       ae = NULL;
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_support_compression_all(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_open_memory(a, buff, used));
+
+       /*
+        * Read and verify first file.
+        */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt(1, archive_entry_mtime(ae));
+       /* Zip doesn't store high-resolution mtime. */
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualInt(0, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_ctime(ae));
+       assertEqualString("file", archive_entry_pathname(ae));
+       //assertEqualInt((S_IFREG | 0755), archive_entry_mode(ae));
+       assertEqualInt(8, archive_entry_size(ae));
+       assertEqualIntA(a, archive_entry_size(ae),
+           archive_read_data(a, filedata, sizeof(filedata)));
+       assertEqualMem(filedata, "12345678", 8);
+
+
+       /*
+        * Read the second file back.
+        */
+       if (!assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae))){
+               free(buff);
+               return;
+       }
+       assertEqualInt(1, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualInt(0, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_ctime(ae));
+       assertEqualString("file2", archive_entry_pathname(ae));
+       //assert((S_IFREG | 0755) == archive_entry_mode(ae));
+       assertEqualInt(4, archive_entry_size(ae));
+       assertEqualIntA(a, archive_entry_size(ae),
+           archive_read_data(a, filedata, sizeof(filedata)));
+       assertEqualMem(filedata, "1234", 4);
+
+       /*
+        * Read the dir entry back.
+        */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt(11, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualInt(0, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_ctime(ae));
+       assertEqualString("dir/", archive_entry_pathname(ae));
+       //assertEqualInt((S_IFDIR | 0755), archive_entry_mode(ae));
+       assertEqualInt(0, archive_entry_size(ae));
+       assertEqualIntA(a, 0, archive_read_data(a, filedata, 10));
+
+       /* Verify the end of the archive. */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+       assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_finish(a));
+       free(buff);
+}
diff --git a/libarchive/test/test_write_format_zip_empty.c b/libarchive/test/test_write_format_zip_empty.c
new file mode 100644 (file)
index 0000000..ef90b8d
--- /dev/null
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2008 Anselm Strauss
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Development supported by Google Summer of Code 2008.
+ */
+
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+DEFINE_TEST(test_write_format_zip_empty)
+{
+       struct archive *a;
+       char buff[256];
+       size_t used;
+
+       /* Zip format: Create a new archive in memory. */
+       assert((a = archive_write_new()) != NULL);
+       assertA(0 == archive_write_set_format_zip(a));
+       assertA(0 == archive_write_set_compression_none(a));
+       assertA(0 == archive_write_set_bytes_per_block(a, 1));
+       assertA(0 == archive_write_set_bytes_in_last_block(a, 1));
+       assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used));
+
+       /* Close out the archive without writing anything. */
+       assertA(0 == archive_write_close(a));
+       assertA(0 == archive_write_finish(a));
+
+       /* Verify the correct format for an empy Zip archive. */
+       assertEqualInt(used, 22);
+       assertEqualMem(buff,
+           "PK\005\006\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
+           22);
+}
diff --git a/libarchive/test/test_write_format_zip_no_compression.c b/libarchive/test/test_write_format_zip_no_compression.c
new file mode 100644 (file)
index 0000000..d1573ab
--- /dev/null
@@ -0,0 +1,278 @@
+/*-
+ * Copyright (c) 2008 Anselm Strauss
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Development supported by Google Summer of Code 2008.
+ */
+
+#include "test.h"
+#include <zlib.h>
+__FBSDID("$FreeBSD$");
+
+/* Quick and dirty: Read 2-byte and 4-byte integers from Zip file. */
+static int i2(const char *p) { return ((p[0] & 0xff) | ((p[1] & 0xff) << 8)); }
+static int i4(const char *p) { return (i2(p) | (i2(p + 2) << 16)); }
+
+DEFINE_TEST(test_write_format_zip_no_compression)
+{
+       /* Buffer data */
+       struct archive *a;
+       struct archive_entry *entry;
+       char buff[100000];
+       const char *buffend;
+       /* p is the pointer to walk over the central directory,
+        * q walks over the local headers, the data and the data descriptors. */
+       const char *p, *q;
+       size_t used;
+
+       /* File data */
+       char file_name[] = "file";
+       char file_data1[] = {'1', '2', '3', '4', '5'};
+       char file_data2[] = {'6', '7', '8', '9', '0'};
+       int file_perm = 00644;
+       short file_uid = 10;
+       short file_gid = 20;
+
+       /* Folder data */
+       char folder_name[] = "folder/";
+       int folder_perm = 00755;
+       short folder_uid = 30;
+       short folder_gid = 40;
+
+       /* Time data */
+       time_t t;
+       struct tm *tm;
+       t = time(NULL);
+       tm = localtime(&t);
+
+       /* Misc variables */
+       int crc;
+
+       /* Create new ZIP archive in memory without padding. */
+       assert((a = archive_write_new()) != NULL);
+       assertA(0 == archive_write_set_format_zip(a));
+       assertA(0 == archive_write_set_compression_none(a));
+       assertA(0 == archive_write_set_bytes_per_block(a, 1));
+       assertA(0 == archive_write_set_bytes_in_last_block(a, 1));
+       assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used));
+
+       /* Write entries. */
+
+       /* Regular file */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_pathname(entry, file_name);
+       archive_entry_set_mode(entry, S_IFREG | 0644);
+       archive_entry_set_size(entry, sizeof(file_data1) + sizeof(file_data2));
+       archive_entry_set_uid(entry, file_uid);
+       archive_entry_set_gid(entry, file_gid);
+       archive_entry_set_mtime(entry, t, 0);
+       archive_entry_set_atime(entry, t, 0);
+       archive_entry_set_ctime(entry, t, 0);
+       assertEqualIntA(a, 0, archive_write_header(a, entry));
+       assertEqualIntA(a, sizeof(file_data1), archive_write_data(a, file_data1, sizeof(file_data1)));
+       assertEqualIntA(a, sizeof(file_data2), archive_write_data(a, file_data2, sizeof(file_data2)));
+       archive_entry_free(entry);
+
+       /* Folder */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_pathname(entry, folder_name);
+       archive_entry_set_mode(entry, S_IFDIR | folder_perm);
+       archive_entry_set_size(entry, 0);
+       archive_entry_set_uid(entry, folder_uid);
+       archive_entry_set_gid(entry, folder_gid);
+       archive_entry_set_mtime(entry, t, 0);
+       archive_entry_set_atime(entry, t, 0);
+       archive_entry_set_ctime(entry, t, 0);
+       assertEqualIntA(a, 0, archive_write_header(a, entry));
+       archive_entry_free(entry);
+
+       /* Close the archive . */
+       assertA(0 == archive_write_close(a));
+       assertA(0 == archive_write_finish(a));
+
+       /* Remember the end of the archive in memory. */
+       buffend = buff + used;
+
+       /* Verify "End of Central Directory" record. */
+       /* Get address of end-of-central-directory record. */
+       p = buffend - 22; /* Assumes there is no zip comment field. */
+       failure("End-of-central-directory begins with PK\\005\\006 signature");
+       assertEqualMem(p, "PK\005\006", 4);
+       failure("This must be disk 0");
+       assertEqualInt(i2(p + 4), 0);
+       failure("Central dir must start on disk 0");
+       assertEqualInt(i2(p + 6), 0);
+       failure("All central dir entries are on this disk");
+       assertEqualInt(i2(p + 8), i2(p + 10));
+       failure("CD start (%d) + CD length (%d) should == archive size - 22",
+           i4(p + 12), i4(p + 16));
+       assertEqualInt(i4(p + 12) + i4(p + 16), used - 22);
+       failure("no zip comment");
+       assertEqualInt(i2(p + 20), 0);
+
+       /* Get address of first entry in central directory. */
+       p = buff + i4(buffend - 6);
+       failure("Central file record at offset %d should begin with"
+           " PK\\001\\002 signature",
+           i4(buffend - 10));
+
+       /* Verify file entry in central directory. */
+       assertEqualMem(p, "PK\001\002", 4); /* Signature */
+       assertEqualInt(i2(p + 4), 3 * 256 + 20); /* Version made by */
+       assertEqualInt(i2(p + 6), 20); /* Version needed to extract */
+       assertEqualInt(i2(p + 8), 8); /* Flags */
+       assertEqualInt(i2(p + 10), 0); /* Compression method */
+       assertEqualInt(i2(p + 12), (tm->tm_hour * 2048) + (tm->tm_min * 32) + (tm->tm_sec / 2)); /* File time */
+       assertEqualInt(i2(p + 14), ((tm->tm_year - 80) * 512) + ((tm->tm_mon + 1) * 32) + tm->tm_mday); /* File date */
+       crc = crc32(0, file_data1, sizeof(file_data1));
+       crc = crc32(crc, file_data2, sizeof(file_data2));
+       assertEqualInt(i4(p + 16), crc); /* CRC-32 */
+       assertEqualInt(i4(p + 20), sizeof(file_data1) + sizeof(file_data2)); /* Compressed size */
+       assertEqualInt(i4(p + 24), sizeof(file_data1) + sizeof(file_data2)); /* Uncompressed size */
+       assertEqualInt(i2(p + 28), strlen(file_name)); /* Pathname length */
+       assertEqualInt(i2(p + 30), 13); /* Extra field length */
+       assertEqualInt(i2(p + 32), 0); /* File comment length */
+       assertEqualInt(i2(p + 34), 0); /* Disk number start */
+       assertEqualInt(i2(p + 36), 0); /* Internal file attrs */
+       assertEqualInt(i4(p + 38) >> 16 & 01777, file_perm); /* External file attrs */
+       assertEqualInt(i4(p + 42), 0); /* Offset of local header */
+       assertEqualMem(p + 46, file_name, strlen(file_name)); /* Pathname */
+       p = p + 46 + strlen(file_name);
+       assertEqualInt(i2(p), 0x5455); /* 'UT' extension header */
+       assertEqualInt(i2(p + 2), 5); /* 'UT' size */
+       assertEqualInt(p[4], 7); /* 'UT' flags */
+       assertEqualInt(i4(p + 5), t); /* 'UT' mtime */
+       p = p + 9;
+       assertEqualInt(i2(p), 0x7855); /* 'Ux' extension header */
+       assertEqualInt(i2(p + 2), 0); /* 'Ux' size */
+       p = p + 4;
+
+       /* Verify local header of file entry. */
+       q = buff;
+       assertEqualMem(q, "PK\003\004", 4); /* Signature */
+       assertEqualInt(i2(q + 4), 20); /* Version needed to extract */
+       assertEqualInt(i2(q + 6), 8); /* Flags */
+       assertEqualInt(i2(q + 8), 0); /* Compression method */
+       assertEqualInt(i2(q + 10), (tm->tm_hour * 2048) + (tm->tm_min * 32) + (tm->tm_sec / 2)); /* File time */
+       assertEqualInt(i2(q + 12), ((tm->tm_year - 80) * 512) + ((tm->tm_mon + 1) * 32) + tm->tm_mday); /* File date */
+       assertEqualInt(i4(q + 14), 0); /* CRC-32 */
+       assertEqualInt(i4(q + 18), sizeof(file_data1) + sizeof(file_data2)); /* Compressed size */
+       assertEqualInt(i4(q + 22), sizeof(file_data1) + sizeof(file_data2)); /* Uncompressed size */
+       assertEqualInt(i2(q + 26), strlen(file_name)); /* Pathname length */
+       assertEqualInt(i2(q + 28), 25); /* Extra field length */
+       assertEqualMem(q + 30, file_name, strlen(file_name)); /* Pathname */
+       q = q + 30 + strlen(file_name);
+       assertEqualInt(i2(q), 0x5455); /* 'UT' extension header */
+       assertEqualInt(i2(q + 2), 13); /* 'UT' size */
+       assertEqualInt(q[4], 7); /* 'UT' flags */
+       assertEqualInt(i4(q + 5), t); /* 'UT' mtime */
+       assertEqualInt(i4(q + 9), t); /* 'UT' atime */
+       assertEqualInt(i4(q + 13), t); /* 'UT' ctime */
+       q = q + 17;
+       assertEqualInt(i2(q), 0x7855); /* 'Ux' extension header */
+       assertEqualInt(i2(q + 2), 4); /* 'Ux' size */
+       assertEqualInt(i2(q + 4), file_uid); /* 'Ux' UID */
+       assertEqualInt(i2(q + 6), file_gid); /* 'Ux' GID */
+       q = q + 8;
+
+       /* Verify data of file entry. */
+       assertEqualMem(q, file_data1, sizeof(file_data1));
+       assertEqualMem(q + sizeof(file_data1), file_data2, sizeof(file_data2));
+       q = q + sizeof(file_data1) + sizeof(file_data2);
+
+       /* Verify data descriptor of file entry. */
+       assertEqualMem(q, "PK\007\010", 4); /* Signature */
+       assertEqualInt(i4(q + 4), crc); /* CRC-32 */
+       assertEqualInt(i4(q + 8), sizeof(file_data1) + sizeof(file_data2)); /* Compressed size */
+       assertEqualInt(i4(q + 12), sizeof(file_data1) + sizeof(file_data2)); /* Uncompressed size */
+       q = q + 16;
+
+       /* Verify folder entry in central directory. */
+       assertEqualMem(p, "PK\001\002", 4); /* Signature */
+       assertEqualInt(i2(p + 4), 3 * 256 + 20); /* Version made by */
+       assertEqualInt(i2(p + 6), 20); /* Version needed to extract */
+       assertEqualInt(i2(p + 8), 8); /* Flags */
+       assertEqualInt(i2(p + 10), 0); /* Compression method */
+       assertEqualInt(i2(p + 12), (tm->tm_hour * 2048) + (tm->tm_min * 32) + (tm->tm_sec / 2)); /* File time */
+       assertEqualInt(i2(p + 14), ((tm->tm_year - 80) * 512) + ((tm->tm_mon + 1) * 32) + tm->tm_mday); /* File date */
+       crc = 0;
+       assertEqualInt(i4(p + 16), crc); /* CRC-32 */
+       assertEqualInt(i4(p + 20), 0); /* Compressed size */
+       assertEqualInt(i4(p + 24), 0); /* Uncompressed size */
+       assertEqualInt(i2(p + 28), strlen(folder_name)); /* Pathname length */
+       assertEqualInt(i2(p + 30), 13); /* Extra field length */
+       assertEqualInt(i2(p + 32), 0); /* File comment length */
+       assertEqualInt(i2(p + 34), 0); /* Disk number start */
+       assertEqualInt(i2(p + 36), 0); /* Internal file attrs */
+       assertEqualInt(i4(p + 38) >> 16 & 01777, folder_perm); /* External file attrs */
+       assertEqualInt(i4(p + 42), q - buff); /* Offset of local header */
+       assertEqualMem(p + 46, folder_name, strlen(folder_name)); /* Pathname */
+       p = p + 46 + strlen(folder_name);
+       assertEqualInt(i2(p), 0x5455); /* 'UT' extension header */
+       assertEqualInt(i2(p + 2), 5); /* 'UT' size */
+       assertEqualInt(p[4], 7); /* 'UT' flags */
+       assertEqualInt(i4(p + 5), t); /* 'UT' mtime */
+       p = p + 9;
+       assertEqualInt(i2(p), 0x7855); /* 'Ux' extension header */
+       assertEqualInt(i2(p + 2), 0); /* 'Ux' size */
+       p = p + 4;
+
+       /* Verify local header of folder entry. */
+       assertEqualMem(q, "PK\003\004", 4); /* Signature */
+       assertEqualInt(i2(q + 4), 20); /* Version needed to extract */
+       assertEqualInt(i2(q + 6), 8); /* Flags */
+       assertEqualInt(i2(q + 8), 0); /* Compression method */
+       assertEqualInt(i2(q + 10), (tm->tm_hour * 2048) + (tm->tm_min * 32) + (tm->tm_sec / 2)); /* File time */
+       assertEqualInt(i2(q + 12), ((tm->tm_year - 80) * 512) + ((tm->tm_mon + 1) * 32) + tm->tm_mday); /* File date */
+       assertEqualInt(i4(q + 14), 0); /* CRC-32 */
+       assertEqualInt(i4(q + 18), 0); /* Compressed size */
+       assertEqualInt(i4(q + 22), 0); /* Uncompressed size */
+       assertEqualInt(i2(q + 26), strlen(folder_name)); /* Pathname length */
+       assertEqualInt(i2(q + 28), 25); /* Extra field length */
+       assertEqualMem(q + 30, folder_name, strlen(folder_name)); /* Pathname */
+       q = q + 30 + strlen(folder_name);
+       assertEqualInt(i2(q), 0x5455); /* 'UT' extension header */
+       assertEqualInt(i2(q + 2), 13); /* 'UT' size */
+       assertEqualInt(q[4], 7); /* 'UT' flags */
+       assertEqualInt(i4(q + 5), t); /* 'UT' mtime */
+       assertEqualInt(i4(q + 9), t); /* 'UT' atime */
+       assertEqualInt(i4(q + 13), t); /* 'UT' ctime */
+       q = q + 17;
+       assertEqualInt(i2(q), 0x7855); /* 'Ux' extension header */
+       assertEqualInt(i2(q + 2), 4); /* 'Ux' size */
+       assertEqualInt(i2(q + 4), folder_uid); /* 'Ux' UID */
+       assertEqualInt(i2(q + 6), folder_gid); /* 'Ux' GID */
+       q = q + 8;
+
+       /* There should not be any data in the folder entry,
+        * meaning next is the data descriptor header. */
+
+       /* Verify data descriptor of folder entry. */
+       assertEqualMem(q, "PK\007\010", 4); /* Signature */
+       assertEqualInt(i4(q + 4), crc); /* CRC-32 */
+       assertEqualInt(i4(q + 8), 0); /* Compressed size */
+       assertEqualInt(i4(q + 12), 0); /* Uncompressed size */
+       q = q + 16;
+}