From: Mostyn Bramley-Moore Date: Mon, 21 Aug 2023 11:07:11 +0000 (+0200) Subject: 7zip reader: translate windows permissions to unix permissions (#1943) X-Git-Tag: v3.7.2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a7ea541f74e1229eb0d75319dd12afe15368c6b5;p=thirdparty%2Flibarchive.git 7zip reader: translate windows permissions to unix permissions (#1943) 7z archives created on windows 7zip can lack unix permission info. In this case, we need to translate the windows permissions into reasonable unix equivalents. --- diff --git a/Makefile.am b/Makefile.am index 83bbeee0b..a79afb5cc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -790,6 +790,7 @@ libarchive_test_EXTRA_DIST=\ libarchive/test/test_read_format_7zip_ppmd.7z.uu \ libarchive/test/test_read_format_7zip_solid_zstd.7z.uu \ libarchive/test/test_read_format_7zip_symbolic_name.7z.uu \ + libarchive/test/test_read_format_7zip_win_attrib.7z.uu \ libarchive/test/test_read_format_7zip_zstd_arm.7z.uu \ libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu \ libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu \ diff --git a/libarchive/archive_read_support_format_7zip.c b/libarchive/archive_read_support_format_7zip.c index b171bea01..97ec1f436 100644 --- a/libarchive/archive_read_support_format_7zip.c +++ b/libarchive/archive_read_support_format_7zip.c @@ -116,6 +116,30 @@ __FBSDID("$FreeBSD$"); #define kEncodedHeader 0x17 #define kDummy 0x19 +// Check that some windows file attribute constants are defined. +// Reference: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +#ifndef FILE_ATTRIBUTE_READONLY +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#endif + +#ifndef FILE_ATTRIBUTE_HIDDEN +#define FILE_ATTRIBUTE_HIDDEN 0x00000002 +#endif + +#ifndef FILE_ATTRIBUTE_SYSTEM +#define FILE_ATTRIBUTE_SYSTEM 0x00000004 +#endif + +#ifndef FILE_ATTRIBUTE_DIRECTORY +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#endif + +// This value is defined in 7zip with the comment "trick for Unix". +// +// 7z archives created on unix have this bit set in the high 16 bits of +// the attr field along with the unix permissions. +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 + struct _7z_digests { unsigned char *defineds; uint32_t *digests; @@ -739,6 +763,37 @@ archive_read_format_7zip_read_header(struct archive_read *a, archive_entry_set_size(entry, 0); } + // These attributes are supported by the windows implementation of archive_write_disk. + const int supported_attrs = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM; + + if (zip_entry->attr & supported_attrs) { + char *fflags_text, *ptr; + /* allocate for "rdonly,hidden,system," */ + fflags_text = malloc(22 * sizeof(char)); + if (fflags_text != NULL) { + ptr = fflags_text; + if (zip_entry->attr & FILE_ATTRIBUTE_READONLY) { + strcpy(ptr, "rdonly,"); + ptr = ptr + 7; + } + if (zip_entry->attr & FILE_ATTRIBUTE_HIDDEN) { + strcpy(ptr, "hidden,"); + ptr = ptr + 7; + } + if (zip_entry->attr & FILE_ATTRIBUTE_SYSTEM) { + strcpy(ptr, "system,"); + ptr = ptr + 7; + } + if (ptr > fflags_text) { + /* Delete trailing comma */ + *(ptr - 1) = '\0'; + archive_entry_copy_fflags_text(entry, + fflags_text); + } + free(fflags_text); + } + } + /* If there's no body, force read_data() to return EOF immediately. */ if (zip->entry_bytes_remaining < 1) zip->end_of_entry = 1; @@ -2666,6 +2721,28 @@ read_Header(struct archive_read *a, struct _7z_header_info *h, entries[i].flg |= HAS_STREAM; /* The high 16 bits of attributes is a posix file mode. */ entries[i].mode = entries[i].attr >> 16; + + if (!(entries[i].attr & FILE_ATTRIBUTE_UNIX_EXTENSION)) { + // Only windows permissions specified for this entry. Translate to + // reasonable corresponding unix permissions. + + if (entries[i].attr & FILE_ATTRIBUTE_DIRECTORY) { + if (entries[i].attr & FILE_ATTRIBUTE_READONLY) { + // Read-only directory. + entries[i].mode = AE_IFDIR | 0555; + } else { + // Read-write directory. + entries[i].mode = AE_IFDIR | 0755; + } + } else if (entries[i].attr & FILE_ATTRIBUTE_READONLY) { + // Readonly file. + entries[i].mode = AE_IFREG | 0444; + } else { + // Assume read-write file. + entries[i].mode = AE_IFREG | 0644; + } + } + if (entries[i].flg & HAS_STREAM) { if ((size_t)sindex >= si->ss.unpack_streams) return (-1); @@ -2706,7 +2783,7 @@ read_Header(struct archive_read *a, struct _7z_header_info *h, } entries[i].ssIndex = -1; } - if (entries[i].attr & 0x01) + if (entries[i].attr & FILE_ATTRIBUTE_READONLY) entries[i].mode &= ~0222;/* Read only. */ if ((entries[i].flg & HAS_STREAM) == 0 && indexInFolder == 0) { diff --git a/libarchive/archive_write_set_format_7zip.c b/libarchive/archive_write_set_format_7zip.c index 1e40601c4..bca48ba9b 100644 --- a/libarchive/archive_write_set_format_7zip.c +++ b/libarchive/archive_write_set_format_7zip.c @@ -91,6 +91,26 @@ __FBSDID("$FreeBSD$"); #define kAttributes 0x15 #define kEncodedHeader 0x17 +// Check that some windows file attribute constants are defined. +// Reference: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +#ifndef FILE_ATTRIBUTE_READONLY +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#endif + +#ifndef FILE_ATTRIBUTE_DIRECTORY +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#endif + +#ifndef FILE_ATTRIBUTE_ARCHIVE +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 +#endif + +// This value is defined in 7zip with the comment "trick for Unix". +// +// 7z archives created on unix have this bit set in the high 16 bits of +// the attr field along with the unix permissions. +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 + enum la_zaction { ARCHIVE_Z_FINISH, ARCHIVE_Z_RUN @@ -1424,14 +1444,19 @@ make_header(struct archive_write *a, uint64_t offset, uint64_t pack_size, * High 16bits is unix mode. * Low 16bits is Windows attributes. */ - uint32_t encattr, attr; + uint32_t encattr, attr = 0; + if (file->dir) - attr = 0x8010; + attr |= FILE_ATTRIBUTE_DIRECTORY; else - attr = 0x8020; + attr |= FILE_ATTRIBUTE_ARCHIVE; + if ((file->mode & 0222) == 0) - attr |= 1;/* Read Only. */ + attr |= FILE_ATTRIBUTE_READONLY; + + attr |= FILE_ATTRIBUTE_UNIX_EXTENSION; attr |= ((uint32_t)file->mode) << 16; + archive_le32enc(&encattr, attr); r = (int)compress_out(a, &encattr, 4, ARCHIVE_Z_RUN); if (r < 0) diff --git a/libarchive/test/test_read_format_7zip.c b/libarchive/test/test_read_format_7zip.c index 1eca3936e..1249963ec 100644 --- a/libarchive/test/test_read_format_7zip.c +++ b/libarchive/test/test_read_format_7zip.c @@ -61,7 +61,7 @@ test_copy(int use_open_fd) /* Verify regular file1. */ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualInt((AE_IFREG | 0666), archive_entry_mode(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); assertEqualString("file1", archive_entry_pathname(ae)); assertEqualInt(86401, archive_entry_mtime(ae)); assertEqualInt(60, archive_entry_size(ae)); @@ -765,7 +765,7 @@ test_ppmd(void) /* Verify regular file1. */ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualInt((AE_IFREG | 0666), archive_entry_mode(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); assertEqualString("ppmd_test.txt", archive_entry_pathname(ae)); assertEqualInt(1322464589, archive_entry_mtime(ae)); assertEqualInt(102400, archive_entry_size(ae)); @@ -1180,5 +1180,81 @@ DEFINE_TEST(test_read_format_7zip_deflate_arm64) test_arm64_filter("test_read_format_7zip_deflate_arm64.7z"); } + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + +DEFINE_TEST(test_read_format_7zip_win_attrib) +{ + struct archive *a; + + assert((a = archive_read_new()) != NULL); + + if (ARCHIVE_OK != archive_read_support_filter_lzma(a)) { + skipping( + "7zip:lzma decoding is not supported on this platform"); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + return; + } + + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + + // This archive has four files and four directories: + // * hidden directory + // * readonly directory + // * regular directory + // * system directory + // * regular "archive" file + // * hidden file + // * readonly file + // * system file + const char *refname = "test_read_format_7zip_win_attrib.7z"; + extract_reference_file(refname); + + assertEqualIntA(a, ARCHIVE_OK, + archive_read_open_filename(a, refname, 10240)); + + struct archive_entry *ae; + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("hidden_dir/", archive_entry_pathname(ae)); + assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualString("hidden", archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("readonly_dir/", archive_entry_pathname(ae)); + assertEqualInt((AE_IFDIR | 0555), archive_entry_mode(ae)); + assertEqualString("rdonly", archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("regular_dir/", archive_entry_pathname(ae)); + assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualString(NULL, archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("system_dir/", archive_entry_pathname(ae)); + assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualString("system", archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("archive_file.txt", archive_entry_pathname(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString(NULL, archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("hidden_file.txt", archive_entry_pathname(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("hidden", archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("readonly_file.txt", archive_entry_pathname(ae)); + assertEqualInt((AE_IFREG | 0444), archive_entry_mode(ae)); + assertEqualString("rdonly", archive_entry_fflags_text(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("system_file.txt", archive_entry_pathname(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("system", archive_entry_fflags_text(ae)); + + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); } diff --git a/libarchive/test/test_read_format_7zip_packinfo_digests.c b/libarchive/test/test_read_format_7zip_packinfo_digests.c index 7f105d1f2..b8e8d9efa 100644 --- a/libarchive/test/test_read_format_7zip_packinfo_digests.c +++ b/libarchive/test/test_read_format_7zip_packinfo_digests.c @@ -49,7 +49,7 @@ DEFINE_TEST(test_read_format_7zip_packinfo_digests) /* Verify regular file1. */ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualInt((AE_IFREG | 0666), archive_entry_mode(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); assertEqualString("a.txt", archive_entry_pathname(ae)); assertEqualInt(1576808819, archive_entry_mtime(ae)); assertEqualInt(4, archive_entry_size(ae)); @@ -61,7 +61,7 @@ DEFINE_TEST(test_read_format_7zip_packinfo_digests) /* Verify regular file2. */ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); - assertEqualInt((AE_IFREG | 0666), archive_entry_mode(ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); assertEqualString("b.txt", archive_entry_pathname(ae)); assertEqualInt(1576808819, archive_entry_mtime(ae)); assertEqualInt(4, archive_entry_size(ae)); diff --git a/libarchive/test/test_read_format_7zip_win_attrib.7z.uu b/libarchive/test/test_read_format_7zip_win_attrib.7z.uu new file mode 100644 index 000000000..dc7495bcb --- /dev/null +++ b/libarchive/test/test_read_format_7zip_win_attrib.7z.uu @@ -0,0 +1,10 @@ +begin 644 test_read_format_7zip_win_attrib.7z +M-WJ\KR<<``0:MZ25^0`````````B`````````,/QN>$!`!IA7-T96T```"!,P>N#\_\\&P/Z^J%U\S6&7C"[<$<2GL2\B,** +MW`0[F2;R)EO2:N)4RB6G.3+R6%97BQ]K"X),-'`E&./R#`2A\R&D1#U8FUC& +MANR65$7S:?$CR7DS8<)L'MR%.5Y)T-;62DU\S8W?$4=U2972R-0]5J81R3E/ +M\K&`%O\H:/`E4``7!A\!"8#:``<+`0`!(P,!`05=`!````R!C@H!`M-R5@`` +` +end