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 render(self, request): response = Response() response.stream = MemoryStream((yield self.iCalendarZipArchiveData())) # FIXME: Use content-encoding instead? response.headers.setHeader( b"content-type", MimeType.fromString(b"application/zip") ) returnValue(response)
def render(self, request): # yield self.handleQueryArguments(request) htmlContent = yield flattenString(request, self.elementClass()) response = Response() response.stream = MemoryStream(htmlContent) response.headers.setHeader( b"content-type", MimeType.fromString(b"text/html; 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): lastID = request.headers.getRawHeaders(u"last-event-id") response = Response() response.stream = EventStream(self._eventDecoder, self._events, lastID) response.headers.setHeader(b"content-type", MimeType.fromString(b"text/event-stream")) # Keep track of the event streams def cleanupFilter(_request, _response): self._streams.remove(response.stream) return _response request.addResponseFilter(cleanupFilter) self._streams.add(response.stream) return response
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 self.formats: 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(format=format if format != "text/plain" else None) response = Response() response.stream = MemoryStream(tzdata) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (format,))) return response
def render(self, request): output = """<html> <head> <title>{rtype} Inbox Resource</title> </head> <body> <h1>{rtype} Inbox Resource.</h1> </body </html>""".format(rtype="Podding" if self._podding else "iSchedule") response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def actionGet(self, request, tzid): """ Return the specified timezone data. """ if set(request.args.keys()) - set(("start", "end",)): self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST) accepted_type = bestAcceptType(request.headers.getHeader("accept"), self.formats) if accepted_type is None: self.problemReport("invalid-format", "Accept header does not match available media types", responsecode.NOT_ACCEPTABLE) calendar = self.timezones.getTimezone(tzid) if calendar is None: self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND) tzdata = calendar.getText(format=accepted_type if accepted_type != "text/plain" else None) response = Response() response.stream = MemoryStream(tzdata) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) return response
def render(self, request): output = """<html> <head> <title>Timezone Standard Service Resource</title> </head> <body> <h1>Timezone Standard Service Resource.</h1> </body </html>""" response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def render(self, request): output = """<html> <head> <title>Podding Conduit Resource</title> </head> <body> <h1>Podding Conduit Resource.</h1> </body </html>""" response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def declineShare(self, request, inviteUID): # Remove it if it is in the DB try: result = yield self._newStoreHome.declineShare(inviteUID) except DirectoryRecordNotFoundError: # Missing sharer record => just treat decline as success result = True if not result: raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (calendarserver_namespace, "invalid-share"), "Invite UID not valid", )) returnValue(Response(code=responsecode.NO_CONTENT))
def render(self, request): output = """<html> <head> <title>DomainKey Resource</title> </head> <body> <h1>DomainKey Resource.</h1> <a href="%s">Domain: %s<br> Selector: %s</a> </body </html>""" % (joinURL(request.uri, self.domain, self.selector), self.domain, self.selector,) response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def actionGet(self, request, tzid): """ Return the specified timezone data. """ if set(request.args.keys()) - set(( "start", "end", )): self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST) accepted_type = bestAcceptType(request.headers.getHeader("accept"), self.formats) if accepted_type is None: self.problemReport( "invalid-format", "Accept header does not match available media types", responsecode.NOT_ACCEPTABLE) calendar = self.timezones.getTimezone(tzid) if calendar is None: self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND) tzdata = calendar.getText( format=accepted_type if accepted_type != "text/plain" else None) response = Response() response.stream = MemoryStream(tzdata) response.headers.setHeader( "content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type, ))) return response
def report_DAV__principal_search_property_set(self, request, principal_search_property_set): """ Generate a principal-search-property-set REPORT. (RFC 3744, section 9.5) """ # Verify root element if not isinstance(principal_search_property_set, davxml.PrincipalSearchPropertySet): raise ValueError("%s expected as root element, not %s." % (davxml.PrincipalSearchPropertySet.sname(), principal_search_property_set.sname())) # Only handle Depth: 0 depth = request.headers.getHeader("depth", "0") if depth != "0": log.error("Error in principal-search-property-set REPORT, Depth set to %s" % (depth,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,))) # Get details from the resource result = self.principalSearchPropertySet() if result is None: log.error("Error in principal-search-property-set REPORT not supported on: %s" % (self,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Not allowed on this resource")) yield Response(code=responsecode.OK, stream=MemoryStream(result.toxml()))
def createRequest(self): self.stream = ProducerStream(self.length) self.response = Response(self.code, self.inHeaders, self.stream) self.stream.registerProducer(self, True) del self.inHeaders
def render(self, request): response = Response(200, {}, self.data) response.headers.setHeader("content-type", self.content_type) return response
def gotBody(output): mime_params = {"charset": "utf-8"} response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html", mime_params)) return response
caldavxml.caldav_namespace, "supported-calendar-component-set"): yield self.setSupportedComponentSet(property) set_supported_component_set = True elif not isinstance(property, davxml.ResourceType): yield self.writeProperty(property, request) except HTTPError: errors.add(Failure(), property) got_an_error = True else: errors.add(responsecode.OK, property) if got_an_error: # Clean up errors.error() raise HTTPError( Response(code=responsecode.FORBIDDEN, stream=mkcolxml.MakeCollectionResponse( errors.response()).toxml())) # When calendar collections are single component only, default MKCALENDAR is VEVENT only if rtype == "calendar" and not set_supported_component_set and config.RestrictCalendarsToOneComponentType: yield self.setSupportedComponents(("VEVENT", )) yield returnValue(responsecode.CREATED) else: # No request body so it is a standard MKCOL result = yield super(CalDAVResource, self).http_MKCOL(request) returnValue(result)
def render(self, req): return Response(200)
def render(self, request): response = Response() response.stream = MemoryStream(self.renderOutput) 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 = 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 _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 = 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)
class WebCalendarResource(ReadOnlyResourceMixIn, DAVFile): def defaultAccessControlList(self): return succeed( davxml.ACL( davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Read()), ), davxml.Protected(), TwistedACLInheritable(), ), )) def etag(self): # Can't be calculated here return succeed(None) def contentLength(self): # Can't be calculated here return None def lastModified(self): return None def exists(self): return True def displayName(self): return "Web Calendar" def contentType(self): return MimeType.fromString("text/html; charset=utf-8") def contentEncoding(self): return None def createSimilarFile(self, path): return DAVFile(path, principalCollections=self.principalCollections()) _htmlContent_lastCheck = 0 _htmlContent_statInfo = 0 _htmlContentDebug_lastCheck = 0 _htmlContentDebug_statInfo = 0 def htmlContent(self, debug=False): if debug: cacheAttr = "_htmlContentDebug" templateFileName = "debug_standalone.html" else: cacheAttr = "_htmlContent" templateFileName = "standalone.html" templateFileName = os.path.join(config.WebCalendarRoot, templateFileName) # # See if the file changed, and dump the cached template if so. # Don't bother to check if we've checked in the past minute. # We don't cache if debug is true. # if not debug and hasattr(self, cacheAttr): currentTime = time() if currentTime - getattr(self, cacheAttr + "_lastCheck") > 60: statInfo = os.stat(templateFileName) statInfo = (statInfo.st_mtime, statInfo.st_size) if statInfo != getattr(self, cacheAttr + "_statInfo"): delattr(self, cacheAttr) setattr(self, cacheAttr + "_statInfo", statInfo) setattr(self, cacheAttr + "_lastCheck", currentTime) # # If we don't have a cached template, load it up. # if not hasattr(self, cacheAttr): templateFile = open(templateFileName) try: htmlContent = templateFile.read() finally: templateFile.close() if debug: # Don't cache return htmlContent setattr(self, cacheAttr, htmlContent) return getattr(self, cacheAttr) def render(self, request): if not self.fp.isdir(): return responsecode.NOT_FOUND # # Get URL of authenticated principal. # Don't need to authenticate here because the ACL will have already # required it. # authenticatedPrincipalURL = request.authnUser.principalURL() def queryValue(arg): query = parse_qs(urlparse(request.uri).query, True) return query.get(arg, [""])[0] # # Parse debug query arg # debug = queryValue("debug") debug = debug is not None and debug.lower() in ("1", "true", "yes") # # Parse TimeZone query arg # tzid = queryValue("tzid") if not tzid: tzid = getLocalTimezone() self.log.debug("Determined timezone to be %s" % (tzid, )) # # Make some HTML # try: htmlContent = self.htmlContent(debug) % { "tzid": tzid, "principalURL": authenticatedPrincipalURL, } except IOError, e: self.log.error("Unable to obtain WebCalendar template: %s" % (e, )) return responsecode.NOT_FOUND 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) return response
(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(fbcalendar.getText(accepted_type)) response.headers.setHeader( "content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type, ))) returnValue(response)
principal = yield self.resourceOwnerPrincipal(request) organizer = recipient = LocalCalendarUser(principal.canonicalCalendarUserAddress(), principal.record) timerange = Period(timerange.start, timerange.end) try: fbresult = yield FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange).generateAttendeeFreeBusyResponse(fbset=fbset, method=None) except NumberOfMatchesWithinLimits: log.error("Too many matching components in free-busy report") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits(), "Too many components" )) except TimeRangeLowerLimit, e: raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, caldavxml.MinDateTime(), "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),) )) 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),) )) response = Response() response.stream = MemoryStream(fbresult.getText(accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) returnValue(response)
fbresult = yield FreebusyQuery( organizer=organizer, recipient=recipient, timerange=timerange).generateAttendeeFreeBusyResponse(fbset=fbset, method=None) except NumberOfMatchesWithinLimits: log.error("Too many matching components in free-busy report") raise HTTPError( ErrorResponse(responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits(), "Too many components")) except TimeRangeLowerLimit, e: raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, caldavxml.MinDateTime(), "Time-range value too far in the past. Must be on or after %s." % (str(e.limit), ))) 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), ))) response = Response() response.stream = MemoryStream(fbresult.getText(accepted_type)) response.headers.setHeader( "content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type, ))) returnValue(response)
def _defer(data): response = Response() response.stream = MemoryStream(str(data)) response.headers.setHeader("content-type", MimeType.fromString("text/calendar")) return response
def getResponseForRequest(self, request): """ Try to match a request and a response cache entry. We first get the request key and match that, then pull the cache entry and decompose it into tokens and response. We then compare the cached tokens with their current values. If all match, we can return the cached response data. """ try: key = (yield self._hashedRequestKey(request)) self.log.debug("Checking cache for: {key!r}", key=key) _ignore_flags, value = (yield self.getCachePool().get(key)) if value is None: self.log.debug("Not in cache: {key!r}", key=key) returnValue(None) (principalToken, directoryToken, uriToken, childTokens, (code, headers, body)) = cPickle.loads(value) self.log.debug( "Found in cache: {key!r} = {value!r}", key=key, value=( principalToken, directoryToken, uriToken, childTokens, ) ) currentTokens = (yield self._getTokens(request)) if currentTokens[0] != principalToken: self.log.debug( "Principal token doesn't match for {key!r}: {currentToken!r} != {principalToken!r}", key=request.cacheKey, currentToken=currentTokens[0], principalToken=principalToken, ) returnValue(None) if currentTokens[1] != directoryToken: self.log.debug( "Directory Record Token doesn't match for {key!r}: {currentToken!r} != {directoryToken!r}", key=request.cacheKey, currentToken=currentTokens[1], directoryToken=directoryToken, ) returnValue(None) if currentTokens[2] != uriToken: self.log.debug( "URI token doesn't match for {key!r}: {currentToken!r} != {uriToken!r}", key=request.cacheKey, currentToken=currentTokens[2], uriToken=uriToken, ) returnValue(None) for childuri, token in childTokens.items(): currentToken = (yield self._tokenForURI(childuri)) if currentToken != token: self.log.debug( "Child {uri} token doesn't match for {key!r}: {currentToken!r} != {token!r}", uri=childuri, key=request.cacheKey, currentToken=currentToken, token=token, ) returnValue(None) self.log.debug("Response cache matched") r = Response(code, stream=MemoryStream(body)) for key, value in headers.iteritems(): r.headers.setRawHeaders(key, value) returnValue(r) except URINotFoundException, e: self.log.debug("Could not locate URI: {e!r}", e=e) returnValue(None)
def http_GET(self, request): if self.exists(): # Special sharing request on a calendar or address book if self.isCalendarCollection() or self.isAddressBookCollection(): # Check for action=share if request.args: action = request.args.get("action", ("",)) if len(action) != 1: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action"), "Invalid action parameter: %s" % (action,), )) action = action[0] dispatch = { "share": self.directShare, }.get(action, None) if dispatch is None: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action"), "Action not supported: %s" % (action,), )) response = (yield dispatch(request)) returnValue(response) else: # FIXME: this should be implemented in storebridge.CalendarObject.render # Look for calendar access restriction on existing resource. parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource(parent): # Check authorization first yield self.authorize(request, (davxml.Read(),)) # Accept header handling accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes()) if accepted_type is None: raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type")) caldata = (yield self.componentForUser()) # Filter any attendee hidden instances caldata = HiddenInstanceFilter().filter(caldata) if self.accessMode: # Non DAV:owner's have limited access to the data isowner = (yield self.isOwner(request)) # Now "filter" the resource calendar data caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata) response = Response() response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) # Add Schedule-Tag header if property is present if self.scheduleTag: response.headers.setHeader("Schedule-Tag", self.scheduleTag) returnValue(response) # Do normal GET behavior response = (yield super(CalDAVResource, self).http_GET(request)) returnValue(response)
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(fbcalendar.getText(accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) returnValue(response)
def renderResponse(self, code, body=None): response = Response(code, {}, body) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def http_POST(self, request): """ The server-to-server POST method. """ # Check shared secret if not self.store.directoryService().serversDB().getThisServer( ).checkSharedSecret(request.headers): self.log.error("Invalid shared secret header in cross-pod request") raise HTTPError( StatusResponse(responsecode.FORBIDDEN, "Not authorized to make this request")) # Look for XPOD header xpod = request.headers.getRawHeaders("XPOD") contentType = request.headers.getHeader("content-type") if xpod is not None: # Attachments are sent in the request body with the JSON data in a header. We # decode the header and add the request.stream as an attribute of the JSON object. xpod = xpod[0] try: j = json.loads(base64.b64decode(xpod)) except (TypeError, ValueError) as e: self.log.error("Invalid JSON header in request: {ex}\n{xpod}", ex=e, xpod=xpod) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "Invalid JSON header in request: {}\n{}".format( e, xpod))) j["stream"] = request.stream j["streamType"] = contentType else: # Check content first if "{}/{}".format(contentType.mediaType, contentType.mediaSubtype) != "application/json": self.log.error("MIME type {mime} not allowed in request", mime=contentType) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "MIME type {} not allowed in request".format( contentType))) body = (yield allDataFromStream(request.stream)) try: j = json.loads(body) except ValueError as e: self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body))) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems[ "xpod"] = j["action"] if "action" in j else "unknown" # Look for a streaming action which needs special handling if self.store.conduit.isStreamAction(j): # Get the conduit to process the data stream try: stream = ProducerStream() class StreamProtocol(Protocol): def connectionMade(self): stream.registerProducer(self.transport, False) def dataReceived(self, data): stream.write(data) def connectionLost(self, reason): stream.finish() result = yield self.store.conduit.processRequestStream( j, StreamProtocol()) try: ct, name = result except ValueError: code = responsecode.BAD_REQUEST else: headers = {"content-type": MimeType.fromString(ct)} headers["content-disposition"] = MimeDisposition( "attachment", params={"filename": name}) returnValue(Response(responsecode.OK, headers, stream)) except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join(( e.__class__.__module__, e.__class__.__name__, )), "details": str(e), } code = responsecode.BAD_REQUEST else: # Get the conduit to process the data try: result = yield self.store.conduit.processRequest(j) code = responsecode.OK if result[ "result"] == "ok" else responsecode.BAD_REQUEST except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join(( e.__class__.__module__, e.__class__.__name__, )), "details": str(e), } code = responsecode.BAD_REQUEST response = JSONResponse(code, result) returnValue(response)
def _processRequest(self): """ Process the request by sending it to the relevant server. @return: the HTTP response. @rtype: L{Response} """ store = self.storeMap[self.server.details()] # Force a failure of the entire request with the supplied exception type if getattr(store, "_poddingFailure", None) is not None: raise store._poddingFailure("Failed cross-pod request") j = json.loads(self.data) if self.stream is not None: j["stream"] = self.stream j["streamType"] = self.streamType try: # Force a BAD cross-pod request with the supplied exception type if getattr(store, "_poddingError", None) is not None: raise store._poddingError("Failed cross-pod request") if store.conduit.isStreamAction(j): stream = ProducerStream() class StreamProtocol(Protocol): def connectionMade(self): stream.registerProducer(self.transport, False) def dataReceived(self, data): stream.write(data) def connectionLost(self, reason): stream.finish() result = yield store.conduit.processRequestStream( j, StreamProtocol()) try: ct, name = result except ValueError: code = responsecode.BAD_REQUEST else: headers = {"content-type": MimeType.fromString(ct)} headers["content-disposition"] = MimeDisposition( "attachment", params={"filename": name}) returnValue(Response(responsecode.OK, headers, stream)) else: result = yield store.conduit.processRequest(j) code = responsecode.OK except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join(( e.__class__.__module__, e.__class__.__name__, )), "details": str(e), } code = responsecode.BAD_REQUEST response = JSONResponse(code, result) 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: # FIXME: this should be implemented in storebridge.CalendarObject.render # Look for calendar access restriction on existing resource. parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource(parent): # Check authorization first yield self.authorize(request, (davxml.Read(),)) # Accept header handling accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes()) if accepted_type is None: raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type")) caldata = (yield self.iCalendarForUser(request)) # Filter any attendee hidden instances caldata = HiddenInstanceFilter().filter(caldata) if self.accessMode: # Non DAV:owner's have limited access to the data isowner = (yield self.isOwner(request)) # Now "filter" the resource calendar data caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata) response = Response() response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) # Add Schedule-Tag header if property is present if self.scheduleTag: response.headers.setHeader("Schedule-Tag", self.scheduleTag) returnValue(response) # Do normal GET behavior response = (yield super(CalDAVResource, self).http_GET(request)) returnValue(response)