def get_meta(self, key=None): logger.debug("get_meta") if not key: return self._meta_cache if self.deep_path >= 2: if not self._meta_cache: try: collection = DBCollection.objects.get( name=self.attributes[1]) self._meta_cache = collection.tags self._last_modified = collection.updated except DBCollection.DoesNotExist: self._last_modified = None try: check_and_sanitize_props(self._meta_cache) except ValueError as e: raise RuntimeError( "Failed to load properties of collection " "%r: %s" % (self.path, e)) from e else: self._meta_cache = {} return self._meta_cache.get(key)
def proppatch(base_prefix, path, xml_request, collection): """Read and answer PROPPATCH requests. Read rfc4918-9.2 for info. """ props_to_set = props_from_request(xml_request, actions=("set",)) props_to_remove = props_from_request(xml_request, actions=("remove",)) multistatus = ET.Element(_tag("D", "multistatus")) response = ET.Element(_tag("D", "response")) multistatus.append(response) href = ET.Element(_tag("D", "href")) href.text = _href(base_prefix, path) response.append(href) 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
def do_MKCOL(self, environ, base_prefix, path, user): """Manage MKCOL request.""" if not self.Rights.authorized(user, path, "w"): return NOT_ALLOWED try: xml_content = self._read_xml_content(environ) except RuntimeError as e: self.logger.warning( "Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST except socket.timeout as e: self.logger.debug("client timed out", exc_info=True) return REQUEST_TIMEOUT with self.Collection.acquire_lock("w", user): item = next(self.Collection.discover(path), None) if item: 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( "Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST return client.CREATED, {}, None
def do_MKCOL(self, environ, base_prefix, path, user): """Manage MKCOL request.""" if not self.Rights.authorized(user, path, "w"): return NOT_ALLOWED try: xml_content = self._read_xml_content(environ) except RuntimeError as e: self.logger.warning("Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST except socket.timeout as e: self.logger.debug("client timed out", exc_info=True) return REQUEST_TIMEOUT with self.Collection.acquire_lock("w", user): item = next(self.Collection.discover(path), None) if item: 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("Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST return client.CREATED, {}, None
def proppatch(base_prefix, path, xml_request, collection): """Read and answer PROPPATCH requests. Read rfc4918-9.2 for info. """ props_to_set = props_from_request(xml_request, actions=("set",)) props_to_remove = props_from_request(xml_request, actions=("remove",)) multistatus = ET.Element(_tag("D", "multistatus")) response = ET.Element(_tag("D", "response")) multistatus.append(response) href = ET.Element(_tag("D", "href")) href.text = _href(base_prefix, path) response.append(href) 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
def do_MKCOL(self, environ, base_prefix, path, user): """Manage MKCOL request.""" permissions = self.Rights.authorized(user, path, "Ww") if not permissions: return NOT_ALLOWED try: xml_content = self._read_xml_content(environ) except RuntimeError as e: logger.warning("Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST except socket.timeout as e: logger.debug("client timed out", exc_info=True) return REQUEST_TIMEOUT # Prepare before locking props = xmlutils.props_from_request(xml_content) try: storage.check_and_sanitize_props(props) except ValueError as e: logger.warning("Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST if (props.get("tag") and "w" not in permissions or not props.get("tag") and "W" not in permissions): return NOT_ALLOWED with self.Collection.acquire_lock("w", user): item = next(self.Collection.discover(path), None) if item: return METHOD_NOT_ALLOWED parent_path = storage.sanitize_path( "/%s/" % posixpath.dirname(path.strip("/"))) parent_item = next(self.Collection.discover(parent_path), None) if not parent_item: return CONFLICT if (not isinstance(parent_item, storage.BaseCollection) or parent_item.get_meta("tag")): return FORBIDDEN try: self.Collection.create_collection(path, props=props) except ValueError as e: logger.warning("Bad MKCOL request on %r: %s", path, e, exc_info=True) return BAD_REQUEST return client.CREATED, {}, None
def do_MKCALENDAR(self, environ, base_prefix, path, user): """Manage MKCALENDAR request.""" if not self.Rights.authorized(user, path, "w"): return NOT_ALLOWED try: xml_content = self._read_xml_content(environ) except RuntimeError as e: logger.warning("Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) return BAD_REQUEST except socket.timeout as e: logger.debug("client timed out", exc_info=True) return REQUEST_TIMEOUT # Prepare before locking props = xmlutils.props_from_request(xml_content) props["tag"] = "VCALENDAR" # TODO: use this? # timezone = props.get("C:calendar-timezone") try: storage.check_and_sanitize_props(props) except ValueError as e: logger.warning("Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) with self.Collection.acquire_lock("w", user): item = next(self.Collection.discover(path), None) if item: return self._webdav_error_response("D", "resource-must-be-null") parent_path = storage.sanitize_path( "/%s/" % posixpath.dirname(path.strip("/"))) parent_item = next(self.Collection.discover(parent_path), None) if not parent_item: return CONFLICT if (not isinstance(parent_item, storage.BaseCollection) or parent_item.get_meta("tag")): return FORBIDDEN try: self.Collection.create_collection(path, props=props) except ValueError as e: logger.warning("Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) return BAD_REQUEST return client.CREATED, {}, None
def do_PUT(self, environ, base_prefix, path, user): """Manage PUT request.""" if not self._access(user, path, "w"): return NOT_ALLOWED try: content = self._read_content(environ) except RuntimeError as e: self.logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST except socket.timeout: self.logger.debug("client timed out", exc_info=True) return REQUEST_TIMEOUT parent_path = storage.sanitize_path("/%s/" % posixpath.dirname(path.strip("/"))) item = next(self.Collection.discover(path), None) parent_item = next(self.Collection.discover(parent_path), None) if not parent_item: return CONFLICT write_whole_collection = isinstance( item, storage.BaseCollection) or not parent_item.get_meta("tag") if write_whole_collection: if not self.Rights.authorized(user, path, "w"): return NOT_ALLOWED elif not self.Rights.authorized_item(user, path, "w"): return NOT_ALLOWED etag = environ.get("HTTP_IF_MATCH", "") if not item and etag: # Etag asked but no item found: item has been removed return PRECONDITION_FAILED if item and etag and item.etag != etag: # Etag asked but item not matching: item has changed return PRECONDITION_FAILED match = environ.get("HTTP_IF_NONE_MATCH", "") == "*" if item and match: # Creation asked but item found: item can't be replaced return PRECONDITION_FAILED try: items = tuple(vobject.readComponents(content or "")) if write_whole_collection: content_type = environ.get("CONTENT_TYPE", "").split(";")[0] tags = { value: key for key, value in xmlutils.MIMETYPES.items() } tag = tags.get(content_type) if items and items[0].name == "VCALENDAR": tag = "VCALENDAR" elif items and items[0].name in ("VCARD", "VLIST"): tag = "VADDRESSBOOK" else: tag = parent_item.get_meta("tag") if tag == "VCALENDAR" and len(items) > 1: raise RuntimeError("VCALENDAR collection contains %d " "components" % len(items)) for i in items: storage.check_and_sanitize_item( i, is_collection=write_whole_collection, uid=item.uid if not write_whole_collection and item else None, tag=tag, ) except Exception as e: self.logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST if write_whole_collection: props = {} if tag: props["tag"] = tag if tag == "VCALENDAR" and items: if hasattr(items[0], "x_wr_calname"): calname = items[0].x_wr_calname.value if calname: props["D:displayname"] = calname if hasattr(items[0], "x_wr_caldesc"): caldesc = items[0].x_wr_caldesc.value if caldesc: props["C:calendar-description"] = caldesc try: storage.check_and_sanitize_props(props) new_item = self.Collection.create_collection( 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: 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("Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST headers = {"ETag": new_item.etag} return client.CREATED, headers, None
def do_PUT(self, environ, base_prefix, path, user): """Manage PUT request.""" if not self._access(user, path, "w"): return NOT_ALLOWED try: content = self._read_content(environ) except RuntimeError as e: self.logger.warning( "Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST except socket.timeout as e: self.logger.debug("client timed out", exc_info=True) return REQUEST_TIMEOUT with self.Collection.acquire_lock("w", user): parent_path = storage.sanitize_path( "/%s/" % posixpath.dirname(path.strip("/"))) item = next(self.Collection.discover(path), None) parent_item = next(self.Collection.discover(parent_path), None) write_whole_collection = ( isinstance(item, storage.BaseCollection) or not parent_item or ( not next(parent_item.list(), None) and parent_item.get_meta("tag") not in ( "VADDRESSBOOK", "VCALENDAR"))) if write_whole_collection: if not self.Rights.authorized(user, path, "w"): return NOT_ALLOWED elif not self.Rights.authorized_item(user, path, "w"): return NOT_ALLOWED etag = environ.get("HTTP_IF_MATCH", "") if not item and etag: # Etag asked but no item found: item has been removed return PRECONDITION_FAILED if item and etag and item.etag != etag: # Etag asked but item not matching: item has changed return PRECONDITION_FAILED match = environ.get("HTTP_IF_NONE_MATCH", "") == "*" if item and match: # Creation asked but item found: item can't be replaced return PRECONDITION_FAILED try: items = tuple(vobject.readComponents(content or "")) if not write_whole_collection and len(items) != 1: raise RuntimeError( "Item contains %d components" % len(items)) if write_whole_collection or not parent_item.get_meta("tag"): content_type = environ.get("CONTENT_TYPE", "").split(";")[0] tags = {value: key for key, value in xmlutils.MIMETYPES.items()} tag = tags.get(content_type) if items and items[0].name == "VCALENDAR": tag = "VCALENDAR" elif items and items[0].name in ("VCARD", "VLIST"): tag = "VADDRESSBOOK" else: tag = parent_item.get_meta("tag") if tag == "VCALENDAR" and len(items) > 1: raise RuntimeError("VCALENDAR collection contains %d " "components" % len(items)) for i in items: storage.check_and_sanitize_item( i, is_collection=write_whole_collection, uid=item.uid if not write_whole_collection and item else None, tag=tag) except Exception as e: self.logger.warning( "Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST if write_whole_collection: props = {} if tag: props["tag"] = tag if tag == "VCALENDAR" and items: if hasattr(items[0], "x_wr_calname"): calname = items[0].x_wr_calname.value if calname: props["D:displayname"] = calname if hasattr(items[0], "x_wr_caldesc"): caldesc = items[0].x_wr_caldesc.value if caldesc: props["C:calendar-description"] = caldesc try: storage.check_and_sanitize_props(props) new_item = self.Collection.create_collection( 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: 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( "Bad PUT request on %r: %s", path, e, exc_info=True) return BAD_REQUEST headers = {"ETag": new_item.etag} return client.CREATED, headers, None
def prepare(vobject_items, tag=None, write_whole_collection=None): if (write_whole_collection or permissions and not parent_permissions): write_whole_collection = True content_type = environ.get("CONTENT_TYPE", "").split(";")[0] tags = { value: key for key, value in xmlutils.MIMETYPES.items() } tag = storage.predict_tag_of_whole_collection( vobject_items, tags.get(content_type)) if not tag: raise ValueError("Can't determine collection tag") collection_path = storage.sanitize_path(path).strip("/") elif (write_whole_collection is not None and not write_whole_collection or not permissions and parent_permissions): write_whole_collection = False if tag is None: tag = storage.predict_tag_of_parent_collection( vobject_items) collection_path = posixpath.dirname( storage.sanitize_path(path).strip("/")) props = None stored_exc_info = None items = [] try: if tag: storage.check_and_sanitize_items( vobject_items, is_collection=write_whole_collection, tag=tag) if write_whole_collection and tag == "VCALENDAR": vobject_components = [] vobject_item, = vobject_items for content in ("vevent", "vtodo", "vjournal"): vobject_components.extend( getattr(vobject_item, "%s_list" % content, [])) vobject_components_by_uid = itertools.groupby( sorted(vobject_components, key=storage.get_uid), storage.get_uid) for uid, components in vobject_components_by_uid: vobject_collection = vobject.iCalendar() for component in components: vobject_collection.add(component) item = storage.Item( collection_path=collection_path, vobject_item=vobject_collection) item.prepare() items.append(item) elif write_whole_collection and tag == "VADDRESSBOOK": for vobject_item in vobject_items: item = storage.Item( collection_path=collection_path, vobject_item=vobject_item) item.prepare() items.append(item) elif not write_whole_collection: vobject_item, = vobject_items item = storage.Item(collection_path=collection_path, vobject_item=vobject_item) item.prepare() items.append(item) if write_whole_collection: props = {} if tag: props["tag"] = tag if tag == "VCALENDAR" and vobject_items: if hasattr(vobject_items[0], "x_wr_calname"): calname = vobject_items[0].x_wr_calname.value if calname: props["D:displayname"] = calname if hasattr(vobject_items[0], "x_wr_caldesc"): caldesc = vobject_items[0].x_wr_caldesc.value if caldesc: props["C:calendar-description"] = caldesc storage.check_and_sanitize_props(props) except Exception: stored_exc_info = sys.exc_info() # Use generator for items and delete references to free memory # early def items_generator(): while items: yield items.pop(0) return (items_generator(), tag, write_whole_collection, props, stored_exc_info)