From 11cc5c6d300e5734ba8e274a945bd688aae9b1f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kuba=20Podg=C3=B3rski?= <kuba--@users.noreply.github.com>
Date: Mon, 10 Jan 2022 23:19:36 +0100
Subject: [PATCH] permissions xattr. logic from zip info (#227)

---
 src/miniz.h        | 39 ++++++++++++----------
 src/zip.c          | 81 ++++++++++++++++++++++++++++++++++++++--------
 test/test_append.c |  4 +--
 3 files changed, 90 insertions(+), 34 deletions(-)

diff --git a/src/miniz.h b/src/miniz.h
index 46c93e6..168b176 100644
--- a/src/miniz.h
+++ b/src/miniz.h
@@ -1357,11 +1357,10 @@ MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip,
                                                 mz_uint64 file_start_ofs,
                                                 mz_uint64 archive_size);
 MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2_rpb(mz_zip_archive *pZip,
-                                                const char *pFilename,
-                                                mz_uint flags,
-                                                mz_uint64 file_start_ofs,
-                                                mz_uint64 archive_size);
-
+                                                    const char *pFilename,
+                                                    mz_uint flags,
+                                                    mz_uint64 file_start_ofs,
+                                                    mz_uint64 archive_size);
 
 /* Read an archive from an already opened FILE, beginning at the current file
  * position. */
@@ -1625,9 +1624,8 @@ MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip,
 MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip,
                                                        const char *pFilename,
                                                        mz_uint flags);
-MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2_noreopen(mz_zip_archive *pZip,
-                                                       const char *pFilename,
-                                                       mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2_noreopen(
+    mz_zip_archive *pZip, const char *pFilename, mz_uint flags);
 
 /* Adds the contents of a memory buffer to an archive. These functions record
  * the current local time into the archive. */
@@ -5010,6 +5008,13 @@ static int mz_mkdir(const char *pDirname) {
 #endif /* #ifdef _MSC_VER */
 #endif /* #ifdef MINIZ_NO_STDIO */
 
+#ifndef CHMOD
+// Upon successful completion, a value of 0 is returned.
+// Otherwise, a value of -1 is returned and errno is set to indicate the error.
+// int chmod(const char *path, mode_t mode);
+#define CHMOD(f, m) chmod(f, m)
+#endif
+
 #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
 
 /* Various ZIP archive enums. To completely avoid cross platform compiler
@@ -5975,9 +5980,10 @@ mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename,
   return MZ_TRUE;
 }
 
-mz_bool mz_zip_reader_init_file_v2_rpb(mz_zip_archive *pZip, const char *pFilename,
-                                   mz_uint flags, mz_uint64 file_start_ofs,
-                                   mz_uint64 archive_size) {
+mz_bool mz_zip_reader_init_file_v2_rpb(mz_zip_archive *pZip,
+                                       const char *pFilename, mz_uint flags,
+                                       mz_uint64 file_start_ofs,
+                                       mz_uint64 archive_size) {
   mz_uint64 file_size;
   MZ_FILE *pFile;
 
@@ -8055,8 +8061,8 @@ mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip,
 }
 
 mz_bool mz_zip_writer_init_from_reader_v2_noreopen(mz_zip_archive *pZip,
-                                          const char *pFilename,
-                                          mz_uint flags) {
+                                                   const char *pFilename,
+                                                   mz_uint flags) {
   mz_zip_internal_state *pState;
 
   if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
@@ -8368,8 +8374,7 @@ mz_bool mz_zip_writer_add_mem_ex_v2(
     mz_uint user_extra_data_central_len) {
   mz_uint16 method = 0, dos_time = 0, dos_date = 0;
   mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
-  mz_uint64 local_dir_header_ofs = 0,
-            cur_archive_file_ofs = 0, comp_size = 0;
+  mz_uint64 local_dir_header_ofs = 0, cur_archive_file_ofs = 0, comp_size = 0;
   size_t archive_name_size;
   mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
   tdefl_compressor *pComp = NULL;
@@ -8679,8 +8684,8 @@ mz_bool mz_zip_writer_add_read_buf_callback(
                             : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
   mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;
   mz_uint16 method = 0, dos_time = 0, dos_date = 0;
-  mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = 0,
-                                  uncomp_size = 0, comp_size = 0;
+  mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = 0, uncomp_size = 0,
+                                  comp_size = 0;
   size_t archive_name_size;
   mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
   mz_uint8 *pExtra_data = NULL;
diff --git a/src/zip.c b/src/zip.c
index b5b219b..09b3f89 100644
--- a/src/zip.c
+++ b/src/zip.c
@@ -66,6 +66,14 @@
     }                                                                          \
   } while (0)
 
+#define UNX_IFDIR 0040000  /* Unix directory */
+#define UNX_IFREG 0100000  /* Unix regular file */
+#define UNX_IFSOCK 0140000 /* Unix socket (BSD, not SysV or Amiga) */
+#define UNX_IFLNK 0120000  /* Unix symbolic link (not SysV, Amiga) */
+#define UNX_IFBLK 0060000  /* Unix block special       (not Amiga) */
+#define UNX_IFCHR 0020000  /* Unix character special   (not Amiga) */
+#define UNX_IFIFO 0010000  /* Unix fifo    (BCC, not MSC or Amiga) */
+
 struct zip_entry_t {
   int index;
   char *name;
@@ -375,8 +383,8 @@ static int zip_archive_extract(mz_zip_archive *zip_archive, const char *dir,
       (void)xattr; // unused
 #else
       xattr = (info.m_external_attr >> 16) & 0xFFFF;
-      if (xattr > 0) {
-        if (chmod(path, (mode_t)xattr) < 0) {
+      if (xattr > 0 && xattr <= MZ_UINT16_MAX) {
+        if (CHMOD(path, (mode_t)xattr) < 0) {
           err = ZIP_ENOPERM;
           goto out;
         }
@@ -830,7 +838,8 @@ struct zip_t *zip_open(const char *zipname, int level, char mode) {
       goto cleanup;
     }
     if ((mode == 'a' || mode == 'd')) {
-      if (!mz_zip_writer_init_from_reader_v2_noreopen(&(zip->archive), zipname, 0)) {
+      if (!mz_zip_writer_init_from_reader_v2_noreopen(&(zip->archive), zipname,
+                                                      0)) {
         mz_zip_reader_end(&(zip->archive));
         goto cleanup;
       }
@@ -957,7 +966,7 @@ int zip_entry_open(struct zip_t *zip, const char *entryname) {
 
   // UNIX or APPLE
 #if MZ_PLATFORM == 3 || MZ_PLATFORM == 19
-  // regular file with rw-r--r-- persmissions
+  // regular file with rw-r--r-- permissions
   zip->entry.external_attr = (mz_uint32)(0100644) << 16;
 #else
   zip->entry.external_attr = 0;
@@ -1315,6 +1324,7 @@ int zip_entry_fwrite(struct zip_t *zip, const char *filename) {
   MZ_FILE *stream = NULL;
   mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE];
   struct MZ_FILE_STAT_STRUCT file_stat;
+  mz_uint16 modes;
 
   if (!zip) {
     // zip_t handler is not initialized
@@ -1328,11 +1338,32 @@ int zip_entry_fwrite(struct zip_t *zip, const char *filename) {
     return ZIP_ENOENT;
   }
 
-  if ((file_stat.st_mode & 0200) == 0) {
-    // MS-DOS read-only attribute
-    zip->entry.external_attr |= 0x01;
+#if defined(_WIN32) || defined(__WIN32__)
+  (void)modes; // unused
+#else
+  /* Initialize with permission bits--which are not implementation-optional */
+  modes = file_stat.st_mode &
+          (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX);
+  if (S_ISDIR(file_stat.st_mode))
+    modes |= UNX_IFDIR;
+  if (S_ISREG(file_stat.st_mode))
+    modes |= UNX_IFREG;
+  if (S_ISLNK(file_stat.st_mode))
+    modes |= UNX_IFLNK;
+  if (S_ISBLK(file_stat.st_mode))
+    modes |= UNX_IFBLK;
+  if (S_ISCHR(file_stat.st_mode))
+    modes |= UNX_IFCHR;
+  if (S_ISFIFO(file_stat.st_mode))
+    modes |= UNX_IFIFO;
+  if (S_ISSOCK(file_stat.st_mode))
+    modes |= UNX_IFSOCK;
+  zip->entry.external_attr = (modes << 16) | !(file_stat.st_mode & S_IWRITE);
+  if ((file_stat.st_mode & S_IFMT) == S_IFDIR) {
+    zip->entry.external_attr |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG;
   }
-  zip->entry.external_attr |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16);
+#endif
+
   zip->entry.m_time = file_stat.st_mtime;
 
   if (!(stream = MZ_FOPEN(filename, "rb"))) {
@@ -1440,8 +1471,8 @@ int zip_entry_fread(struct zip_t *zip, const char *filename) {
   }
 
   xattr = (info.m_external_attr >> 16) & 0xFFFF;
-  if (xattr > 0) {
-    if (chmod(filename, (mode_t)xattr) < 0) {
+  if (xattr > 0 && xattr <= MZ_UINT16_MAX) {
+    if (CHMOD(filename, (mode_t)xattr) < 0) {
       return ZIP_ENOPERM;
     }
   }
@@ -1607,6 +1638,7 @@ int zip_create(const char *zipname, const char *filenames[], size_t len) {
   mz_zip_archive zip_archive;
   struct MZ_FILE_STAT_STRUCT file_stat;
   mz_uint32 ext_attributes = 0;
+  mz_uint16 modes;
 
   if (!zipname || strlen(zipname) < 1) {
     // zip_t archive name is empty or NULL
@@ -1641,11 +1673,32 @@ int zip_create(const char *zipname, const char *filenames[], size_t len) {
       break;
     }
 
-    if ((file_stat.st_mode & 0200) == 0) {
-      // MS-DOS read-only attribute
-      ext_attributes |= 0x01;
+#if defined(_WIN32) || defined(__WIN32__)
+    (void)modes; // unused
+#else
+
+    /* Initialize with permission bits--which are not implementation-optional */
+    modes = file_stat.st_mode &
+            (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX);
+    if (S_ISDIR(file_stat.st_mode))
+      modes |= UNX_IFDIR;
+    if (S_ISREG(file_stat.st_mode))
+      modes |= UNX_IFREG;
+    if (S_ISLNK(file_stat.st_mode))
+      modes |= UNX_IFLNK;
+    if (S_ISBLK(file_stat.st_mode))
+      modes |= UNX_IFBLK;
+    if (S_ISCHR(file_stat.st_mode))
+      modes |= UNX_IFCHR;
+    if (S_ISFIFO(file_stat.st_mode))
+      modes |= UNX_IFIFO;
+    if (S_ISSOCK(file_stat.st_mode))
+      modes |= UNX_IFSOCK;
+    ext_attributes = (modes << 16) | !(file_stat.st_mode & S_IWRITE);
+    if ((file_stat.st_mode & S_IFMT) == S_IFDIR) {
+      ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG;
     }
-    ext_attributes |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16);
+#endif
 
     if (!mz_zip_writer_add_file(&zip_archive, zip_basename(name), name, "", 0,
                                 ZIP_DEFAULT_COMPRESSION_LEVEL,
diff --git a/test/test_append.c b/test/test_append.c
index d79c4dc..8450322 100644
--- a/test/test_append.c
+++ b/test/test_append.c
@@ -24,9 +24,7 @@ void test_setup(void) {
   zip_close(zip);
 }
 
-void test_teardown(void) {
-  remove(ZIPNAME);
-}
+void test_teardown(void) { remove(ZIPNAME); }
 
 #define TESTDATA2 "Some test data 2...\0"
 #define CRC32DATA2 2532008468