From 05b1e8296cfde448def134fd9ff2123ef6738468 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sat, 22 Jul 2017 21:25:36 +0200 Subject: [PATCH] Check collection properties --- radicale/__init__.py | 22 ++++++++++++++++----- radicale/storage.py | 47 +++++++++++++++++++++++++++++++------------- radicale/xmlutils.py | 16 ++++++++++----- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/radicale/__init__.py b/radicale/__init__.py index 0fc964a..f781912 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -621,6 +621,7 @@ class Application: # TODO: use this? # timezone = props.get("C:calendar-timezone") try: + storage.check_and_sanitize_props(props) self.Collection.create_collection(path, props=props) except ValueError as e: self.logger.warning( @@ -647,6 +648,7 @@ class Application: return WEBDAV_PRECONDITION_FAILED props = xmlutils.props_from_request(xml_content) try: + storage.check_and_sanitize_props(props) self.Collection.create_collection(path, props=props) except ValueError as e: self.logger.warning( @@ -764,8 +766,13 @@ class Application: return WEBDAV_PRECONDITION_FAILED headers = {"DAV": DAV_HEADERS, "Content-Type": "text/xml; charset=%s" % self.encoding} - xml_answer = xmlutils.proppatch(base_prefix, path, xml_content, - item) + try: + xml_answer = xmlutils.proppatch(base_prefix, path, xml_content, + 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, self._write_xml_content(xml_answer)) @@ -841,18 +848,23 @@ class Application: return BAD_REQUEST if write_whole_collection: + props = {"tag": tag} if tag else {} try: + storage.check_and_sanitize_props(props) new_item = self.Collection.create_collection( - path, items, {"tag": tag} if tag else None) + path, items, props) except ValueError as e: self.logger.warning( "Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST else: - if tag and not parent_item.get_meta("tag"): - parent_item.set_meta({"tag": tag}) href = posixpath.basename(path.strip("/")) 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]) except ValueError as e: self.logger.warning( diff --git a/radicale/storage.py b/radicale/storage.py index 0c7669c..5054ad2 100644 --- a/radicale/storage.py +++ b/radicale/storage.py @@ -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")) +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(): """Generate a pseudo-random UUID""" r = "%016x" % getrandbits(128) @@ -589,9 +596,24 @@ class BaseCollection: ``props`` a dict with updates for properties. If a value is empty, the property must be deleted. + DEPRECATED: use ``set_meta_all`` instead + """ 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 def last_modified(self): """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") os.makedirs(tmp_filesystem_path) self = cls("/", folder=tmp_filesystem_path) - self.set_meta(props) + self.set_meta_all(props) if collection: if props.get("tag") == "VCALENDAR": @@ -1292,23 +1314,20 @@ class Collection(BaseCollection): # reuse cached value if the storage is read-only if self._writer or self._meta_cache is None: try: - with open(self._props_path, encoding=self.encoding) as f: - self._meta_cache = json.load(f) - except FileNotFoundError: - self._meta_cache = {} + try: + with open(self._props_path, encoding=self.encoding) as f: + self._meta_cache = json.load(f) + except FileNotFoundError: + self._meta_cache = {} + check_and_sanitize_props(self._meta_cache) except ValueError as e: - raise RuntimeError("Failed to load properties of collect" - "ion %r: %s" % (self.path, e)) from e + raise RuntimeError("Failed to load properties of collection " + "%r: %s" % (self.path, e)) from e return self._meta_cache.get(key) if key else self._meta_cache - def set_meta(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] + def set_meta_all(self, props): with self._atomic_write(self._props_path, "w") as f: - json.dump(new_props, f) + json.dump(props, f) @property def last_modified(self): diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index 7a7b3a0..8d20fb5 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -998,12 +998,18 @@ def proppatch(base_prefix, path, xml_request, collection): href.text = _href(base_prefix, path) response.append(href) - for short_name in props_to_remove: - props_to_set[short_name] = "" - collection.set_meta(props_to_set) - - for short_name in props_to_set: + new_props = collection.get_meta() + for short_name, value in props_to_set.items(): + new_props[short_name] = value _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