get.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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 sys
  20. import time
  21. import vobject
  22. import radicale.item as radicale_item
  23. from radicale import pathutils
  24. from radicale.log import logger
  25. class CollectionGetMixin:
  26. def __init__(self):
  27. super().__init__()
  28. self._item_cache_cleaned = False
  29. def _list(self):
  30. for entry in os.scandir(self._filesystem_path):
  31. if not entry.is_file():
  32. continue
  33. href = entry.name
  34. if not pathutils.is_safe_filesystem_path_component(href):
  35. if not href.startswith(".Radicale"):
  36. logger.debug("Skipping item %r in %r", href, self.path)
  37. continue
  38. yield href
  39. def _get(self, href, verify_href=True):
  40. if verify_href:
  41. try:
  42. if not pathutils.is_safe_filesystem_path_component(href):
  43. raise pathutils.UnsafePathError(href)
  44. path = pathutils.path_to_filesystem(
  45. self._filesystem_path, href)
  46. except ValueError as e:
  47. logger.debug(
  48. "Can't translate name %r safely to filesystem in %r: %s",
  49. href, self.path, e, exc_info=True)
  50. return None
  51. else:
  52. path = os.path.join(self._filesystem_path, href)
  53. try:
  54. with open(path, "rb") as f:
  55. raw_text = f.read()
  56. except (FileNotFoundError, IsADirectoryError):
  57. return None
  58. except PermissionError:
  59. # Windows raises ``PermissionError`` when ``path`` is a directory
  60. if (sys.platform == "win32" and
  61. os.path.isdir(path) and os.access(path, os.R_OK)):
  62. return None
  63. raise
  64. # The hash of the component in the file system. This is used to check,
  65. # if the entry in the cache is still valid.
  66. input_hash = self._item_cache_hash(raw_text)
  67. cache_hash, uid, etag, text, name, tag, start, end = \
  68. self._load_item_cache(href, input_hash)
  69. if input_hash != cache_hash:
  70. with self._acquire_cache_lock("item"):
  71. # Lock the item cache to prevent multpile processes from
  72. # generating the same data in parallel.
  73. # This improves the performance for multiple requests.
  74. if self._storage._lock.locked == "r":
  75. # Check if another process created the file in the meantime
  76. cache_hash, uid, etag, text, name, tag, start, end = \
  77. self._load_item_cache(href, input_hash)
  78. if input_hash != cache_hash:
  79. try:
  80. vobject_items = list(vobject.readComponents(
  81. raw_text.decode(self._encoding)))
  82. radicale_item.check_and_sanitize_items(
  83. vobject_items, tag=self.tag)
  84. vobject_item, = vobject_items
  85. temp_item = radicale_item.Item(
  86. collection=self, vobject_item=vobject_item)
  87. cache_hash, uid, etag, text, name, tag, start, end = \
  88. self._store_item_cache(
  89. href, temp_item, input_hash)
  90. except Exception as e:
  91. raise RuntimeError("Failed to load item %r in %r: %s" %
  92. (href, self.path, e)) from e
  93. # Clean cache entries once after the data in the file
  94. # system was edited externally.
  95. if not self._item_cache_cleaned:
  96. self._item_cache_cleaned = True
  97. self._clean_item_cache()
  98. last_modified = time.strftime(
  99. "%a, %d %b %Y %H:%M:%S GMT",
  100. time.gmtime(os.path.getmtime(path)))
  101. # Don't keep reference to ``vobject_item``, because it requires a lot
  102. # of memory.
  103. return radicale_item.Item(
  104. collection=self, href=href, last_modified=last_modified, etag=etag,
  105. text=text, uid=uid, name=name, component_name=tag,
  106. time_range=(start, end))
  107. def get_multi(self, hrefs):
  108. # It's faster to check for file name collissions here, because
  109. # we only need to call os.listdir once.
  110. files = None
  111. for href in hrefs:
  112. if files is None:
  113. # List dir after hrefs returned one item, the iterator may be
  114. # empty and the for-loop is never executed.
  115. files = os.listdir(self._filesystem_path)
  116. path = os.path.join(self._filesystem_path, href)
  117. if (not pathutils.is_safe_filesystem_path_component(href) or
  118. href not in files and os.path.lexists(path)):
  119. logger.debug(
  120. "Can't translate name safely to filesystem: %r", href)
  121. yield (href, None)
  122. else:
  123. yield (href, self._get(href, verify_href=False))
  124. def get_all(self):
  125. # We don't need to check for collissions, because the the file names
  126. # are from os.listdir.
  127. return (self._get(href, verify_href=False) for href in self._list())