Check collection properties

This commit is contained in:
Unrud 2017-07-22 21:25:36 +02:00 committed by Unrud
parent 863c70f35f
commit 05b1e8296c
3 changed files with 61 additions and 24 deletions

View File

@ -621,6 +621,7 @@ class Application:
# TODO: use this? # TODO: use this?
# timezone = props.get("C:calendar-timezone") # timezone = props.get("C:calendar-timezone")
try: try:
storage.check_and_sanitize_props(props)
self.Collection.create_collection(path, props=props) self.Collection.create_collection(path, props=props)
except ValueError as e: except ValueError as e:
self.logger.warning( self.logger.warning(
@ -647,6 +648,7 @@ class Application:
return WEBDAV_PRECONDITION_FAILED return WEBDAV_PRECONDITION_FAILED
props = xmlutils.props_from_request(xml_content) props = xmlutils.props_from_request(xml_content)
try: try:
storage.check_and_sanitize_props(props)
self.Collection.create_collection(path, props=props) self.Collection.create_collection(path, props=props)
except ValueError as e: except ValueError as e:
self.logger.warning( self.logger.warning(
@ -764,8 +766,13 @@ class Application:
return WEBDAV_PRECONDITION_FAILED return WEBDAV_PRECONDITION_FAILED
headers = {"DAV": DAV_HEADERS, headers = {"DAV": DAV_HEADERS,
"Content-Type": "text/xml; charset=%s" % self.encoding} "Content-Type": "text/xml; charset=%s" % self.encoding}
try:
xml_answer = xmlutils.proppatch(base_prefix, path, xml_content, xml_answer = xmlutils.proppatch(base_prefix, path, xml_content,
item) item)
except ValueError as e:
self.logger.warning(
"Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
return BAD_REQUEST
return (client.MULTI_STATUS, headers, return (client.MULTI_STATUS, headers,
self._write_xml_content(xml_answer)) self._write_xml_content(xml_answer))
@ -841,18 +848,23 @@ class Application:
return BAD_REQUEST return BAD_REQUEST
if write_whole_collection: if write_whole_collection:
props = {"tag": tag} if tag else {}
try: try:
storage.check_and_sanitize_props(props)
new_item = self.Collection.create_collection( new_item = self.Collection.create_collection(
path, items, {"tag": tag} if tag else None) path, items, props)
except ValueError as e: except ValueError as e:
self.logger.warning( self.logger.warning(
"Bad PUT request on %r: %s", path, e, exc_info=True) "Bad PUT request on %r: %s", path, e, exc_info=True)
return BAD_REQUEST return BAD_REQUEST
else: else:
if tag and not parent_item.get_meta("tag"):
parent_item.set_meta({"tag": tag})
href = posixpath.basename(path.strip("/")) href = posixpath.basename(path.strip("/"))
try: try:
if tag and not parent_item.get_meta("tag"):
new_props = parent_item.get_meta()
new_props["tag"] = tag
storage.check_and_sanitize_props(new_props)
parent_item.set_meta_all(new_props)
new_item = parent_item.upload(href, items[0]) new_item = parent_item.upload(href, items[0])
except ValueError as e: except ValueError as e:
self.logger.warning( self.logger.warning(

View File

@ -178,6 +178,13 @@ def check_and_sanitize_item(vobject_item, is_collection=False, uid=None,
(vobject_item.name, repr(tag) if tag else "generic")) (vobject_item.name, repr(tag) if tag else "generic"))
def check_and_sanitize_props(props):
"""Check collection properties for common errors."""
tag = props.get("tag")
if tag and tag not in ("VCALENDAR", "VADDRESSBOOK"):
raise ValueError("Unsupported collection tag: %r" % tag)
def random_uuid4(): def random_uuid4():
"""Generate a pseudo-random UUID""" """Generate a pseudo-random UUID"""
r = "%016x" % getrandbits(128) r = "%016x" % getrandbits(128)
@ -589,9 +596,24 @@ class BaseCollection:
``props`` a dict with updates for properties. If a value is empty, the ``props`` a dict with updates for properties. If a value is empty, the
property must be deleted. property must be deleted.
DEPRECATED: use ``set_meta_all`` instead
""" """
raise NotImplementedError raise NotImplementedError
def set_meta_all(self, props):
"""Set metadata values for collection.
``props`` a dict with values for properties.
"""
delta_props = self.get_meta()
for key in delta_props.keys():
if key not in props:
delta_props[key] = ""
delta_props.update(props)
self.set_meta(self, delta_props)
@property @property
def last_modified(self): def last_modified(self):
"""Get the HTTP-datetime of when the collection was modified.""" """Get the HTTP-datetime of when the collection was modified."""
@ -850,7 +872,7 @@ class Collection(BaseCollection):
tmp_filesystem_path = os.path.join(tmp_dir, "collection") tmp_filesystem_path = os.path.join(tmp_dir, "collection")
os.makedirs(tmp_filesystem_path) os.makedirs(tmp_filesystem_path)
self = cls("/", folder=tmp_filesystem_path) self = cls("/", folder=tmp_filesystem_path)
self.set_meta(props) self.set_meta_all(props)
if collection: if collection:
if props.get("tag") == "VCALENDAR": if props.get("tag") == "VCALENDAR":
@ -1291,24 +1313,21 @@ class Collection(BaseCollection):
def get_meta(self, key=None): def get_meta(self, key=None):
# reuse cached value if the storage is read-only # reuse cached value if the storage is read-only
if self._writer or self._meta_cache is None: if self._writer or self._meta_cache is None:
try:
try: try:
with open(self._props_path, encoding=self.encoding) as f: with open(self._props_path, encoding=self.encoding) as f:
self._meta_cache = json.load(f) self._meta_cache = json.load(f)
except FileNotFoundError: except FileNotFoundError:
self._meta_cache = {} self._meta_cache = {}
check_and_sanitize_props(self._meta_cache)
except ValueError as e: except ValueError as e:
raise RuntimeError("Failed to load properties of collect" raise RuntimeError("Failed to load properties of collection "
"ion %r: %s" % (self.path, e)) from e "%r: %s" % (self.path, e)) from e
return self._meta_cache.get(key) if key else self._meta_cache return self._meta_cache.get(key) if key else self._meta_cache
def set_meta(self, props): def set_meta_all(self, props):
new_props = self.get_meta()
new_props.update(props)
for key in tuple(new_props.keys()):
if not new_props[key]:
del new_props[key]
with self._atomic_write(self._props_path, "w") as f: with self._atomic_write(self._props_path, "w") as f:
json.dump(new_props, f) json.dump(props, f)
@property @property
def last_modified(self): def last_modified(self):

View File

@ -998,12 +998,18 @@ def proppatch(base_prefix, path, xml_request, collection):
href.text = _href(base_prefix, path) href.text = _href(base_prefix, path)
response.append(href) response.append(href)
for short_name in props_to_remove: new_props = collection.get_meta()
props_to_set[short_name] = "" for short_name, value in props_to_set.items():
collection.set_meta(props_to_set) new_props[short_name] = value
for short_name in props_to_set:
_add_propstat_to(response, short_name, 200) _add_propstat_to(response, short_name, 200)
for short_name in props_to_remove:
try:
del new_props[short_name]
except KeyError:
pass
_add_propstat_to(response, short_name, 200)
storage.check_and_sanitize_props(new_props)
collection.set_meta_all(new_props)
return multistatus return multistatus