def _deserialize(self, data): """ Convert a JSON compatible serialization of this object into the actual object. """ self.start = DateTime.parseText(data["start"]) if data["start"] else None self.end = DateTime.parseText(data["end"]) if data["end"] else None self.tzinfo = Timezone(tzid=data["tzinfo"]) if data["tzinfo"] else None
def testRoundtrip(self): data1 = ( "20110102", "20110103T121212", "20110103T121212Z", "00010102", "00010103T121212", "00010103T121212Z", ) data2 = ( ("20110102", "20110102"), ("2011-01-02", "20110102"), ("20110103T121212", "20110103T121212"), ("2011-01-03T12:12:12", "20110103T121212"), ("20110103T121212Z", "20110103T121212Z"), ("2011-01-03T12:12:12Z", "20110103T121212Z"), ("20110103T121212+0100", "20110103T121212+0100"), ("2011-01-03T12:12:12-0500", "20110103T121212-0500"), ("20110103T121212,123", "20110103T121212"), ("2011-01-03T12:12:12,123", "20110103T121212"), ("20110103T121212,123Z", "20110103T121212Z"), ("2011-01-03T12:12:12,123Z", "20110103T121212Z"), ("20110103T121212,123+0100", "20110103T121212+0100"), ("2011-01-03T12:12:12,123-0500", "20110103T121212-0500"), ) for item in data1: dt = DateTime.parseText(item, False) self.assertEqual(dt.getText(), item, "Failed on: %s" % (item, )) for item, result in data2: dt = DateTime.parseText(item, True) self.assertEqual(dt.getText(), result, "Failed on: %s" % (item, ))
def actionExpand(self, request, tzid): """ Expand a timezone within specified start/end dates. """ if set(request.args.keys()) - set(("start", "end", "changedsince",)): self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST) start = request.args.get("start", ()) if len(start) == 0: self.problemReport("invalid-start", "Missing start request-URI query parameter", responsecode.BAD_REQUEST) if len(start) > 1: self.problemReport("invalid-start", "Too many start request-URI query parameters", responsecode.BAD_REQUEST) elif len(start) == 1: try: if len(start[0]) != 20: raise ValueError() start = DateTime.parseText(start[0], fullISO=True) except ValueError: self.problemReport("invalid-start", "Invalid start request-URI query parameter value", responsecode.BAD_REQUEST) end = request.args.get("end", ()) if len(end) == 0: self.problemReport("invalid-end", "Missing end request-URI query parameter", responsecode.BAD_REQUEST) if len(end) > 1: self.problemReport("invalid-end", "Too many end request-URI query parameters", responsecode.BAD_REQUEST) elif len(end) == 1: try: if len(end[0]) != 20: raise ValueError() end = DateTime.parseText(end[0], fullISO=True) except ValueError: self.problemReport("invalid-end", "Invalid end request-URI query parameter value", responsecode.BAD_REQUEST) if end <= start: self.problemReport("invalid-end", "Invalid end request-URI query parameter value - earlier than start", responsecode.BAD_REQUEST) tzdata = self.timezones.getTimezone(tzid) if tzdata is None: self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND) # Now do the expansion (but use a cache to avoid re-calculating TZs) observances = self.expandcache.get((tzid, start, end), None) if observances is None: observances = tzexpandlocal(tzdata, start, end, utc_onset=True) self.expandcache[(tzid, start, end)] = observances # Turn into JSON result = { "dtstamp": self.timezones.dtstamp, "tzid": tzid, "observances": [ { "name": name, "onset": onset.getXMLText(), "utc-offset-from": utc_offset_from, "utc-offset-to": utc_offset_to, } for onset, utc_offset_from, utc_offset_to, name in observances ], } return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
def doIt(self, txn): uid = raw_input("Owner UID/Name: ") start = raw_input("Start Time (UTC YYYYMMDDTHHMMSSZ or YYYYMMDD): ") if len(start) == 8: start += "T000000Z" end = raw_input("End Time (UTC YYYYMMDDTHHMMSSZ or YYYYMMDD): ") if len(end) == 8: end += "T000000Z" try: start = DateTime.parseText(start) except ValueError: print("Invalid start value") returnValue(None) try: end = DateTime.parseText(end) except ValueError: print("Invalid end value") returnValue(None) timerange = caldavxml.TimeRange(start=start.getText(), end=end.getText()) home = yield txn.calendarHomeWithUID(uid) if home is None: print("Could not find calendar home") returnValue(None) yield self.eventsForEachCalendar(home, uid, timerange)
def test_freebusy(self): """ Test that action=component works. """ yield self.createShare("user01", "puser01") calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar") yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield self.commitTransaction(0) fbstart = "{now:04d}0102T000000Z".format(**self.nowYear) fbend = "{now:04d}0103T000000Z".format(**self.nowYear) shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar") fbinfo = FreebusyQuery.FBInfo([], [], []) timerange = Period(DateTime.parseText(fbstart), DateTime.parseText(fbend)) organizer = recipient = (yield calendarUserFromCalendarUserAddress("mailto:[email protected]", self.theTransactionUnderTest(1))) freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange) matchtotal = (yield freebusy.generateFreeBusyInfo([shared, ], fbinfo)) self.assertEqual(matchtotal, 1) self.assertEqual(fbinfo[0], [Period.parseText("{now:04d}0102T140000Z/PT1H".format(**self.nowYear)), ]) self.assertEqual(len(fbinfo[1]), 0) self.assertEqual(len(fbinfo[2]), 0) yield self.commitTransaction(1)
def testRoundtrip(self): data1 = ( "20110102", "20110103T121212", "20110103T121212Z", "00010102", "00010103T121212", "00010103T121212Z", ) data2 = ( ("20110102", "20110102"), ("2011-01-02", "20110102"), ("20110103T121212", "20110103T121212"), ("2011-01-03T12:12:12", "20110103T121212"), ("20110103T121212Z", "20110103T121212Z"), ("2011-01-03T12:12:12Z", "20110103T121212Z"), ("20110103T121212+0100", "20110103T121212+0100"), ("2011-01-03T12:12:12-0500", "20110103T121212-0500"), ("20110103T121212,123", "20110103T121212"), ("2011-01-03T12:12:12,123", "20110103T121212"), ("20110103T121212,123Z", "20110103T121212Z"), ("2011-01-03T12:12:12,123Z", "20110103T121212Z"), ("20110103T121212,123+0100", "20110103T121212+0100"), ("2011-01-03T12:12:12,123-0500", "20110103T121212-0500"), ) for item in data1: dt = DateTime.parseText(item, False) self.assertEqual(dt.getText(), item, "Failed on: %s" % (item,)) for item, result in data2: dt = DateTime.parseText(item, True) self.assertEqual(dt.getText(), result, "Failed on: %s" % (item,))
def actionExpand(self, request, tzid): """ Expand a timezone within specified start/end dates. """ if set(request.args.keys()) - set(("start", "end",)): self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST) start = request.args.get("start", ()) if len(start) == 0: self.problemReport("invalid-start", "Missing start request-URI query parameter", responsecode.BAD_REQUEST) if len(start) > 1: self.problemReport("invalid-start", "Too many start request-URI query parameters", responsecode.BAD_REQUEST) elif len(start) == 1: try: if len(start[0]) != 20: raise ValueError() start = DateTime.parseText(start[0], fullISO=True) except ValueError: self.problemReport("invalid-start", "Invalid start request-URI query parameter value", responsecode.BAD_REQUEST) end = request.args.get("end", ()) if len(end) == 0: self.problemReport("invalid-end", "Missing end request-URI query parameter", responsecode.BAD_REQUEST) if len(end) > 1: self.problemReport("invalid-end", "Too many end request-URI query parameters", responsecode.BAD_REQUEST) elif len(end) == 1: try: if len(end[0]) != 20: raise ValueError() end = DateTime.parseText(end[0], fullISO=True) except ValueError: self.problemReport("invalid-end", "Invalid end request-URI query parameter value", responsecode.BAD_REQUEST) if end <= start: self.problemReport("invalid-end", "Invalid end request-URI query parameter value - earlier than start", responsecode.BAD_REQUEST) tzdata = self.timezones.getTimezone(tzid) if tzdata is None: self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND) # Now do the expansion (but use a cache to avoid re-calculating TZs) observances = self.expandcache.get((tzid, start, end), None) if observances is None: observances = tzexpandlocal(tzdata, start, end, utc_onset=True) self.expandcache[(tzid, start, end)] = observances # Turn into JSON result = { "dtstamp": self.timezones.dtstamp, "tzid": tzid, "observances": [ { "name": name, "onset": onset.getXMLText(), "utc-offset-from": utc_offset_from, "utc-offset-to": utc_offset_to, } for onset, utc_offset_from, utc_offset_to, name in observances ], } return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
def doPOSTExpand(self, request): """ Expand a timezone within specified start/end dates. """ tzid = request.args.get("tzid", ()) if len(tzid) != 1: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-timezone"), "Invalid tzid query parameter", )) tzid = tzid[0] try: tzdata = readTZ(tzid) except TimezoneException: raise HTTPError(ErrorResponse( responsecode.NOT_FOUND, (calendarserver_namespace, "timezone-available"), "Timezone not found", )) try: start = request.args.get("start", ()) if len(start) != 1: raise ValueError() start = DateTime.parseText(start[0]) except ValueError: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-start-date"), "Invalid start query parameter", )) try: end = request.args.get("end", ()) if len(end) != 1: raise ValueError() end = DateTime.parseText(end[0]) if end <= start: raise ValueError() except ValueError: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-end-date"), "Invalid end query parameter", )) # Now do the expansion (but use a cache to avoid re-calculating TZs) observances = self.cache.get((tzid, start, end), None) if observances is None: observances = tzexpand(tzdata, start, end) self.cache[(tzid, start, end)] = observances # Turn into XML result = customxml.TZData( *[customxml.Observance(customxml.Onset(onset), customxml.UTCOffset(utc_offset)) for onset, utc_offset in observances] ) return XMLResponse(responsecode.OK, result)
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 __init__(self, xml_element): super(TimeRange, self).__init__(xml_element) if xml_element is None: return # One of start or end must be present if "start" not in xml_element.attributes and "end" not in xml_element.attributes: raise ValueError("One of 'start' or 'end' must be present in CALDAV:time-range") self.start = DateTime.parseText(xml_element.attributes["start"]) if "start" in xml_element.attributes else None self.end = DateTime.parseText(xml_element.attributes["end"]) if "end" in xml_element.attributes else None self.tzinfo = None
def actionList(self, request): """ Return a list of all timezones known to the server. """ if set(request.args.keys()) - set(("changedsince",)): self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST) changedsince = request.args.get("changedsince", ()) if len(changedsince) > 1: self.problemReport("invalid-changedsince", "Too many changedsince request-URI query parameters", responsecode.BAD_REQUEST) if len(changedsince) == 1: # Validate a date-time stamp changedsince = changedsince[0] try: dt = DateTime.parseText(changedsince, fullISO=True) except ValueError: self.problemReport("invalid-changedsince", "Invalid changedsince request-URI query parameter value", responsecode.BAD_REQUEST) if not dt.utc(): self.problemReport("invalid-changedsince", "Invalid changedsince request-URI query parameter value - not UTC", responsecode.BAD_REQUEST) timezones = [] for tz in self.timezones.listTimezones(changedsince): timezones.append({ "tzid": tz.tzid, "last-modified": tz.dtstamp, "aliases": tz.aliases, }) result = { "dtstamp": self.timezones.dtstamp, "timezones": timezones, } return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
def doList(self, request): """ Return a list of all timezones known to the server. """ changedsince = request.args.get("changedsince", ()) if len(changedsince) > 1: raise HTTPError( JSONResponse( responsecode.BAD_REQUEST, {"error": "invalid-changedsince", "description": "Invalid changedsince query parameter"}, ) ) if len(changedsince) == 1: # Validate a date-time stamp changedsince = changedsince[0] try: dt = DateTime.parseText(changedsince) except ValueError: raise HTTPError( JSONResponse( responsecode.BAD_REQUEST, {"error": "invalid-changedsince", "description": "Invalid changedsince query parameter"}, ) ) if not dt.utc(): raise HTTPError(JSONResponse(responsecode.BAD_REQUEST, "Invalid changedsince query parameter value")) timezones = [] for tz in self.timezones.listTimezones(changedsince): timezones.append({"tzid": tz.tzid, "last-modified": tz.dtstamp, "aliases": tz.aliases}) result = {"dtstamp": self.timezones.dtstamp, "timezones": timezones} return JSONResponse(responsecode.OK, result)
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) attendeeAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() calendar = (yield resource.componentForUser()) organizer = calendar.validOrganizerForScheduling() # Deserialize "" as None changedRids = map(lambda x: DateTime.parseText(x) if x else None, self.changedRids.split(",")) if self.changedRids else None log.debug("ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(),)) itipmsg = iTipGenerator.generateAttendeeReply(calendar, attendee, changedRids=changedRids) # Send scheduling message and process response response = (yield self.sendToOrganizer(home, "REPLY", itipmsg, attendee, organizer)) responses, all_delivered = self.extractSchedulingResponse((response,)) if not all_delivered: changed = yield self.handleSchedulingResponse(responses, calendar, False) if changed: yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE) self._dequeued() except Exception, e: # FIXME: calendar may not be set here! log.debug("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e)) raise
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID( self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) attendeeAddress = yield calendarUserFromCalendarUserUID( home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() calendar = (yield resource.componentForUser()) organizer = calendar.validOrganizerForScheduling() # Deserialize "" as None changedRids = map( lambda x: DateTime.parseText(x) if x else None, self.changedRids.split(",")) if self.changedRids else None log.debug( "ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire( self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(), )) itipmsg = iTipGenerator.generateAttendeeReply( calendar, attendee, changedRids=changedRids) # Send scheduling message and process response response = (yield self.sendToOrganizer(home, "REPLY", itipmsg, attendee, organizer)) responses, all_delivered = self.extractSchedulingResponse( (response, )) if not all_delivered: changed = yield self.handleSchedulingResponse( responses, calendar, False) if changed: yield resource._setComponentInternal( calendar, internal_state=ComponentUpdateState. ATTENDEE_ITIP_UPDATE) self._dequeued() except Exception, e: # FIXME: calendar may not be set here! log.debug( "ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e)) raise
def testExampleRules(self): examples = os.path.join(os.path.dirname(__file__), "rrule_examples.json") with open(examples) as f: examples = json.loads(f.read()) for ctr, i in enumerate(examples): recur = Recurrence() recur.parse(i["rule"]) start = DateTime.parseText(i["start"]) end = DateTime.parseText(i["end"]) results = map(DateTime.parseText, i["results"]) items = [] range = Period(start, end) recur.expand(start, range, items) self.assertEqual(items, results, msg="Failed rule: #{} {}".format( ctr + 1, i["rule"]))
def testExampleRules(self): examples = os.path.join(os.path.dirname(__file__), "rrule_examples.json") with open(examples) as f: examples = json.loads(f.read()) for ctr, i in enumerate(examples): recur = Recurrence() recur.parse(i["rule"]) start = DateTime.parseText(i["start"]) end = DateTime.parseText(i["end"]) results = map(DateTime.parseText, i["results"]) items = [] range = Period(start, end) recur.expand(start, range, items) self.assertEqual( items, results, msg="Failed rule: #{} {}".format(ctr + 1, i["rule"]) )
def instances(start, rrule): """ Expand an RRULE. """ recur = Recurrence() recur.parse(rrule) start = DateTime.parseText(start) end = start.duplicate() end.offsetYear(100) items = [] range = Period(start, end) recur.expand(start, range, items) print("DTSTART:{}".format(start)) print("RRULE:{}".format(rrule)) print("Instances: {}".format(", ".join(map(str, items))))
def testBadParseFixed(self): self._patch(ParserContext, "INVALID_DATETIME_LEADINGSPACE", ParserContext.PARSER_ALLOW) data = ( (" 10102", "00010102"), ("2001 102", "20010102"), ("200101 2", "20010102"), (" 10102T010101", "00010102T010101"), ("2001 102T010101", "20010102T010101"), ("200101 2T010101", "20010102T010101"), ) for item, result in data: dt = DateTime.parseText(item) self.assertEqual(str(dt), result)
def _processFBURL(self, request): # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(),)) # Extract query parameters from the URL args = ('start', 'end', 'duration', 'token', 'format', 'user',) for arg in args: setattr(self, arg, request.args.get(arg, [None])[0]) # Some things we do not handle if self.token or self.user: raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-query-parameter"), "Invalid query parameter", )) # Check format if self.format: self.format = self.format.split(";")[0] if self.format not in ("text/calendar", "text/plain"): raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-format"), "Invalid return format requested", )) else: self.format = "text/calendar" # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values try: if self.start: self.start = DateTime.parseText(self.start) if not self.start.utc(): raise ValueError() if self.end: self.end = DateTime.parseText(self.end) if not self.end.utc(): raise ValueError() if self.duration: self.duration = Duration.parseText(self.duration) except ValueError: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Sanity check start/end/duration # End and duration cannot both be present if self.end and self.duration: raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Duration must be positive if self.duration and self.duration.getTotalSeconds() < 0: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Now fill in the missing pieces if self.start is None: self.start = DateTime.getNowUTC() self.start.setHHMMSS(0, 0, 0) if self.duration: self.end = self.start + self.duration if self.end is None: self.end = self.start + Duration(days=config.FreeBusyURL.TimePeriod) # End > start if self.end <= self.start: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long) # Now lookup the principal details for the targeted user principal = (yield self.parent.principalForRecord()) # Pick the first mailto cu address or the first other type cuaddr = None for item in principal.calendarUserAddresses(): if cuaddr is None: cuaddr = item if item.startswith("mailto:"): cuaddr = item break # Get inbox details inboxURL = principal.scheduleInboxURL() if inboxURL is None: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox URL for principal: %s" % (principal,))) try: inbox = (yield request.locateResource(inboxURL)) except: log.error("No schedule inbox for principal: %s" % (principal,)) inbox = None if inbox is None: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox for principal: %s" % (principal,))) organizer = recipient = LocalCalendarUser(cuaddr, principal.record) recipient.inbox = inbox._newStoreObject attendeeProp = Property("ATTENDEE", recipient.cuaddr) timerange = Period(self.start, self.end) fbresult = yield FreebusyQuery( organizer=organizer, recipient=recipient, attendeeProp=attendeeProp, timerange=timerange, ).generateAttendeeFreeBusyResponse() response = Response() response.stream = MemoryStream(str(fbresult)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,))) returnValue(response)
def _processFBURL(self, request): # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(),)) # Extract query parameters from the URL args = ('start', 'end', 'duration', 'token', 'format', 'user',) for arg in args: setattr(self, arg, request.args.get(arg, [None])[0]) # Some things we do not handle if self.token or self.user: raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-query-parameter"), "Invalid query parameter", )) # Check format if self.format: self.format = self.format.split(";")[0] if self.format not in ("text/calendar", "text/plain"): raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-format"), "Invalid return format requested", )) else: self.format = "text/calendar" # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values try: if self.start: self.start = DateTime.parseText(self.start) if not self.start.utc(): raise ValueError() if self.end: self.end = DateTime.parseText(self.end) if not self.end.utc(): raise ValueError() if self.duration: self.duration = Duration.parseText(self.duration) except ValueError: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Sanity check start/end/duration # End and duration cannot both be present if self.end and self.duration: raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Duration must be positive if self.duration and self.duration.getTotalSeconds() < 0: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Now fill in the missing pieces if self.start is None: self.start = DateTime.getNowUTC() self.start.setHHMMSS(0, 0, 0) if self.duration: self.end = self.start + self.duration if self.end is None: self.end = self.start + Duration(days=config.FreeBusyURL.TimePeriod) # End > start if self.end <= self.start: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long) # Now lookup the principal details for the targeted user principal = (yield self.parent.principalForRecord()) # Pick the first mailto cu address or the first other type cuaddr = None for item in principal.calendarUserAddresses(): if cuaddr is None: cuaddr = item if item.startswith("mailto:"): cuaddr = item break # Get inbox details inboxURL = principal.scheduleInboxURL() if inboxURL is None: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox URL for principal: %s" % (principal,))) try: inbox = (yield request.locateResource(inboxURL)) except: log.error("No schedule inbox for principal: {p}", p=principal) inbox = None if inbox is None: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox for principal: %s" % (principal,))) organizer = recipient = LocalCalendarUser(cuaddr, principal.record) recipient.inbox = inbox._newStoreObject attendeeProp = Property("ATTENDEE", recipient.cuaddr) timerange = Period(self.start, self.end) fbresult = yield FreebusyQuery( organizer=organizer, recipient=recipient, attendeeProp=attendeeProp, timerange=timerange, ).generateAttendeeFreeBusyResponse() response = Response() response.stream = MemoryStream(str(fbresult)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,))) returnValue(response)
def doExpand(self, request): """ Expand a timezone within specified start/end dates. """ tzids = request.args.get("tzid", ()) if len(tzids) != 1: raise HTTPError( JSONResponse( responsecode.BAD_REQUEST, {"error": "invalid-tzid", "description": "Invalid tzid query parameter"} ) ) try: start = request.args.get("start", ()) if len(start) > 1: raise ValueError() elif len(start) == 1: start = DateTime.parseText("{}0101".format(int(start[0]))) else: start = DateTime.getToday() start.setDay(1) start.setMonth(1) except ValueError: raise HTTPError( JSONResponse( responsecode.BAD_REQUEST, {"error": "invalid-start", "description": "Invalid start query parameter"} ) ) try: end = request.args.get("end", ()) if len(end) > 1: raise ValueError() elif len(end) == 1: end = DateTime.parseText("{}0101".format(int(end[0]))) else: end = DateTime.getToday() end.setDay(1) end.setMonth(1) end.offsetYear(10) if end <= start: raise ValueError() except ValueError: raise HTTPError( JSONResponse( responsecode.BAD_REQUEST, {"error": "invalid-end", "description": "Invalid end query parameter"} ) ) tzid = tzids[0] tzdata = self.timezones.getTimezone(tzid) if tzdata is None: raise HTTPError( JSONResponse( responsecode.NOT_FOUND, {"error": "tzid-not-found", "description": "Tzid could not be found"} ) ) # Now do the expansion (but use a cache to avoid re-calculating TZs) observances = self.expandcache.get((tzid, start, end), None) if observances is None: observances = tzexpandlocal(tzdata, start, end) self.expandcache[(tzid, start, end)] = observances # Turn into JSON result = { "dtstamp": self.timezones.dtstamp, "observances": [ { "name": name, "onset": onset.getXMLText(), "utc-offset-from": utc_offset_from, "utc-offset-to": utc_offset_to, } for onset, utc_offset_from, utc_offset_to, name in observances ], } return JSONResponse(responsecode.OK, result)