From: Tim Kientzle Date: Sun, 7 Nov 2010 02:14:21 +0000 (-0400) Subject: If we see junk when we're expecting a 'PK' signature X-Git-Tag: v3.0.0a~858 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d6855a5790a51e8acfb082e77fa99aadcaeb3764;p=thirdparty%2Flibarchive.git If we see junk when we're expecting a 'PK' signature block, scan forward to see if we can find a suitable signature. This is necessary to read archives that have been modified by some Zip utilities that update entries in-place without compacting the entire archive. Of course, this would be very natural if libarchive used the Central directory. But even when libarchive does support the Central directory, this kind of logic will still be useful for reading Zip archives in streaming mode. SVN-Revision: 2781 --- diff --git a/libarchive/archive_read_support_format_zip.c b/libarchive/archive_read_support_format_zip.c index 8cd1a1f8c..92a947bc7 100644 --- a/libarchive/archive_read_support_format_zip.c +++ b/libarchive/archive_read_support_format_zip.c @@ -129,6 +129,7 @@ static int archive_read_format_zip_read_data(struct archive_read *, static int archive_read_format_zip_read_data_skip(struct archive_read *a); static int archive_read_format_zip_read_header(struct archive_read *, struct archive_entry *); +static int search_next_signature(struct archive_read *); static int zip_read_data_deflate(struct archive_read *a, const void **buff, size_t *size, int64_t *offset); static int zip_read_data_none(struct archive_read *a, const void **buff, @@ -321,10 +322,17 @@ archive_read_format_zip_read_header(struct archive_read *a, signature = (const char *)h; } + /* If we don't see a PK signature here, scan forward. */ if (signature[0] != 'P' || signature[1] != 'K') { - archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Bad ZIP file"); - return (ARCHIVE_FATAL); + r = search_next_signature(a); + if (r != ARCHIVE_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad ZIP file"); + return (ARCHIVE_FATAL); + } + if ((h = __archive_read_ahead(a, 4, NULL)) == NULL) + return (ARCHIVE_FATAL); + signature = (const char *)h; } /* @@ -378,6 +386,42 @@ archive_read_format_zip_read_header(struct archive_read *a, return (ARCHIVE_FATAL); } +static int +search_next_signature(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t skip; + ssize_t bytes; + int64_t skipped = 0; + + for (;;) { + h = __archive_read_ahead(a, 4, &bytes); + if (h == NULL) + return (ARCHIVE_FATAL); + p = h; + q = p + bytes; + + while (p + 4 <= q) { + if (p[0] == 'P' && p[1] == 'K') { + if ((p[2] == '\001' && p[3] == '\002') + || (p[2] == '\003' && p[3] == '\004') + || (p[2] == '\005' && p[3] == '\006') + || (p[2] == '\007' && p[3] == '\010') + || (p[2] == '0' && p[3] == '0')) { + skip = p - (const char *)h; + __archive_read_consume(a, skip); + return (ARCHIVE_OK); + } + } + ++p; + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + skipped += skip; + } +} + static int zip_read_file_header(struct archive_read *a, struct archive_entry *entry, struct zip *zip) @@ -924,6 +968,9 @@ process_extra(const void* extra, struct zip* zip) if (datasize >= 4) zip->gid = archive_le16dec(p + offset + 2); break; + case 0x7875: + /* Info-Zip Unix Extra Field (type 3) "ux". */ + break; default: break; } diff --git a/libarchive/test/test_compat_zip.c b/libarchive/test/test_compat_zip.c index 1c5747ddf..d7253eda7 100644 --- a/libarchive/test/test_compat_zip.c +++ b/libarchive/test/test_compat_zip.c @@ -67,10 +67,43 @@ finish: assertEqualInt(ARCHIVE_OK, archive_read_free(a)); } +/* + * Verify that we skip junk between entries. The compat_zip_2.zip file + * has several bytes of junk between 'file1' and 'file2'. Such + * junk is routinely introduced by some Zip writers when they manipulate + * existing zip archives. + */ +static void +test_compat_zip_2(void) +{ + char name[] = "test_compat_zip_2.zip"; + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_zip(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 10240)); + + /* Read first entry. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("file1", archive_entry_pathname(ae)); + + /* Read first entry. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("file2", archive_entry_pathname(ae)); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + DEFINE_TEST(test_compat_zip) { test_compat_zip_1(); + test_compat_zip_2(); } diff --git a/libarchive/test/test_compat_zip_2.zip.uu b/libarchive/test/test_compat_zip_2.zip.uu new file mode 100644 index 000000000..f8ada3116 --- /dev/null +++ b/libarchive/test/test_compat_zip_2.zip.uu @@ -0,0 +1,8 @@ +begin 644 test_compat_zip_2.zip +M4$L#!`H``````'V59CT````````````````%````9FEL93$M2E5.2RU02P,$ +M"@``````@95F/<>D!,D&````!@````4```!F:6QE,F9I;&4R"E!+`0(>`PH` +M`````'V59CT````````````````%``````````````"D@0````!F:6QE,5!+ +M`0(>`PH``````(&59CW'I`3)!@````8````%``````````````"D@2D```!F +::6QE,E!+!08``````@`"`&8```!2```````` +` +end