예제 #1
0
파일: move.py 프로젝트: xuxingyude/Radicale
    def do_MOVE(self, environ, base_prefix, path, user):
        """Manage MOVE request."""
        raw_dest = environ.get("HTTP_DESTINATION", "")
        to_url = urlparse(raw_dest)
        if to_url.netloc != environ["HTTP_HOST"]:
            logger.info("Unsupported destination address: %r", raw_dest)
            # Remote destination server, not supported
            return httputils.REMOTE_DESTINATION
        if not self.access(user, path, "w"):
            return httputils.NOT_ALLOWED
        to_path = pathutils.sanitize_path(to_url.path)
        if not (to_path + "/").startswith(base_prefix + "/"):
            logger.warning(
                "Destination %r from MOVE request on %r doesn't "
                "start with base prefix", to_path, path)
            return httputils.NOT_ALLOWED
        to_path = to_path[len(base_prefix):]
        if not self.access(user, to_path, "w"):
            return httputils.NOT_ALLOWED

        with self.Collection.acquire_lock("w", user):
            item = next(self.Collection.discover(path), None)
            if not item:
                return httputils.NOT_FOUND
            if (not self.access(user, path, "w", item)
                    or not self.access(user, to_path, "w", item)):
                return httputils.NOT_ALLOWED
            if isinstance(item, storage.BaseCollection):
                # TODO: support moving collections
                return httputils.METHOD_NOT_ALLOWED

            to_item = next(self.Collection.discover(to_path), None)
            if isinstance(to_item, storage.BaseCollection):
                return httputils.FORBIDDEN
            to_parent_path = pathutils.unstrip_path(
                posixpath.dirname(pathutils.strip_path(to_path)), True)
            to_collection = next(self.Collection.discover(to_parent_path),
                                 None)
            if not to_collection:
                return httputils.CONFLICT
            tag = item.collection.get_meta("tag")
            if not tag or tag != to_collection.get_meta("tag"):
                return httputils.FORBIDDEN
            if to_item and environ.get("HTTP_OVERWRITE", "F") != "T":
                return httputils.PRECONDITION_FAILED
            if (to_item and item.uid != to_item.uid or not to_item
                    and to_collection.path != item.collection.path
                    and to_collection.has_uid(item.uid)):
                return self.webdav_error_response(
                    "C" if tag == "VCALENDAR" else "CR", "no-uid-conflict")
            to_href = posixpath.basename(pathutils.strip_path(to_path))
            try:
                self.Collection.move(item, to_collection, to_href)
            except ValueError as e:
                logger.warning("Bad MOVE request on %r: %s",
                               path,
                               e,
                               exc_info=True)
                return httputils.BAD_REQUEST
            return client.NO_CONTENT if to_item else client.CREATED, {}, None
예제 #2
0
파일: move.py 프로젝트: Kozea/Radicale
    def do_MOVE(self, environ, base_prefix, path, user):
        """Manage MOVE request."""
        raw_dest = environ.get("HTTP_DESTINATION", "")
        to_url = urlparse(raw_dest)
        if to_url.netloc != environ["HTTP_HOST"]:
            logger.info("Unsupported destination address: %r", raw_dest)
            # Remote destination server, not supported
            return httputils.REMOTE_DESTINATION
        if not self.access(user, path, "w"):
            return httputils.NOT_ALLOWED
        to_path = pathutils.sanitize_path(to_url.path)
        if not (to_path + "/").startswith(base_prefix + "/"):
            logger.warning("Destination %r from MOVE request on %r doesn't "
                           "start with base prefix", to_path, path)
            return httputils.NOT_ALLOWED
        to_path = to_path[len(base_prefix):]
        if not self.access(user, to_path, "w"):
            return httputils.NOT_ALLOWED

        with self.Collection.acquire_lock("w", user):
            item = next(self.Collection.discover(path), None)
            if not item:
                return httputils.NOT_FOUND
            if (not self.access(user, path, "w", item) or
                    not self.access(user, to_path, "w", item)):
                return httputils.NOT_ALLOWED
            if isinstance(item, storage.BaseCollection):
                # TODO: support moving collections
                return httputils.METHOD_NOT_ALLOWED

            to_item = next(self.Collection.discover(to_path), None)
            if isinstance(to_item, storage.BaseCollection):
                return httputils.FORBIDDEN
            to_parent_path = pathutils.unstrip_path(
                posixpath.dirname(pathutils.strip_path(to_path)), True)
            to_collection = next(
                self.Collection.discover(to_parent_path), None)
            if not to_collection:
                return httputils.CONFLICT
            tag = item.collection.get_meta("tag")
            if not tag or tag != to_collection.get_meta("tag"):
                return httputils.FORBIDDEN
            if to_item and environ.get("HTTP_OVERWRITE", "F") != "T":
                return httputils.PRECONDITION_FAILED
            if (to_item and item.uid != to_item.uid or
                    not to_item and
                    to_collection.path != item.collection.path and
                    to_collection.has_uid(item.uid)):
                return self.webdav_error_response(
                    "C" if tag == "VCALENDAR" else "CR", "no-uid-conflict")
            to_href = posixpath.basename(pathutils.strip_path(to_path))
            try:
                self.Collection.move(item, to_collection, to_href)
            except ValueError as e:
                logger.warning(
                    "Bad MOVE request on %r: %s", path, e, exc_info=True)
                return httputils.BAD_REQUEST
            return client.NO_CONTENT if to_item else client.CREATED, {}, None
예제 #3
0
    def authorization(self, user, path):
        if not user:
            return ""

        sane_path = pathutils.strip_path(path)
        if not sane_path:
            return "R"

        attributes = sane_path.split('/')
        if user != attributes[0]:
            return ""

        if "/" not in sane_path:
            return "RW"

        if sane_path.count("/") == 1:
            journal_uid = attributes[1]

            with etesync_for_user(user) as (etesync, _):
                try:
                    journal = etesync.get(journal_uid)
                except api.exceptions.DoesNotExist:
                    return ''

            return 'rw' if not journal.read_only else 'r'

        return ""
예제 #4
0
파일: discover.py 프로젝트: wkchan/Radicale
    def discover(cls,
                 path,
                 depth="0",
                 child_context_manager=(
                     lambda path, href=None: contextlib.ExitStack())):
        # Path should already be sanitized
        sane_path = pathutils.strip_path(path)
        attributes = sane_path.split("/") if sane_path else []

        folder = cls._get_collection_root_folder()
        # Create the root collection
        cls._makedirs_synced(folder)
        try:
            filesystem_path = pathutils.path_to_filesystem(folder, sane_path)
        except ValueError as e:
            # Path is unsafe
            logger.debug("Unsafe path %r requested from storage: %s",
                         sane_path,
                         e,
                         exc_info=True)
            return

        # Check if the path exists and if it leads to a collection or an item
        if not os.path.isdir(filesystem_path):
            if attributes and os.path.isfile(filesystem_path):
                href = attributes.pop()
            else:
                return
        else:
            href = None

        sane_path = "/".join(attributes)
        collection = cls(pathutils.unstrip_path(sane_path, True))

        if href:
            yield collection._get(href)
            return

        yield collection

        if depth == "0":
            return

        for href in collection._list():
            with child_context_manager(sane_path, href):
                yield collection._get(href)

        for entry in os.scandir(filesystem_path):
            if not entry.is_dir():
                continue
            href = entry.name
            if not pathutils.is_safe_filesystem_path_component(href):
                if not href.startswith(".Radicale"):
                    logger.debug("Skipping collection %r in %r", href,
                                 sane_path)
                continue
            sane_child_path = posixpath.join(sane_path, href)
            child_path = pathutils.unstrip_path(sane_child_path, True)
            with child_context_manager(sane_child_path):
                yield cls(child_path)
예제 #5
0
    def create_collection(self, href, collection=None, props=None):
        stripped_path = strip_path(href)

        c, created = DBCollection.objects.get_or_create(
            path=stripped_path, parent_path=os.path.dirname(stripped_path))

        return c.as_collection()
예제 #6
0
 def _access(self, user, path, permission, item=None):
     if permission not in "rw":
         raise ValueError("Invalid permission argument: %r" % permission)
     if not item:
         permissions = permission + permission.upper()
         parent_permissions = permission
     elif isinstance(item, storage.BaseCollection):
         if item.get_meta("tag"):
             permissions = permission
         else:
             permissions = permission.upper()
         parent_permissions = ""
     else:
         permissions = ""
         parent_permissions = permission
     if permissions and rights.intersect(
             self._rights.authorization(user, path), permissions):
         return True
     if parent_permissions:
         parent_path = pathutils.unstrip_path(
             posixpath.dirname(pathutils.strip_path(path)), True)
         if rights.intersect(self._rights.authorization(user, parent_path),
                             parent_permissions):
             return True
     return False
예제 #7
0
def _cleanup(path):
    sane_path = pathutils.strip_path(path)
    attributes = sane_path.split("/") if sane_path else []

    if len(attributes) < 2:
        return ""
    return attributes[0] + "/" + attributes[1]
예제 #8
0
    def discover(self,
                 path,
                 depth="0",
                 child_context_manager=(
                     lambda path, href=None: contextlib.ExitStack())):
        collections = list(super().discover(path, depth,
                                            child_context_manager))
        for collection in collections:
            yield collection

        if depth == "0":
            return

        attributes = _get_attributes_from_path(path)

        if len(attributes) == 0:
            return
        elif len(attributes) == 1:
            username = attributes[0]
            known_paths = [collection.path for collection in collections]
            for sync_type in ["contacts", "calendars", "tasks", "memos"]:
                for collection in Decsync.list_collections(
                        self.decsync_dir, sync_type):
                    child_path = "/%s/%s-%s/" % (username, sync_type,
                                                 collection)
                    if pathutils.strip_path(child_path) in known_paths:
                        continue
                    if Decsync.get_static_info(self.decsync_dir, sync_type,
                                               collection, "deleted") == True:
                        continue

                    props = {}
                    if sync_type == "contacts":
                        props["tag"] = "VADDRESSBOOK"
                    else:
                        props["tag"] = "VCALENDAR"
                        if sync_type == "calendars":
                            props[
                                "C:supported-calendar-component-set"] = "VEVENT"
                        elif sync_type == "tasks":
                            props[
                                "C:supported-calendar-component-set"] = "VTODO"
                        elif sync_type == "memos":
                            props[
                                "C:supported-calendar-component-set"] = "VJOURNAL"
                        else:
                            raise RuntimeError("Unknown sync type " +
                                               sync_type)
                    child = super().create_collection(child_path, props=props)
                    child.decsync.init_stored_entries()
                    child.decsync.execute_stored_entries_for_path_exact(
                        ["info"], child)
                    child.decsync.execute_stored_entries_for_path_prefix(
                        ["resources"], child)
                    yield child
        elif len(attributes) == 2:
            return
        else:
            raise ValueError("Invalid number of attributes")
예제 #9
0
 def __init__(self, rights, user, path):
     self._rights = rights
     self.user = user
     self.path = path
     self.parent_path = pathutils.unstrip_path(
         posixpath.dirname(pathutils.strip_path(path)), True)
     self.permissions = self._rights.authorization(self.user, self.path)
     self._parent_permissions = None
예제 #10
0
 def authorization(self, user, path):
     if self._verify_user and not user:
         return ""
     sane_path = pathutils.strip_path(path)
     if "/" not in sane_path:
         return "RW"
     if sane_path.count("/") == 1:
         return "rw"
     return ""
예제 #11
0
 def authorized(self, user, path, permissions):
     if self._verify_user and not user:
         return ""
     sane_path = pathutils.strip_path(path)
     if "/" not in sane_path:
         return rights.intersect_permissions(permissions, "RW")
     if sane_path.count("/") == 1:
         return rights.intersect_permissions(permissions, "rw")
     return ""
예제 #12
0
 def authorized(self, user, path, permissions):
     if self._verify_user and not user:
         return ""
     sane_path = pathutils.strip_path(path)
     if "/" not in sane_path:
         return rights.intersect_permissions(permissions, "RW")
     if sane_path.count("/") == 1:
         return rights.intersect_permissions(permissions, "rw")
     return ""
예제 #13
0
 def __init__(self, path, filesystem_path=None):
     folder = self._get_collection_root_folder()
     # Path should already be sanitized
     self.path = pathutils.strip_path(path)
     self._encoding = self.configuration.get("encoding", "stock")
     if filesystem_path is None:
         filesystem_path = pathutils.path_to_filesystem(folder, self.path)
     self._filesystem_path = filesystem_path
     self._etag_cache = None
     super().__init__()
예제 #14
0
파일: __init__.py 프로젝트: Kozea/Radicale
 def __init__(self, path, filesystem_path=None):
     folder = self._get_collection_root_folder()
     # Path should already be sanitized
     self.path = pathutils.strip_path(path)
     self._encoding = self.configuration.get("encoding", "stock")
     if filesystem_path is None:
         filesystem_path = pathutils.path_to_filesystem(folder, self.path)
     self._filesystem_path = filesystem_path
     self._etag_cache = None
     super().__init__()
예제 #15
0
파일: discover.py 프로젝트: Kozea/Radicale
    def discover(cls, path, depth="0", child_context_manager=(
                 lambda path, href=None: contextlib.ExitStack())):
        # Path should already be sanitized
        sane_path = pathutils.strip_path(path)
        attributes = sane_path.split("/") if sane_path else []

        folder = cls._get_collection_root_folder()
        # Create the root collection
        cls._makedirs_synced(folder)
        try:
            filesystem_path = pathutils.path_to_filesystem(folder, sane_path)
        except ValueError as e:
            # Path is unsafe
            logger.debug("Unsafe path %r requested from storage: %s",
                         sane_path, e, exc_info=True)
            return

        # Check if the path exists and if it leads to a collection or an item
        if not os.path.isdir(filesystem_path):
            if attributes and os.path.isfile(filesystem_path):
                href = attributes.pop()
            else:
                return
        else:
            href = None

        sane_path = "/".join(attributes)
        collection = cls(pathutils.unstrip_path(sane_path, True))

        if href:
            yield collection._get(href)
            return

        yield collection

        if depth == "0":
            return

        for href in collection._list():
            with child_context_manager(sane_path, href):
                yield collection._get(href)

        for entry in os.scandir(filesystem_path):
            if not entry.is_dir():
                continue
            href = entry.name
            if not pathutils.is_safe_filesystem_path_component(href):
                if not href.startswith(".Radicale"):
                    logger.debug("Skipping collection %r in %r",
                                 href, sane_path)
                continue
            sane_child_path = posixpath.join(sane_path, href)
            child_path = pathutils.unstrip_path(sane_child_path, True)
            with child_context_manager(sane_child_path):
                yield cls(child_path)
예제 #16
0
파일: __init__.py 프로젝트: Kozea/Radicale
    def __init__(self, collection_path=None, collection=None,
                 vobject_item=None, href=None, last_modified=None, text=None,
                 etag=None, uid=None, name=None, component_name=None,
                 time_range=None):
        """Initialize an item.

        ``collection_path`` the path of the parent collection (optional if
        ``collection`` is set).

        ``collection`` the parent collection (optional).

        ``href`` the href of the item.

        ``last_modified`` the HTTP-datetime of when the item was modified.

        ``text`` the text representation of the item (optional if
        ``vobject_item`` is set).

        ``vobject_item`` the vobject item (optional if ``text`` is set).

        ``etag`` the etag of the item (optional). See ``get_etag``.

        ``uid`` the UID of the object (optional). See ``get_uid_from_object``.

        ``name`` the name of the item (optional). See ``vobject_item.name``.

        ``component_name`` the name of the primary component (optional).
        See ``find_tag``.

        ``time_range`` the enclosing time range.
        See ``find_tag_and_time_range``.

        """
        if text is None and vobject_item is None:
            raise ValueError(
                "at least one of 'text' or 'vobject_item' must be set")
        if collection_path is None:
            if collection is None:
                raise ValueError("at least one of 'collection_path' or "
                                 "'collection' must be set")
            collection_path = collection.path
        assert collection_path == pathutils.strip_path(
            pathutils.sanitize_path(collection_path))
        self._collection_path = collection_path
        self.collection = collection
        self.href = href
        self.last_modified = last_modified
        self._text = text
        self._vobject_item = vobject_item
        self._etag = etag
        self._uid = uid
        self._name = name
        self._component_name = component_name
        self._time_range = time_range
예제 #17
0
    def __init__(self, collection_path=None, collection=None,
                 vobject_item=None, href=None, last_modified=None, text=None,
                 etag=None, uid=None, name=None, component_name=None,
                 time_range=None):
        """Initialize an item.

        ``collection_path`` the path of the parent collection (optional if
        ``collection`` is set).

        ``collection`` the parent collection (optional).

        ``href`` the href of the item.

        ``last_modified`` the HTTP-datetime of when the item was modified.

        ``text`` the text representation of the item (optional if
        ``vobject_item`` is set).

        ``vobject_item`` the vobject item (optional if ``text`` is set).

        ``etag`` the etag of the item (optional). See ``get_etag``.

        ``uid`` the UID of the object (optional). See ``get_uid_from_object``.

        ``name`` the name of the item (optional). See ``vobject_item.name``.

        ``component_name`` the name of the primary component (optional).
        See ``find_tag``.

        ``time_range`` the enclosing time range.
        See ``find_tag_and_time_range``.

        """
        if text is None and vobject_item is None:
            raise ValueError(
                "at least one of 'text' or 'vobject_item' must be set")
        if collection_path is None:
            if collection is None:
                raise ValueError("at least one of 'collection_path' or "
                                 "'collection' must be set")
            collection_path = collection.path
        assert collection_path == pathutils.strip_path(
            pathutils.sanitize_path(collection_path))
        self._collection_path = collection_path
        self.collection = collection
        self.href = href
        self.last_modified = last_modified
        self._text = text
        self._vobject_item = vobject_item
        self._etag = etag
        self._uid = uid
        self._name = name
        self._component_name = component_name
        self._time_range = time_range
예제 #18
0
 def authorization(self, user, path):
     if self._verify_user and not user:
         return ""
     sane_path = pathutils.strip_path(path)
     if not sane_path:
         return "R"
     if self._verify_user and user != sane_path.split("/", maxsplit=1)[0]:
         return ""
     if "/" not in sane_path:
         return "RW"
     if sane_path.count("/") == 1:
         return "rw"
     return ""
예제 #19
0
 def exception_cm(path, href=None):
     nonlocal item_errors, collection_errors
     sane_path = pathutils.strip_path(path)
     try:
         yield
     except Exception as e:
         if href:
             item_errors += 1
             name = "item %r in %r" % (href, sane_path)
         else:
             collection_errors += 1
             name = "collection %r" % sane_path
         logger.error("Invalid %s: %s", name, e, exc_info=True)
예제 #20
0
 def __init__(self, path, filesystem_path=None):
     folder = self._get_collection_root_folder()
     # Path should already be sanitized
     self.path = pathutils.strip_path(path)
     self._encoding = self.configuration.get("encoding", "stock")
     if filesystem_path is None:
         filesystem_path = pathutils.path_to_filesystem(folder, self.path)
     self._filesystem_path = filesystem_path
     self._props_path = os.path.join(self._filesystem_path,
                                     ".Radicale.props")
     self._meta_cache = None
     self._etag_cache = None
     self._item_cache_cleaned = False
예제 #21
0
 def do_MKCOL(self, environ, base_prefix, path, user, context=None):
     """Manage MKCOL request."""
     permissions = self._rights.authorization(user, path)
     if not rights.intersect(permissions, "Ww"):
         return httputils.NOT_ALLOWED
     try:
         xml_content = self._read_xml_request_body(environ)
     except RuntimeError as e:
         logger.warning("Bad MKCOL request on %r: %s",
                        path,
                        e,
                        exc_info=True)
         return httputils.BAD_REQUEST
     except socket.timeout:
         logger.debug("Client timed out", exc_info=True)
         return httputils.REQUEST_TIMEOUT
     # Prepare before locking
     props = xmlutils.props_from_request(xml_content)
     props = {k: v for k, v in props.items() if v is not None}
     try:
         radicale_item.check_and_sanitize_props(props)
     except ValueError as e:
         logger.warning("Bad MKCOL request on %r: %s",
                        path,
                        e,
                        exc_info=True)
         return httputils.BAD_REQUEST
     if (props.get("tag") and "w" not in permissions
             or not props.get("tag") and "W" not in permissions):
         return httputils.NOT_ALLOWED
     with self._storage.acquire_lock("w", user):
         item = next(self._storage.discover(path), None)
         if item:
             return httputils.METHOD_NOT_ALLOWED
         parent_path = pathutils.unstrip_path(
             posixpath.dirname(pathutils.strip_path(path)), True)
         parent_item = next(self._storage.discover(parent_path), None)
         if not parent_item:
             return httputils.CONFLICT
         if (not isinstance(parent_item, storage.BaseCollection)
                 or parent_item.get_meta("tag")):
             return httputils.FORBIDDEN
         try:
             self._storage.create_collection(path, props=props)
         except ValueError as e:
             logger.warning("Bad MKCOL request on %r: %s",
                            path,
                            e,
                            exc_info=True)
             return httputils.BAD_REQUEST
         return client.CREATED, {}, None
예제 #22
0
 def do_MKCALENDAR(self, environ, base_prefix, path, user):
     """Manage MKCALENDAR request."""
     if "w" not in self._rights.authorization(user, path):
         return httputils.NOT_ALLOWED
     try:
         xml_content = self._read_xml_request_body(environ)
     except RuntimeError as e:
         logger.warning("Bad MKCALENDAR request on %r: %s",
                        path,
                        e,
                        exc_info=True)
         return httputils.BAD_REQUEST
     except socket.timeout:
         logger.debug("Client timed out", exc_info=True)
         return httputils.REQUEST_TIMEOUT
     # Prepare before locking
     props = xmlutils.props_from_request(xml_content)
     props = {k: v for k, v in props.items() if v is not None}
     props["tag"] = "VCALENDAR"
     # TODO: use this?
     # timezone = props.get("C:calendar-timezone")
     try:
         radicale_item.check_and_sanitize_props(props)
     except ValueError as e:
         logger.warning("Bad MKCALENDAR request on %r: %s",
                        path,
                        e,
                        exc_info=True)
         return httputils.BAD_REQUEST
     with self._storage.acquire_lock("w", user):
         item = next(self._storage.discover(path), None)
         if item:
             return self._webdav_error_response(client.CONFLICT,
                                                "D:resource-must-be-null")
         parent_path = pathutils.unstrip_path(
             posixpath.dirname(pathutils.strip_path(path)), True)
         parent_item = next(self._storage.discover(parent_path), None)
         if not parent_item:
             return httputils.CONFLICT
         if (not isinstance(parent_item, storage.BaseCollection)
                 or parent_item.get_meta("tag")):
             return httputils.FORBIDDEN
         try:
             self._storage.create_collection(path, props=props)
         except ValueError as e:
             logger.warning("Bad MKCALENDAR request on %r: %s",
                            path,
                            e,
                            exc_info=True)
             return httputils.BAD_REQUEST
         return client.CREATED, {}, None
예제 #23
0
 def do_GET(self, environ, base_prefix, path, user):
     """Manage GET request."""
     # Redirect to .web if the root URL is requested
     if not pathutils.strip_path(path):
         web_path = ".web"
         if not environ.get("PATH_INFO"):
             web_path = posixpath.join(posixpath.basename(base_prefix),
                                       web_path)
         return (client.FOUND, {
             "Location": web_path,
             "Content-Type": "text/plain"
         }, "Redirected to %s" % web_path)
     # Dispatch .web URL to web module
     if path == "/.web" or path.startswith("/.web/"):
         return self._web.get(environ, base_prefix, path, user)
     access = app.Access(self._rights, user, path)
     if not access.check("r") and "i" not in access.permissions:
         return httputils.NOT_ALLOWED
     with self._storage.acquire_lock("r", user):
         item = next(self._storage.discover(path), None)
         if not item:
             return httputils.NOT_FOUND
         if access.check("r", item):
             limited_access = False
         elif "i" in access.permissions:
             limited_access = True
         else:
             return httputils.NOT_ALLOWED
         if isinstance(item, storage.BaseCollection):
             tag = item.get_meta("tag")
             if not tag:
                 return (httputils.NOT_ALLOWED
                         if limited_access else httputils.DIRECTORY_LISTING)
             content_type = xmlutils.MIMETYPES[tag]
             content_disposition = self._content_disposition_attachement(
                 propose_filename(item))
         elif limited_access:
             return httputils.NOT_ALLOWED
         else:
             content_type = xmlutils.OBJECT_MIMETYPES[item.name]
             content_disposition = ""
         headers = {
             "Content-Type": content_type,
             "Last-Modified": item.last_modified,
             "ETag": item.etag
         }
         if content_disposition:
             headers["Content-Disposition"] = content_disposition
         answer = item.serialize()
         return client.OK, headers, answer
예제 #24
0
 def authorization(self, user, path):
     if self._verify_user and not user:
         return ""
     sane_path = pathutils.strip_path(path)
     if not sane_path:
         return "R"
     if self._verify_user:
         owned = user == sane_path.split("/", maxsplit=1)[0]
     else:
         owned = True
     if "/" not in sane_path:
         return "RW" if owned else "R"
     if sane_path.count("/") == 1:
         return "rw" if owned else "r"
     return ""
예제 #25
0
 def authorized(self, user, path, permissions):
     if self._verify_user and not user:
         return ""
     sane_path = pathutils.strip_path(path)
     if not sane_path:
         return rights.intersect_permissions(permissions, "R")
     if self._verify_user:
         owned = user == sane_path.split("/", maxsplit=1)[0]
     else:
         owned = True
     if "/" not in sane_path:
         return rights.intersect_permissions(permissions,
                                             "RW" if owned else "R")
     if sane_path.count("/") == 1:
         return rights.intersect_permissions(permissions,
                                             "rw" if owned else "r")
     return ""
예제 #26
0
    def authorized(self, user, path, permissions):
        logger.debug(
            "User %r is trying to access path %r. Permissions: %r",
            user,
            path,
            permissions,
        )

        # everybody can access the root collection
        if path == "/":
            logger.debug("Accessing root path. Access granted.")
            return True

        user = user or ""
        sane_path = strip_path(path)
        full_access = "rw" if ("/" in sane_path) else "RW"
        pathowner, _ = sane_path.split("/", maxsplit=1)

        # pathowner can be a user...
        if user == pathowner:
            logger.debug("User %r is pathowner. Read & Write Access granted.",
                         user)
            return full_access

        # ...or a group
        maybe_groupname = self.group_prefix + pathowner
        try:
            group = grp.getgrnam(maybe_groupname)
            if user in group.gr_mem:
                logger.debug(
                    "User %r is in pathowner group %r. Read & Write Access granted.",
                    user,
                    pathowner,
                )
                return full_access
        except KeyError:
            logger.debug(
                "Pathowner %r is neither the user nor a valid group.",
                pathowner,
            )

        logger.debug("Access to path %r is not granted to user %r.", pathowner,
                     user)
        return ""
예제 #27
0
파일: mkcol.py 프로젝트: Kozea/Radicale
 def do_MKCOL(self, environ, base_prefix, path, user):
     """Manage MKCOL request."""
     permissions = self.Rights.authorized(user, path, "Ww")
     if not permissions:
         return httputils.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 httputils.BAD_REQUEST
     except socket.timeout:
         logger.debug("client timed out", exc_info=True)
         return httputils.REQUEST_TIMEOUT
     # Prepare before locking
     props = xmlutils.props_from_request(xml_content)
     try:
         radicale_item.check_and_sanitize_props(props)
     except ValueError as e:
         logger.warning(
             "Bad MKCOL request on %r: %s", path, e, exc_info=True)
         return httputils.BAD_REQUEST
     if (props.get("tag") and "w" not in permissions or
             not props.get("tag") and "W" not in permissions):
         return httputils.NOT_ALLOWED
     with self.Collection.acquire_lock("w", user):
         item = next(self.Collection.discover(path), None)
         if item:
             return httputils.METHOD_NOT_ALLOWED
         parent_path = pathutils.unstrip_path(
             posixpath.dirname(pathutils.strip_path(path)), True)
         parent_item = next(self.Collection.discover(parent_path), None)
         if not parent_item:
             return httputils.CONFLICT
         if (not isinstance(parent_item, storage.BaseCollection) or
                 parent_item.get_meta("tag")):
             return httputils.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 httputils.BAD_REQUEST
         return client.CREATED, {}, None
예제 #28
0
 def do_MKCALENDAR(self, environ, base_prefix, path, user):
     """Manage MKCALENDAR request."""
     if not self.Rights.authorized(user, path, "w"):
         return httputils.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 httputils.BAD_REQUEST
     except socket.timeout:
         logger.debug("client timed out", exc_info=True)
         return httputils.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:
         radicale_item.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 = pathutils.unstrip_path(
             posixpath.dirname(pathutils.strip_path(path)), True)
         parent_item = next(self.Collection.discover(parent_path), None)
         if not parent_item:
             return httputils.CONFLICT
         if (not isinstance(parent_item, storage.BaseCollection) or
                 parent_item.get_meta("tag")):
             return httputils.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 httputils.BAD_REQUEST
         return client.CREATED, {}, None
예제 #29
0
    def create_collection(self, href, items=None, props=None):
        folder = self._get_collection_root_folder()

        # Path should already be sanitized
        sane_path = pathutils.strip_path(href)
        filesystem_path = pathutils.path_to_filesystem(folder, sane_path)

        if not props:
            self._makedirs_synced(filesystem_path)
            return self._collection_class(
                self, pathutils.unstrip_path(sane_path, True))

        parent_dir = os.path.dirname(filesystem_path)
        self._makedirs_synced(parent_dir)

        # Create a temporary directory with an unsafe name
        with TemporaryDirectory(prefix=".Radicale.tmp-",
                                dir=parent_dir) as tmp_dir:
            # The temporary directory itself can't be renamed
            tmp_filesystem_path = os.path.join(tmp_dir, "collection")
            os.makedirs(tmp_filesystem_path)
            col = self._collection_class(self,
                                         pathutils.unstrip_path(
                                             sane_path, True),
                                         filesystem_path=tmp_filesystem_path)
            col.set_meta(props)
            if items is not None:
                if props.get("tag") == "VCALENDAR":
                    col._upload_all_nonatomic(items, suffix=".ics")
                elif props.get("tag") == "VADDRESSBOOK":
                    col._upload_all_nonatomic(items, suffix=".vcf")

            # This operation is not atomic on the filesystem level but it's
            # very unlikely that one rename operations succeeds while the
            # other fails or that only one gets written to disk.
            if os.path.exists(filesystem_path):
                os.rename(filesystem_path, os.path.join(tmp_dir, "delete"))
            os.rename(tmp_filesystem_path, filesystem_path)
            self._sync_directory(parent_dir)

        return self._collection_class(self,
                                      pathutils.unstrip_path(sane_path, True))
예제 #30
0
파일: get.py 프로젝트: Kozea/Radicale
 def do_GET(self, environ, base_prefix, path, user):
     """Manage GET request."""
     # Redirect to .web if the root URL is requested
     if not pathutils.strip_path(path):
         web_path = ".web"
         if not environ.get("PATH_INFO"):
             web_path = posixpath.join(posixpath.basename(base_prefix),
                                       web_path)
         return (client.FOUND,
                 {"Location": web_path, "Content-Type": "text/plain"},
                 "Redirected to %s" % web_path)
     # Dispatch .web URL to web module
     if path == "/.web" or path.startswith("/.web/"):
         return self.Web.get(environ, base_prefix, path, user)
     if not self.access(user, path, "r"):
         return httputils.NOT_ALLOWED
     with self.Collection.acquire_lock("r", user):
         item = next(self.Collection.discover(path), None)
         if not item:
             return httputils.NOT_FOUND
         if not self.access(user, path, "r", item):
             return httputils.NOT_ALLOWED
         if isinstance(item, storage.BaseCollection):
             tag = item.get_meta("tag")
             if not tag:
                 return httputils.DIRECTORY_LISTING
             content_type = xmlutils.MIMETYPES[tag]
             content_disposition = self._content_disposition_attachement(
                 propose_filename(item))
         else:
             content_type = xmlutils.OBJECT_MIMETYPES[item.name]
             content_disposition = ""
         headers = {
             "Content-Type": content_type,
             "Last-Modified": item.last_modified,
             "ETag": item.etag}
         if content_disposition:
             headers["Content-Disposition"] = content_disposition
         answer = item.serialize()
         return client.OK, headers, answer
예제 #31
0
 def authorized(self, user, path, permissions):
     user = user or ""
     sane_path = pathutils.strip_path(path)
     # Prevent "regex injection"
     user_escaped = re.escape(user)
     sane_path_escaped = re.escape(sane_path)
     rights_config = configparser.ConfigParser({
         "login": user_escaped,
         "path": sane_path_escaped
     })
     try:
         if not rights_config.read(self.filename):
             raise RuntimeError("No such file: %r" % self.filename)
     except Exception as e:
         raise RuntimeError("Failed to load rights file %r: %s" %
                            (self.filename, e)) from e
     for section in rights_config.sections():
         try:
             user_pattern = rights_config.get(section, "user")
             collection_pattern = rights_config.get(section, "collection")
             user_match = re.fullmatch(user_pattern, user)
             collection_match = user_match and re.fullmatch(
                 collection_pattern.format(
                     *map(re.escape, user_match.groups())), sane_path)
         except Exception as e:
             raise RuntimeError("Error in section %r of rights file %r: "
                                "%s" % (section, self.filename, e)) from e
         if user_match and collection_match:
             logger.debug("Rule %r:%r matches %r:%r from section %r", user,
                          sane_path, user_pattern, collection_pattern,
                          section)
             return rights.intersect_permissions(
                 permissions, rights_config.get(section, "permissions"))
         else:
             logger.debug("Rule %r:%r doesn't match %r:%r from section %r",
                          user, sane_path, user_pattern, collection_pattern,
                          section)
     logger.info("Rights: %r:%r doesn't match any section", user, sane_path)
     return ""
예제 #32
0
    def create_collection(self, href, items=None, props=None):
        folder = self._get_collection_root_folder()

        # Path should already be sanitized
        sane_path = pathutils.strip_path(href)
        filesystem_path = pathutils.path_to_filesystem(folder, sane_path)

        if not props:
            self._makedirs_synced(filesystem_path)
            return self._collection_class(
                self, pathutils.unstrip_path(sane_path, True))

        parent_dir = os.path.dirname(filesystem_path)
        self._makedirs_synced(parent_dir)

        # Create a temporary directory with an unsafe name
        with TemporaryDirectory(
                prefix=".Radicale.tmp-", dir=parent_dir) as tmp_dir:
            # The temporary directory itself can't be renamed
            tmp_filesystem_path = os.path.join(tmp_dir, "collection")
            os.makedirs(tmp_filesystem_path)
            col = self._collection_class(
                self, pathutils.unstrip_path(sane_path, True),
                filesystem_path=tmp_filesystem_path)
            col.set_meta(props)
            if items is not None:
                if props.get("tag") == "VCALENDAR":
                    col._upload_all_nonatomic(items, suffix=".ics")
                elif props.get("tag") == "VADDRESSBOOK":
                    col._upload_all_nonatomic(items, suffix=".vcf")

            if os.path.lexists(filesystem_path):
                pathutils.rename_exchange(tmp_filesystem_path, filesystem_path)
            else:
                os.rename(tmp_filesystem_path, filesystem_path)
            self._sync_directory(parent_dir)

        return self._collection_class(
            self, pathutils.unstrip_path(sane_path, True))
예제 #33
0
    def discover(self, path, depth='0'):
        stripped_path = strip_path(path)

        if stripped_path == '':
            yield Collection('')
            return

        for c in DBCollection.objects.filter(
                path=stripped_path).as_collections():
            yield c

        prefix, _, name = stripped_path.rpartition('/')
        for i in DBItem.objects.filter(collection__path=prefix,
                                       name=name).as_items():
            yield i

        if depth == '0':
            return

        for i in DBItem.objects.filter(
                collection__path=stripped_path).as_items():
            yield i
예제 #34
0
파일: __init__.py 프로젝트: Kozea/Radicale
 def access(self, user, path, permission, item=None):
     if permission not in "rw":
         raise ValueError("Invalid permission argument: %r" % permission)
     if not item:
         permissions = permission + permission.upper()
         parent_permissions = permission
     elif isinstance(item, storage.BaseCollection):
         if item.get_meta("tag"):
             permissions = permission
         else:
             permissions = permission.upper()
         parent_permissions = ""
     else:
         permissions = ""
         parent_permissions = permission
     if permissions and self.Rights.authorized(user, path, permissions):
         return True
     if parent_permissions:
         parent_path = pathutils.unstrip_path(
             posixpath.dirname(pathutils.strip_path(path)), True)
         if self.Rights.authorized(user, parent_path, parent_permissions):
             return True
     return False
예제 #35
0
 def authorization(self, user, path):
     user = user or ""
     sane_path = pathutils.strip_path(path)
     # Prevent "regex injection"
     escaped_user = re.escape(user)
     rights_config = configparser.ConfigParser()
     try:
         if not rights_config.read(self._filename):
             raise RuntimeError("No such file: %r" %
                                self._filename)
     except Exception as e:
         raise RuntimeError("Failed to load rights file %r: %s" %
                            (self._filename, e)) from e
     for section in rights_config.sections():
         try:
             user_pattern = rights_config.get(section, "user")
             collection_pattern = rights_config.get(section, "collection")
             # Use empty format() for harmonized handling of curly braces
             user_match = re.fullmatch(user_pattern.format(), user)
             collection_match = user_match and re.fullmatch(
                 collection_pattern.format(
                     *map(re.escape, user_match.groups()),
                     user=escaped_user), sane_path)
         except Exception as e:
             raise RuntimeError("Error in section %r of rights file %r: "
                                "%s" % (section, self._filename, e)) from e
         if user_match and collection_match:
             logger.debug("Rule %r:%r matches %r:%r from section %r",
                          user, sane_path, user_pattern,
                          collection_pattern, section)
             return rights_config.get(section, "permissions")
         logger.debug("Rule %r:%r doesn't match %r:%r from section %r",
                      user, sane_path, user_pattern, collection_pattern,
                      section)
     logger.info("Rights: %r:%r doesn't match any section", user, sane_path)
     return ""
예제 #36
0
파일: put.py 프로젝트: Kozea/Radicale
        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 = radicale_item.predict_tag_of_whole_collection(
                    vobject_items, tags.get(content_type))
                if not tag:
                    raise ValueError("Can't determine collection tag")
                collection_path = pathutils.strip_path(path)
            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 = radicale_item.predict_tag_of_parent_collection(
                        vobject_items)
                collection_path = posixpath.dirname(
                    pathutils.strip_path(path))
            props = None
            stored_exc_info = None
            items = []
            try:
                if tag:
                    radicale_item.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=radicale_item.get_uid),
                            radicale_item.get_uid)
                        for uid, components in vobject_components_by_uid:
                            vobject_collection = vobject.iCalendar()
                            for component in components:
                                vobject_collection.add(component)
                            item = radicale_item.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 = radicale_item.Item(
                                collection_path=collection_path,
                                vobject_item=vobject_item)
                            item.prepare()
                            items.append(item)
                    elif not write_whole_collection:
                        vobject_item, = vobject_items
                        item = radicale_item.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
                    radicale_item.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)
예제 #37
0
파일: rights.py 프로젝트: Kozea/Radicale
 def authorized(self, user, path, permissions):
     sane_path = pathutils.strip_path(path)
     if sane_path not in ("tmp", "other"):
         return ""
     return rights.intersect_permissions(permissions)
예제 #38
0
파일: put.py 프로젝트: Kozea/Radicale
    def do_PUT(self, environ, base_prefix, path, user):
        """Manage PUT request."""
        if not self.access(user, path, "w"):
            return httputils.NOT_ALLOWED
        try:
            content = self.read_content(environ)
        except RuntimeError as e:
            logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True)
            return httputils.BAD_REQUEST
        except socket.timeout:
            logger.debug("client timed out", exc_info=True)
            return httputils.REQUEST_TIMEOUT
        # Prepare before locking
        parent_path = pathutils.unstrip_path(
            posixpath.dirname(pathutils.strip_path(path)), True)
        permissions = self.Rights.authorized(user, path, "Ww")
        parent_permissions = self.Rights.authorized(user, parent_path, "w")

        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 = radicale_item.predict_tag_of_whole_collection(
                    vobject_items, tags.get(content_type))
                if not tag:
                    raise ValueError("Can't determine collection tag")
                collection_path = pathutils.strip_path(path)
            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 = radicale_item.predict_tag_of_parent_collection(
                        vobject_items)
                collection_path = posixpath.dirname(
                    pathutils.strip_path(path))
            props = None
            stored_exc_info = None
            items = []
            try:
                if tag:
                    radicale_item.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=radicale_item.get_uid),
                            radicale_item.get_uid)
                        for uid, components in vobject_components_by_uid:
                            vobject_collection = vobject.iCalendar()
                            for component in components:
                                vobject_collection.add(component)
                            item = radicale_item.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 = radicale_item.Item(
                                collection_path=collection_path,
                                vobject_item=vobject_item)
                            item.prepare()
                            items.append(item)
                    elif not write_whole_collection:
                        vobject_item, = vobject_items
                        item = radicale_item.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
                    radicale_item.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)

        try:
            vobject_items = tuple(vobject.readComponents(content or ""))
        except Exception as e:
            logger.warning(
                "Bad PUT request on %r: %s", path, e, exc_info=True)
            return httputils.BAD_REQUEST
        (prepared_items, prepared_tag, prepared_write_whole_collection,
         prepared_props, prepared_exc_info) = prepare(vobject_items)

        with self.Collection.acquire_lock("w", user):
            item = next(self.Collection.discover(path), None)
            parent_item = next(self.Collection.discover(parent_path), None)
            if not parent_item:
                return httputils.CONFLICT

            write_whole_collection = (
                isinstance(item, storage.BaseCollection) or
                not parent_item.get_meta("tag"))

            if write_whole_collection:
                tag = prepared_tag
            else:
                tag = parent_item.get_meta("tag")

            if write_whole_collection:
                if not self.Rights.authorized(user, path, "w" if tag else "W"):
                    return httputils.NOT_ALLOWED
            elif not self.Rights.authorized(user, parent_path, "w"):
                return httputils.NOT_ALLOWED

            etag = environ.get("HTTP_IF_MATCH", "")
            if not item and etag:
                # Etag asked but no item found: item has been removed
                return httputils.PRECONDITION_FAILED
            if item and etag and item.etag != etag:
                # Etag asked but item not matching: item has changed
                return httputils.PRECONDITION_FAILED

            match = environ.get("HTTP_IF_NONE_MATCH", "") == "*"
            if item and match:
                # Creation asked but item found: item can't be replaced
                return httputils.PRECONDITION_FAILED

            if (tag != prepared_tag or
                    prepared_write_whole_collection != write_whole_collection):
                (prepared_items, prepared_tag, prepared_write_whole_collection,
                 prepared_props, prepared_exc_info) = prepare(
                    vobject_items, tag, write_whole_collection)
            props = prepared_props
            if prepared_exc_info:
                logger.warning(
                    "Bad PUT request on %r: %s", path, prepared_exc_info[1],
                    exc_info=prepared_exc_info)
                return httputils.BAD_REQUEST

            if write_whole_collection:
                try:
                    etag = self.Collection.create_collection(
                        path, prepared_items, props).etag
                except ValueError as e:
                    logger.warning(
                        "Bad PUT request on %r: %s", path, e, exc_info=True)
                    return httputils.BAD_REQUEST
            else:
                prepared_item, = prepared_items
                if (item and item.uid != prepared_item.uid or
                        not item and parent_item.has_uid(prepared_item.uid)):
                    return self.webdav_error_response(
                        "C" if tag == "VCALENDAR" else "CR",
                        "no-uid-conflict")

                href = posixpath.basename(pathutils.strip_path(path))
                try:
                    etag = parent_item.upload(href, prepared_item).etag
                except ValueError as e:
                    logger.warning(
                        "Bad PUT request on %r: %s", path, e, exc_info=True)
                    return httputils.BAD_REQUEST

            headers = {"ETag": etag}
            return client.CREATED, headers, None
예제 #39
0
파일: rights.py 프로젝트: wkchan/Radicale
 def authorized(self, user, path, permissions):
     sane_path = pathutils.strip_path(path)
     if sane_path not in ("tmp", "other"):
         return ""
     return rights.intersect_permissions(permissions)
예제 #40
0
파일: put.py 프로젝트: wkchan/Radicale
    def do_PUT(self, environ, base_prefix, path, user):
        """Manage PUT request."""
        if not self.access(user, path, "w"):
            return httputils.NOT_ALLOWED
        try:
            content = self.read_content(environ)
        except RuntimeError as e:
            logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True)
            return httputils.BAD_REQUEST
        except socket.timeout:
            logger.debug("client timed out", exc_info=True)
            return httputils.REQUEST_TIMEOUT
        # Prepare before locking
        parent_path = pathutils.unstrip_path(
            posixpath.dirname(pathutils.strip_path(path)), True)
        permissions = self.Rights.authorized(user, path, "Ww")
        parent_permissions = self.Rights.authorized(user, parent_path, "w")

        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 = radicale_item.predict_tag_of_whole_collection(
                    vobject_items, tags.get(content_type))
                if not tag:
                    raise ValueError("Can't determine collection tag")
                collection_path = pathutils.strip_path(path)
            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 = radicale_item.predict_tag_of_parent_collection(
                        vobject_items)
                collection_path = posixpath.dirname(pathutils.strip_path(path))
            props = None
            stored_exc_info = None
            items = []
            try:
                if tag:
                    radicale_item.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=radicale_item.get_uid),
                            radicale_item.get_uid)
                        for uid, components in vobject_components_by_uid:
                            vobject_collection = vobject.iCalendar()
                            for component in components:
                                vobject_collection.add(component)
                            item = radicale_item.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 = radicale_item.Item(
                                collection_path=collection_path,
                                vobject_item=vobject_item)
                            item.prepare()
                            items.append(item)
                    elif not write_whole_collection:
                        vobject_item, = vobject_items
                        item = radicale_item.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
                    radicale_item.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)

        try:
            vobject_items = tuple(vobject.readComponents(content or ""))
        except Exception as e:
            logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True)
            return httputils.BAD_REQUEST
        (prepared_items, prepared_tag, prepared_write_whole_collection,
         prepared_props, prepared_exc_info) = prepare(vobject_items)

        with self.Collection.acquire_lock("w", user):
            item = next(self.Collection.discover(path), None)
            parent_item = next(self.Collection.discover(parent_path), None)
            if not parent_item:
                return httputils.CONFLICT

            write_whole_collection = (isinstance(item, storage.BaseCollection)
                                      or not parent_item.get_meta("tag"))

            if write_whole_collection:
                tag = prepared_tag
            else:
                tag = parent_item.get_meta("tag")

            if write_whole_collection:
                if not self.Rights.authorized(user, path, "w" if tag else "W"):
                    return httputils.NOT_ALLOWED
            elif not self.Rights.authorized(user, parent_path, "w"):
                return httputils.NOT_ALLOWED

            etag = environ.get("HTTP_IF_MATCH", "")
            if not item and etag:
                # Etag asked but no item found: item has been removed
                return httputils.PRECONDITION_FAILED
            if item and etag and item.etag != etag:
                # Etag asked but item not matching: item has changed
                return httputils.PRECONDITION_FAILED

            match = environ.get("HTTP_IF_NONE_MATCH", "") == "*"
            if item and match:
                # Creation asked but item found: item can't be replaced
                return httputils.PRECONDITION_FAILED

            if (tag != prepared_tag or
                    prepared_write_whole_collection != write_whole_collection):
                (prepared_items, prepared_tag, prepared_write_whole_collection,
                 prepared_props,
                 prepared_exc_info) = prepare(vobject_items, tag,
                                              write_whole_collection)
            props = prepared_props
            if prepared_exc_info:
                logger.warning("Bad PUT request on %r: %s",
                               path,
                               prepared_exc_info[1],
                               exc_info=prepared_exc_info)
                return httputils.BAD_REQUEST

            if write_whole_collection:
                try:
                    etag = self.Collection.create_collection(
                        path, prepared_items, props).etag
                except ValueError as e:
                    logger.warning("Bad PUT request on %r: %s",
                                   path,
                                   e,
                                   exc_info=True)
                    return httputils.BAD_REQUEST
            else:
                prepared_item, = prepared_items
                if (item and item.uid != prepared_item.uid or not item
                        and parent_item.has_uid(prepared_item.uid)):
                    return self.webdav_error_response(
                        "C" if tag == "VCALENDAR" else "CR", "no-uid-conflict")

                href = posixpath.basename(pathutils.strip_path(path))
                try:
                    etag = parent_item.upload(href, prepared_item).etag
                except ValueError as e:
                    logger.warning("Bad PUT request on %r: %s",
                                   path,
                                   e,
                                   exc_info=True)
                    return httputils.BAD_REQUEST

            headers = {"ETag": etag}
            return client.CREATED, headers, None
예제 #41
0
def _get_attributes_from_path(path):
    attributes = pathutils.strip_path(path).split("/")
    if not attributes[0]:
        attributes.pop()
    return attributes
예제 #42
0
파일: put.py 프로젝트: wkchan/Radicale
        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 = radicale_item.predict_tag_of_whole_collection(
                    vobject_items, tags.get(content_type))
                if not tag:
                    raise ValueError("Can't determine collection tag")
                collection_path = pathutils.strip_path(path)
            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 = radicale_item.predict_tag_of_parent_collection(
                        vobject_items)
                collection_path = posixpath.dirname(pathutils.strip_path(path))
            props = None
            stored_exc_info = None
            items = []
            try:
                if tag:
                    radicale_item.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=radicale_item.get_uid),
                            radicale_item.get_uid)
                        for uid, components in vobject_components_by_uid:
                            vobject_collection = vobject.iCalendar()
                            for component in components:
                                vobject_collection.add(component)
                            item = radicale_item.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 = radicale_item.Item(
                                collection_path=collection_path,
                                vobject_item=vobject_item)
                            item.prepare()
                            items.append(item)
                    elif not write_whole_collection:
                        vobject_item, = vobject_items
                        item = radicale_item.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
                    radicale_item.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)