Store item on upload in the item cache

This rejects items that break the item cache before they are stored in the storage.
This commit is contained in:
Unrud 2017-07-27 19:08:07 +02:00
parent 796ba54f42
commit 9bde9d82f4

View File

@ -941,14 +941,27 @@ class Collection(BaseCollection):
""" """
with contextlib.ExitStack() as stack: with contextlib.ExitStack() as stack:
cache_folder = os.path.join(self._filesystem_path,
".Radicale.cache", "item")
self._makedirs_synced(cache_folder)
fs = [] fs = []
for href, item in vobject_items.items(): for href, vobject_item in vobject_items.items():
if not is_safe_filesystem_path_component(href): if not is_safe_filesystem_path_component(href):
raise UnsafePathError(href) raise UnsafePathError(href)
try:
cache_content = self._item_cache_content(href,
vobject_item)
_, _, _, text, _, _, _ = cache_content
except Exception as e:
raise ValueError("Failed to store item %r in temporary "
"collection: %s" % (href, e)) from e
fs.append(stack.enter_context(
open(os.path.join(cache_folder, href), "wb")))
pickle.dump(cache_content, fs[-1])
path = path_to_filesystem(self._filesystem_path, href) path = path_to_filesystem(self._filesystem_path, href)
fs.append(stack.enter_context( fs.append(stack.enter_context(
open(path, "w", encoding=self._encoding, newline=""))) open(path, "w", encoding=self._encoding, newline="")))
fs[-1].write(item.serialize()) fs[-1].write(text)
# sync everything at once because it's slightly faster. # sync everything at once because it's slightly faster.
for f in fs: for f in fs:
self._fsync(f.fileno()) self._fsync(f.fileno())
@ -1184,26 +1197,29 @@ class Collection(BaseCollection):
_hash.update(raw_text) _hash.update(raw_text)
return _hash.hexdigest() return _hash.hexdigest()
def _store_item_cache(self, href, vobject_item, cache_hash=None): def _item_cache_content(self, href, vobject_item, cache_hash=None):
cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache",
"item")
text = vobject_item.serialize() text = vobject_item.serialize()
if cache_hash is None: if cache_hash is None:
cache_hash = self._item_cache_hash(text.encode(self._encoding)) cache_hash = self._item_cache_hash(text.encode(self._encoding))
etag = get_etag(text) etag = get_etag(text)
uid = get_uid_from_object(vobject_item) uid = get_uid_from_object(vobject_item)
tag, start, end = xmlutils.find_tag_and_time_range(vobject_item) tag, start, end = xmlutils.find_tag_and_time_range(vobject_item)
return cache_hash, uid, etag, text, tag, start, end
def _store_item_cache(self, href, vobject_item, cache_hash=None):
cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache",
"item")
content = self._item_cache_content(href, vobject_item, cache_hash)
self._makedirs_synced(cache_folder) self._makedirs_synced(cache_folder)
try: try:
# Race: Other processes might have created and locked the # Race: Other processes might have created and locked the
# file. # file.
with self._atomic_write(os.path.join(cache_folder, href), with self._atomic_write(os.path.join(cache_folder, href),
"wb") as f: "wb") as f:
pickle.dump((cache_hash, uid, etag, text, pickle.dump(content, f)
tag, start, end), f)
except PermissionError: except PermissionError:
pass pass
return cache_hash, uid, etag, text, tag, start, end return content
def _load_item_cache(self, href): def _load_item_cache(self, href):
cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache", cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache",
@ -1319,10 +1335,20 @@ class Collection(BaseCollection):
def upload(self, href, vobject_item): def upload(self, href, vobject_item):
if not is_safe_filesystem_path_component(href): if not is_safe_filesystem_path_component(href):
raise UnsafePathError(href) raise UnsafePathError(href)
try:
cache_hash, uid, etag, text, _, _, _ = self._store_item_cache(
href, vobject_item)
except Exception as e:
raise ValueError("Failed to store item %r in collection %r: %s" %
(href, self.path, e)) from e
path = path_to_filesystem(self._filesystem_path, href) path = path_to_filesystem(self._filesystem_path, href)
item = Item(self, href=href, item=vobject_item)
with self._atomic_write(path, newline="") as fd: with self._atomic_write(path, newline="") as fd:
fd.write(item.serialize()) fd.write(text)
# Clean the cache after the actual item is stored, or the cache entry
# will be removed again.
self._clean_item_cache()
item = Item(self, href=href, etag=etag, text=text, item=vobject_item,
uid=uid)
# Track the change # Track the change
self._update_history_etag(href, item) self._update_history_etag(href, item)
self._clean_history_cache() self._clean_history_cache()