summaryrefslogtreecommitdiff
path: root/tests/utils_tests/test_archive.py
blob: ed7908df8295b1ea5bb3f6995979edc315c5ec19 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import os
import shutil
import stat
import sys
import tempfile
import unittest

from django.core.exceptions import SuspiciousOperation
from django.test import SimpleTestCase
from django.utils.archive import Archive, extract

TEST_DIR = os.path.join(os.path.dirname(__file__), 'archives')


class ArchiveTester:
    archive = None

    def setUp(self):
        """
        Create temporary directory for testing extraction.
        """
        self.old_cwd = os.getcwd()
        self.tmpdir = tempfile.mkdtemp()
        self.addCleanup(shutil.rmtree, self.tmpdir)
        self.archive_path = os.path.join(TEST_DIR, self.archive)
        self.archive_lead_path = os.path.join(TEST_DIR, "leadpath_%s" % self.archive)
        # Always start off in TEST_DIR.
        os.chdir(TEST_DIR)

    def tearDown(self):
        os.chdir(self.old_cwd)

    def test_extract_method(self):
        with Archive(self.archive) as archive:
            archive.extract(self.tmpdir)
        self.check_files(self.tmpdir)

    def test_extract_method_no_to_path(self):
        os.chdir(self.tmpdir)
        with Archive(self.archive_path) as archive:
            archive.extract()
        self.check_files(self.tmpdir)

    def test_extract_function(self):
        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)
        filepath = os.path.join(self.tmpdir, 'no_permissions')
        # The file is readable even though it doesn't have permission data in
        # the archive.
        self.assertTrue(os.stat(filepath).st_mode & stat.S_IROTH)

    def test_extract_function_with_leadpath(self):
        extract(self.archive_lead_path, self.tmpdir)
        self.check_files(self.tmpdir)

    def test_extract_function_no_to_path(self):
        os.chdir(self.tmpdir)
        extract(self.archive_path)
        self.check_files(self.tmpdir)

    def check_files(self, tmpdir):
        self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '1')))
        self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '2')))
        self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '1')))
        self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '2')))
        self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '1')))
        self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '2')))


class TestZip(ArchiveTester, unittest.TestCase):
    archive = 'foobar.zip'


class TestTar(ArchiveTester, unittest.TestCase):
    archive = 'foobar.tar'


class TestGzipTar(ArchiveTester, unittest.TestCase):
    archive = 'foobar.tar.gz'


class TestBzip2Tar(ArchiveTester, unittest.TestCase):
    archive = 'foobar.tar.bz2'


class TestArchiveInvalid(SimpleTestCase):
    def test_extract_function_traversal(self):
        archives_dir = os.path.join(os.path.dirname(__file__), 'traversal_archives')
        tests = [
            ('traversal.tar', '..'),
            ('traversal_absolute.tar', '/tmp/evil.py'),
        ]
        if sys.platform == 'win32':
            tests += [
                ('traversal_disk_win.tar', 'd:evil.py'),
                ('traversal_disk_win.zip', 'd:evil.py'),
            ]
        msg = "Archive contains invalid path: '%s'"
        for entry, invalid_path in tests:
            with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
                with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
                    extract(os.path.join(archives_dir, entry), tmpdir)