Zipfile in Python file permission
Asked Answered
A

2

6

i used zipfile lib to extract file from zip and now after unzip the directory i found the permission of my file has been corrupted ,

import zipfile
fh = open('sample.zip', 'rb')
z = zipfile.ZipFile(fh)
print z.namelist()
for name in z.namelist():
    z.extract(name, '/tmp/')
fh.close()

but when i use linux unzip tools this issue don't happen i try to use

os.system('unzip sample.zip')

but i still want to do this with zipfile

Arneson answered 19/2, 2017 at 11:3 Comment(2)
This code worked proper for me. Can i have the source zip file ?Salomesalomi
make zip file with executable file inside . for some reason i can't upload the source zip file.Arneson
B
12

The related Python issues provide some insight as to why the issue existed in the first place: https://bugs.python.org/issue18262 and https://bugs.python.org/issue15795

Additionally the original Zip spec can be found here: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

The important sections are:

4.4.2 version made by (2 bytes)

        4.4.2.1 The upper byte indicates the compatibility of the file
        attribute information.  If the external file attributes 
        are compatible with MS-DOS and can be read by PKZIP for 
        DOS version 2.04g then this value will be zero.  If these 
        attributes are not compatible, then this value will 
        identify the host system on which the attributes are 
        compatible.  Software can use this information to determine
        the line record format for text files etc.  

        4.4.2.2 The current mappings are:

         0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
         1 - Amiga                     2 - OpenVMS
         3 - UNIX                      4 - VM/CMS
         5 - Atari ST                  6 - OS/2 H.P.F.S.
         7 - Macintosh                 8 - Z-System
         9 - CP/M                     10 - Windows NTFS
        11 - MVS (OS/390 - Z/OS)      12 - VSE
        13 - Acorn Risc               14 - VFAT
        15 - alternate MVS            16 - BeOS
        17 - Tandem                   18 - OS/400
        19 - OS X (Darwin)            20 thru 255 - unused
...
4.4.15 external file attributes: (4 bytes)

       The mapping of the external attributes is
       host-system dependent (see 'version made by').  For
       MS-DOS, the low order byte is the MS-DOS directory
       attribute byte.  If input came from standard input, this
       field is set to zero.

That means that the external file attributes are system specific. Interpreting the external file attributes for a different system could potentially make it worse. If we only care about UNIX, we could check the ZipInfo.created_system and compare it with 3 (for UNIX). Unfortunately the spec doesn't help us much further in how to interpret the external attributes.

There is something in this Wiki http://forensicswiki.org/wiki/Zip#External_file_attributes

The external attributes UNIX (3) is 4 bytes of size and consists of:

╔═════════╦══════════╦════════╦═══════════════════════════════════════════════════════╗
║ Offset  ║  Size    ║ Value  ║                      Description                      ║
╠═════════╬══════════╬════════╬═══════════════════════════════════════════════════════╣
║      0  ║ 1        ║        ║ FAT (MS-DOS) file attributes.                         ║
║      1  ║ 1        ║        ║ Unknown                                               ║
║      2  ║ 16 bits  ║        ║ The UNIX mode (or permission).                        ║
║         ║          ║        ║ The value seems to be similar to stat.st_mode value.  ║
╚═════════╩══════════╩════════╩═══════════════════════════════════════════════════════╝

While that is rather observational, it seems to be the consensus.

Putting this together:

from zipfile import ZipFile

ZIP_UNIX_SYSTEM = 3

def extract_all_with_permission(zf, target_dir):
  for info in zf.infolist():
    extracted_path = zf.extract(info, target_dir)

    if info.create_system == ZIP_UNIX_SYSTEM:
      unix_attributes = info.external_attr >> 16
      if unix_attributes:
        os.chmod(extracted_path, unix_attributes)

with ZipFile('sample.zip', 'r') as zf:
  extract_all_with_permission(zf, '/tmp')

There may be a question coming up why we want to preserve the permissions in the first place. Some may dare to say that we only want to keep the executable flag. A slightly safer option in that case could be to only restore the executable flag, for files only.

from zipfile import ZipFile
from stat import S_IXUSR

ZIP_UNIX_SYSTEM = 3

def extract_all_with_executable_permission(zf, target_dir):
  for info in zf.infolist():
    extracted_path = zf.extract(info, target_dir)

    if info.create_system == ZIP_UNIX_SYSTEM and os.path.isfile(extracted_path):
      unix_attributes = info.external_attr >> 16
      if unix_attributes & S_IXUSR:
        os.chmod(extracted_path, os.stat(extracted_path).st_mode | S_IXUSR)

with ZipFile('sample.zip', 'r') as zf:
  extract_all_with_executable_permission(zf, '/tmp')
Bartholomeo answered 19/10, 2017 at 19:16 Comment(2)
You have linked the same issue (bugs.python.org/issue18262) twice. That aside, great answer!Athirst
@FabioTurati thank you for pointing that out. That's fixed now.Bartholomeo
Z
1
import zipfile
import os

unZipFile = zipfile.ZipFile("sample.zip", "r")

tmp_dir = "/tmp"
try:
    for info in unZipFile.infolist():
        real_path = unZipFile.extract(info, tmp_dir)

        # permission
        unix_attributes = info.external_attr >> 16
        target = os.path.join(tmp_dir, info.filename)
        if unix_attributes:
            os.chmod(target, unix_attributes)

        if not real_path:
            print "Extract failed: " + info.filename
finally:
    unZipFile.close()
Zymo answered 31/5, 2017 at 7:17 Comment(1)
When answering a question, please provide explanation associated with your code. Some people might not understand your code or don't see how it answers the question. See how to write a good answerPanter

© 2022 - 2024 — McMap. All rights reserved.