2

When I make ZIP archive using PHP ZipArchive object, all files inside the archive have permissions set to 666, although original files have permissions set to 644.

My script makes the zip archives properly, just the permissions are messed up.

////// Make Template archive object
$templateArchive = new ZipArchive();
$templateArchive->open(PATH_TO_TEMPLATES.'_files/'.$templateName.'/_pack/'.$templateName.'.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);

$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($templateDir."/templates/".$template_archive_name),
    RecursiveIteratorIterator::LEAVES_ONLY
);

foreach ($files as $name => $file)
{

    // Skip directories (they would be added automatically)
    if (!$file->isDir())
    {
        // Get real and relative path for current file
        $filePath = $file->getRealPath();

        // relative path is full path, reduced with length of templateDir string and 11 more chars for /templates/
        $relativePath = substr($filePath, strlen($templateDir) + 11);

        // Add current file to archive
        $templateArchive->addFile($filePath, $relativePath);
    }
}

// Template Zip archive will be created only after closing object
$templateArchive->close();

P.S. I am working of MAMP on Mac. I just found out that issue is only when PHP 5.6.10 version is selected. When I select 5.5.26, the permissions of files are correct.

Milos Stankovic
  • 471
  • 5
  • 7
  • Related: [Maintain file and folder permissions inside archives](http://stackoverflow.com/questions/10735297/maintain-file-and-folder-permissions-inside-archives#10739353). – Álvaro González Mar 29 '16 at 14:46

1 Answers1

2

You can preserve the Unix permissions in the files you archive by adding the following code after your ZipArchive::addFile() statement:

$templateArchive->setExternalAttributesName($relativePath,
                                            ZipArchive::OPSYS_UNIX,
                                            fileperms($filePath) << 16);

This updates the external attributes of the entry at $filePath with the actual file's permissions.

Unix permissions can be stored in ZIP files in each entry's external attribute, but you have to shift 16 bits to store the permissions in the right place in the external attribute, which is why << 16 is applied to the output of fileperms($filePath).

The documentation of ZipArchive::setExternalAttributesName() also has an example that features Unix permission setting: https://secure.php.net/manual/en/ziparchive.setexternalattributesname.php

As for the permissions of the directories, you'll need to add the directories with ZipArchive::addEmptyDir() (because ZipArchive::addFile() on a directory will result in an error) and then apply the permissions with ZipArchive::setExternalAttributesName() the same way you did for the regular files.

Due to the way that RecursiveDirectoryIterator works, the current directory name will end with /., and due to the way that ZipArchive::addEmptyDir() works, stored directories will end with /. To apply ZipArchive::setExternalAttributesName(), you have to provide the exact entry name, so I suggest lopping off the trailing dot for both methods.

Here is your code edited to support the preservation of permissions of files and directories:

////// Make Template archive object
$templateArchive = new ZipArchive();
$templateArchive->open(PATH_TO_TEMPLATES.'_files/'.$templateName.'/_pack/'.$templateName.'.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);

$files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($templateDir."/templates/".$template_archive_name),
        RecursiveIteratorIterator::LEAVES_ONLY
);      

foreach ($files as $name => $file)
{               

        // Get real and relative path for current file
        $filePath = $file->getRealPath();

        // relative path is full path, reduced with length of templateDir string and 11 more chars for /templates/
        $relativePath = substr($filePath, strlen($templateDir) + 11);

        // Add regular files
        if (!$file->isDir())
        {
                // Add current file to archive
                $templateArchive->addFile($filePath, $relativePath);
        }
        elseif (substr($relativePath, -2) === "/.")
        {
                // Remove the dot representing the current directory
                $relativePath = substr($relativePath, 0, -1);
                // Add current directory to archive
                $templateArchive->addEmptyDir($relativePath);
        }
        else
        {
                continue;
        }

        $templateArchive->setExternalAttributesName($relativePath,
                ZipArchive::OPSYS_UNIX,
                fileperms($filePath) << 16);
}

// Template Zip archive will be created only after closing object
$templateArchive->close();
Deltik
  • 1,129
  • 7
  • 32
  • Thank you for your suggestion! I added this after addFile statement, but, after unzipping on Unix host, files are still CHMOD 666. I am running the PHP script on Mac (MAMP). I am not sure if it makes any difference. – Milos Stankovic Jan 02 '19 at 18:33
  • 1
    @MilosStankovic: When you check the resulting ZIP file, are the permissions correct? You can see the permissions by running the `zipinfo` command with the ZIP file as the first argument. If the permissions are correct, that means the unzipping process is ignoring the permissions. – Deltik Jan 02 '19 at 19:11
  • When I run this command I get the full list of archive files and information that each file has permissions set to 666, in example: -rw-rw-rw- 6.3 unx 31 b- defX 14-Oct-01 15:52 media/media/js/index.html Originally, the files permissions are 644 and when I make the archives using PHP 5.6 or older, the permissions are unchanged. – Milos Stankovic Jan 04 '19 at 14:32
  • 1
    @MilosStankovic: The next thing to check would be if `fileperms($filePath)` is actually returning the correct permissions. You should get `33188` as an integer (`100644` in octal) if your input file has 0644 permissions. – Deltik Jan 04 '19 at 15:46
  • I added line: echo substr(sprintf('%o', fileperms($filePath)), -4); after: $templateArchive->addFile($filePath, $relativePath); and got correct permissions, 0644 for all files and 0755 for all directories that are included in ZIP archive. But still, when I upload that ZIP to the Unix host and unzip it, all files have permissions 0666, and all folders 0755. – Milos Stankovic Jan 06 '19 at 09:43
  • @MilosStankovic: I made a mistake in my answer. The first argument to `ZipArchive::setExternalAttributesName()` should be your `$relativePath` variable, not the `$filePath` variable. The external attributes are applied to the so-called "localname" that you have, not the full path on the file system. I have edited the answer to correct this error. – Deltik Jan 06 '19 at 10:15
  • Great progress! Now, both the files and folders have CHMOD set to 755. I need folders to be 755 (they are 755 even with original code) and files to be 644. I guess 16 should be changed to something else? – Milos Stankovic Jan 08 '19 at 09:26
  • @MilosStankovic: I fiddled around with your code and found a way to preserve both file and folder permissions. The updated answer reflects my findings. The `<< 16` bit shift is still correct. – Deltik Jan 08 '19 at 12:04
  • No changes. All files and folders are still 755. I changed value of << 16 and the closest to what I need is when I put 18. Then I get 664 for files and 755 for folders. I added all other values as a comment in your code. Please check. – Milos Stankovic Jan 10 '19 at 10:21
  • ([Link to OP's bit shift fiddling](https://stackoverflow.com/review/suggested-edits/21894348)) | @MilosStankovic: [I cannot reproduce your issue.](https://pastebin.com/LWbXx55d) I even installed PHP 5.6.10, the version you said you had in the original question. – Deltik Jan 10 '19 at 12:35
  • I wanted to thank you for this answer. We have been running (for years) where we had to alter and zip up small MacOS apps. This worked just fine, they executed. However an upgrade to php7 broke it. ZipArchive was not preserving the executable bit. This answer solved the problem exactly, and it now all works again, under php7. – IncredibleHat Jan 24 '20 at 22:26