]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
7zip reader: translate windows permissions to unix permissions (#1943)
authorMostyn Bramley-Moore <mostyn@antipode.se>
Mon, 21 Aug 2023 11:07:11 +0000 (13:07 +0200)
committerGitHub <noreply@github.com>
Mon, 21 Aug 2023 11:07:11 +0000 (13:07 +0200)
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.

Makefile.am
libarchive/archive_read_support_format_7zip.c
libarchive/archive_write_set_format_7zip.c
libarchive/test/test_read_format_7zip.c
libarchive/test/test_read_format_7zip_packinfo_digests.c
libarchive/test/test_read_format_7zip_win_attrib.7z.uu [new file with mode: 0644]

index 83bbeee0b7e9cdd9ebb8c37c047d245936427502..a79afb5cc8c00df1f412a9d29a18ec8a77bc5e27 100644 (file)
@@ -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 \
index b171bea01a2fc24a7fa3f0362bdd67868134679a..97ec1f4364a6ea2605e89a85d7a9cee82ecfe2e4 100644 (file)
@@ -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) {
index 1e40601c4e28d757aee476882f86ed604bc94cfa..bca48ba9b40556f70926bc7c0c848a41e59a4c29 100644 (file)
@@ -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)
index 1eca3936e5b46fbe389fa5995d2339610d483390..1249963ec2f915090d1ecfdeb611d7c53f7e097c 100644 (file)
@@ -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));
 }
index 7f105d1f2806e7c3a288fd6abe44cb96f2ec98af..b8e8d9efa7f66f9f96a8d30fa8628e3ef8fdc9e5 100644 (file)
@@ -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 (file)
index 0000000..dc7495b
--- /dev/null
@@ -0,0 +1,10 @@
+begin 644 test_read_format_7zip_win_attrib.7z
+M-WJ\KR<<``0:MZ25^0`````````B`````````,/QN>$!`!IA<F-H:79E:&ED
+M9&5N<F5A9&]N;'ES>7-T96T```"!,P>N#\_\\&P/Z^J<OS8]_GH-_C9=&O:;
+MN?AF5PM%@/8R"MH"PK!D-+92R@HB57B\_G7B9D`HLH<G4Z@!).&WAAA(R4U?
+M%0%=EB_VM=2[WZJHK$/PHCG(E(Y#O[W'JQD2%>%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