Skip to content

Zipfile perserve permissions #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ Changelog

In Development
==============
* Preserving file permissions when using zip files.
* Update tox.ini to not use a specific pypi index server. Removed support
for Python 2.6.
* Allow for stripping/removing comments (needed by certain tools which
bundle python-archive)
* Use relative import to allow module relocation.
* Moved tests outside of the archive package directory.


Expand Down
27 changes: 26 additions & 1 deletion archive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@
import tarfile
import zipfile

from archive.compat import IS_PY2, is_string
from .compat import IS_PY2, is_string


class ArchiveException(Exception):
"""Base exception class for all archive errors."""
pass


class UnrecognizedArchiveFormat(ArchiveException):
"""Error raised when passed file is not a recognized archive format."""
pass


class UnsafeArchive(ArchiveException):
"""
Error raised when passed file contains paths that would be extracted
outside of the target directory.
"""
pass


def extract(path, to_path='', ext='', **kwargs):
Expand Down Expand Up @@ -157,6 +160,28 @@ def list(self, *args, **kwargs):
def filenames(self):
return self._archive.namelist()

def _extract_file(self, info, to_path):
out_path = self._archive.extract(info.filename, path=to_path)

permissions = info.external_attr >> 16

# If no specific file permissions are specified we use the
# default used by Python when creating the files.
if permissions:
os.chmod(out_path, permissions)

def _extract(self, to_path):
"""
Overrides the BaseArchive _extract method. For zip files we need
to take special care to perserve file permissions:

burgundywall.com/post/preserving-file-perms-with-python-zipfile-module

"""
for info in self._archive.infolist():
self._extract_file(info=info, to_path=to_path)


extension_map = {
'.docx': ZipArchive,
'.egg': ZipArchive,
Expand Down
Binary file added tests/files/endian-4.0.0.zip
Binary file not shown.
18 changes: 18 additions & 0 deletions tests/test_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,21 @@ class TestOutsideRelative(UnsafeArchiveTester, unittest.TestCase):
class TestOutsideAbsolute(UnsafeArchiveTester, unittest.TestCase):
"""An archive that goes outside the destination using absolute paths."""
archive = pathjoin('bad', 'absolute.tar.gz')

class PerserveFilePermissionsTests(TempDirMixin, unittest.TestCase):
"""
The zipfile module in Pyhton does not perserve file permissions. See
burgundywall.com/post/preserving-file-perms-with-python-zipfile-module
"""
def setUp(self):
super(PerserveFilePermissionsTests, self).setUp()

def test_extract_function(self):

endianzip = pathjoin(TEST_DIR, 'files', 'endian-4.0.0.zip')

extract(endianzip, self.tmpdir)
filepath = pathjoin(self.tmpdir, 'endian-4.0.0', 'waf')

self.assertTrue(isfile(filepath))
self.assertTrue(os.access(filepath, os.X_OK))
8 changes: 3 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
[tox]
envlist = py26, py27, py32
indexserver =
pypi = http://pypi.python.org/simple
envlist = py27, py35

[testenv]
deps=
:pypi:pytest
:pypi:pep8
pytest
pep8

commands =
py.test -v tests
Expand Down