From ae3506d82f54e54696f638bd989c35ca35135abc Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Fri, 27 Dec 2013 00:41:48 -0800 Subject: [PATCH] Read Zip64 EOCD; work around an Info-Zip bug. --- libarchive/archive_read_support_format_zip.c | 70 ++++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/libarchive/archive_read_support_format_zip.c b/libarchive/archive_read_support_format_zip.c index 83e1d9799..f895b1923 100644 --- a/libarchive/archive_read_support_format_zip.c +++ b/libarchive/archive_read_support_format_zip.c @@ -162,9 +162,6 @@ static time_t zip_time(const char *); static const char *compression_name(int compression); static void process_extra(const char *, size_t, struct zip_entry *); -int archive_read_support_format_zip_streamable(struct archive *); -int archive_read_support_format_zip_seekable(struct archive *); - int archive_read_support_format_zip_streamable(struct archive *_a) { @@ -302,13 +299,11 @@ read_eocd(struct zip *zip, const char *p, int64_t current_offset) /* Sanity-check the EOCD we've found. */ /* This must be the first volume. */ - if (archive_le16dec(p + 4) != 0) { + if (archive_le16dec(p + 4) != 0) return 0; - } /* Central directory must be on this volume. */ - if (archive_le16dec(p + 4) != archive_le16dec(p + 6)) { + if (archive_le16dec(p + 4) != archive_le16dec(p + 6)) return 0; - } /* All central directory entries must be on this volume. */ if (archive_le16dec(p + 10) != archive_le16dec(p + 8)) return 0; @@ -328,13 +323,48 @@ read_eocd(struct zip *zip, const char *p, int64_t current_offset) } static int -read_zip64_eocd(struct zip *zip, const char *p, int64_t current_offset) +read_zip64_eocd(struct archive_read *a, struct zip *zip, + const char *p, int64_t current_offset) { - (void)zip; /* UNUSED */ - (void)p; /* UNUSED */ - (void)current_offset; /* UNUSED */ - /* Not yet supported. */ - return 0; + int64_t eocd64_offset; + int64_t eocd64_size; + + /* Sanity-check the locator record. */ + + /* Central dir must be on first volume. */ + if (archive_le32dec(p + 4) != 0) + return 0; + /* Must be only a single volume. */ + if (archive_le32dec(p + 16) != 1) + return 0; + + /* Find the Zip64 EOCD record. */ + eocd64_offset = archive_le64dec(p + 8); + current_offset = __archive_read_seek(a, eocd64_offset, SEEK_SET); + if (current_offset < 0) + return 0; + if ((p = __archive_read_ahead(a, 56, NULL)) == NULL) + return 0; + /* Make sure we can read all of it. */ + eocd64_size = archive_le64dec(p + 4) + 12; + if (eocd64_size < 56 || eocd64_size > 16384) + return 0; + if ((p = __archive_read_ahead(a, eocd64_size, NULL)) == NULL) + return 0; + + /* Sanity-check the EOCD64 */ + if (archive_le32dec(p + 16) != 0) /* Must be disk #0 */ + return 0; + if (archive_le32dec(p + 20) != 0) /* CD must be on disk #0 */ + return 0; + /* CD can't be split. */ + if (archive_le64dec(p + 24) != archive_le64dec(p + 32)) + return 0; + + /* Save the central directory offset for later use. */ + zip->central_directory_offset = archive_le64dec(p + 48); + + return 32; } @@ -364,6 +394,10 @@ archive_read_format_zip_seekable_bid(struct archive_read *a, int best_bid) return 0; if ((p = __archive_read_ahead(a, (size_t)tail, NULL)) == NULL) return 0; + /* TODO: Rework this to search backwards from the end. We + * normally expect the EOCD record to be at the very end, so + * that should be significantly faster. Tricky part: Make + * sure we still prefer the Zip64 locator if it's present. */ for (i = 0; i <= tail - 22;) { switch (p[i + 3]) { case 'P': i += 3; break; @@ -379,7 +413,8 @@ archive_read_format_zip_seekable_bid(struct archive_read *a, int best_bid) break; case 007: if (memcmp(p + i, "PK\006\007", 4) == 0) { - int ret = read_zip64_eocd(zip, p + i, current_offset + i); + int ret = read_zip64_eocd(a, zip, + p + i, current_offset + i); if (ret > 0) return (ret); } @@ -1189,6 +1224,13 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, } zip_read_consume(a, filename_length); + /* Work around a bug in Info-Zip: When reading from a pipe, it + * stats the pipe instead of synthesizing a file entry. */ + if ((zip_entry->mode & AE_IFMT) == AE_IFIFO) { + zip_entry->mode &= ~ AE_IFMT; + zip_entry->mode |= AE_IFREG; + } + if ((zip_entry->mode & AE_IFMT) == 0) { /* Especially in streaming mode, we can end up here without having seen proper mode information. -- 2.47.2