]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Fix recognition of dirs when only MSDOS file attributes are available.
authorTim Kientzle <kientzle@acm.org>
Sun, 3 Apr 2016 04:10:42 +0000 (21:10 -0700)
committerTim Kientzle <kientzle@acm.org>
Sun, 3 Apr 2016 04:10:42 +0000 (21:10 -0700)
libarchive/archive_read_support_format_zip.c
libarchive/test/test_read_format_zip_msdos.c
libarchive/test/test_read_format_zip_msdos.zip.uu

index c0b47c86010ea0cf26fd7d5900abfbe5122af43c..0f8262c625621a8fd7dbba6354940741c2e22edc 100644 (file)
@@ -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
index fe287e1ac3845e80c4fcc1e9fdf1efe298bd4e66..5f147d5577845c66d57db345140814c77c199c56 100644 (file)
  */
 #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);
 }
index 1691ca97b9cc1084d32ae842820369343acaca2a..4503d095ba7f884e7384cab127f13130a17bd199 100644 (file)
@@ -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=<R(;BYP;F=03D=%3D102P$"/P`4````
-M``!F84Y#`````````````````@`D`````````!``````````86(*`"``````
-M``$`&`"N2J&TQ<C.`:Y*H;3%R,X!C?R@M,7(S@%02P$"/P`*``````"TG&\\
-MT8=6-08````&````#````````````(`````@````82]G<G7,B&XN<&YG4$L%
-3!@`````"``(`C@```%``````````
+M4$L#!`H``````!"2@D@````````````````#`!P`86)C550)``/P;@!7;'0`
+M5W5X"P`!!/4!```$%````%!+`P0*``````#PE()(`````````````````P`<
+M`&1E9E54"0`#4W0`5U-T`%=U>`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