_os.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import os
  2. import stat
  3. import sys
  4. import tempfile
  5. from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
  6. from django.utils.encoding import force_text
  7. from django.utils import six
  8. try:
  9. WindowsError = WindowsError
  10. except NameError:
  11. class WindowsError(Exception):
  12. pass
  13. if six.PY2:
  14. fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
  15. # Under Python 2, define our own abspath function that can handle joining
  16. # unicode paths to a current working directory that has non-ASCII characters
  17. # in it. This isn't necessary on Windows since the Windows version of abspath
  18. # handles this correctly. It also handles drive letters differently than the
  19. # pure Python implementation, so it's best not to replace it.
  20. if six.PY3 or os.name == 'nt':
  21. abspathu = abspath
  22. else:
  23. def abspathu(path):
  24. """
  25. Version of os.path.abspath that uses the unicode representation
  26. of the current working directory, thus avoiding a UnicodeDecodeError
  27. in join when the cwd has non-ASCII characters.
  28. """
  29. if not isabs(path):
  30. path = join(os.getcwdu(), path)
  31. return normpath(path)
  32. def upath(path):
  33. """
  34. Always return a unicode path.
  35. """
  36. if six.PY2 and not isinstance(path, six.text_type):
  37. return path.decode(fs_encoding)
  38. return path
  39. def npath(path):
  40. """
  41. Always return a native path, that is unicode on Python 3 and bytestring on
  42. Python 2.
  43. """
  44. if six.PY2 and not isinstance(path, bytes):
  45. return path.encode(fs_encoding)
  46. return path
  47. def safe_join(base, *paths):
  48. """
  49. Joins one or more path components to the base path component intelligently.
  50. Returns a normalized, absolute version of the final path.
  51. The final path must be located inside of the base path component (otherwise
  52. a ValueError is raised).
  53. """
  54. base = force_text(base)
  55. paths = [force_text(p) for p in paths]
  56. final_path = abspathu(join(base, *paths))
  57. base_path = abspathu(base)
  58. # Ensure final_path starts with base_path (using normcase to ensure we
  59. # don't false-negative on case insensitive operating systems like Windows),
  60. # further, one of the following conditions must be true:
  61. # a) The next character is the path separator (to prevent conditions like
  62. # safe_join("/dir", "/../d"))
  63. # b) The final path must be the same as the base path.
  64. # c) The base path must be the most root path (meaning either "/" or "C:\\")
  65. if (not normcase(final_path).startswith(normcase(base_path + sep)) and
  66. normcase(final_path) != normcase(base_path) and
  67. dirname(normcase(base_path)) != normcase(base_path)):
  68. raise ValueError('The joined path (%s) is located outside of the base '
  69. 'path component (%s)' % (final_path, base_path))
  70. return final_path
  71. def rmtree_errorhandler(func, path, exc_info):
  72. """
  73. On Windows, some files are read-only (e.g. in in .svn dirs), so when
  74. rmtree() tries to remove them, an exception is thrown.
  75. We catch that here, remove the read-only attribute, and hopefully
  76. continue without problems.
  77. """
  78. exctype, value = exc_info[:2]
  79. # looking for a windows error
  80. if exctype is not WindowsError or 'Access is denied' not in str(value):
  81. raise
  82. # file type should currently be read only
  83. if ((os.stat(path).st_mode & stat.S_IREAD) != stat.S_IREAD):
  84. raise
  85. # convert to read/write
  86. os.chmod(path, stat.S_IWRITE)
  87. # use the original function to repeat the operation
  88. func(path)
  89. def symlinks_supported():
  90. """
  91. A function to check if creating symlinks are supported in the
  92. host platform and/or if they are allowed to be created (e.g.
  93. on Windows it requires admin permissions).
  94. """
  95. tmpdir = tempfile.mkdtemp()
  96. original_path = os.path.join(tmpdir, 'original')
  97. symlink_path = os.path.join(tmpdir, 'symlink')
  98. os.makedirs(original_path)
  99. try:
  100. os.symlink(original_path, symlink_path)
  101. supported = True
  102. except (OSError, NotImplementedError, AttributeError):
  103. supported = False
  104. else:
  105. os.remove(symlink_path)
  106. finally:
  107. os.rmdir(original_path)
  108. os.rmdir(tmpdir)
  109. return supported