You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

103 lines
3.5 KiB

  1. from __future__ import unicode_literals
  2. import os
  3. import sys
  4. import tempfile
  5. from os.path import abspath, dirname, isabs, join, normcase, normpath, sep
  6. from django.core.exceptions import SuspiciousFileOperation
  7. from django.utils import six
  8. from django.utils.encoding import force_text
  9. if six.PY2:
  10. fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
  11. # Under Python 2, define our own abspath function that can handle joining
  12. # unicode paths to a current working directory that has non-ASCII characters
  13. # in it. This isn't necessary on Windows since the Windows version of abspath
  14. # handles this correctly. It also handles drive letters differently than the
  15. # pure Python implementation, so it's best not to replace it.
  16. if six.PY3 or os.name == 'nt':
  17. abspathu = abspath
  18. else:
  19. def abspathu(path):
  20. """
  21. Version of os.path.abspath that uses the unicode representation
  22. of the current working directory, thus avoiding a UnicodeDecodeError
  23. in join when the cwd has non-ASCII characters.
  24. """
  25. if not isabs(path):
  26. path = join(os.getcwdu(), path)
  27. return normpath(path)
  28. def upath(path):
  29. """
  30. Always return a unicode path.
  31. """
  32. if six.PY2 and not isinstance(path, six.text_type):
  33. return path.decode(fs_encoding)
  34. return path
  35. def npath(path):
  36. """
  37. Always return a native path, that is unicode on Python 3 and bytestring on
  38. Python 2.
  39. """
  40. if six.PY2 and not isinstance(path, bytes):
  41. return path.encode(fs_encoding)
  42. return path
  43. def safe_join(base, *paths):
  44. """
  45. Joins one or more path components to the base path component intelligently.
  46. Returns a normalized, absolute version of the final path.
  47. The final path must be located inside of the base path component (otherwise
  48. a ValueError is raised).
  49. """
  50. base = force_text(base)
  51. paths = [force_text(p) for p in paths]
  52. final_path = abspathu(join(base, *paths))
  53. base_path = abspathu(base)
  54. # Ensure final_path starts with base_path (using normcase to ensure we
  55. # don't false-negative on case insensitive operating systems like Windows),
  56. # further, one of the following conditions must be true:
  57. # a) The next character is the path separator (to prevent conditions like
  58. # safe_join("/dir", "/../d"))
  59. # b) The final path must be the same as the base path.
  60. # c) The base path must be the most root path (meaning either "/" or "C:\\")
  61. if (not normcase(final_path).startswith(normcase(base_path + sep)) and
  62. normcase(final_path) != normcase(base_path) and
  63. dirname(normcase(base_path)) != normcase(base_path)):
  64. raise SuspiciousFileOperation(
  65. 'The joined path ({}) is located outside of the base path '
  66. 'component ({})'.format(final_path, base_path))
  67. return final_path
  68. def symlinks_supported():
  69. """
  70. A function to check if creating symlinks are supported in the
  71. host platform and/or if they are allowed to be created (e.g.
  72. on Windows it requires admin permissions).
  73. """
  74. tmpdir = tempfile.mkdtemp()
  75. original_path = os.path.join(tmpdir, 'original')
  76. symlink_path = os.path.join(tmpdir, 'symlink')
  77. os.makedirs(original_path)
  78. try:
  79. os.symlink(original_path, symlink_path)
  80. supported = True
  81. except (OSError, NotImplementedError, AttributeError):
  82. supported = False
  83. else:
  84. os.remove(symlink_path)
  85. finally:
  86. os.rmdir(original_path)
  87. os.rmdir(tmpdir)
  88. return supported