archive-zip: support archives bigger than 4GB
authorRené Scharfe <l.s.r@web.de>
Mon, 24 Apr 2017 17:32:36 +0000 (19:32 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 25 Apr 2017 05:10:51 +0000 (22:10 -0700)
Add a zip64 extended information extra field to the central directory
and emit the zip64 end of central directory records as well as locator
if the offset of an entry within the archive exceeds 4GB.

Signed-off-by: Rene Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
archive-zip.c
t/t5004-archive-corner-cases.sh
index 2d52bb3adec33e135fc5ce33297f7d3a64370078..7d6f2a85d09612c3849c266b581b9947b8581198 100644 (file)
@@ -14,7 +14,7 @@ static int zip_time;
 /* We only care about the "buf" part here. */
 static struct strbuf zip_dir;
 
-static unsigned int zip_offset;
+static uintmax_t zip_offset;
 static uint64_t zip_dir_entries;
 
 static unsigned int max_creator_version;
@@ -145,6 +145,11 @@ static void copy_le16_clamp(unsigned char *dest, uint64_t n, int *clamped)
        copy_le16(dest, clamp_max(n, 0xffff, clamped));
 }
 
+static void copy_le32_clamp(unsigned char *dest, uint64_t n, int *clamped)
+{
+       copy_le32(dest, clamp_max(n, 0xffffffff, clamped));
+}
+
 static int strbuf_add_le(struct strbuf *sb, size_t size, uintmax_t n)
 {
        while (size-- > 0) {
@@ -154,6 +159,12 @@ static int strbuf_add_le(struct strbuf *sb, size_t size, uintmax_t n)
        return -!!n;
 }
 
+static uint32_t clamp32(uintmax_t n)
+{
+       const uintmax_t max = 0xffffffff;
+       return (n < max) ? n : max;
+}
+
 static void *zlib_deflate_raw(void *data, unsigned long size,
                              int compression_level,
                              unsigned long *compressed_size)
@@ -254,6 +265,8 @@ static int write_zip_entry(struct archiver_args *args,
        int is_binary = -1;
        const char *path_without_prefix = path + args->baselen;
        unsigned int creator_version = 0;
+       size_t zip_dir_extra_size = ZIP_EXTRA_MTIME_SIZE;
+       size_t zip64_dir_extra_payload_size = 0;
 
        crc = crc32(0, NULL, 0);
 
@@ -433,6 +446,11 @@ static int write_zip_entry(struct archiver_args *args,
        free(deflated);
        free(buffer);
 
+       if (offset > 0xffffffff) {
+               zip64_dir_extra_payload_size += 8;
+               zip_dir_extra_size += 2 + 2 + zip64_dir_extra_payload_size;
+       }
+
        strbuf_add_le(&zip_dir, 4, 0x02014b50); /* magic */
        strbuf_add_le(&zip_dir, 2, creator_version);
        strbuf_add_le(&zip_dir, 2, 10);         /* version */
@@ -444,14 +462,20 @@ static int write_zip_entry(struct archiver_args *args,
        strbuf_add_le(&zip_dir, 4, compressed_size);
        strbuf_add_le(&zip_dir, 4, size);
        strbuf_add_le(&zip_dir, 2, pathlen);
-       strbuf_add_le(&zip_dir, 2, ZIP_EXTRA_MTIME_SIZE);
+       strbuf_add_le(&zip_dir, 2, zip_dir_extra_size);
        strbuf_add_le(&zip_dir, 2, 0);          /* comment length */
        strbuf_add_le(&zip_dir, 2, 0);          /* disk */
        strbuf_add_le(&zip_dir, 2, !is_binary);
        strbuf_add_le(&zip_dir, 4, attr2);
-       strbuf_add_le(&zip_dir, 4, offset);
+       strbuf_add_le(&zip_dir, 4, clamp32(offset));
        strbuf_add(&zip_dir, path, pathlen);
        strbuf_add(&zip_dir, &extra, ZIP_EXTRA_MTIME_SIZE);
+       if (zip64_dir_extra_payload_size) {
+               strbuf_add_le(&zip_dir, 2, 0x0001);     /* magic */
+               strbuf_add_le(&zip_dir, 2, zip64_dir_extra_payload_size);
+               if (offset >= 0xffffffff)
+                       strbuf_add_le(&zip_dir, 8, offset);
+       }
        zip_dir_entries++;
 
        return 0;
@@ -494,7 +518,7 @@ static void write_zip_trailer(const unsigned char *sha1)
                        &clamped);
        copy_le16_clamp(trailer.entries, zip_dir_entries, &clamped);
        copy_le32(trailer.size, zip_dir.len);
-       copy_le32(trailer.offset, zip_offset);
+       copy_le32_clamp(trailer.offset, zip_offset, &clamped);
        copy_le16(trailer.comment_length, sha1 ? GIT_SHA1_HEXSZ : 0);
 
        write_or_die(1, zip_dir.buf, zip_dir.len);
index 5c886fa823f723a2d87d74f4fe1688e652ec3b32..41183ea2cf5ed01cf1a99ef83e76aeae5d93b85d 100755 (executable)
@@ -155,7 +155,7 @@ test_expect_success ZIPINFO 'zip archive with many entries' '
        test_cmp expect actual
 '
 
-test_expect_failure EXPENSIVE,UNZIP 'zip archive bigger than 4GB' '
+test_expect_success EXPENSIVE,UNZIP 'zip archive bigger than 4GB' '
        # build string containing 65536 characters
        s=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef &&
        s=$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s &&