def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None, method=None, event_details=None): """ Generate a VCALENDAR object containing a single VFREEBUSY that is the aggregate of the free busy info passed in. @param fbinfo: the array of busy periods to use. @param timerange: the L{TimeRange} for the query. @param organizer: the L{Property} for the Organizer of the free busy request, or None. @param attendee: the L{Property} for the Attendee responding to the free busy request, or None. @param uid: the UID value from the free busy request. @param method: the METHOD property value to insert. @param event_details: VEVENT components to add. @return: the L{Component} containing the calendar data. """ # Merge overlapping time ranges in each fb info section normalizePeriodList(fbinfo[0]) normalizePeriodList(fbinfo[1]) normalizePeriodList(fbinfo[2]) # Now build a new calendar object with the free busy info we have fbcalendar = Component("VCALENDAR") fbcalendar.addProperty(Property("VERSION", "2.0")) fbcalendar.addProperty(Property("PRODID", iCalendarProductID)) if method: fbcalendar.addProperty(Property("METHOD", method)) fb = Component("VFREEBUSY") fbcalendar.addComponent(fb) if organizer is not None: fb.addProperty(organizer) if attendee is not None: fb.addProperty(attendee) fb.addProperty(Property("DTSTART", timerange.start)) fb.addProperty(Property("DTEND", timerange.end)) fb.addProperty(Property("DTSTAMP", DateTime.getNowUTC())) if len(fbinfo[0]) != 0: fb.addProperty(Property("FREEBUSY", fbinfo[0], {"FBTYPE": "BUSY"})) if len(fbinfo[1]) != 0: fb.addProperty( Property("FREEBUSY", fbinfo[1], {"FBTYPE": "BUSY-TENTATIVE"})) if len(fbinfo[2]) != 0: fb.addProperty( Property("FREEBUSY", fbinfo[2], {"FBTYPE": "BUSY-UNAVAILABLE"})) if uid is not None: fb.addProperty(Property("UID", uid)) else: uid = str(uuid.uuid4()) fb.addProperty(Property("UID", uid)) if event_details: for vevent in event_details: fbcalendar.addComponent(vevent) return fbcalendar
def buildFreeBusyResult(self, fbinfo, method=None): """ Generate a VCALENDAR object containing a single VFREEBUSY that is the aggregate of the free busy info passed in. @param fbinfo: the array of busy periods to use. @param method: the METHOD property value to insert. @return: the L{Component} containing the calendar data. """ # Merge overlapping time ranges in each fb info section normalizePeriodList(fbinfo.busy) normalizePeriodList(fbinfo.tentative) normalizePeriodList(fbinfo.unavailable) # Now build a new calendar object with the free busy info we have fbcalendar = Component("VCALENDAR") fbcalendar.addProperty(Property("VERSION", "2.0")) fbcalendar.addProperty(Property("PRODID", iCalendarProductID)) if method: fbcalendar.addProperty(Property("METHOD", method)) fb = Component("VFREEBUSY") fbcalendar.addComponent(fb) if self.organizerProp is not None: fb.addProperty(self.organizerProp) if self.attendeeProp is not None: fb.addProperty(self.attendeeProp) fb.addProperty(Property("DTSTART", self.timerange.getStart())) fb.addProperty(Property("DTEND", self.timerange.getEnd())) fb.addProperty(Property("DTSTAMP", DateTime.getNowUTC())) if len(fbinfo.busy) != 0: fb.addProperty( Property("FREEBUSY", fbinfo.busy, {"FBTYPE": "BUSY"})) if len(fbinfo.tentative) != 0: fb.addProperty( Property("FREEBUSY", fbinfo.tentative, {"FBTYPE": "BUSY-TENTATIVE"})) if len(fbinfo.unavailable) != 0: fb.addProperty( Property("FREEBUSY", fbinfo.unavailable, {"FBTYPE": "BUSY-UNAVAILABLE"})) if self.uid is not None: fb.addProperty(Property("UID", self.uid)) else: uid = str(uuid.uuid4()) fb.addProperty(Property("UID", uid)) if self.event_details: for vevent in self.event_details: fbcalendar.addComponent(vevent) return fbcalendar
def test_single_events(self): """ Single events in calendar collection """ work = [] stream = self.openHolidays() calendar = Component.fromStream(stream) for subcomponent in calendar.subcomponents(): if subcomponent.name() == "VEVENT": subcalendar = Component("VCALENDAR") subcalendar.addComponent(subcomponent) for property in calendar.properties(): subcalendar.addProperty(property) work.append((MemoryStream(str(subcalendar)), responsecode.CREATED)) return self._test_file_in_calendar("single event in calendar", *work)
def compFilter(self, comp, component): """ Returns a calendar component object containing the data in the given component which is specified by this CalendarComponent. """ if comp.type != component.name(): raise ValueError( "%s of type %r can't get data from component of type %r" % (comp.sname(), comp.type, component.name())) result = Component(comp.type) xml_components = comp.components xml_properties = comp.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( self.compFilter(xml_subcomponent, 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
def test_calendar_query_timezone(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) return self.calendar_query(query, got_xml)
def test_calendar_query_wrong_timezone_elements(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) query.children += (caldavxml.TimeZoneID.fromString(tzid1), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.BAD_REQUEST) self.assertTrue("Only one of" in result)
def _calendarTimezoneUpgrade_setup(self): TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tz1 = Component(None, pycalendar=readVTZ("Etc/GMT+1")) tz2 = Component(None, pycalendar=readVTZ("Etc/GMT+2")) tz3 = Component(None, pycalendar=readVTZ("Etc/GMT+3")) # Share user01 calendar with user03 calendar = (yield self.calendarUnderTest(name="calendar_1", home="user01")) home3 = yield self.homeUnderTest(name="user03") shared_name = yield calendar.shareWith(home3, _BIND_MODE_WRITE) user_details = ( ("user01", "calendar_1", tz1), ("user02", "calendar_1", tz2), ("user03", "calendar_1", None), ("user03", shared_name, tz3), ) # Set dead properties on calendars for user, calname, tz in user_details: calendar = (yield self.calendarUnderTest(name=calname, home=user)) if tz: calendar.properties()[PropertyName.fromElement( caldavxml.CalendarTimeZone )] = caldavxml.CalendarTimeZone.fromString(str(tz)) # Force data version to previous home = (yield self.homeUnderTest(name=user)) ch = home._homeSchema yield Update( { ch.DATAVERSION: 4 }, Where=ch.RESOURCE_ID == home._resourceID, ).on(self.transactionUnderTest()) yield self.commit() for user, calname, tz in user_details: calendar = (yield self.calendarUnderTest(name=calname, home=user)) self.assertEqual(calendar.getTimezone(), None) self.assertEqual( PropertyName.fromElement(caldavxml.CalendarTimeZone) in calendar.properties(), tz is not None) yield self.commit() # Create "fake" entry for non-existent share txn = self.transactionUnderTest() calendar = (yield self.calendarUnderTest(name="calendar_1", home="user01")) rp = schema.RESOURCE_PROPERTY yield Insert({ rp.RESOURCE_ID: calendar._resourceID, rp.NAME: PropertyName.fromElement(caldavxml.CalendarTimeZone).toString(), rp.VALUE: caldavxml.CalendarTimeZone.fromString(str(tz3)).toxml(), rp.VIEWER_UID: "user04", }).on(txn) yield self.commit() returnValue(user_details)
def init_peruser_component(): peruser = Component(PERUSER_COMPONENT) peruser.addProperty(Property("UID", ical.resourceUID())) peruser.addProperty(Property(PERUSER_UID, self.uid)) return peruser
def _splitPerUserData(self, ical): """ Split the per-user data out of the "normal" iCalendar components into separate per-user components. Along the way keep the iCalendar representation in a "minimal" state by eliminating any components that are the same as the master derived component. @param ical: calendar data to process @type ical: L{Component} """ def init_peruser_component(): peruser = Component(PERUSER_COMPONENT) peruser.addProperty(Property("UID", ical.resourceUID())) peruser.addProperty(Property(PERUSER_UID, self.uid)) return peruser components = tuple(ical.subcomponents()) peruser_component = init_peruser_component() if self.uid else None perinstance_components = {} for component in components: if component.name() == "VTIMEZONE": continue rid = component.propertyValue("RECURRENCE-ID") rid = rid.duplicate() if rid is not None else None perinstance_component = Component( PERINSTANCE_COMPONENT) if self.uid else None perinstance_id_different = False # Special case certain default property values self._defaultMerge(component) # Transfer per-user properties from main component to per-instance component for property in tuple(component.properties()): if property.name( ) in PerUserDataFilter.PERUSER_PROPERTIES or property.name( ).startswith("X-") and property.name( ) not in PerUserDataFilter.IGNORE_X_PROPERTIES: if self.uid: perinstance_component.addProperty(property) component.removeProperty(property) perinstance_id_different = True # Transfer per-user components from main component to per-instance component for subcomponent in tuple(component.subcomponents()): if subcomponent.name( ) in PerUserDataFilter.PERUSER_SUBCOMPONENTS or subcomponent.name( ).startswith("X-"): if self.uid: perinstance_component.addComponent(subcomponent) component.removeComponent(subcomponent) perinstance_id_different = True if perinstance_id_different and perinstance_component: perinstance_components[rid] = perinstance_component if self.uid: # Add unique per-instance components into the per-user component peruser_component_different = False master_perinstance = perinstance_components.get(None) if master_perinstance: peruser_component.addComponent(master_perinstance) peruser_component_different = True for rid, perinstance in sorted(perinstance_components.iteritems(), key=lambda x: x[0]): if rid is None: continue if master_perinstance is None or perinstance != master_perinstance: perinstance.addProperty(Property("RECURRENCE-ID", rid)) peruser_component.addComponent(perinstance) peruser_component_different = True if peruser_component_different: ical.addComponent(peruser_component) self._compactInstances(ical)
def report_urn_ietf_params_xml_ns_caldav_calendar_query( self, request, calendar_query): """ Generate a calendar-query REPORT. (CalDAV-access-09, section 7.6) """ # Verify root element if calendar_query.qname() != (caldav_namespace, "calendar-query"): raise ValueError( "{CalDAV:}calendar-query expected as root element, not %s." % (calendar_query.sname(), )) if not self.isCollection(): parent = (yield self.locateParent(request, request.uri)) if not parent.isPseudoCalendarCollection(): log.error( "calendar-query report is not allowed on a resource outside of a calendar collection {s!r}", s=self) raise HTTPError( StatusResponse( responsecode.FORBIDDEN, "Must be calendar collection or calendar resource")) responses = [] xmlfilter = calendar_query.filter filter = Filter(xmlfilter) props = calendar_query.props assert props is not None # Get the original timezone provided in the query, if any, and validate it now query_timezone = None if calendar_query.timezone: query_tz = calendar_query.timezone if not query_tz.valid(): msg = "CalDAV:timezone must contain one VTIMEZONE component only: %s" % ( query_tz, ) log.error(msg) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), "Invalid calendar-data", )) filter.settimezone(query_tz) query_timezone = tuple(query_tz.calendar().subcomponents())[0] elif calendar_query.timezone_id: query_tzid = calendar_query.timezone_id.toString() try: query_tz = Component(None, pycalendar=readVTZ(query_tzid)) except TimezoneException: raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-timezone"), "Invalid timezone-id", )) filter.settimezone(query_tz) query_timezone = tuple(query_tz.subcomponents())[0] if props.qname() == ("DAV:", "allprop"): propertiesForResource = report_common.allPropertiesForResource generate_calendar_data = False elif props.qname() == ("DAV:", "propname"): propertiesForResource = report_common.propertyNamesForResource generate_calendar_data = False elif props.qname() == ("DAV:", "prop"): propertiesForResource = report_common.propertyListForResource # Verify that any calendar-data element matches what we can handle result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion( props) if not result: log.error(message) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data"), "Invalid calendar-data", )) else: raise AssertionError("We shouldn't be here") # Verify that the filter element is valid if (filter is None) or not filter.valid(): log.error("Invalid filter element: {f!r}", f=xmlfilter) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-filter"), "Invalid filter element", )) matchcount = [0] max_number_of_results = [ config.MaxQueryWithDataResults if generate_calendar_data else None, ] @inlineCallbacks def doQuery(calresource, uri): """ Run a query on the specified calendar collection accumulating the query responses. @param calresource: the L{CalDAVResource} for a calendar collection. @param uri: the uri for the calendar collection resource. """ @inlineCallbacks def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True): """ Run a query on the specified calendar. @param resource: the L{CalDAVResource} for the calendar. @param uri: the uri of the resource. @param name: the name of the resource. @param calendar: the L{Component} calendar read from the resource. """ # Handle private events access restrictions if not isowner: access = resource.accessMode else: access = None if query_ok or filter.match(calendar, access): # Check size of results is within limit matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[ 0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during query: {h}", h=href) # Check whether supplied resource is a calendar or a calendar object resource if calresource.isPseudoCalendarCollection(): # Get the timezone property from the collection if one was not set in the query, # and store in the query filter for later use timezone = query_timezone if timezone is None: has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request)) if has_prop: tz = (yield calresource.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Do some optimization of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield calresource.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield calresource.isOwner(request)) # Check for disabled access if filteredaces is not None: index_query_ok = True try: # Get list of children that match the search and have read access names = [ name for name, ignore_uid, ignore_type in ( yield calresource.search(filter)) ] except IndexedSearchException: names = yield calresource.listChildren() index_query_ok = False if not names: returnValue(True) # Now determine which valid resources are readable and which are not ok_resources = [] yield calresource.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), None, None, None, names, (davxml.Read(), ), inherited_aces=filteredaces) for child, child_uri in ok_resources: child_uri_name = child_uri[child_uri.rfind("/") + 1:] if generate_calendar_data or not index_query_ok: calendar = (yield child.componentForUser()) assert calendar is not None, "Calendar %s is missing from calendar collection %r" % ( child_uri_name, self) else: calendar = None yield queryCalendarObjectResource(child, uri, child_uri_name, calendar, timezone, query_ok=index_query_ok, isowner=isowner) else: # Get the timezone property from the collection if one was not set in the query, # and store in the query object for later use timezone = query_timezone if timezone is None: parent = (yield calresource.locateParent(request, uri)) assert parent is not None and parent.isPseudoCalendarCollection( ) has_prop = (yield parent.hasProperty(CalendarTimeZone(), request)) if has_prop: tz = (yield parent.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Check private events access status isowner = (yield calresource.isOwner(request)) calendar = (yield calresource.componentForUser()) yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone) returnValue(True) # Run report taking depth into account try: depth = request.headers.getHeader("depth", "0") yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(), )) except TooManyInstancesError, ex: log.error( "Too many instances need to be computed in calendar-query report") raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, MaxInstances.fromString(str(ex.max_allowed)), "Too many instances", ))
hour -= 12 else: assert meridian == "PM" else: if meridian == "PM": hour += 12 else: assert meridian == "AM" start_time = datetime.time(hour, int(time[3:5])) return datetime.datetime.combine(start_date, start_time) reader = csv.reader(file(csv_filename, "rb")) calendar = Component("VCALENDAR") # Ignore first line reader.next() priorities = { "High" : "1", "Medium" : "5", "Low" : "9", } for row in reader: event = Component("VEVENT") title = row[0] event.addProperty(Property("SUMMARY", title))
# for each subcomponent therein. # These split-up calendars are useable as CalDAV resources. # import os from twistedcaldav.ical import Component monolithic_filename = os.path.join(os.path.dirname(__file__), "Holidays.ics") calendar = Component.fromStream(file(monolithic_filename)) assert calendar.name() == "VCALENDAR" for subcomponent in calendar.subcomponents(): subcalendar = Component("VCALENDAR") # # Add top-level properties from monolithic calendar to top-level properties # of subcomponent calendar. # for property in calendar.properties(): subcalendar.addProperty(property) subcalendar.addComponent(subcomponent) uid = subcalendar.resourceUID() subcalendar_filename = os.path.join(os.path.dirname(__file__), "Holidays", uid + ".ics") print "Writing %s" % (subcalendar_filename, )