get.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # This file is part of Radicale Server - Calendar Server
  2. # Copyright © 2014 Jean-Marc Martins
  3. # Copyright © 2012-2017 Guillaume Ayoub
  4. # Copyright © 2017-2018 Unrud<unrud@outlook.com>
  5. #
  6. # This library is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  18. import os
  19. import time
  20. import vobject
  21. from radicale import item as radicale_item
  22. from radicale import pathutils
  23. from radicale.log import logger
  24. class CollectionGetMixin:
  25. def __init__(self):
  26. super().__init__()
  27. self._item_cache_cleaned = False
  28. def _list(self):
  29. for entry in os.scandir(self._filesystem_path):
  30. if not entry.is_file():
  31. continue
  32. href = entry.name
  33. if not pathutils.is_safe_filesystem_path_component(href):
  34. if not href.startswith(".Radicale"):
  35. logger.debug("Skipping item %r in %r", href, self.path)
  36. continue
  37. yield href
  38. def _get(self, href, verify_href=True):
  39. if verify_href:
  40. try:
  41. if not pathutils.is_safe_filesystem_path_component(href):
  42. raise pathutils.UnsafePathError(href)
  43. path = pathutils.path_to_filesystem(
  44. self._filesystem_path, href)
  45. except ValueError as e:
  46. logger.debug(
  47. "Can't translate name %r safely to filesystem in %r: %s",
  48. href, self.path, e, exc_info=True)
  49. return None
  50. else:
  51. path = os.path.join(self._filesystem_path, href)
  52. try:
  53. with open(path, "rb") as f:
  54. raw_text = f.read()
  55. except (FileNotFoundError, IsADirectoryError):
  56. return None
  57. except PermissionError:
  58. # Windows raises ``PermissionError`` when ``path`` is a directory
  59. if (os.name == "nt" and
  60. os.path.isdir(path) and os.access(path, os.R_OK)):
  61. return None
  62. raise
  63. # The hash of the component in the file system. This is used to check,
  64. # if the entry in the cache is still valid.
  65. input_hash = self._item_cache_hash(raw_text)
  66. cache_hash, uid, etag, text, name, tag, start, end = \
  67. self._load_item_cache(href, input_hash)
  68. if input_hash != cache_hash:
  69. with self._acquire_cache_lock("item"):
  70. # Lock the item cache to prevent multpile processes from
  71. # generating the same data in parallel.
  72. # This improves the performance for multiple requests.
  73. if self._lock.locked == "r":
  74. # Check if another process created the file in the meantime
  75. cache_hash, uid, etag, text, name, tag, start, end = \
  76. self._load_item_cache(href, input_hash)
  77. if input_hash != cache_hash:
  78. try:
  79. vobject_items = tuple(vobject.readComponents(
  80. raw_text.decode(self._encoding)))
  81. radicale_item.check_and_sanitize_items(
  82. vobject_items, tag=self.get_meta("tag"))
  83. vobject_item, = vobject_items
  84. temp_item = radicale_item.Item(
  85. collection=self, vobject_item=vobject_item)
  86. cache_hash, uid, etag, text, name, tag, start, end = \
  87. self._store_item_cache(
  88. href, temp_item, input_hash)
  89. except Exception as e:
  90. raise RuntimeError("Failed to load item %r in %r: %s" %
  91. (href, self.path, e)) from e
  92. # Clean cache entries once after the data in the file
  93. # system was edited externally.
  94. if not self._item_cache_cleaned:
  95. self._item_cache_cleaned = True
  96. self._clean_item_cache()
  97. last_modified = time.strftime(
  98. "%a, %d %b %Y %H:%M:%S GMT",
  99. time.gmtime(os.path.getmtime(path)))
  100. # Don't keep reference to ``vobject_item``, because it requires a lot
  101. # of memory.
  102. return radicale_item.Item(
  103. collection=self, href=href, last_modified=last_modified, etag=etag,
  104. text=text, uid=uid, name=name, component_name=tag,
  105. time_range=(start, end))
  106. def get_multi(self, hrefs):
  107. # It's faster to check for file name collissions here, because
  108. # we only need to call os.listdir once.
  109. files = None
  110. for href in hrefs:
  111. if files is None:
  112. # List dir after hrefs returned one item, the iterator may be
  113. # empty and the for-loop is never executed.
  114. files = os.listdir(self._filesystem_path)
  115. path = os.path.join(self._filesystem_path, href)
  116. if (not pathutils.is_safe_filesystem_path_component(href) or
  117. href not in files and os.path.lexists(path)):
  118. logger.debug(
  119. "Can't translate name safely to filesystem: %r", href)
  120. yield (href, None)
  121. else:
  122. yield (href, self._get(href, verify_href=False))
  123. def get_all(self):
  124. # We don't need to check for collissions, because the the file names
  125. # are from os.listdir.
  126. return (self._get(href, verify_href=False) for href in self._list())