diff --git a/django/utils/archive.py b/django/utils/archive.py index 13f8afa32f..f8c1a0f83b 100644 --- a/django/utils/archive.py +++ b/django/utils/archive.py @@ -164,6 +164,7 @@ class TarArchive(BaseArchive): os.makedirs(dirname) with open(filename, 'wb') as outfile: shutil.copyfileobj(extracted, outfile) + os.chmod(filename, member.mode) finally: if extracted: extracted.close() @@ -185,6 +186,7 @@ class ZipArchive(BaseArchive): leading = self.has_leading_dir(namelist) for name in namelist: data = self._archive.read(name) + info = self._archive.getinfo(name) if leading: name = self.split_leading_dir(name)[1] filename = os.path.join(to_path, name) @@ -198,6 +200,9 @@ class ZipArchive(BaseArchive): else: with open(filename, 'wb') as outfile: outfile.write(data) + # convert ZipInfo.external_attr to mode + mode = info.external_attr >> 16 + os.chmod(filename, mode) def close(self): self._archive.close() diff --git a/tests/utils_tests/archives/foobar.tar b/tests/utils_tests/archives/foobar.tar index 5418405070..44886fb33b 100644 Binary files a/tests/utils_tests/archives/foobar.tar and b/tests/utils_tests/archives/foobar.tar differ diff --git a/tests/utils_tests/archives/foobar.tar.bz2 b/tests/utils_tests/archives/foobar.tar.bz2 index 693529dcef..5a3d8a402e 100644 Binary files a/tests/utils_tests/archives/foobar.tar.bz2 and b/tests/utils_tests/archives/foobar.tar.bz2 differ diff --git a/tests/utils_tests/archives/foobar.tar.gz b/tests/utils_tests/archives/foobar.tar.gz index 5b596bd58e..8582a87139 100644 Binary files a/tests/utils_tests/archives/foobar.tar.gz and b/tests/utils_tests/archives/foobar.tar.gz differ diff --git a/tests/utils_tests/archives/foobar.zip b/tests/utils_tests/archives/foobar.zip index dc3e2fc067..b02bb3e95a 100644 Binary files a/tests/utils_tests/archives/foobar.zip and b/tests/utils_tests/archives/foobar.zip differ diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py index d1dc5f2841..42f445ce9c 100644 --- a/tests/utils_tests/test_archive.py +++ b/tests/utils_tests/test_archive.py @@ -1,5 +1,7 @@ import os import shutil +import stat +import sys import tempfile import unittest @@ -42,6 +44,14 @@ class ArchiveTester(object): extract(self.archive_path, self.tmpdir) self.check_files(self.tmpdir) + @unittest.skipIf(sys.platform == 'win32', 'Python on Windows has a limited os.chmod().') + def test_extract_file_permissions(self): + """Archive.extract() preserves file permissions.""" + extract(self.archive_path, self.tmpdir) + filepath = os.path.join(self.tmpdir, 'executable') + # The file has executable permission. + self.assertTrue(os.stat(filepath).st_mode & stat.S_IXOTH) + def test_extract_function_with_leadpath(self): extract(self.archive_lead_path, self.tmpdir) self.check_files(self.tmpdir)