_os.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import os
  2. import tempfile
  3. from os.path import abspath, curdir, dirname, join, normcase, sep
  4. from pathlib import Path
  5. from django.core.exceptions import SuspiciousFileOperation
  6. # Copied verbatim (minus `os.path` fixes) from:
  7. # https://github.com/python/cpython/pull/23901.
  8. # Python versions >= PY315 may include this fix, so periodic checks are needed
  9. # to remove this vendored copy of `makedirs` once solved upstream.
  10. def makedirs(name, mode=0o777, exist_ok=False, *, parent_mode=None):
  11. """makedirs(name [, mode=0o777][, exist_ok=False][, parent_mode=None])
  12. Super-mkdir; create a leaf directory and all intermediate ones. Works like
  13. mkdir, except that any intermediate path segment (not just the rightmost)
  14. will be created if it does not exist. If the target directory already
  15. exists, raise an OSError if exist_ok is False. Otherwise no exception is
  16. raised. If parent_mode is not None, it will be used as the mode for any
  17. newly-created, intermediate-level directories. Otherwise, intermediate
  18. directories are created with the default permissions (respecting umask).
  19. This is recursive.
  20. """
  21. head, tail = os.path.split(name)
  22. if not tail:
  23. head, tail = os.path.split(head)
  24. if head and tail and not os.path.exists(head):
  25. try:
  26. if parent_mode is not None:
  27. makedirs(
  28. head, mode=parent_mode, exist_ok=exist_ok, parent_mode=parent_mode
  29. )
  30. else:
  31. makedirs(head, exist_ok=exist_ok)
  32. except FileExistsError:
  33. # Defeats race condition when another thread created the path
  34. pass
  35. cdir = curdir
  36. if isinstance(tail, bytes):
  37. cdir = bytes(curdir, "ASCII")
  38. if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists
  39. return
  40. try:
  41. os.mkdir(name, mode)
  42. # PY315: The call to `chmod()` is not in the CPython proposed code.
  43. # Apply `chmod()` after `mkdir()` to enforce the exact requested
  44. # permissions, since the kernel masks the mode argument with the
  45. # process umask. This guarantees consistent directory permissions
  46. # without mutating global umask state.
  47. os.chmod(name, mode)
  48. except OSError:
  49. # Cannot rely on checking for EEXIST, since the operating system
  50. # could give priority to other errors like EACCES or EROFS
  51. if not exist_ok or not os.path.isdir(name):
  52. raise
  53. def safe_makedirs(name, mode=0o777, exist_ok=False):
  54. """Create directories recursively with explicit `mode` on each level."""
  55. makedirs(name=name, mode=mode, exist_ok=exist_ok, parent_mode=mode)
  56. def safe_join(base, *paths):
  57. """
  58. Join one or more path components to the base path component intelligently.
  59. Return a normalized, absolute version of the final path.
  60. Raise ValueError if the final path isn't located inside of the base path
  61. component.
  62. """
  63. final_path = abspath(join(base, *paths))
  64. base_path = abspath(base)
  65. # Ensure final_path starts with base_path (using normcase to ensure we
  66. # don't false-negative on case insensitive operating systems like Windows),
  67. # further, one of the following conditions must be true:
  68. # a) The next character is the path separator (to prevent conditions like
  69. # safe_join("/dir", "/../d"))
  70. # b) The final path must be the same as the base path.
  71. # c) The base path must be the most root path (meaning either "/" or "C:\\")
  72. if (
  73. not normcase(final_path).startswith(normcase(base_path + sep))
  74. and normcase(final_path) != normcase(base_path)
  75. and dirname(normcase(base_path)) != normcase(base_path)
  76. ):
  77. raise SuspiciousFileOperation(
  78. "The joined path ({}) is located outside of the base path "
  79. "component ({})".format(final_path, base_path)
  80. )
  81. return final_path
  82. def symlinks_supported():
  83. """
  84. Return whether or not creating symlinks are supported in the host platform
  85. and/or if they are allowed to be created (e.g. on Windows it requires admin
  86. permissions).
  87. """
  88. with tempfile.TemporaryDirectory() as temp_dir:
  89. original_path = os.path.join(temp_dir, "original")
  90. symlink_path = os.path.join(temp_dir, "symlink")
  91. os.makedirs(original_path)
  92. try:
  93. os.symlink(original_path, symlink_path)
  94. supported = True
  95. except (OSError, NotImplementedError):
  96. supported = False
  97. return supported
  98. def to_path(value):
  99. """Convert value to a pathlib.Path instance, if not already a Path."""
  100. if isinstance(value, Path):
  101. return value
  102. elif not isinstance(value, str):
  103. raise TypeError("Invalid path type: %s" % type(value).__name__)
  104. return Path(value)