1. import os
    
  2. import stat
    
  3. import sys
    
  4. import tempfile
    
  5. import unittest
    
  6. 
    
  7. from django.core.exceptions import SuspiciousOperation
    
  8. from django.test import SimpleTestCase
    
  9. from django.utils import archive
    
  10. 
    
  11. try:
    
  12.     import bz2  # NOQA
    
  13. 
    
  14.     HAS_BZ2 = True
    
  15. except ImportError:
    
  16.     HAS_BZ2 = False
    
  17. 
    
  18. try:
    
  19.     import lzma  # NOQA
    
  20. 
    
  21.     HAS_LZMA = True
    
  22. except ImportError:
    
  23.     HAS_LZMA = False
    
  24. 
    
  25. 
    
  26. class TestArchive(unittest.TestCase):
    
  27.     def setUp(self):
    
  28.         self.testdir = os.path.join(os.path.dirname(__file__), "archives")
    
  29.         self.old_cwd = os.getcwd()
    
  30.         os.chdir(self.testdir)
    
  31. 
    
  32.     def tearDown(self):
    
  33.         os.chdir(self.old_cwd)
    
  34. 
    
  35.     def test_extract_function(self):
    
  36.         with os.scandir(self.testdir) as entries:
    
  37.             for entry in entries:
    
  38.                 with self.subTest(entry.name), tempfile.TemporaryDirectory() as tmpdir:
    
  39.                     if (entry.name.endswith(".bz2") and not HAS_BZ2) or (
    
  40.                         entry.name.endswith((".lzma", ".xz")) and not HAS_LZMA
    
  41.                     ):
    
  42.                         continue
    
  43.                     archive.extract(entry.path, tmpdir)
    
  44.                     self.assertTrue(os.path.isfile(os.path.join(tmpdir, "1")))
    
  45.                     self.assertTrue(os.path.isfile(os.path.join(tmpdir, "2")))
    
  46.                     self.assertTrue(os.path.isfile(os.path.join(tmpdir, "foo", "1")))
    
  47.                     self.assertTrue(os.path.isfile(os.path.join(tmpdir, "foo", "2")))
    
  48.                     self.assertTrue(
    
  49.                         os.path.isfile(os.path.join(tmpdir, "foo", "bar", "1"))
    
  50.                     )
    
  51.                     self.assertTrue(
    
  52.                         os.path.isfile(os.path.join(tmpdir, "foo", "bar", "2"))
    
  53.                     )
    
  54. 
    
  55.     @unittest.skipIf(
    
  56.         sys.platform == "win32", "Python on Windows has a limited os.chmod()."
    
  57.     )
    
  58.     def test_extract_file_permissions(self):
    
  59.         """archive.extract() preserves file permissions."""
    
  60.         mask = stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
    
  61.         umask = os.umask(0)
    
  62.         os.umask(umask)  # Restore the original umask.
    
  63.         with os.scandir(self.testdir) as entries:
    
  64.             for entry in entries:
    
  65.                 if (
    
  66.                     entry.name.startswith("leadpath_")
    
  67.                     or (entry.name.endswith(".bz2") and not HAS_BZ2)
    
  68.                     or (entry.name.endswith((".lzma", ".xz")) and not HAS_LZMA)
    
  69.                 ):
    
  70.                     continue
    
  71.                 with self.subTest(entry.name), tempfile.TemporaryDirectory() as tmpdir:
    
  72.                     archive.extract(entry.path, tmpdir)
    
  73.                     # An executable file in the archive has executable
    
  74.                     # permissions.
    
  75.                     filepath = os.path.join(tmpdir, "executable")
    
  76.                     self.assertEqual(os.stat(filepath).st_mode & mask, 0o775)
    
  77.                     # A file is readable even if permission data is missing.
    
  78.                     filepath = os.path.join(tmpdir, "no_permissions")
    
  79.                     self.assertEqual(os.stat(filepath).st_mode & mask, 0o666 & ~umask)
    
  80. 
    
  81. 
    
  82. class TestArchiveInvalid(SimpleTestCase):
    
  83.     def test_extract_function_traversal(self):
    
  84.         archives_dir = os.path.join(os.path.dirname(__file__), "traversal_archives")
    
  85.         tests = [
    
  86.             ("traversal.tar", ".."),
    
  87.             ("traversal_absolute.tar", "/tmp/evil.py"),
    
  88.         ]
    
  89.         if sys.platform == "win32":
    
  90.             tests += [
    
  91.                 ("traversal_disk_win.tar", "d:evil.py"),
    
  92.                 ("traversal_disk_win.zip", "d:evil.py"),
    
  93.             ]
    
  94.         msg = "Archive contains invalid path: '%s'"
    
  95.         for entry, invalid_path in tests:
    
  96.             with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
    
  97.                 with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
    
  98.                     archive.extract(os.path.join(archives_dir, entry), tmpdir)