*
* 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
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);
* 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;
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;
}
__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) {
}
}
- /* 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. */
/* 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
*/
#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;
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);
}
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