def __init__(self, code, error, description=None): """ @param code: a response code. @param error: an L{WebDAVElement} identifying the error, or a tuple C{(namespace, name)} with which to create an empty element denoting the error. (The latter is useful in the case of preconditions ans postconditions, not all of which have defined XML element classes.) @param description: an optional string that, if present, will get wrapped in a (twisted_dav_namespace, error-description) element. """ if type(error) is tuple: xml_namespace, xml_name = error error = element.WebDAVUnknownElement() error.namespace = xml_namespace error.name = xml_name self.description = description if self.description: output = element.Error(error, element.ErrorDescription(self.description)).toxml() else: output = element.Error(error).toxml() Response.__init__(self, code=code, stream=output) self.headers.setHeader("content-type", MimeType("text", "xml")) self.error = error
def doPOSTGet(self, request): """ Return the specified timezone data. """ 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", )) response = Response() response.stream = MemoryStream(tzdata) response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8")) return response
def __init__(self, xml_responses): """ @param xml_responses: an interable of element.Response objects. """ Response.__init__(self, code=responsecode.MULTI_STATUS, stream=element.MultiStatus(*xml_responses).toxml()) self.headers.setHeader("content-type", MimeType("text", "xml"))
def _defer(htmlContent): response = Response() response.stream = MemoryStream(str(htmlContent)) for (header, value) in ( ("content-type", self.contentType()), ("content-encoding", self.contentEncoding()), ): if value is not None: response.headers.setHeader(header, value) 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.err("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] 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{CalDAVFile} 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.err("Too many matching components in free-busy report") raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits())) # Now build a new calendar object with the free busy info we have fbcalendar = report_common.buildFreeBusyResult(fbinfo, timerange) response = Response() response.stream = MemoryStream(str(fbcalendar)) response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8")) returnValue(response)
def __init__(self, schedule_response_element, xml_responses, location=None): """ @param xml_responses: an iterable of davxml.Response objects. @param location: the value of the location header to return in the response, or None. """ Response.__init__(self, code=responsecode.OK, stream=schedule_response_element(*xml_responses).toxml()) self.headers.setHeader("content-type", MimeType("text", "xml")) if location is not None: self.headers.setHeader("location", location)
def render(self, request): """ Create a L{WebAdminPage} to render HTML content for this request, and return a response. """ resourceId = request.args.get('resourceId', [''])[0] if resourceId: principal = self.getResourceById(request, resourceId) yield self.resourceActions(request, principal) htmlContent = yield flattenString(request, WebAdminPage(self)) response = Response() response.stream = MemoryStream(htmlContent) for (header, value) in ( ("content-type", self.contentType()), ("content-encoding", self.contentEncoding()), ): if value is not None: response.headers.setHeader(header, value) returnValue(response)
def doGet(self, request): """ Return the specified timezone data. """ tzids = request.args.get("tzid", ()) if len(tzids) != 1: raise HTTPError(JSONResponse( responsecode.BAD_REQUEST, { "error": "invalid-tzid", "description": "Invalid tzid query parameter", }, )) format = request.args.get("format", ("text/calendar",)) if len(format) != 1 or format[0] not in ("text/calendar", "text/plain",): raise HTTPError(JSONResponse( responsecode.BAD_REQUEST, { "error": "invalid-format", "description": "Invalid format query parameter", }, )) format = format[0] calendar = self.timezones.getTimezone(tzids[0]) if calendar is None: raise HTTPError(JSONResponse( responsecode.NOT_FOUND, { "error": "missing-tzid", "description": "Tzid could not be found", } )) tzdata = calendar.getText() response = Response() response.stream = MemoryStream(tzdata) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (format,))) return response
def render(self, request): response = Response() response.stream = MemoryStream(self.renderOutput) return response
def _defer(data): response = Response() response.stream = MemoryStream(str(data)) response.headers.setHeader("content-type", MimeType.fromString("text/calendar")) return 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 = PyCalendarDateTime.parseText(self.start) if not self.start.utc(): raise ValueError() if self.end: self.end = PyCalendarDateTime.parseText(self.end) if not self.end.utc(): raise ValueError() if self.duration: self.duration = PyCalendarDuration.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 = PyCalendarDateTime.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 + PyCalendarDuration(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 = 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,))) scheduler = Scheduler(request, self) scheduler.timeRange = TimeRange(start="20000101T000000Z", end="20070102T000000Z") scheduler.timeRange.start = self.start scheduler.timeRange.end = self.end scheduler.organizer = LocalCalendarUser(cuaddr, principal, inbox, inboxURL) attendeeProp = Property("ATTENDEE", scheduler.organizer.cuaddr) requestor = ScheduleViaCalDAV(scheduler, (), [], True) fbresult = (yield requestor.generateAttendeeFreeBusyResponse( scheduler.organizer, None, None, None, attendeeProp, True, )) response = Response() response.stream = MemoryStream(str(fbresult)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,))) returnValue(response)
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),) )) except TimeRangeUpperLimit, e: raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, caldavxml.MaxDateTime(), "Time-range value too far in the future. Must be on or before %s." % (str(e.limit),) )) # Now build a new calendar object with the free busy info we have fbcalendar = report_common.buildFreeBusyResult(fbinfo, timerange) response = Response() response.stream = MemoryStream(str(fbcalendar)) response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8")) returnValue(response)
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"))) action = action[0] dispatch = { "share" : self.directShare, }.get(action, None) if dispatch is None: raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action"))) response = (yield dispatch(request)) returnValue(response) else: # 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(),)) caldata = (yield self.iCalendarForUser(request)) try: access = self.readDeadProperty(TwistedCalendarAccessProperty) except HTTPError: access = None if access: # Non DAV:owner's have limited access to the data isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True)) # Now "filter" the resource calendar data caldata = PrivateEventFilter(access, isowner).filter(caldata) response = Response() response.stream = MemoryStream(str(caldata)) response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8")) # Add Schedule-Tag header if property is present if self.hasDeadProperty(ScheduleTag): scheduletag = self.readDeadProperty(ScheduleTag) if scheduletag: response.headers.setHeader("Schedule-Tag", str(scheduletag)) returnValue(response) # Do normal GET behavior response = (yield super(CalDAVResource, self).http_GET(request)) returnValue(response)
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: # 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(),)) 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)) response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8")) # 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)