Ejemplo n.º 1
0
def report_urn_ietf_params_xml_ns_caldav_free_busy_query(self, request, freebusy):  # @UnusedVariable
    """
    Generate a free-busy REPORT.
    (CalDAV-access-09, section 7.8)
    """
    if not self.isCollection():
        log.error("freebusy report is only allowed on collection resources %s" % (self,))
        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Not a calendar collection"))

    if freebusy.qname() != (caldavxml.caldav_namespace, "free-busy-query"):
        raise ValueError("{CalDAV:}free-busy-query expected as root element, not %s." % (freebusy.sname(),))

    timerange = freebusy.timerange
    if not timerange.valid():
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid time-range specified"))

    # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
    fbinfo = ([], [], [])

    matchcount = [0]

    accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes())
    if accepted_type is None:
        raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type"))

    def generateFreeBusyInfo(calresource, uri):  # @UnusedVariable
        """
        Run a free busy report on the specified calendar collection
        accumulating the free busy info for later processing.
        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collecton resource.
        """

        def _gotResult(result):
            matchcount[0] = result
            return True

        d = report_common.generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchcount[0])
        d.addCallback(_gotResult)
        return d

    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToCalendarCollections(
            self, request, request.uri, depth, generateFreeBusyInfo, (caldavxml.ReadFreeBusy(),)
        )
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in free-busy report")
        raise HTTPError(
            ErrorResponse(responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits(), "Too many components")
        )
    except TimeRangeLowerLimit, e:
        raise HTTPError(
            ErrorResponse(
                responsecode.FORBIDDEN,
                caldavxml.MinDateTime(),
                "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),),
            )
        )
Ejemplo n.º 2
0
def report_urn_ietf_params_xml_ns_caldav_free_busy_query(self, request, freebusy):
    """
    Generate a free-busy REPORT.
    (CalDAV-access-09, section 7.8)
    """
    if not self.isCollection():
        log.error("freebusy report is only allowed on collection resources %s" % (self,))
        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Not a calendar collection"))

    if freebusy.qname() != (caldavxml.caldav_namespace, "free-busy-query"):
        raise ValueError("{CalDAV:}free-busy-query expected as root element, not %s." % (freebusy.sname(),))

    timerange = freebusy.timerange
    if not timerange.valid():
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid time-range specified"))

    fbset = []

    accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes())
    if accepted_type is None:
        raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type"))


    def getCalendarList(calresource, uri): #@UnusedVariable
        """
        Store the calendars that match the query in L{fbset} which will then be used with the
        freebusy query.

        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collection resource.
        """

        fbset.append(calresource._newStoreObject)
        return succeed(True)

    # Run report taking depth into account
    depth = request.headers.getHeader("depth", "0")
    yield report_common.applyToCalendarCollections(self, request, request.uri, depth, getCalendarList, (caldavxml.ReadFreeBusy(),))

    # Do the actual freebusy query against the set of matched calendars
    principal = yield self.resourceOwnerPrincipal(request)
    organizer = recipient = LocalCalendarUser(principal.canonicalCalendarUserAddress(), principal.record)
    timerange = Period(timerange.start, timerange.end)
    try:
        fbresult = yield FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange).generateAttendeeFreeBusyResponse(fbset=fbset, method=None)
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in free-busy report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            davxml.NumberOfMatchesWithinLimits(),
            "Too many components"
        ))
    except TimeRangeLowerLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MinDateTime(),
            "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),)
        ))
Ejemplo n.º 3
0
def report_urn_ietf_params_xml_ns_caldav_free_busy_query(self, request, freebusy):
    """
    Generate a free-busy REPORT.
    (CalDAV-access-09, section 7.8)
    """
    if not self.isCollection():
        log.error("freebusy report is only allowed on collection resources {s!r}", s=self)
        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Not a calendar collection"))

    if freebusy.qname() != (caldavxml.caldav_namespace, "free-busy-query"):
        raise ValueError("{CalDAV:}free-busy-query expected as root element, not %s." % (freebusy.sname(),))

    timerange = freebusy.timerange
    if not timerange.valid():
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid time-range specified"))

    fbset = []

    accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes())
    if accepted_type is None:
        raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type"))

    def getCalendarList(calresource, uri):  # @UnusedVariable
        """
        Store the calendars that match the query in L{fbset} which will then be used with the
        freebusy query.

        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collection resource.
        """

        fbset.append(calresource._newStoreObject)
        return succeed(True)

    # Run report taking depth into account
    depth = request.headers.getHeader("depth", "0")
    yield report_common.applyToCalendarCollections(self, request, request.uri, depth, getCalendarList, (caldavxml.ReadFreeBusy(),))

    # Do the actual freebusy query against the set of matched calendars
    principal = yield self.resourceOwnerPrincipal(request)
    organizer = recipient = LocalCalendarUser(principal.canonicalCalendarUserAddress(), principal.record)
    timerange = Period(timerange.start, timerange.end)
    try:
        fbresult = yield FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange).generateAttendeeFreeBusyResponse(fbset=fbset, method=None)
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in free-busy report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            davxml.NumberOfMatchesWithinLimits(),
            "Too many components"
        ))
    except TimeRangeLowerLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MinDateTime(),
            "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),)
        ))
Ejemplo n.º 4
0
    def actionGet(self, request, tzid):
        """
        Return the specified timezone data.
        """

        if set(request.args.keys()) - set(("start", "end",)):
            self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST)

        accepted_type = bestAcceptType(request.headers.getHeader("accept"), self.formats)
        if accepted_type is None:
            self.problemReport("invalid-format", "Accept header does not match available media types", responsecode.NOT_ACCEPTABLE)

        calendar = self.timezones.getTimezone(tzid)
        if calendar is None:
            self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND)

        tzdata = calendar.getText(format=accepted_type if accepted_type != "text/plain" else None)

        response = Response()
        response.stream = MemoryStream(tzdata)
        response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,)))
        return response
Ejemplo n.º 5
0
    def actionGet(self, request, tzid):
        """
        Return the specified timezone data.
        """

        if set(request.args.keys()) - set(("start", "end",)):
            self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST)

        accepted_type = bestAcceptType(request.headers.getHeader("accept"), self.formats)
        if accepted_type is None:
            self.problemReport("invalid-format", "Accept header does not match available media types", responsecode.NOT_ACCEPTABLE)

        calendar = self.timezones.getTimezone(tzid)
        if calendar is None:
            self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND)

        tzdata = calendar.getText(format=accepted_type if accepted_type != "text/plain" else None)

        response = Response()
        response.stream = MemoryStream(tzdata)
        response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,)))
        return response
Ejemplo n.º 6
0
def report_urn_ietf_params_xml_ns_caldav_free_busy_query(
        self, request, freebusy):  #@UnusedVariable
    """
    Generate a free-busy REPORT.
    (CalDAV-access-09, section 7.8)
    """
    if not self.isCollection():
        log.error(
            "freebusy report is only allowed on collection resources %s" %
            (self, ))
        raise HTTPError(
            StatusResponse(responsecode.FORBIDDEN,
                           "Not a calendar collection"))

    if freebusy.qname() != (caldavxml.caldav_namespace, "free-busy-query"):
        raise ValueError(
            "{CalDAV:}free-busy-query expected as root element, not %s." %
            (freebusy.sname(), ))

    timerange = freebusy.timerange
    if not timerange.valid():
        raise HTTPError(
            StatusResponse(responsecode.BAD_REQUEST,
                           "Invalid time-range specified"))

    # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
    fbinfo = ([], [], [])

    matchcount = [0]

    accepted_type = bestAcceptType(request.headers.getHeader("accept"),
                                   Component.allowedTypes())
    if accepted_type is None:
        raise HTTPError(
            StatusResponse(responsecode.NOT_ACCEPTABLE,
                           "Cannot generate requested data type"))

    def generateFreeBusyInfo(calresource, uri):  #@UnusedVariable
        """
        Run a free busy report on the specified calendar collection
        accumulating the free busy info for later processing.
        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collecton resource.
        """
        def _gotResult(result):
            matchcount[0] = result
            return True

        d = report_common.generateFreeBusyInfo(request, calresource, fbinfo,
                                               timerange, matchcount[0])
        d.addCallback(_gotResult)
        return d

    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToCalendarCollections(
            self, request, request.uri, depth, generateFreeBusyInfo,
            (caldavxml.ReadFreeBusy(), ))
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in free-busy report")
        raise HTTPError(
            ErrorResponse(responsecode.FORBIDDEN,
                          davxml.NumberOfMatchesWithinLimits(),
                          "Too many components"))
    except TimeRangeLowerLimit, e:
        raise HTTPError(
            ErrorResponse(
                responsecode.FORBIDDEN, caldavxml.MinDateTime(),
                "Time-range value too far in the past. Must be on or after %s."
                % (str(e.limit), )))
Ejemplo n.º 7
0
    def test_bestAcceptType(self):

        data = (
            (
                "#1.1",
                ("Accept", "text/plain"),
                ["text/plain"],
                "text/plain",
            ),
            (
                "#1.2",
                ("Accept", "text/plain"),
                ["text/calendar"],
                None,
            ),
            (
                "#1.3",
                ("Accept", "text/*"),
                ["text/plain"],
                "text/plain",
            ),
            (
                "#1.4",
                ("Accept", "*/*"),
                ["text/plain"],
                "text/plain",
            ),
            (
                "#2.1",
                ("Accept", "text/plain"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#2.2",
                ("Accept", "text/plain"),
                [
                    "text/calendar",
                    "application/text",
                ],
                None,
            ),
            (
                "#2.3",
                ("Accept", "text/*"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#2.4",
                ("Accept", "*/*"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#2.5",
                ("Accept", "application/text"),
                [
                    "text/plain",
                    "application/text",
                ],
                "application/text",
            ),
            (
                "#2.6",
                ("Accept", "application/*"),
                [
                    "text/plain",
                    "application/text",
                ],
                "application/text",
            ),
            (
                "#3.1",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#3.2",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                [
                    "text/calendar",
                    "application/calendar",
                ],
                None,
            ),
            (
                "#3.3",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#3.4",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#3.5",
                ("Accept", "text/plain;q=0.3, application/text;q=0.5"),
                [
                    "text/plain",
                    "application/text",
                ],
                "application/text",
            ),
            (
                "#3.6",
                ("Accept", "text/plain;q=0.5, application/*;q=0.3"),
                [
                    "text/plain",
                    "application/text",
                ],
                "text/plain",
            ),
            (
                "#4.1",
                ("Accept",
                 "text/plain;q=0.5, application/text;q=0.2, text/*;q=0.3"),
                [
                    "text/calendar",
                    "application/text",
                ],
                "text/calendar",
            ),
            (
                "#5.1",
                None,
                [
                    "text/calendar",
                    "application/text",
                ],
                "text/calendar",
            ),
        )

        for title, hdr, allowedTypes, result in data:
            hdrs = Headers()
            if hdr:
                hdrs.addRawHeader(*hdr)
            check = bestAcceptType(hdrs.getHeader("accept"), allowedTypes)
            self.assertEqual(check, result, msg="Failed %s" % (title, ))
Ejemplo n.º 8
0
def http_GET(self, request):

    if self.exists():
        # Special sharing request on a calendar or address book
        if self.isCalendarCollection() or self.isAddressBookCollection():

            # Check for action=share
            if request.args:
                action = request.args.get("action", ("",))
                if len(action) != 1:
                    raise HTTPError(ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "valid-action"),
                        "Invalid action parameter: %s" % (action,),
                    ))
                action = action[0]

                dispatch = {
                    "share": self.directShare,
                }.get(action, None)

                if dispatch is None:
                    raise HTTPError(ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "supported-action"),
                        "Action not supported: %s" % (action,),
                    ))

                response = (yield dispatch(request))
                returnValue(response)

        else:
            # FIXME: this should be implemented in storebridge.CalendarObject.render

            # Look for calendar access restriction on existing resource.
            parentURL = parentForURL(request.uri)
            parent = (yield request.locateResource(parentURL))
            if isPseudoCalendarCollectionResource(parent):

                # Check authorization first
                yield self.authorize(request, (davxml.Read(),))

                # Accept header handling
                accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes())
                if accepted_type is None:
                    raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type"))

                caldata = (yield self.componentForUser())

                # Filter any attendee hidden instances
                caldata = HiddenInstanceFilter().filter(caldata)

                if self.accessMode:

                    # Non DAV:owner's have limited access to the data
                    isowner = (yield self.isOwner(request))

                    # Now "filter" the resource calendar data
                    caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)

                response = Response()
                response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type))
                response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,)))

                # Add Schedule-Tag header if property is present
                if self.scheduleTag:
                    response.headers.setHeader("Schedule-Tag", self.scheduleTag)

                returnValue(response)

    # Do normal GET behavior
    response = (yield super(CalDAVResource, self).http_GET(request))
    returnValue(response)
Ejemplo n.º 9
0
    def test_bestAcceptType(self):

        data = (
            ("#1.1", ("Accept", "text/plain"), ["text/plain"], "text/plain"),
            ("#1.2", ("Accept", "text/plain"), ["text/calendar"], None),
            ("#1.3", ("Accept", "text/*"), ["text/plain"], "text/plain"),
            ("#1.4", ("Accept", "*/*"), ["text/plain"], "text/plain"),
            ("#2.1", ("Accept", "text/plain"), ["text/plain", "application/text"], "text/plain"),
            ("#2.2", ("Accept", "text/plain"), ["text/calendar", "application/text"], None),
            ("#2.3", ("Accept", "text/*"), ["text/plain", "application/text"], "text/plain"),
            ("#2.4", ("Accept", "*/*"), ["text/plain", "application/text"], "text/plain"),
            ("#2.5", ("Accept", "application/text"), ["text/plain", "application/text"], "application/text"),
            ("#2.6", ("Accept", "application/*"), ["text/plain", "application/text"], "application/text"),
            (
                "#3.1",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                ["text/plain", "application/text"],
                "text/plain",
            ),
            (
                "#3.2",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                ["text/calendar", "application/calendar"],
                None,
            ),
            (
                "#3.3",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                ["text/plain", "application/text"],
                "text/plain",
            ),
            (
                "#3.4",
                ("Accept", "text/plain;q=0.5, application/text;q=0.3"),
                ["text/plain", "application/text"],
                "text/plain",
            ),
            (
                "#3.5",
                ("Accept", "text/plain;q=0.3, application/text;q=0.5"),
                ["text/plain", "application/text"],
                "application/text",
            ),
            (
                "#3.6",
                ("Accept", "text/plain;q=0.5, application/*;q=0.3"),
                ["text/plain", "application/text"],
                "text/plain",
            ),
            (
                "#4.1",
                ("Accept", "text/plain;q=0.5, application/text;q=0.2, text/*;q=0.3"),
                ["text/calendar", "application/text"],
                "text/calendar",
            ),
            ("#5.1", None, ["text/calendar", "application/text"], "text/calendar"),
        )

        for title, hdr, allowedTypes, result in data:
            hdrs = Headers()
            if hdr:
                hdrs.addRawHeader(*hdr)
            check = bestAcceptType(hdrs.getHeader("accept"), allowedTypes)
            self.assertEqual(check, result, msg="Failed %s" % (title,))
Ejemplo n.º 10
0
def http_GET(self, request):

    if self.exists():
        # Special sharing request on a calendar or address book
        if self.isCalendarCollection() or self.isAddressBookCollection():

            # Check for action=share
            if request.args:
                action = request.args.get("action", ("",))
                if len(action) != 1:
                    raise HTTPError(ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "valid-action"),
                        "Invalid action parameter: %s" % (action,),
                    ))
                action = action[0]

                dispatch = {
                    "share"   : self.directShare,
                }.get(action, None)

                if dispatch is None:
                    raise HTTPError(ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "supported-action"),
                        "Action not supported: %s" % (action,),
                    ))

                response = (yield dispatch(request))
                returnValue(response)

        else:
            # FIXME: this should be implemented in storebridge.CalendarObject.render

            # Look for calendar access restriction on existing resource.
            parentURL = parentForURL(request.uri)
            parent = (yield request.locateResource(parentURL))
            if isPseudoCalendarCollectionResource(parent):

                # Check authorization first
                yield self.authorize(request, (davxml.Read(),))

                # Accept header handling
                accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes())
                if accepted_type is None:
                    raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type"))

                caldata = (yield self.iCalendarForUser(request))

                # Filter any attendee hidden instances
                caldata = HiddenInstanceFilter().filter(caldata)

                if self.accessMode:

                    # Non DAV:owner's have limited access to the data
                    isowner = (yield self.isOwner(request))

                    # Now "filter" the resource calendar data
                    caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)

                response = Response()
                response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type))
                response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,)))

                # Add Schedule-Tag header if property is present
                if self.scheduleTag:
                    response.headers.setHeader("Schedule-Tag", self.scheduleTag)

                returnValue(response)

    # Do normal GET behavior
    response = (yield super(CalDAVResource, self).http_GET(request))
    returnValue(response)