Esempio n. 1
0
    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
Esempio n. 3
0
    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"))
Esempio n. 4
0
 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)
Esempio n. 6
0
    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)
Esempio n. 7
0
 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
Esempio n. 9
0
 def render(self, request):
     response = Response()
     response.stream = MemoryStream(self.renderOutput)
     return response
Esempio n. 10
0
 def _defer(data):
     response = Response()
     response.stream = MemoryStream(str(data))
     response.headers.setHeader("content-type", MimeType.fromString("text/calendar"))
     return response
Esempio n. 11
0
    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)
Esempio n. 13
0
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)
Esempio n. 14
0
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)