int64_t uncompressed_size;
int64_t gid;
int64_t uid;
- struct archive_entry *entry;
struct archive_string rsrcname;
time_t mtime;
time_t atime;
uint16_t flags;
char compression;
char system;
+ char have_zip64;
};
struct zip {
{
unsigned i;
int64_t correction;
+ const char *p;
static const struct archive_rb_tree_ops rb_ops = {
&cmp_node, &cmp_key
};
struct zip_entry *zip_entry = &zip->zip_entries[i];
size_t filename_length, extra_length, comment_length;
uint32_t external_attributes;
- const char *name, *p, *r;
+ const char *name, *r;
if ((p = __archive_read_ahead(a, 46, NULL)) == NULL)
return ARCHIVE_FATAL;
find and read local file header to get the
filename) at the cost of requiring a lot of extra
space. */
- /* We don't read the extra block here. We assume it
- will be duplicated at the local file header. */
- __archive_read_consume(a,
- 46 + filename_length + extra_length + comment_length);
+ __archive_read_consume(a, 46 + filename_length);
+
+ /* Read and process the extra data. */
+ if ((p = __archive_read_ahead(a, extra_length, NULL)) == NULL) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated ZIP file header");
+ return (ARCHIVE_FATAL);
+ }
+ process_extra(p, extra_length, zip_entry);
+ __archive_read_consume(a, extra_length);
+
+ /* Skip the comment, if any. */
+ __archive_read_consume(a, comment_length);
}
return ARCHIVE_OK;
size_t len, filename_length, extra_length;
struct archive_string_conv *sconv;
struct zip_entry *zip_entry = zip->entry;
+ struct zip_entry zip_entry_original = *zip_entry;
uint32_t local_crc32;
int64_t compressed_size, uncompressed_size;
int ret = ARCHIVE_OK;
zip_read_consume(a, 30);
- if (zip->have_central_directory) {
- /* If we read the central dir entry, we must have size
- * information as well, so ignore the length-at-end flag. */
- zip_entry->flags &= ~ZIP_LENGTH_AT_END;
- /* If we have values from both the local file header
- and the central directory, warn about mismatches
- which might indicate a damaged file. But some
- writers always put zero in the local header; don't
- bother warning about that. */
- if (local_crc32 != 0 && local_crc32 != zip_entry->crc32) {
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_FILE_FORMAT,
- "Inconsistent CRC32 values");
- ret = ARCHIVE_WARN;
- }
- if (compressed_size != 0
- && compressed_size != zip_entry->compressed_size) {
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_FILE_FORMAT,
- "Inconsistent compressed size");
- ret = ARCHIVE_WARN;
- }
- if (uncompressed_size != 0
- && uncompressed_size != zip_entry->uncompressed_size) {
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_FILE_FORMAT,
- "Inconsistent uncompressed size");
- ret = ARCHIVE_WARN;
- }
- } else {
- /* If we don't have the CD info, use whatever we do have. */
- zip_entry->crc32 = local_crc32;
- zip_entry->compressed_size = compressed_size;
- zip_entry->uncompressed_size = uncompressed_size;
- }
-
/* Read the filename. */
if ((h = __archive_read_ahead(a, filename_length, NULL)) == NULL) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
zip_entry->mode |= AE_IFREG;
}
if (zip_entry->mode == AE_IFDIR) {
- zip_entry->mode |= 0777;
+ zip_entry->mode |= 0775;
} else if (zip_entry->mode == AE_IFREG) {
- zip_entry->mode |= 0666;
+ zip_entry->mode |= 0664;
}
}
process_extra(h, extra_length, zip_entry);
zip_read_consume(a, extra_length);
+ if (zip->have_central_directory) {
+ /* If we read the central dir entry, we must have size
+ * information as well, so ignore the length-at-end flag. */
+ zip_entry->flags &= ~ZIP_LENGTH_AT_END;
+ /* If we have values from both the local file header
+ and the central directory, warn about mismatches
+ which might indicate a damaged file. But some
+ writers always put zero in the local header; don't
+ bother warning about that. */
+ if (zip_entry->crc32 != 0
+ && zip_entry->crc32 != zip_entry_original.crc32) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Inconsistent CRC32 values");
+ ret = ARCHIVE_WARN;
+ }
+ if (zip_entry->compressed_size != 0
+ && zip_entry->compressed_size != zip_entry_original.compressed_size) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Inconsistent compressed size: "
+ "%jd in central directory, %jd in local header",
+ (intmax_t)zip_entry_original.compressed_size,
+ (intmax_t)zip_entry->compressed_size);
+ ret = ARCHIVE_WARN;
+ }
+ if (zip_entry->uncompressed_size != 0
+ && zip_entry->uncompressed_size != zip_entry_original.uncompressed_size) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Inconsistent uncompressed size: "
+ "%jd in central directory, %jd in local header",
+ (intmax_t)zip_entry_original.uncompressed_size,
+ (intmax_t)zip_entry->uncompressed_size);
+ ret = ARCHIVE_WARN;
+ }
+ } else {
+ /* If we don't have the CD info, use whatever we do have. */
+ zip_entry->crc32 = local_crc32;
+ zip_entry->compressed_size = compressed_size;
+ zip_entry->uncompressed_size = uncompressed_size;
+ }
+
/* Populate some additional entry fields: */
archive_entry_set_mode(entry, zip_entry->mode);
archive_entry_set_uid(entry, zip_entry->uid);
!= (zip->entry_uncompressed_bytes_read & UINT32_MAX)) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"ZIP uncompressed data is wrong size "
- "(read %jd, expected %jd)",
+ "(read %jd, expected %jd)\n",
(intmax_t)zip->entry_uncompressed_bytes_read,
(intmax_t)zip->entry->uncompressed_size);
return (ARCHIVE_WARN);
* TODO: Technically, the PK\007\010 signature is optional.
* In the original spec, the data descriptor contained CRC
* and size fields but had no leading signature. In practice,
- * newer writers seem to provide the signature pretty consistently,
- * but we might need to do something more complex here if
- * we want to handle older archives that lack that signature.
+ * newer writers seem to provide the signature pretty consistently.
+ *
+ * For uncompressed data, the PK\007\010 marker seems essential
+ * to be sure we've actually seen the end of the entry.
*
* Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets
* zip->end_of_entry if it consumes all of the data.
if (zip->entry->flags & ZIP_LENGTH_AT_END) {
const char *p;
- /* Grab at least 16 bytes. */
- buff = __archive_read_ahead(a, 16, &bytes_avail);
- if (bytes_avail < 16) {
+ /* Grab at least 24 bytes. */
+ buff = __archive_read_ahead(a, 24, &bytes_avail);
+ if (bytes_avail < 24) {
/* Zip archives have end-of-archive markers
that are longer than this, so a failure to get at
- least 16 bytes really does indicate a truncated
+ least 24 bytes really does indicate a truncated
file. */
archive_set_error(&a->archive,
ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated ZIP file data");
return (ARCHIVE_FATAL);
}
- /* Check for a complete PK\007\010 signature. */
+ /* Check for a complete PK\007\010 signature, followed
+ * by the correct 4-byte CRC. */
p = buff;
if (p[0] == 'P' && p[1] == 'K'
&& p[2] == '\007' && p[3] == '\010'
- && archive_le32dec(p + 4) == zip->entry_crc32
- && archive_le32dec(p + 8) ==
- zip->entry_compressed_bytes_read
- && archive_le32dec(p + 12) ==
- zip->entry_uncompressed_bytes_read) {
- zip->entry->crc32 = archive_le32dec(p + 4);
- zip->entry->compressed_size = archive_le32dec(p + 8);
- zip->entry->uncompressed_size = archive_le32dec(p + 12);
+ && archive_le32dec(p + 4) == zip->entry_crc32) {
+ if (zip->entry->have_zip64) {
+ zip->entry->crc32 = archive_le32dec(p + 4);
+ zip->entry->compressed_size = archive_le64dec(p + 8);
+ zip->entry->uncompressed_size = archive_le64dec(p + 16);
+ zip->unconsumed = 24;
+ } else {
+ zip->entry->crc32 = archive_le32dec(p + 4);
+ zip->entry->compressed_size = archive_le32dec(p + 8);
+ zip->entry->uncompressed_size = archive_le32dec(p + 12);
+ zip->unconsumed = 16;
+ }
zip->end_of_entry = 1;
- zip->unconsumed = 16;
return (ARCHIVE_OK);
}
/* If not at EOF, ensure we consume at least one byte. */
if (zip->end_of_entry && (zip->entry->flags & ZIP_LENGTH_AT_END)) {
const char *p;
- if (NULL == (p = __archive_read_ahead(a, 16, NULL))) {
+ if (NULL == (p = __archive_read_ahead(a, 24, NULL))) {
archive_set_error(&a->archive,
ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated ZIP end-of-file record");
/* Consume the optional PK\007\010 marker. */
if (p[0] == 'P' && p[1] == 'K' &&
p[2] == '\007' && p[3] == '\010') {
- zip->entry->crc32 = archive_le32dec(p + 4);
- zip->entry->compressed_size = archive_le32dec(p + 8);
- zip->entry->uncompressed_size = archive_le32dec(p + 12);
- zip->unconsumed = 16;
+ p += 4;
+ zip->unconsumed = 4;
+ }
+ if (zip->entry->have_zip64) {
+ zip->entry->crc32 = archive_le32dec(p);
+ zip->entry->compressed_size = archive_le64dec(p + 4);
+ zip->entry->uncompressed_size = archive_le64dec(p + 12);
+ zip->unconsumed += 20;
+ } else {
+ zip->entry->crc32 = archive_le32dec(p);
+ zip->entry->compressed_size = archive_le32dec(p + 4);
+ zip->entry->uncompressed_size = archive_le32dec(p + 8);
+ zip->unconsumed += 12;
}
}
else if (p[3] == '\007') { p += 1; }
else if (p[3] == '\010' && p[2] == '\007'
&& p[1] == 'K' && p[0] == 'P') {
- zip_read_consume(a, p - buff + 16);
+ if (zip->entry->have_zip64)
+ zip_read_consume(a, p - buff + 24);
+ else
+ zip_read_consume(a, p - buff + 16);
return ARCHIVE_OK;
} else { p += 4; }
}
switch (headerid) {
case 0x0001:
/* Zip64 extended information extra field. */
- if (datasize >= 8 &&
- zip_entry->uncompressed_size == 0xffffffff)
+ zip_entry->have_zip64 = 1;
+ if (zip_entry->uncompressed_size == 0xffffffff) {
+ if (datasize < 8)
+ break;
zip_entry->uncompressed_size =
archive_le64dec(p + offset);
- if (datasize >= 16 &&
- zip_entry->compressed_size == 0xffffffff)
+ offset += 8;
+ datasize -= 8;
+ }
+ if (zip_entry->compressed_size == 0xffffffff) {
+ if (datasize < 8)
+ break;
zip_entry->compressed_size =
- archive_le64dec(p + offset + 8);
+ archive_le64dec(p + offset);
+ offset += 8;
+ datasize -= 8;
+ }
+ if (zip_entry->local_header_offset == 0xffffffff) {
+ if (datasize < 8)
+ break;
+ zip_entry->local_header_offset =
+ archive_le64dec(p + offset);
+ offset += 8;
+ datasize -= 8;
+ }
+ /* archive_le32dec(p + offset) gives disk
+ * on which file starts, but we don't handle
+ * multi-volume Zip files. */
break;
case 0x5455:
{
assertEqualString("Metadata/Job_PT.xml", archive_entry_pathname(ae));
assertEqualInt(3559, archive_entry_size(ae));
assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
- assertEqualInt(0666, archive_entry_perm(ae));
+ assertEqualInt(0664, archive_entry_perm(ae));
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("Metadata/MXDC_Empty_PT.xml", archive_entry_pathname(ae));
assertEqualInt(456, archive_entry_size(ae));
assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
- assertEqualInt(0666, archive_entry_perm(ae));
+ assertEqualInt(0664, archive_entry_perm(ae));
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("Documents/1/Metadata/Page1_Thumbnail.JPG", archive_entry_pathname(ae));
assertEqualInt(1495, archive_entry_size(ae));
assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
- assertEqualInt(0666, archive_entry_perm(ae));
+ assertEqualInt(0664, archive_entry_perm(ae));
/* TODO: Read some of the file data and verify it.
The code to read uncompressed Zip entries with "file at end" semantics
is tricky and should be verified more carefully. */
assertEqualInt(0, archive_entry_size(ae));
assert(!archive_entry_size_is_set(ae));
assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
- assertEqualInt(0666, archive_entry_perm(ae));
+ assertEqualInt(0664, archive_entry_perm(ae));
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("Metadata/MXDC_Empty_PT.xml", archive_entry_pathname(ae));
assertEqualInt(0, archive_entry_size(ae));
assert(!archive_entry_size_is_set(ae));
assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
- assertEqualInt(0666, archive_entry_perm(ae));
+ assertEqualInt(0664, archive_entry_perm(ae));
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("Documents/1/Metadata/Page1_Thumbnail.JPG", archive_entry_pathname(ae));
assertEqualInt(0, archive_entry_size(ae));
assert(!archive_entry_size_is_set(ae));
assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
- assertEqualInt(0666, archive_entry_perm(ae));
+ assertEqualInt(0664, archive_entry_perm(ae));
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("Documents/1/Pages/_rels/1.fpage.rels", archive_entry_pathname(ae));
--- /dev/null
+/*-
+ * Copyright (c) 2013 Tim Kientzle
+ * 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$");
+
+/*
+ * Sample file was created with:
+ * echo file0 | zip
+ * Info-Zip uses Zip64 extensions in this case.
+ */
+static void
+verify_file0_seek(struct archive *a)
+{
+ char *p;
+ size_t s;
+ struct archive_entry *ae;
+ char data[16];
+
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualString("-", archive_entry_pathname(ae));
+ assertEqualInt(AE_IFREG | 0660, archive_entry_mode(ae));
+ assertEqualInt(6, archive_entry_size(ae));
+ assertEqualIntA(a, 6, archive_read_data(a, data, 16));
+ assertEqualMem(data, "file0\x0a", 6);
+
+ 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));
+}
+
+static void
+verify_file0_stream(struct archive *a)
+{
+ char *p;
+ size_t s;
+ struct archive_entry *ae;
+ char data[16];
+
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualString("-", archive_entry_pathname(ae));
+ assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae));
+ assertEqualInt(0, archive_entry_size_is_set(ae));
+ assertEqualIntA(a, 6, archive_read_data(a, data, 16));
+ assertEqualMem(data, "file0\x0a", 6);
+
+ 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));
+}
+
+DEFINE_TEST(test_read_format_zip_zip64a)
+{
+ const char *refname = "test_read_format_zip_zip64a.zip";
+ struct archive *a;
+ char *p;
+ size_t s;
+
+ extract_reference_file(refname);
+ p = slurpfile(&s, refname);
+
+ /* First read with seeking. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_zip_seekable(a));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, p, s, 1));
+ verify_file0_seek(a);
+
+ /* Then read streaming. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_zip_streamable(a));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, p, s, 1));
+ verify_file0_stream(a);
+ free(p);
+}
+
+DEFINE_TEST(test_read_format_zip_zip64b)
+{
+ const char *refname = "test_read_format_zip_zip64b.zip";
+ struct archive *a;
+ char *p;
+ size_t s;
+
+ extract_reference_file(refname);
+ p = slurpfile(&s, refname);
+
+ /* First read with seeking. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_zip_seekable(a));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, p, s, 1));
+ verify_file0_seek(a);
+
+ /* Then read streaming. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_zip_streamable(a));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, p, s, 1));
+ verify_file0_stream(a);
+ free(p);
+}
+