]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add support for __MACOSX directory in Zip archives, which resource
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Thu, 8 Nov 2012 06:29:29 +0000 (15:29 +0900)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Sun, 11 Nov 2012 09:17:29 +0000 (18:17 +0900)
forks are stored in.

Makefile.am
libarchive/archive_read_support_format_zip.c
libarchive/test/CMakeLists.txt
libarchive/test/test_read_format_zip_mac_metadata.c [new file with mode: 0644]
libarchive/test/test_read_format_zip_mac_metadata.zip.uu [new file with mode: 0644]

index 3fd14cfa38d683fc1bb1e24f8fb5aaf5929a1b94..2a6fe8a3beb5dc40282fcecaa35caeb0504639de 100644 (file)
@@ -404,6 +404,7 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_read_format_zip.c                  \
        libarchive/test/test_read_format_zip_comment_stored.c   \
        libarchive/test/test_read_format_zip_filename.c         \
+       libarchive/test/test_read_format_zip_mac_metadata.c     \
        libarchive/test/test_read_large.c                       \
        libarchive/test/test_read_pax_truncated.c               \
        libarchive/test/test_read_position.c                    \
@@ -643,6 +644,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_zip_filename_utf8_ru2.zip.uu   \
        libarchive/test/test_read_format_zip_filename_utf8_ru.zip.uu    \
        libarchive/test/test_read_format_zip_length_at_end.zip.uu       \
+       libarchive/test/test_read_format_zip_mac_metadata.zip.uu        \
        libarchive/test/test_read_format_zip_symlink.zip.uu             \
        libarchive/test/test_read_format_zip_ux.zip.uu                  \
        libarchive/test/test_read_large_splitted_rar_aa.uu              \
index b730a70011bb52e06bf893f85118b72ef3ddd963..ad3ecf1f03c0f09958384484165ae7c3242a2f69 100644 (file)
@@ -57,6 +57,7 @@ struct zip_entry {
        int64_t                 gid;
        int64_t                 uid;
        struct archive_entry    *entry;
+       struct archive_string   rsrcname;
        time_t                  mtime;
        time_t                  atime;
        time_t                  ctime;
@@ -80,6 +81,7 @@ struct zip {
        struct zip_entry        *zip_entries;
        struct zip_entry        *entry;
        struct archive_rb_tree  tree;
+       struct archive_rb_tree  tree_rsrc;
 
        size_t                  unconsumed;
 
@@ -134,7 +136,9 @@ static int  archive_read_format_zip_seekable_read_header(
                    struct archive_read *, struct archive_entry *);
 static int     archive_read_format_zip_streamable_read_header(
                    struct archive_read *, struct archive_entry *);
+static ssize_t zip_get_local_file_header_size(struct archive_read *, size_t);
 #ifdef HAVE_ZLIB_H
+static int     zip_deflate_init(struct archive_read *, struct zip *);
 static int     zip_read_data_deflate(struct archive_read *a, const void **buff,
                    size_t *size, int64_t *offset);
 #endif
@@ -341,6 +345,65 @@ cmp_key(const struct archive_rb_node *n, const void *key)
        return 1;
 }
 
+static int
+rsrc_cmp_node(const struct archive_rb_node *n1,
+    const struct archive_rb_node *n2)
+{
+       const struct zip_entry *e1 = (const struct zip_entry *)n1;
+       const struct zip_entry *e2 = (const struct zip_entry *)n2;
+
+       return (strcmp(e2->rsrcname.s, e1->rsrcname.s));
+}
+
+static int
+rsrc_cmp_key(const struct archive_rb_node *n, const void *key)
+{
+       const struct zip_entry *e = (const struct zip_entry *)n;
+       return (strcmp((const char *)key, e->rsrcname.s));
+}
+
+static const char *
+basename(const char *name, size_t name_length)
+{
+       const char *s, *r;
+
+       r = s = name;
+       for (;;) {
+               s = memchr(s, '/', name_length - (s - name));
+               if (s == NULL)
+                       break;
+               r = ++s;
+       }
+       return (r);
+}
+
+static void
+expose_parent_dirs(struct zip *zip, const char *name, size_t name_length)
+{
+       struct archive_string str;
+       struct zip_entry *dir;
+       char *s;
+
+       archive_string_init(&str);
+       archive_strncpy(&str, name, name_length);
+       for (;;) {
+               s = strrchr(str.s, '/');
+               if (s == NULL)
+                       break;
+               *s = '\0';
+               /* Transfer the parent directory from zip->tree_rsrc RB
+                * tree to zip->tree RB tree to expose. */
+               dir = (struct zip_entry *)
+                   __archive_rb_tree_find_node(&zip->tree_rsrc, str.s);
+               if (dir == NULL)
+                       break;
+               __archive_rb_tree_remove_node(&zip->tree_rsrc, &dir->node);
+               archive_string_free(&dir->rsrcname);
+               __archive_rb_tree_insert_node(&zip->tree, &dir->node);
+       }
+       archive_string_free(&str);
+}
+
 static int
 slurp_central_directory(struct archive_read *a, struct zip *zip)
 {
@@ -348,10 +411,14 @@ slurp_central_directory(struct archive_read *a, struct zip *zip)
        static const struct archive_rb_tree_ops rb_ops = {
                &cmp_node, &cmp_key
        };
+       static const struct archive_rb_tree_ops rb_rsrc_ops = {
+               &rsrc_cmp_node, &rsrc_cmp_key
+       };
 
        __archive_read_seek(a, zip->central_directory_offset, SEEK_SET);
        zip->offset = zip->central_directory_offset;
        __archive_rb_tree_init(&zip->tree, &rb_ops);
+       __archive_rb_tree_init(&zip->tree_rsrc, &rb_rsrc_ops);
 
        zip->zip_entries = calloc(zip->central_directory_entries,
                                sizeof(struct zip_entry));
@@ -359,7 +426,7 @@ slurp_central_directory(struct archive_read *a, struct zip *zip)
                struct zip_entry *zip_entry = &zip->zip_entries[i];
                size_t filename_length, extra_length, comment_length;
                uint32_t external_attributes;
-               const char *p;
+               const char *name, *p, *r;
 
                if ((p = __archive_read_ahead(a, 46, NULL)) == NULL)
                        return ARCHIVE_FATAL;
@@ -393,8 +460,50 @@ slurp_central_directory(struct archive_read *a, struct zip *zip)
                if (zip_entry->system == 3) {
                        zip_entry->mode = external_attributes >> 16;
                }
-               /* Register an entry to RB tree to sort it by file offset. */
-               __archive_rb_tree_insert_node(&zip->tree, &zip_entry->node);
+
+               /*
+                * Mac resource fork files are stored under the
+                * "__MACOSX/" directory, so we should check if
+                * it is.
+                */
+               /* Make sure we have the file name. */
+               if ((p = __archive_read_ahead(a, 46 + filename_length, NULL))
+                   == NULL)
+                       return ARCHIVE_FATAL;
+               name = p + 46;
+               r = basename(name, filename_length);
+               if (filename_length >= 9 &&
+                   strncmp("__MACOSX/", name, 9) == 0) {
+                       /* If this file is not a resource fork nor
+                        * a directory. We should treat it as a non
+                        * resource fork file to expose it. */
+                       if (name[filename_length-1] != '/' &&
+                           (r - name < 3 || r[0] != '.' || r[1] != '_')) {
+                               __archive_rb_tree_insert_node(&zip->tree,
+                                   &zip_entry->node);
+                               /* Expose its parent directories. */
+                               expose_parent_dirs(zip, name, filename_length);
+                       } else {
+                               /* This file is a resource fork file or
+                                * a directory. */
+                               archive_strncpy(&(zip_entry->rsrcname), name,
+                                   filename_length);
+                               __archive_rb_tree_insert_node(&zip->tree_rsrc,
+                                   &zip_entry->node);
+                       }
+               } else {
+                       /* Generate resource fork name to find its resource
+                        * file at zip->tree_rsrc. */
+                       archive_strcpy(&(zip_entry->rsrcname), "__MACOSX/");
+                       archive_strncat(&(zip_entry->rsrcname), name, r - name);
+                       archive_strcat(&(zip_entry->rsrcname), "._");
+                       archive_strncat(&(zip_entry->rsrcname),
+                           name + (r - name), filename_length - (r - name));
+                       /* Register an entry to RB tree to sort it by
+                        * file offset. */
+                       __archive_rb_tree_insert_node(&zip->tree,
+                           &zip_entry->node);
+               }
 
                /* We don't read the filename until we get to the
                   local file header.  Reading it here would speed up
@@ -423,11 +532,143 @@ zip_read_consume(struct archive_read *a, int64_t bytes)
        return (skip);
 }
 
+static int
+zip_read_mac_metadata(struct archive_read *a, struct archive_entry *entry,
+    struct zip_entry *rsrc)
+{
+       struct zip *zip = (struct zip *)a->format->data;
+       unsigned char *metadata, *mp;
+       int64_t offset = zip->offset;
+       size_t remaining_bytes, metadata_bytes;
+       ssize_t hsize;
+       int r, ret = ARCHIVE_OK, eof;
+
+       switch(rsrc->compression) {
+       case 0:  /* No compression. */
+#ifdef HAVE_ZLIB_H
+       case 8: /* Deflate compression. */
+#endif
+               break;
+       default: /* Unsupported compression. */
+               /* Return a warning. */
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                   "Unsupported ZIP compression method (%s)",
+                   compression_name(rsrc->compression));
+               /* We can't decompress this entry, but we will
+                * be able to skip() it and try the next entry. */
+               return (ARCHIVE_WARN);
+       }
+
+       if (rsrc->uncompressed_size > (128 * 1024)) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                   "Mac metadata is too large: %jd > 128K bytes",
+                   (intmax_t)rsrc->uncompressed_size);
+               return (ARCHIVE_WARN);
+       }
+
+       metadata = malloc(rsrc->uncompressed_size);
+       if (metadata == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate memory for Mac metadata");
+               return (ARCHIVE_FATAL);
+       }
+
+       if (zip->offset < rsrc->local_header_offset)
+               zip_read_consume(a, rsrc->local_header_offset - zip->offset);
+       else if (zip->offset != rsrc->local_header_offset) {
+               __archive_read_seek(a, rsrc->local_header_offset, SEEK_SET);
+               zip->offset = zip->entry->local_header_offset;
+       }
+
+       hsize = zip_get_local_file_header_size(a, 0);
+       zip_read_consume(a, hsize);
+
+       remaining_bytes = rsrc->compressed_size;
+       metadata_bytes = rsrc->uncompressed_size;
+       mp = metadata;
+       eof = 0;
+       while (!eof && remaining_bytes) {
+               const unsigned char *p;
+               ssize_t bytes_avail;
+               size_t bytes_used;
+
+               p = __archive_read_ahead(a, 1, &bytes_avail);
+               if (p == NULL) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "Truncated ZIP file header");
+                       ret = ARCHIVE_WARN;
+                       goto exit_mac_metadata;
+               }
+               if ((size_t)bytes_avail > remaining_bytes)
+                       bytes_avail = remaining_bytes;
+               switch(rsrc->compression) {
+               case 0:  /* No compression. */
+                       memcpy(mp, p, bytes_avail);
+                       bytes_used = (size_t)bytes_avail;
+                       metadata_bytes -= bytes_used;
+                       mp += bytes_used;
+                       if (metadata_bytes == 0)
+                               eof = 1;
+                       break;
+#ifdef HAVE_ZLIB_H
+               case 8: /* Deflate compression. */
+                       ret = zip_deflate_init(a, zip);
+                       if (ret != ARCHIVE_OK)
+                               goto exit_mac_metadata;
+                       zip->stream.next_in =
+                           (Bytef *)(uintptr_t)(const void *)p;
+                       zip->stream.avail_in = (uInt)bytes_avail;
+                       zip->stream.total_in = 0;
+                       zip->stream.next_out = mp;
+                       zip->stream.avail_out = (uInt)metadata_bytes;
+                       zip->stream.total_out = 0;
+
+                       r = inflate(&zip->stream, 0);
+                       switch (r) {
+                       case Z_OK:
+                               break;
+                       case Z_STREAM_END:
+                               eof = 1;
+                               break;
+                       case Z_MEM_ERROR:
+                               archive_set_error(&a->archive, ENOMEM,
+                                   "Out of memory for ZIP decompression");
+                               ret = ARCHIVE_FATAL;
+                               goto exit_mac_metadata;
+                       default:
+                               archive_set_error(&a->archive,
+                                   ARCHIVE_ERRNO_MISC,
+                                   "ZIP decompression failed (%d)", r);
+                               ret = ARCHIVE_FATAL;
+                               goto exit_mac_metadata;
+                       }
+                       bytes_used = zip->stream.total_in;
+                       metadata_bytes -= zip->stream.total_out;
+                       mp += zip->stream.total_out;
+                       break;
+#endif
+               }
+               zip_read_consume(a, bytes_used);
+               remaining_bytes -= bytes_used;
+       }
+       archive_entry_copy_mac_metadata(entry, metadata,
+           rsrc->uncompressed_size - metadata_bytes);
+
+       __archive_read_seek(a, offset, SEEK_SET);
+       zip->offset = offset;
+exit_mac_metadata:
+       zip->decompress_init = 0;
+       free(metadata);
+       return (ret);
+}
+
 static int
 archive_read_format_zip_seekable_read_header(struct archive_read *a,
        struct archive_entry *entry)
 {
        struct zip *zip = (struct zip *)a->format->data;
+       struct zip_entry *rsrc;
        int r, ret = ARCHIVE_OK;
 
        a->archive.archive_format = ARCHIVE_FORMAT_ZIP;
@@ -453,7 +694,19 @@ archive_read_format_zip_seekable_read_header(struct archive_read *a,
                return ARCHIVE_EOF;
        --zip->entries_remaining;
 
-       if (zip->offset != zip->entry->local_header_offset) {
+       if (zip->entry->rsrcname.s)
+               rsrc = (struct zip_entry *)__archive_rb_tree_find_node(
+                   &zip->tree_rsrc, zip->entry->rsrcname.s);
+       else
+               rsrc = NULL;
+
+       /* File entries are sorted by the header offset, we should mostly
+        * use zip_read_consume to advance a read point to avoid redundant
+        * data reading.  */
+       if (zip->offset < zip->entry->local_header_offset)
+               zip_read_consume(a,
+                   zip->entry->local_header_offset - zip->offset);
+       else if (zip->offset != zip->entry->local_header_offset) {
                __archive_read_seek(a, zip->entry->local_header_offset,
                        SEEK_SET);
                zip->offset = zip->entry->local_header_offset;
@@ -508,6 +761,11 @@ archive_read_format_zip_seekable_read_header(struct archive_read *a,
                        }
                }
        }
+       if (rsrc) {
+               int ret2 = zip_read_mac_metadata(a, entry, rsrc);
+               if (ret2 < ret)
+                       ret = ret2;
+       }
        return (ret);
 }
 
@@ -640,6 +898,29 @@ archive_read_format_zip_streamable_read_header(struct archive_read *a,
        }
 }
 
+static ssize_t
+zip_get_local_file_header_size(struct archive_read *a, size_t extra)
+{
+       const char *p;
+       ssize_t filename_length, extra_length;
+
+       if ((p = __archive_read_ahead(a, extra + 30, NULL)) == NULL) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                   "Truncated ZIP file header");
+               return (ARCHIVE_WARN);
+       }
+       p += extra;
+
+       if (memcmp(p, "PK\003\004", 4) != 0) {
+               archive_set_error(&a->archive, -1, "Damaged Zip archive");
+               return ARCHIVE_WARN;
+       }
+       filename_length = archive_le16dec(p + 26);
+       extra_length = archive_le16dec(p + 28);
+
+       return (30 + filename_length + extra_length);
+}
+
 /*
  * Assumes file pointer is at beginning of local file header.
  */
@@ -1066,6 +1347,31 @@ zip_read_data_none(struct archive_read *a, const void **_buff,
 }
 
 #ifdef HAVE_ZLIB_H
+static int
+zip_deflate_init(struct archive_read *a, struct zip *zip)
+{
+       int r;
+
+       /* If we haven't yet read any data, initialize the decompressor. */
+       if (!zip->decompress_init) {
+               if (zip->stream_valid)
+                       r = inflateReset(&zip->stream);
+               else
+                       r = inflateInit2(&zip->stream,
+                           -15 /* Don't check for zlib header */);
+               if (r != Z_OK) {
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "Can't initialize ZIP decompression.");
+                       return (ARCHIVE_FATAL);
+               }
+               /* Stream structure has been set up. */
+               zip->stream_valid = 1;
+               /* We've initialized decompression for this stream. */
+               zip->decompress_init = 1;
+       }
+       return (ARCHIVE_OK);
+}
+
 static int
 zip_read_data_deflate(struct archive_read *a, const void **buff,
     size_t *size, int64_t *offset)
@@ -1091,23 +1397,9 @@ zip_read_data_deflate(struct archive_read *a, const void **buff,
                }
        }
 
-       /* If we haven't yet read any data, initialize the decompressor. */
-       if (!zip->decompress_init) {
-               if (zip->stream_valid)
-                       r = inflateReset(&zip->stream);
-               else
-                       r = inflateInit2(&zip->stream,
-                           -15 /* Don't check for zlib header */);
-               if (r != Z_OK) {
-                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
-                           "Can't initialize ZIP decompression.");
-                       return (ARCHIVE_FATAL);
-               }
-               /* Stream structure has been set up. */
-               zip->stream_valid = 1;
-               /* We've initialized decompression for this stream. */
-               zip->decompress_init = 1;
-       }
+       r = zip_deflate_init(a, zip);
+       if (r != ARCHIVE_OK)
+               return (r);
 
        /*
         * Note: '1' here is a performance optimization.
@@ -1268,6 +1560,11 @@ archive_read_format_zip_cleanup(struct archive_read *a)
        if (zip->stream_valid)
                inflateEnd(&zip->stream);
 #endif
+       if (zip->zip_entries && zip->central_directory_entries) {
+               unsigned i;
+               for (i = 0; i < zip->central_directory_entries; i++)
+                       archive_string_free(&(zip->zip_entries[i].rsrcname));
+       }
        free(zip->zip_entries);
        free(zip->uncompressed_buffer);
        archive_string_free(&(zip->extra));
index e39b8c13cc9d78fd339967994220c6aa53e15c5e..4a28322455e6960816b4ac2b3dee160546191383 100644 (file)
@@ -139,6 +139,7 @@ IF(ENABLE_TEST)
     test_read_format_zip.c
     test_read_format_zip_comment_stored.c
     test_read_format_zip_filename.c
+    test_read_format_zip_mac_metadata.c
     test_read_large.c
     test_read_pax_truncated.c
     test_read_position.c
diff --git a/libarchive/test/test_read_format_zip_mac_metadata.c b/libarchive/test/test_read_format_zip_mac_metadata.c
new file mode 100644 (file)
index 0000000..9783633
--- /dev/null
@@ -0,0 +1,98 @@
+/*-
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * 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.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+/*
+ * Read a zip file that has a zip comment in the end of the central
+ * directory record.
+ */
+DEFINE_TEST(test_read_format_zip_mac_metadata)
+{
+       const char *refname = "test_read_format_zip_mac_metadata.zip";
+       char *p;
+       const void *metadata;
+       size_t s;
+       struct archive *a;
+       struct archive_entry *ae;
+       const unsigned char appledouble[] = {
+               0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00,
+               0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58,
+               0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+               0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00,
+               0x00, 0x32, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00,
+               0x00, 0x02, 0x00, 0x00, 0x01, 0x1f, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x41, 0x54, 0x54, 0x52,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1f,
+               0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x87,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+               0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x87,
+               0x00, 0x00, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
+               0x70, 0x70, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x6c,
+               0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00,
+               0x21, 0x23, 0x61, 0x63, 0x6c, 0x20, 0x31, 0x0a,
+               0x75, 0x73, 0x65, 0x72, 0x3a, 0x46, 0x46, 0x46,
+               0x46, 0x45, 0x45, 0x45, 0x45, 0x2d, 0x44, 0x44,
+               0x44, 0x44, 0x2d, 0x43, 0x43, 0x43, 0x43, 0x2d,
+               0x42, 0x42, 0x42, 0x42, 0x2d, 0x41, 0x41, 0x41,
+               0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x43,
+               0x39, 0x3a, 0x47, 0x75, 0x65, 0x73, 0x74, 0x3a,
+               0x32, 0x30, 0x31, 0x3a, 0x64, 0x65, 0x6e, 0x79,
+               0x3a, 0x72, 0x65, 0x61, 0x64, 0x0a, 0x67, 0x72,
+               0x6f, 0x75, 0x70, 0x3a, 0x41, 0x42, 0x43, 0x44,
+               0x45, 0x46, 0x41, 0x42, 0x2d, 0x43, 0x44, 0x45,
+               0x46, 0x2d, 0x41, 0x42, 0x43, 0x44, 0x2d, 0x45,
+               0x46, 0x41, 0x42, 0x2d, 0x43, 0x44, 0x45, 0x46,
+               0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x35, 0x30,
+               0x3a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x3a, 0x38,
+               0x30, 0x3a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x3a,
+               0x77, 0x72, 0x69, 0x74, 0x65, 0x0a, 0x00
+       };
+
+       extract_reference_file(refname);
+       p = slurpfile(&s, refname);
+
+       /* Mac metadata can only be extracted with the seeking reader. */
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_zip(a));
+       assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, p, s, 1));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString("file3", archive_entry_pathname(ae));
+       assertEqualInt(AE_IFREG | 0644, archive_entry_mode(ae));
+       failure("Mac metadata should be set");
+       if (assert((metadata = archive_entry_mac_metadata(ae, &s)) != NULL)) {
+               assertEqualMem(metadata, appledouble, sizeof(appledouble));
+       }
+
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_free(a));
+}
diff --git a/libarchive/test/test_read_format_zip_mac_metadata.zip.uu b/libarchive/test/test_read_format_zip_mac_metadata.zip.uu
new file mode 100644 (file)
index 0000000..d54c24d
--- /dev/null
@@ -0,0 +1,17 @@
+begin 644 test_read_format_zip_mac_metadata.zip
+M4$L#!!0`"``(`"UH8T$````````````````%`!``9FEL93-56`P`U?264!:7
+ME%#U`10`2TQ*3DE-2^<"`%!+!P@D*E,-"@````@```!02P,$"@``````5VAF
+M00````````````````D`$`!?7TU!0T]36"]56`P`YHN84.:+F%#U`10`4$L#
+M!!0`"``(`"UH8T$````````````````0`!``7U]-04-/4U@O+E]F:6QE,U58
+M#`#5])90%I>44/4!%`!C8!5C9V!B8/!-3%;P#U:(4(`"D!@#)Q`;`?%;(`;R
+M&>49B`*.(2%!$!98QPP@;D=3PH@0%T[.S]5++"C(2=5+3,[1*TFM*`%**"H#
+M.0J&7*7%J456;D#@"@2Z+D"@ZPP$NDY`H.L(!`9@X&QIY5Z:6EQB961@:)62
+MFE=I592:F,*57I1?6F#EZ.3LXNKFZ*0+HG1!/%TX%Z+=U,`J,24W,\_*`LC(
+MR<DOMRHORBQ)Y6(``%!+!PCX/!>^LP```!\!``!02P$"%0,4``@`"``M:&-!
+M)"I3#0H````(````!0`,``````````!`I($`````9FEL93-56`@`U?264!:7
+ME%!02P$"%0,*``````!7:&9!````````````````"0`,``````````!`_4%-
+M````7U]-04-/4U@O55@(`.:+F%#FBYA04$L!`A4#%``(``@`+6AC0?@\%[ZS
+M````'P$``!``#```````````0*2!A````%]?34%#3U-8+RY?9FEL93-56`@`
+>U?264!:7E%!02P4&``````,``P#,````A0$`````
+`
+end