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.

file_cache.py 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import hashlib
  2. import os
  3. from textwrap import dedent
  4. from ..cache import BaseCache
  5. from ..controller import CacheController
  6. try:
  7. FileNotFoundError
  8. except NameError:
  9. # py2.X
  10. FileNotFoundError = (IOError, OSError)
  11. def _secure_open_write(filename, fmode):
  12. # We only want to write to this file, so open it in write only mode
  13. flags = os.O_WRONLY
  14. # os.O_CREAT | os.O_EXCL will fail if the file already exists, so we only
  15. # will open *new* files.
  16. # We specify this because we want to ensure that the mode we pass is the
  17. # mode of the file.
  18. flags |= os.O_CREAT | os.O_EXCL
  19. # Do not follow symlinks to prevent someone from making a symlink that
  20. # we follow and insecurely open a cache file.
  21. if hasattr(os, "O_NOFOLLOW"):
  22. flags |= os.O_NOFOLLOW
  23. # On Windows we'll mark this file as binary
  24. if hasattr(os, "O_BINARY"):
  25. flags |= os.O_BINARY
  26. # Before we open our file, we want to delete any existing file that is
  27. # there
  28. try:
  29. os.remove(filename)
  30. except (IOError, OSError):
  31. # The file must not exist already, so we can just skip ahead to opening
  32. pass
  33. # Open our file, the use of os.O_CREAT | os.O_EXCL will ensure that if a
  34. # race condition happens between the os.remove and this line, that an
  35. # error will be raised. Because we utilize a lockfile this should only
  36. # happen if someone is attempting to attack us.
  37. fd = os.open(filename, flags, fmode)
  38. try:
  39. return os.fdopen(fd, "wb")
  40. except:
  41. # An error occurred wrapping our FD in a file object
  42. os.close(fd)
  43. raise
  44. class FileCache(BaseCache):
  45. def __init__(
  46. self,
  47. directory,
  48. forever=False,
  49. filemode=0o0600,
  50. dirmode=0o0700,
  51. use_dir_lock=None,
  52. lock_class=None,
  53. ):
  54. if use_dir_lock is not None and lock_class is not None:
  55. raise ValueError("Cannot use use_dir_lock and lock_class together")
  56. try:
  57. from pip._vendor.lockfile import LockFile
  58. from pip._vendor.lockfile.mkdirlockfile import MkdirLockFile
  59. except ImportError:
  60. notice = dedent(
  61. """
  62. NOTE: In order to use the FileCache you must have
  63. lockfile installed. You can install it via pip:
  64. pip install lockfile
  65. """
  66. )
  67. raise ImportError(notice)
  68. else:
  69. if use_dir_lock:
  70. lock_class = MkdirLockFile
  71. elif lock_class is None:
  72. lock_class = LockFile
  73. self.directory = directory
  74. self.forever = forever
  75. self.filemode = filemode
  76. self.dirmode = dirmode
  77. self.lock_class = lock_class
  78. @staticmethod
  79. def encode(x):
  80. return hashlib.sha224(x.encode()).hexdigest()
  81. def _fn(self, name):
  82. # NOTE: This method should not change as some may depend on it.
  83. # See: https://github.com/ionrock/cachecontrol/issues/63
  84. hashed = self.encode(name)
  85. parts = list(hashed[:5]) + [hashed]
  86. return os.path.join(self.directory, *parts)
  87. def get(self, key):
  88. name = self._fn(key)
  89. try:
  90. with open(name, "rb") as fh:
  91. return fh.read()
  92. except FileNotFoundError:
  93. return None
  94. def set(self, key, value):
  95. name = self._fn(key)
  96. # Make sure the directory exists
  97. try:
  98. os.makedirs(os.path.dirname(name), self.dirmode)
  99. except (IOError, OSError):
  100. pass
  101. with self.lock_class(name) as lock:
  102. # Write our actual file
  103. with _secure_open_write(lock.path, self.filemode) as fh:
  104. fh.write(value)
  105. def delete(self, key):
  106. name = self._fn(key)
  107. if not self.forever:
  108. try:
  109. os.remove(name)
  110. except FileNotFoundError:
  111. pass
  112. def url_to_file_path(url, filecache):
  113. """Return the file cache path based on the URL.
  114. This does not ensure the file exists!
  115. """
  116. key = CacheController.cache_url(url)
  117. return filecache._fn(key)