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),), ) )
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),) ))
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),) ))
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
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), )))
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, ))
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)
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,))
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)