I'd like to get this finished for libarchive 2.8.
SVN-Revision: 986
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
)
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
__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 *);
--- /dev/null
+/*-
+ * 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;
+}
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
)
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
--- /dev/null
+/*-
+ * 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);
+}
--- /dev/null
+/*-
+ * 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);
+}
--- /dev/null
+/*-
+ * 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;
+}