OwlCyberSecurity - MANAGER
Edit File: caldavxml.py
# # # Copyright (c) 2005-2017 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # """ CalDAV XML Support. This module provides XML utilities for use with CalDAV. This API is considered private to static.py and is therefore subject to change. See draft spec: http://ietf.webdav.org/caldav/draft-dusseault-caldav.txt """ from pycalendar.datetime import DateTime from pycalendar.timezone import Timezone from txdav.xml.element import registerElement, dav_namespace from txdav.xml.element import WebDAVElement, PCDATAElement from txdav.xml.element import WebDAVEmptyElement, WebDAVTextElement from txdav.xml.element import ResourceType, Collection from twext.python.log import Logger from twistedcaldav.config import config from twistedcaldav.ical import Component as iComponent log = Logger() # # CalDAV objects # caldav_namespace = "urn:ietf:params:xml:ns:caldav" caldav_full_compliance = ( "calendar-access", "calendar-schedule", "calendar-auto-schedule", "calendar-availability", "inbox-availability", ) caldav_implicit_compliance = ( "calendar-access", "calendar-auto-schedule", "calendar-availability", "inbox-availability", ) caldav_query_extended_compliance = ( "calendar-query-extended", ) caldav_timezones_by_reference_compliance = ( "calendar-no-timezone", ) class CalDAVElement (WebDAVElement): """ CalDAV XML element. """ namespace = caldav_namespace class CalDAVEmptyElement (WebDAVEmptyElement): """ CalDAV element with no contents. """ namespace = caldav_namespace class CalDAVTextElement (WebDAVTextElement): """ CalDAV element containing PCDATA. """ namespace = caldav_namespace class CalDAVTimeRangeElement (CalDAVEmptyElement): """ CalDAV element containing a time range. """ allowed_attributes = { "start": False, "end": False, } def __init__(self, *children, **attributes): super(CalDAVTimeRangeElement, self).__init__(*children, **attributes) # One of start or end must be present if "start" not in attributes and "end" not in attributes: raise ValueError("One of 'start' or 'end' must be present in CALDAV:time-range") self.start = DateTime.parseText(attributes["start"]) if "start" in attributes else None self.end = DateTime.parseText(attributes["end"]) if "end" in attributes else None def valid(self, level=0): """ Indicate whether the time-range is valid (must be date-time in UTC). @return: True if valid, False otherwise """ if self.start is not None and self.start.isDateOnly(): log.info("start attribute in <time-range> is not a date-time: {start}", start=self.start) return False if self.end is not None and self.end.isDateOnly(): log.info("end attribute in <time-range> is not a date-time: {end}", end=self.end) return False if self.start is not None and not self.start.utc(): log.info("start attribute in <time-range> is not UTC: {start}", start=self.start) return False if self.end is not None and not self.end.utc(): log.info("end attribute in <time-range> is not UTC: {end}", end=self.end) return False # No other tests return True class CalDAVDataMixin(object): """ A mixin to support accept/returning data in various formats. """ def __init__(self, *children, **attributes): if "content-type" in attributes: self.content_type = attributes["content-type"] else: self.content_type = "text/calendar" if "version" in attributes: self.version = attributes["version"] else: self.version = "2.0" super(CalDAVDataMixin, self).__init__(*children, **attributes) def verifyTypeVersion(self): """ Make sure any content-type and version matches at least one supported set. @return: C{True} if there is at least one match, C{False} otherwise. """ allowedTypes = set() allowedTypes.add(("text/calendar", "2.0",)) if config.EnableJSONData: allowedTypes.add(("application/calendar+json", "2.0",)) for format, version in allowedTypes: if (format == self.content_type) and (version == self.version): return True return False @classmethod def fromCalendar(clazz, calendar, format=None): attrs = {} if format is not None and format != "text/calendar": attrs["content-type"] = format if isinstance(calendar, str): if not calendar: raise ValueError("Missing calendar data") return clazz(PCDATAElement(calendar), **attrs) elif isinstance(calendar, iComponent): assert calendar.name() == "VCALENDAR", "Not a calendar: %r" % (calendar,) return clazz(PCDATAElement(calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=format)), **attrs) else: raise ValueError("Not a calendar: %s" % (calendar,)) fromTextData = fromCalendar fromComponent = fromCalendar def calendar(self): """ Returns a calendar component derived from this element. """ data = self.calendarData() if data: return iComponent.fromString(data, format=self.content_type) else: return None generateComponent = calendar def calendarData(self): """ Returns the calendar data derived from this element. """ data = None for data in self.children: if not isinstance(data, PCDATAElement): return None else: # We guaranteed in __init__() that there is only one child... break return str(data) textData = calendarData class CalDAVTimeZoneElement (CalDAVDataMixin, CalDAVTextElement): """ CalDAV element containing iCalendar data with a single VTIMEZONE component. """ def gettimezone(self): """ Get the timezone to use. If none, return UTC timezone. @return: the L{Timezone} derived from the VTIMEZONE or utc. """ calendar = self.calendar() if calendar is not None: tz = calendar.gettimezone() if tz is not None: return tz # Default to using utc tzinfo return Timezone.UTCTimezone def valid(self): """ Determine whether the content of this element is a valid single VTIMEZONE component. @return: True if valid, False if not. """ try: calendar = self.calendar() if calendar is None: return False except ValueError: return False found = False for subcomponent in calendar.subcomponents(): if (subcomponent.name() == "VTIMEZONE"): if found: return False else: found = True else: return False return found @registerElement class CalendarHomeSet (CalDAVElement): """ The calendar collections URLs for this principal's calendar user. (CalDAV-access, RFC 4791 section 6.2.1) """ name = "calendar-home-set" hidden = True allowed_children = {(dav_namespace, "href"): (0, None)} @registerElement class CalendarDescription (CalDAVTextElement): """ Provides a human-readable description of what this calendar collection represents. (CalDAV-access, RFC 4791 section 5.2.1) """ name = "calendar-description" hidden = True # May be protected; but we'll let the client set this if they like. @registerElement class CalendarTimeZone (CalDAVTimeZoneElement): """ Specifies a time zone on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.2) """ name = "calendar-timezone" hidden = True allowed_attributes = { "content-type": False, "version": False, } @registerElement class CalendarTimeZoneID (CalDAVTextElement): """ Specifies a time zone id on a calendar collection. (draft-ietf-tzdist-caldav-timezone-ref-01, Section-5.2) """ name = "calendar-timezone-id" hidden = True @registerElement class SupportedCalendarComponentSets (CalDAVElement): """ Indicates what set of calendar components the server is willing to allow the client to use in MKCALENDAR. (CalDAV-extensions, draft-daboo-caldav-extensions section XXX) """ name = "supported-calendar-component-sets" hidden = True protected = True allowed_children = {(caldav_namespace, "supported-calendar-component-set"): (0, None)} @registerElement class SupportedCalendarComponentSet (CalDAVElement): """ Indicates what set of calendar components are allowed in a collection. (CalDAV-access, RFC 4791 section 5.2.3) """ name = "supported-calendar-component-set" hidden = True protected = True allowed_children = {(caldav_namespace, "comp"): (0, None)} @registerElement class SupportedCalendarData (CalDAVElement): """ Specifies restrictions on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.4) """ name = "supported-calendar-data" hidden = True protected = True allowed_children = {(caldav_namespace, "calendar-data"): (0, None)} @registerElement class MaxResourceSize (CalDAVTextElement): """ Specifies restrictions on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.5) """ name = "max-resource-size" hidden = True protected = True @registerElement class MinDateTime (CalDAVTextElement): """ Specifies restrictions on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.6) """ name = "min-date-time" hidden = True protected = True @registerElement class MaxDateTime (CalDAVTextElement): """ Specifies restrictions on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.7) """ name = "max-date-time" hidden = True protected = True @registerElement class MaxInstances (CalDAVTextElement): """ Specifies restrictions on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.8) """ name = "max-instances" hidden = True protected = True @registerElement class MaxAttendeesPerInstance (CalDAVTextElement): """ Specifies restrictions on a calendar collection. (CalDAV-access, RFC 4791 section 5.2.9) """ name = "max-attendees-per-instance" hidden = True protected = True @registerElement class Calendar (CalDAVEmptyElement): """ Denotes a calendar collection. (CalDAV-access, RFC 4791 sections 4.2 & 9.1) """ name = "calendar" @registerElement class MakeCalendar (CalDAVElement): """ Top-level element for request body in MKCALENDAR. (CalDAV-access, RFC 4791 section 9.2) """ name = "mkcalendar" allowed_children = {(dav_namespace, "set"): (0, 1)} child_types = {"WebDAVUnknownElement": (0, None)} @registerElement class MakeCalendarResponse (CalDAVElement): """ Top-level element for response body in MKCALENDAR. (CalDAV-access, RFC 4791 section 9.3) """ name = "mkcalendar-response" allowed_children = {WebDAVElement: (0, None)} @registerElement class CalendarQuery (CalDAVElement): """ Defines a report for querying calendar data. (CalDAV-access, RFC 4791 section 9.5) """ name = "calendar-query" allowed_children = { (dav_namespace, "allprop"): (0, None), (dav_namespace, "propname"): (0, None), (dav_namespace, "prop"): (0, None), (caldav_namespace, "timezone"): (0, 1), (caldav_namespace, "timezone-id"): (0, 1), (caldav_namespace, "filter"): (0, 1), # Actually (1, 1) unless element is empty } def __init__(self, *children, **attributes): super(CalendarQuery, self).__init__(*children, **attributes) props = None filter = None timezone = None timezone_id = None for child in self.children: qname = child.qname() if qname in ( (dav_namespace, "allprop"), (dav_namespace, "propname"), (dav_namespace, "prop"), ): if props is not None: raise ValueError("Only one of CalDAV:allprop, CalDAV:propname, CalDAV:prop allowed") props = child elif qname == (caldav_namespace, "filter"): filter = child elif qname == (caldav_namespace, "timezone"): if timezone_id is not None: raise ValueError("Only one of CalDAV:timezone and CalDAV:timezone-id allowed") timezone = child elif qname == (caldav_namespace, "timezone-id"): if timezone is not None: raise ValueError("Only one of CalDAV:timezone and CalDAV:timezone-id allowed") timezone_id = child else: raise AssertionError("We shouldn't be here") if len(self.children) > 0: if filter is None: raise ValueError("CALDAV:filter required") self.props = props self.filter = filter self.timezone = timezone self.timezone_id = timezone_id @registerElement class CalendarData (CalDAVDataMixin, CalDAVElement): """ Defines which parts of a calendar component object should be returned by a report. (CalDAV-access, RFC 4791 section 9.6) """ name = "calendar-data" allowed_children = { (caldav_namespace, "comp"): (0, None), (caldav_namespace, "expand"): (0, 1), (caldav_namespace, "limit-recurrence-set"): (0, 1), (caldav_namespace, "limit-freebusy-set"): (0, 1), PCDATAElement: (0, None), } allowed_attributes = { "content-type": False, "version": False, } def __init__(self, *children, **attributes): super(CalendarData, self).__init__(*children, **attributes) component = None recurrence_set = None freebusy_set = None data = None for child in self.children: qname = child.qname() if qname == (caldav_namespace, "comp"): component = child elif qname in ( (caldav_namespace, "expand"), (caldav_namespace, "limit-recurrence-set"), ): if recurrence_set is not None: raise ValueError("Only one of CalDAV:expand, CalDAV:limit-recurrence-set allowed") recurrence_set = child elif qname == (caldav_namespace, "limit-freebusy-set"): freebusy_set = child elif isinstance(child, PCDATAElement): if data is None: data = child else: data += child else: raise AssertionError("We shouldn't be here") self.component = component self.recurrence_set = recurrence_set self.freebusy_set = freebusy_set if data is not None: try: if component is not None: raise ValueError("Only one of CalDAV:comp (%r) or PCDATA (%r) allowed" % (component, str(data))) if recurrence_set is not None: raise ValueError("%s not allowed with PCDATA (%r)" % (recurrence_set, str(data))) except ValueError: if not data.isWhitespace(): raise else: # Since we've already combined PCDATA elements, we'd may as well # optimize them originals away self.children = (data,) @registerElement class CalendarComponent (CalDAVElement): """ Defines which component types to return. (CalDAV-access, RFC 4791 section 9.6.1) """ name = "comp" allowed_children = { (caldav_namespace, "allcomp"): (0, 1), (caldav_namespace, "comp"): (0, None), (caldav_namespace, "allprop"): (0, 1), (caldav_namespace, "prop"): (0, None), } allowed_attributes = {"name": True} def __init__(self, *children, **attributes): super(CalendarComponent, self).__init__(*children, **attributes) components = None properties = None for child in self.children: qname = child.qname() if qname == (caldav_namespace, "allcomp"): if components is not None: raise ValueError("CalDAV:allcomp and CalDAV:comp may not be combined") components = child elif qname == (caldav_namespace, "comp"): try: components.append(child) except AttributeError: if components is None: components = [child] else: raise ValueError("CalDAV:allcomp and CalDAV:comp may not be combined") elif qname == (caldav_namespace, "allprop"): if properties is not None: raise ValueError( "CalDAV:allprop and CalDAV:prop may not be combined" ) properties = child elif qname == (caldav_namespace, "prop"): try: properties.append(child) except AttributeError: if properties is None: properties = [child] else: raise ValueError("CalDAV:allprop and CalDAV:prop may not be combined") else: raise AssertionError("Unexpected element: %r" % (child,)) self.components = components self.properties = properties self.type = self.attributes["name"] def getFromICalendar(self, component): """ Returns a calendar component object containing the data in the given component which is specified by this CalendarComponent. """ if self.type != component.name(): raise ValueError("%s of type %r can't get data from component of type %r" % (self.sname(), self.type, component.name())) result = iComponent(self.type) xml_components = self.components xml_properties = self.properties # Empty element means do all properties and components if xml_components is None and xml_properties is None: xml_components = AllComponents() xml_properties = AllProperties() if xml_components is not None: if xml_components == AllComponents(): for ical_subcomponent in component.subcomponents(): result.addComponent(ical_subcomponent) else: for xml_subcomponent in xml_components: for ical_subcomponent in component.subcomponents(): if ical_subcomponent.name() == xml_subcomponent.type: result.addComponent(xml_subcomponent.getFromICalendar(ical_subcomponent)) if xml_properties is not None: if xml_properties == AllProperties(): for ical_property in component.properties(): result.addProperty(ical_property) else: for xml_property in xml_properties: name = xml_property.property_name for ical_property in component.properties(name): result.addProperty(ical_property) return result @registerElement class AllComponents (CalDAVEmptyElement): """ Specifies that all components shall be returned. (CalDAV-access, RFC 4791 section 9.6.2) """ name = "allcomp" @registerElement class AllProperties (CalDAVEmptyElement): """ Specifies that all properties shall be returned. (CalDAV-access, RFC 4791 section 9.6.3) """ name = "allprop" @registerElement class Property (CalDAVEmptyElement): """ Defines a property to return in a response. (CalDAV-access, RFC 4791 section 9.6.4) """ name = "prop" allowed_attributes = { "name": True, "novalue": False, } def __init__(self, *children, **attributes): super(Property, self).__init__(*children, **attributes) self.property_name = attributes["name"] if "novalue" in attributes: novalue = attributes["novalue"] if novalue == "yes": self.novalue = True elif novalue == "no": self.novalue = False else: raise ValueError("Invalid novalue: %r" % (novalue,)) else: self.novalue = False @registerElement class Expand (CalDAVTimeRangeElement): """ Specifies that the server should expand recurring components into separate instances. (CalDAV-access, RFC 4791 section 9.6.5) """ name = "expand" @registerElement class LimitRecurrenceSet (CalDAVTimeRangeElement): """ Specifies a time range to limit the set of recurrence instances returned by the server. (CalDAV-access, RFC 4791 section 9.6.6) """ name = "limit-recurrence-set" @registerElement class LimitFreeBusySet (CalDAVTimeRangeElement): """ Specifies a time range to limit the set of FREEBUSY properties returned by the server. (CalDAV-access, RFC 4791 section 9.6.7) """ name = "limit-freebusy-set" @registerElement class Filter (CalDAVElement): """ Determines which matching components are returned. (CalDAV-access, RFC 4791 section 9.7) """ name = "filter" allowed_children = {(caldav_namespace, "comp-filter"): (1, 1)} @registerElement class ComponentFilter (CalDAVElement): """ Limits a search to only the chosen component types. (CalDAV-access, RFC 4791 section 9.7.1) """ name = "comp-filter" allowed_children = { (caldav_namespace, "is-not-defined"): (0, 1), (caldav_namespace, "time-range"): (0, 1), (caldav_namespace, "comp-filter"): (0, None), (caldav_namespace, "prop-filter"): (0, None), } allowed_attributes = { "name": True, "test": False, } @registerElement class PropertyFilter (CalDAVElement): """ Limits a search to specific properties. (CalDAV-access, RFC 4791 section 9.7.2) """ name = "prop-filter" allowed_children = { (caldav_namespace, "is-not-defined"): (0, 1), (caldav_namespace, "time-range"): (0, 1), (caldav_namespace, "text-match"): (0, 1), (caldav_namespace, "param-filter"): (0, None), } allowed_attributes = { "name": True, "test": False, } @registerElement class ParameterFilter (CalDAVElement): """ Limits a search to specific parameters. (CalDAV-access, RFC 4791 section 9.7.3) """ name = "param-filter" allowed_children = { (caldav_namespace, "is-not-defined"): (0, 1), (caldav_namespace, "text-match"): (0, 1), } allowed_attributes = {"name": True} @registerElement class IsNotDefined (CalDAVEmptyElement): """ Specifies that the named iCalendar item does not exist. (CalDAV-access, RFC 4791 section 9.7.4) """ name = "is-not-defined" @registerElement class TextMatch (CalDAVTextElement): """ Specifies a substring match on a property or parameter value. (CalDAV-access, RFC 4791 section 9.7.5) """ name = "text-match" def fromString(clazz, string, caseless=False): # @NoSelf if caseless: caseless = "yes" else: caseless = "no" if type(string) is str: return clazz(PCDATAElement(string), caseless=caseless) elif type(string) is unicode: return clazz(PCDATAElement(string.encode("utf-8")), caseless=caseless) else: return clazz(PCDATAElement(str(string)), caseless=caseless) fromString = classmethod(fromString) allowed_attributes = { "caseless": False, "negate-condition": False, "match-type": False, } @registerElement class TimeZone (CalDAVTimeZoneElement): """ Specifies a time zone component. (CalDAV-access, RFC 4791 section 9.8) """ name = "timezone" allowed_attributes = { "content-type": False, "version": False, } @registerElement class TimeZoneID (CalDAVTextElement): """ Specifies a time zone id. (draft-ietf-tzdist-caldav-timezone-ref-01, Section-5.2) """ name = "timezone-id" @registerElement class TimeRange (CalDAVTimeRangeElement): """ Specifies a time for testing components against. (CalDAV-access, RFC 4791 section 9.9) """ name = "time-range" @registerElement class CalendarMultiGet (CalDAVElement): """ CalDAV report used to retrieve specific calendar component items via their URIs. (CalDAV-access, RFC 4791 section 9.10) """ name = "calendar-multiget" # To allow for an empty element in a supported-report-set property we need # to relax the child restrictions allowed_children = { (dav_namespace, "allprop"): (0, 1), (dav_namespace, "propname"): (0, 1), (dav_namespace, "prop"): (0, 1), (dav_namespace, "href"): (0, None), # Actually ought to be (1, None) } def __init__(self, *children, **attributes): super(CalendarMultiGet, self).__init__(*children, **attributes) property = None resources = [] for child in self.children: qname = child.qname() if qname in ( (dav_namespace, "allprop"), (dav_namespace, "propname"), (dav_namespace, "prop"), ): if property is not None: raise ValueError("Only one of DAV:allprop, DAV:propname, DAV:prop allowed") property = child elif qname == (dav_namespace, "href"): resources.append(child) self.property = property self.resources = resources @registerElement class FreeBusyQuery (CalDAVElement): """ CalDAV report used to generate a VFREEBUSY to determine busy time over a specific time range. (CalDAV-access, RFC 4791 section 9.11) """ name = "free-busy-query" # To allow for an empty element in a supported-report-set property we need # to relax the child restrictions allowed_children = {(caldav_namespace, "time-range"): (0, 1)} # Actually ought to be (1, 1) def __init__(self, *children, **attributes): super(FreeBusyQuery, self).__init__(*children, **attributes) timerange = None for child in self.children: qname = child.qname() if qname == (caldav_namespace, "time-range"): if timerange is not None: raise ValueError("Only one time-range element allowed in free-busy-query: %r" % (self,)) timerange = child else: raise ValueError("Unknown element %r in free-busy-query: %r" % (child, self)) self.timerange = timerange @registerElement class ReadFreeBusy(CalDAVEmptyElement): """ Privilege which allows the free busy report to be executed. (CalDAV-access, RFC 4791 section 6.1.1) """ name = "read-free-busy" @registerElement class NoUIDConflict(CalDAVElement): """ CalDAV precondition used to indicate a UID conflict during PUT/COPY/MOVE. The conflicting resource href must be returned as a child. """ name = "no-uid-conflict" allowed_children = {(dav_namespace, "href"): (1, 1)} @registerElement class SupportedFilter(CalDAVElement): """ CalDAV precondition used to indicate an unsupported component type in a query filter. The conflicting filter elements are returned. """ name = "supported-filter" allowed_children = { (caldav_namespace, "comp-filter"): (0, None), (caldav_namespace, "prop-filter"): (0, None), (caldav_namespace, "param-filter"): (0, None) } # # CalDAV Schedule objects # @registerElement class CalendarUserAddressSet (CalDAVElement): """ The list of calendar user addresses for this principal's calendar user. (CalDAV-schedule, section x.x.x) """ name = "calendar-user-address-set" hidden = True allowed_children = {(dav_namespace, "href"): (0, None)} @registerElement class CalendarFreeBusySet (CalDAVElement): """ The list of calendar URIs that contribute to free-busy for this principal's calendar user. This was defined in the old caldav scheduling spec but has been removed from the new one. We still need to support it for backwards compatibility. """ name = "calendar-free-busy-set" hidden = True allowed_children = {(dav_namespace, "href"): (0, None)} @registerElement class ScheduleCalendarTransp (CalDAVElement): """ Indicates whether a calendar should be used for freebusy lookups. """ name = "schedule-calendar-transp" allowed_children = { (caldav_namespace, "opaque"): (0, 1), (caldav_namespace, "transparent"): (0, 1), } @registerElement class Opaque (CalDAVEmptyElement): """ Indicates that a calendar is used in freebusy lookups. """ name = "opaque" @registerElement class Transparent (CalDAVEmptyElement): """ Indicates that a calendar is not used in freebusy lookups. """ name = "transparent" @registerElement class ScheduleDefaultCalendarURL (CalDAVElement): """ A single href indicating which calendar is the default for scheduling. """ name = "schedule-default-calendar-URL" allowed_children = {(dav_namespace, "href"): (0, 1)} @registerElement class ScheduleInboxURL (CalDAVElement): """ A principal property to indicate the schedule INBOX for the principal. (CalDAV-schedule, section x.x.x) """ name = "schedule-inbox-URL" hidden = True protected = True allowed_children = {(dav_namespace, "href"): (0, 1)} @registerElement class ScheduleOutboxURL (CalDAVElement): """ A principal property to indicate the schedule OUTBOX for the principal. (CalDAV-schedule, section x.x.x) """ name = "schedule-outbox-URL" hidden = True protected = True allowed_children = {(dav_namespace, "href"): (0, 1)} @registerElement class ScheduleTag (CalDAVTextElement): """ Property on scheduling resources. (CalDAV-schedule, section x.x.x) """ name = "schedule-tag" hidden = True protected = True @registerElement class ScheduleInbox (CalDAVEmptyElement): """ Denotes the resource type of a calendar schedule Inbox. (CalDAV-schedule-xx, section x.x.x) """ name = "schedule-inbox" @registerElement class ScheduleOutbox (CalDAVEmptyElement): """ Denotes the resource type of a calendar schedule Outbox. (CalDAV-schedule-xx, section x.x.x) """ name = "schedule-outbox" @registerElement class Trash (CalDAVEmptyElement): """ Denotes the resource type of a trash collection. """ name = "trash" @registerElement class ScheduleResponse (CalDAVElement): """ The set of responses for a SCHEDULE method operation. (CalDAV-schedule-xx, section x.x.x) """ name = "schedule-response" allowed_children = {(caldav_namespace, "response"): (0, None)} @registerElement class Response (CalDAVElement): """ A response to an iTIP request against a specific recipient. (CalDAV-schedule-xx, section x.x.x) """ name = "response" allowed_children = { (caldav_namespace, "recipient"): (1, 1), (caldav_namespace, "request-status"): (1, 1), (caldav_namespace, "calendar-data"): (0, 1), (dav_namespace, "error"): (0, 1), # 2518bis (dav_namespace, "responsedescription"): (0, 1) } @registerElement class Recipient (CalDAVElement): """ The recipient for whom this response is for. (CalDAV-schedule, section x.x.x) """ name = "recipient" allowed_children = {(dav_namespace, "href"): (0, None)} # NB Minimum is zero because this is a property name @registerElement class RequestStatus (CalDAVTextElement): """ The iTIP REQUEST-STATUS value for the iTIP operation. (CalDAV-schedule, section x.x.x) """ name = "request-status" @registerElement class Schedule (CalDAVEmptyElement): """ Privilege which allows the SCHEDULE method to be executed. (CalDAV-schedule, section x.x.x) """ name = "schedule" @registerElement class ScheduleDeliver (CalDAVEmptyElement): """ Privilege which controls scheduling messages going into the Inbox. (CalDAV-schedule, section x.x.x) """ name = "schedule-deliver" @registerElement class ScheduleSend (CalDAVEmptyElement): """ Privilege which controls the ability to send scheduling messages. (CalDAV-schedule, section x.x.x) """ name = "schedule-send" @registerElement class CalendarUserType (CalDAVTextElement): """ The CALDAV:calendar-user-type property from section 9.2.4 of caldav-sched-05 """ name = "calendar-user-type" protected = True # # draft-daboo-valarm-extensions # caldav_default_alarms_compliance = ( "calendar-default-alarms", ) class DefaultAlarmBase (CalDAVTextElement): """ Common behavior for default alarm properties. """ calendartxt = None def calendar(self): """ Returns a calendar component derived from this element, which contains exactly one VEVENT with the VALARM embedded component, or C{None} if empty. """ valarm = str(self) return iComponent.fromString(self.calendartxt % str(self)) if valarm else None def valid(self): """ Determine whether the content of this element is a valid single VALARM component or empty. @return: True if valid, False if not. """ if str(self): try: calendar = self.calendar() if calendar is None: return False except ValueError: return False # Make sure there is one alarm component try: valarm = tuple(tuple(calendar.subcomponents())[0].subcomponents())[0] except IndexError: return False if valarm.name().upper() != "VALARM": return False return True @registerElement class DefaultAlarmVEventDateTime (DefaultAlarmBase): name = "default-alarm-vevent-datetime" calendartxt = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:bogus DTSTART:20111129T220000Z DURATION:PT1H DTSTAMP:20111129T220000Z SUMMARY:bogus %sEND:VEVENT END:VCALENDAR """ @registerElement class DefaultAlarmVEventDate (DefaultAlarmBase): name = "default-alarm-vevent-date" calendartxt = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:bogus DTSTART:20111129 DURATION:PT1H DTSTAMP:20111129T220000Z SUMMARY:bogus %sEND:VEVENT END:VCALENDAR """ @registerElement class DefaultAlarmVToDoDateTime (DefaultAlarmBase): name = "default-alarm-vtodo-datetime" calendartxt = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTODO UID:bogus DUE:20111129T220000Z DTSTAMP:20111129T220000Z SUMMARY:bogus %sEND:VTODO END:VCALENDAR """ @registerElement class DefaultAlarmVToDoDate (DefaultAlarmBase): name = "default-alarm-vtodo-date" calendartxt = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTODO UID:bogus DUE:20111129 DTSTAMP:20111129T220000Z SUMMARY:bogus %sEND:VTODO END:VCALENDAR """ # # draft-daboo-caldav-attachments # caldav_managed_attachments_compliance = ( "calendar-managed-attachments", ) @registerElement class ManagedAttachmentsServerURL (CalDAVElement): """ Zero or one href elements defining the base scheme/host for attachments. """ name = "managed-attachments-server-URL" allowed_children = {(dav_namespace, "href"): (0, 1)} # # draft-ietf-tzdist-caldav-timezone-ref # caldav_timezone_service_set_compliance = ( "timezone-service-set", ) @registerElement class TimezoneServiceSet (CalDAVElement): """ Zero or one href elements defining the base scheme/host for attachments. """ name = "timezone-service-set" allowed_children = {(dav_namespace, "href"): (0, 1)} # # Extensions to ResourceType # def _isCalendar(self): return bool(self.childrenOfType(Calendar)) ResourceType.isCalendar = _isCalendar ResourceType.calendar = ResourceType(Collection(), Calendar()) ResourceType.scheduleInbox = ResourceType(Collection(), ScheduleInbox()) ResourceType.scheduleOutbox = ResourceType(Collection(), ScheduleOutbox()) ResourceType.trash = ResourceType(Collection(), Trash())