From: Tim Kientzle Date: Sun, 3 Apr 2016 04:10:42 +0000 (-0700) Subject: Fix recognition of dirs when only MSDOS file attributes are available. X-Git-Tag: v3.1.901a~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4cadc95812e147d6cfc65595c81dc484beaa5339;p=thirdparty%2Flibarchive.git Fix recognition of dirs when only MSDOS file attributes are available. --- diff --git a/libarchive/archive_read_support_format_zip.c b/libarchive/archive_read_support_format_zip.c index c0b47c860..0f8262c62 100644 --- a/libarchive/archive_read_support_format_zip.c +++ b/libarchive/archive_read_support_format_zip.c @@ -39,7 +39,8 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_zip.c 201102 * * History of this code: The streaming Zip reader was first added to * libarchive in January 2005. Support for seekable input sources was - * added in Nov 2011. + * added in Nov 2011. Zip64 support (including a significant code + * refactoring) was added in 2014. */ #ifdef HAVE_ERRNO_H @@ -419,8 +420,9 @@ process_extra(const char *p, size_t extra_length, struct zip_entry* zip_entry) unsigned short datasize = archive_le16dec(p + offset + 2); offset += 4; - if (offset + datasize > extra_length) + if (offset + datasize > extra_length) { break; + } #ifdef DEBUG fprintf(stderr, "Header id 0x%04x, length %d\n", headerid, datasize); @@ -555,7 +557,7 @@ process_extra(const char *p, size_t extra_length, struct zip_entry* zip_entry) * if bitmap & 1, 2 byte "version made by" * if bitmap & 2, 2 byte "internal file attributes" * if bitmap & 4, 4 byte "external file attributes" - * if bitmap * 7, 2 byte comment length + n byte comment + * if bitmap & 8, 2 byte comment length + n byte comment */ int bitmap, bitmap_last; @@ -604,6 +606,19 @@ process_extra(const char *p, size_t extra_length, struct zip_entry* zip_entry) if (zip_entry->system == 3) { zip_entry->mode = external_attributes >> 16; + } else if (zip_entry->system == 0) { + // Interpret MSDOS directory bit + if (0x10 == (external_attributes & 0x10)) { + zip_entry->mode = AE_IFDIR | 0775; + } else { + zip_entry->mode = AE_IFREG | 0664; + } + if (0x01 == (external_attributes & 0x01)) { + // Read-only bit; strip write permissions + zip_entry->mode &= 0555; + } + } else { + zip_entry->mode = 0; } offset += 4; datasize -= 4; @@ -810,6 +825,16 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, } __archive_read_consume(a, filename_length); + /* Read the extra data. */ + if ((h = __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(h, extra_length, zip_entry); + __archive_read_consume(a, extra_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) { @@ -843,16 +868,31 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, } } - /* Read the extra data. */ - if ((h = __archive_read_ahead(a, extra_length, NULL)) == NULL) { - archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Truncated ZIP file header"); - return (ARCHIVE_FATAL); + /* Make sure directories end in '/' */ + if ((zip_entry->mode & AE_IFMT) == AE_IFDIR) { + wp = archive_entry_pathname_w(entry); + if (wp != NULL) { + len = wcslen(wp); + if (len > 0 && wp[len - 1] != L'/') { + struct archive_wstring s; + archive_string_init(&s); + archive_wstrcat(&s, wp); + archive_wstrappend_wchar(&s, L'/'); + archive_entry_copy_pathname_w(entry, s.s); + } + } else { + cp = archive_entry_pathname(entry); + len = (cp != NULL)?strlen(cp):0; + if (len > 0 && cp[len - 1] != '/') { + struct archive_string s; + archive_string_init(&s); + archive_strcat(&s, cp); + archive_strappend_char(&s, '/'); + archive_entry_set_pathname(entry, s.s); + } + } } - process_extra(h, extra_length, zip_entry); - __archive_read_consume(a, extra_length); - if (zip_entry->flags & LA_FROM_CENTRAL_DIRECTORY) { /* If this came from the central dir, it's size info * is definitive, so ignore the length-at-end flag. */ @@ -2614,9 +2654,21 @@ slurp_central_directory(struct archive_read *a, struct zip *zip) /* If we can't guess the mode, leave it zero here; when we read the local file header we might get more information. */ - zip_entry->mode = 0; if (zip_entry->system == 3) { zip_entry->mode = external_attributes >> 16; + } else if (zip_entry->system == 0) { + // Interpret MSDOS directory bit + if (0x10 == (external_attributes & 0x10)) { + zip_entry->mode = AE_IFDIR | 0775; + } else { + zip_entry->mode = AE_IFREG | 0664; + } + if (0x01 == (external_attributes & 0x01)) { + // Read-only bit; strip write permissions + zip_entry->mode &= 0555; + } + } else { + zip_entry->mode = 0; } /* We're done with the regular data; get the filename and diff --git a/libarchive/test/test_read_format_zip_msdos.c b/libarchive/test/test_read_format_zip_msdos.c index fe287e1ac..5f147d557 100644 --- a/libarchive/test/test_read_format_zip_msdos.c +++ b/libarchive/test/test_read_format_zip_msdos.c @@ -25,11 +25,69 @@ */ #include "test.h" +/* + * Test archive contains the following entries with only MSDOS attributes: + * 'abc' -- zero-length file + * 'def' -- directory without trailing slash and without streaming extension + * 'def/foo' -- file in def + * 'ghi/' -- directory with trailing slash and without streaming extension + * 'jkl' -- directory without trailing slash and with streaming extension + * 'mno/' -- directory with trailing slash and streaming extension + * + * Seeking reader should identify all of these correctly using the + * central directory information. + * Streaming reader should correctly identify everything except 'def'; + * since the standard Zip local file header does not include any file + * type information, it will be mis-identified as a zero-length file. + */ + +static void verify(struct archive *a, int streaming) { + struct archive_entry *ae; + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("abc", archive_entry_pathname(ae)); + assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + if (streaming) { + /* Streaming reader has no basis for making this a dir */ + assertEqualString("def", archive_entry_pathname(ae)); + assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae)); + } else { + /* Since 'def' is a dir, '/' should be added */ + assertEqualString("def/", archive_entry_pathname(ae)); + assertEqualInt(AE_IFDIR | 0775, archive_entry_mode(ae)); + } + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("def/foo", archive_entry_pathname(ae)); + assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae)); + + /* Streaming reader can tell this is a dir because it ends in '/' */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("ghi/", archive_entry_pathname(ae)); + assertEqualInt(AE_IFDIR | 0775, archive_entry_mode(ae)); + + /* Streaming reader can tell this is a dir because it has xl + * extension */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + /* '/' gets added because this is a dir */ + assertEqualString("jkl/", archive_entry_pathname(ae)); + assertEqualInt(AE_IFDIR | 0775, archive_entry_mode(ae)); + + /* Streaming reader can tell this is a dir because it ends in + * '/' and has xl extension */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("mno/", archive_entry_pathname(ae)); + assertEqualInt(AE_IFDIR | 0775, archive_entry_mode(ae)); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); +} + DEFINE_TEST(test_read_format_zip_msdos) { const char *refname = "test_read_format_zip_msdos.zip"; struct archive *a; - struct archive_entry *ae; char *p; size_t s; @@ -40,44 +98,19 @@ DEFINE_TEST(test_read_format_zip_msdos) assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, refname, 17)); - - /* 'ab' is marked as a directory in the central dir - * with MSDOS attribute info */ - assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualString("ab/", archive_entry_pathname(ae)); - assertEqualInt(AE_IFDIR | 0775, archive_entry_mode(ae)); - - assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualString("a/gru\xCC\x88n.png", archive_entry_pathname(ae)); - assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae)); - - assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + verify(a, 0); assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); assertEqualInt(ARCHIVE_OK, archive_read_free(a)); - + /* Verify with streaming reader. */ p = slurpfile(&s, refname); assert((a = archive_read_new()) != NULL); assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); assertEqualIntA(a, ARCHIVE_OK, read_open_memory(a, p, s, 31)); - - /* - * 'ab' is not marked as a directory in the local file header - * (local file headers lack external attribute info), so the - * streaming reader can only determine if something is a directory - * by whether the name ends in '/'. - */ - assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualString("ab", archive_entry_pathname(ae)); - assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae)); - - assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualString("a/gru\xCC\x88n.png", archive_entry_pathname(ae)); - assertEqualInt(AE_IFREG | 0664, archive_entry_mode(ae)); - - assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + verify(a, 1); assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + free(p); } diff --git a/libarchive/test/test_read_format_zip_msdos.zip.uu b/libarchive/test/test_read_format_zip_msdos.zip.uu index 1691ca97b..4503d095b 100644 --- a/libarchive/test/test_read_format_zip_msdos.zip.uu +++ b/libarchive/test/test_read_format_zip_msdos.zip.uu @@ -1,9 +1,23 @@ begin 644 test_read_format_zip_msdos.zip -M4$L#!!0``````&9A3D,````````````````"````86)02P,$"@``````M)QO -M/-&'5C4&````!@````P```!A+V=R=`L``03U`0``!!0```!02P,$"@``````[Y2" +M2`````````````````<`'`!D968O9F]O550)``-2=`!7;'0`5W5X"P`!!/4! +M```$%````%!+`P0*```````6DH)(````````````````!``<`&=H:2]55`D` +M`_QN`%?\;@!7=7@+``$$]0$```04````4$L#!`H``````!65@D@````````` +M```````#`"<`:FML550)``.9=`!7F70`5W5X"P`!!/4!```$%````'AL!P`% +M'@`0````4$L#!`H``````!:2@D@````````````````$`"4`;6YO+U54"0`# +M_&X`5_QN`%=U>`L``03U`0``!!0```!X;`4`!!````!02P$"'@`*```````0 +MDH)(`````````````````P`8````````````````````86)C550%``/P;@!7 +M=7@+``$$]0$```04````4$L!`AX`"@``````\)2"2`````````````````,` +M&``````````0````/0```&1E9E54!0`#4W0`5W5X"P`!!/4!```$%````%!+ +M`0(>``H``````.^4@D@````````````````'`!@``````````````'H```!D +M968O9F]O550%``-2=`!7=7@+``$$]0$```04````4$L!`AX`"@``````%I*" +M2`````````````````0`&``````````0````NP```&=H:2]55`4``_QN`%=U +M>`L``03U`0``!!0```!02P$"'@`*```````5E8)(`````````````````P`8 +M`````````!````#Y````:FML550%``.9=`!7=7@+``$$]0$```04````4$L! +M`AX`"@``````%I*"2`````````````````0`&``````````0````00$``&UN +M;R]55`4``_QN`%=U>`L``03U`0``!!0```!02P4&``````8`!@"\`0``B`$` +#```` ` end