How do I set permissions (attributes) on a file in a ZIP file using Python's zipfile module?
Asked Answered
L

8

49

When I extract files from a ZIP file created with the Python zipfile module, all the files are not writable, read only etc.

The file is being created and extracted under Linux and Python 2.5.2.

As best I can tell, I need to set the ZipInfo.external_attr property for each file, but this doesn't seem to be documented anywhere I could find, can anyone enlighten me?

Lazuli answered 12/1, 2009 at 6:51 Comment(0)
L
54

This seems to work (thanks Evan, putting it here so the line is in context):

buffer = "path/filename.zip"  # zip filename to write (or file-like object)
name = "folder/data.txt"      # name of file inside zip 
bytes = "blah blah blah"      # contents of file inside zip

zip = zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED)
info = zipfile.ZipInfo(name)
info.external_attr = 0777 << 16L # give full access to included file
zip.writestr(info, bytes)
zip.close()

I'd still like to see something that documents this... An additional resource I found was a note on the Zip file format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT

Lazuli answered 12/1, 2009 at 7:14 Comment(4)
For python 3, you'd write this 0o777 << 16Doublereed
Also: as written, this code will mark the file being written in the archive as last modified in 1980; the ZipInfo constructor takes the last modified date as another constructor.Doublereed
see newer answer for a more correct solution https://mcmap.net/q/144212/-how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-python-39-s-zipfile-moduleOverjoy
It looks the more correct solution is different in that it sets an additional attribute bit?Celestinecelestite
M
30

This link has more information than anything else I've been able to find on the net. Even the zip source doesn't have anything. Copying the relevant section for posterity. This patch isn't really about documenting this format, which just goes to show how pathetic (read non-existent) the current documentation is.

# external_attr is 4 bytes in size. The high order two
# bytes represent UNIX permission and file type bits,
# while the low order two contain MS-DOS FAT file
# attributes, most notably bit 4 marking directories.
if node.isfile:
    zipinfo.compress_type = ZIP_DEFLATED
    zipinfo.external_attr = 0644 << 16L # permissions -r-wr--r--
    data = node.get_content().read()
    properties = node.get_properties()
    if 'svn:special' in properties and \
           data.startswith('link '):
        data = data[5:]
        zipinfo.external_attr |= 0120000 << 16L # symlink file type
        zipinfo.compress_type = ZIP_STORED
    if 'svn:executable' in properties:
        zipinfo.external_attr |= 0755 << 16L # -rwxr-xr-x
    zipfile.writestr(zipinfo, data)
elif node.isdir and path:
    if not zipinfo.filename.endswith('/'):
        zipinfo.filename += '/'
    zipinfo.compress_type = ZIP_STORED
    zipinfo.external_attr = 040755 << 16L # permissions drwxr-xr-x
    zipinfo.external_attr |= 0x10 # MS-DOS directory flag
    zipfile.writestr(zipinfo, '')

Also, this link has the following. Here the low order byte presumably means the rightmost (lowest) byte of the four bytes. So this one is for MS-DOS and can presumably be left as zero otherwise.

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.

Also, the source file unix/unix.c in the sources for InfoZIP's zip program, downloaded from Debian's archives has the following in comments.

  /* lower-middle external-attribute byte (unused until now):
   *   high bit        => (have GMT mod/acc times) >>> NO LONGER USED! <<<
   *   second-high bit => have Unix UID/GID info
   * NOTE: The high bit was NEVER used in any official Info-ZIP release,
   *       but its future use should be avoided (if possible), since it
   *       was used as "GMT mod/acc times local extra field" flags in Zip beta
   *       versions 2.0j up to 2.0v, for about 1.5 years.
   */

So taking all this together, it looks like only the second highest byte is actually used, at least for Unix.

EDIT: I asked about the Unix aspect of this on Unix.SX, in the question "The zip format's external file attribute". Looks like I got a couple of things wrong. Specifically both of the top two bytes are used for Unix.

Maestro answered 9/6, 2011 at 19:0 Comment(2)
Some of the constants in the example would be more legible if using constants from the stat module (stat.S_IFLNK for example). While looking through this, I found also unix.stackexchange.com/questions/14705/…Jupon
@Jupon Technically, there's not a guarantee that S_IFLNK will be equal to 0120000 - as I mentioned, "The Unix values are the same as on traditional unix implementations" and provided an example from one but the exact numeric values are not guaranteed by POSIX (nor is S_IFLNK actually guaranteed to exist as a constant) but 0120000 always means symlink in a zip context due to it being a cross-platform format.Adlib
B
15

Look at this: Set permissions on a compressed file in python

I'm not entirely sure if that's what you want, but it seems to be.

The key line appears to be:

zi.external_attr = 0777 << 16L

It looks like it sets the permissions to 0777 there.

Bujumbura answered 12/1, 2009 at 6:57 Comment(0)
I
12

The earlier answers did not work for me (on OS X 10.12). I found that as well as the executable flags (octal 755), I also need to set the "regular file" flag (octal 100000). I found this mentioned here: https://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute

A complete example:

zipname = "test.zip"
filename = "test-executable"

zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)

f = open(filename, 'r')
bytes = f.read()
f.close()

info = zipfile.ZipInfo(filename)
info.date_time = time.localtime()
info.external_attr = 0100755 << 16L

zip.writestr(info, bytes, zipfile.ZIP_DEFLATED)

zip.close()

A complete example of my specific usecase, creating a zip of a .app so that everything in the folder Contents/MacOS/ is executable: https://gist.github.com/Draknek/3ce889860cea4f59838386a79cc11a85

Ianthe answered 25/1, 2018 at 4:3 Comment(1)
note bytes is reserved in Python, it's a function that returns a bytes object.Lanctot
P
5

You can extend the ZipFile class to change the default file permission:

from zipfile import ZipFile, ZipInfo
import time

class PermissiveZipFile(ZipFile):
    def writestr(self, zinfo_or_arcname, data, compress_type=None):
        if not isinstance(zinfo_or_arcname, ZipInfo):
            zinfo = ZipInfo(filename=zinfo_or_arcname,
                            date_time=time.localtime(time.time())[:6])

            zinfo.compress_type = self.compression
            if zinfo.filename[-1] == '/':
                zinfo.external_attr = 0o40775 << 16   # drwxrwxr-x
                zinfo.external_attr |= 0x10           # MS-DOS directory flag
            else:
                zinfo.external_attr = 0o664 << 16     # ?rw-rw-r--
        else:
            zinfo = zinfo_or_arcname

        super(PermissiveZipFile, self).writestr(zinfo, data, compress_type)

This example changes the default file permission to 664 and keeps 775 for directories.

Related code:

Prototrophic answered 12/12, 2018 at 11:37 Comment(0)
B
3

To set permissions (Unix attributes) on a file in a ZIP file using Python's zipfile module, pass the attributes as bits 16-31 of the external_attr of ZipInfo.

The Python zipfile module accepts the 16-bit "Mode" field (that stores st_mode field from struct stat, containing user/group/other permissions, setuid/setgid and symlink info, etc) of the ASi extra block for Unix in the external_attr bits above mentioned.

You may also import the Python's "stat" module to get the mode constant definitions.

You may also set 3 in create_system to specify the operating system which created the ZIP archive: 3 = Unix; 0 = Windows.

Here is an example:

#!/usr/bin/python

import stat
import zipfile

def create_zip_with_symlink(output_zip_filename, link_source, link_target):
    zipInfo  = zipfile.ZipInfo(link_source)
    zipInfo.create_system = 3 
    unix_st_mode = stat.S_IFLNK | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH
    zipInfo.external_attr = unix_st_mode << 16 
    zipOut = zipfile.ZipFile(output_zip_filename, 'w', compression=zipfile.ZIP_DEFLATED)
    zipOut.writestr(zipInfo, link_target)
    zipOut.close()

create_zip_with_symlink('cpuinfo.zip', 'cpuinfo.txt', '/proc/cpuinfo')
Busywork answered 21/1, 2021 at 19:28 Comment(0)
B
2

Also look at what Python's zipfile module does:

def write(self, filename, arcname=None, compress_type=None):
    ...
    st = os.stat(filename)
    ...
    zinfo = ZipInfo(arcname, date_time)
    zinfo.external_attr = (st[0] & 0xFFFF) << 16L      # Unix attributes
    ...

```

Bushwhack answered 26/10, 2018 at 11:52 Comment(0)
B
0

When you do it like this, does it work alright?

zf = zipfile.ZipFile("something.zip")
for name in zf.namelist():
    f = open(name, 'wb')
    f.write(self.read(name))
    f.close()

If not, I'd suggest throwing in an os.chmod in the for loop with 0777 permissions like this:

zf = zipfile.ZipFile("something.zip")
for name in zf.namelist():
    f = open(name, 'wb')
    f.write(self.read(name))
    f.close()
    os.chmod(name, 0777)
Bujumbura answered 12/1, 2009 at 7:15 Comment(1)
I'm not using Python to extract the zip, the zip is generated by a webserver and extracted using something on the user's machine. In my case the gnome archive manager program.Lazuli

© 2022 - 2024 — McMap. All rights reserved.