Ejemplo n.º 1
0
    def test_PROPPATCH_basic(self):
        """
        PROPPATCH
        """

        # FIXME:
        # Do PROPFIND to make sure it's still there
        # Test nonexistant resource
        # Test None namespace in property

        def check_patch_response(response):
            response = IResponse(response)

            if response.code != responsecode.MULTI_STATUS:
                self.fail("Incorrect response code for PROPFIND (%s != %s)" %
                          (response.code, responsecode.MULTI_STATUS))

            content_type = response.headers.getHeader("content-type")
            if content_type not in (http_headers.MimeType("text", "xml"),
                                    http_headers.MimeType(
                                        "application", "xml")):
                self.fail(
                    "Incorrect content-type for PROPPATCH response (%r not in %r)"
                    % (content_type,
                       (http_headers.MimeType("text", "xml"),
                        http_headers.MimeType("application", "xml"))))

            return davXMLFromStream(
                response.stream).addCallback(check_patch_xml)

        def check_patch_xml(doc):
            multistatus = doc.root_element

            if not isinstance(multistatus, davxml.MultiStatus):
                self.fail(
                    "PROPFIND response XML root element is not multistatus: %r"
                    % (multistatus, ))

            # Requested a property change one resource, so there should be exactly one response
            response = multistatus.childOfType(davxml.Response)

            # Should have a response description (its contents are arbitrary)
            response.childOfType(davxml.ResponseDescription)

            # Requested property change was on /
            self.failUnless(
                response.childOfType(davxml.HRef) == "/",
                "Incorrect response URI: %s != /" %
                (response.childOfType(davxml.HRef), ))

            # Requested one property change, so there should be exactly one property status
            propstat = response.childOfType(davxml.PropertyStatus)

            # And the contained property should be a SpiffyProperty
            self.failIf(
                propstat.childOfType(
                    davxml.PropertyContainer).childOfType(SpiffyProperty) is
                None, "Not a SpiffyProperty in PROPPATCH property status: %s" %
                (propstat.toxml()))

            # And the status should be 200
            self.failUnless(
                propstat.childOfType(davxml.Status).code == responsecode.OK,
                "Incorrect status code for PROPPATCH of property %s: %s != %s"
                % (propstat.childOfType(davxml.PropertyContainer).toxml(),
                   propstat.childOfType(davxml.Status).code, responsecode.OK))

        patch = davxml.PropertyUpdate(
            davxml.Set(
                davxml.PropertyContainer(
                    SpiffyProperty.fromString("This is a spiffy resource."))))

        request = SimpleRequest(self.site, "PROPPATCH", "/")
        request.stream = MemoryStream(patch.toxml())
        return self.send(request, check_patch_response)
Ejemplo n.º 2
0
    def test_sign(self):

        data = "Hello World!"
        for algorithm, hash_method in (
            (
                "rsa-sha1",
                hashlib.sha1,
            ),
            (
                "rsa-sha256",
                hashlib.sha256,
            ),
        ):
            stream = MemoryStream(data)
            headers = Headers()
            headers.addRawHeader("Originator", "mailto:[email protected]")
            headers.addRawHeader("Recipient", "mailto:[email protected]")
            headers.setHeader(
                "Content-Type",
                MimeType("text", "calendar", **{
                    "component": "VEVENT",
                    "charset": "utf-8"
                }))
            request = DKIMRequest("POST", "/", headers, stream, "example.com",
                                  "dkim", self.private_keyfile, algorithm, (
                                      "Originator",
                                      "Recipient",
                                      "Content-Type",
                                  ), True, True, True, 3600)
            result = (yield request.sign())

            # Manually create what should be the correct thing to sign and make sure signatures match
            bodyhash = base64.b64encode(
                hash_method(DKIMUtils.canonicalizeBody(data)).digest())
            sign_this = """originator:mailto:[email protected]
recipient:mailto:[email protected]
content-type:%s
ischedule-version:1.0
ischedule-message-id:%s
dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=private-exchange:http/well-known:dns/txt; c=ischedule-relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=""".replace(
                "\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0],
                                 request.message_id, request.time,
                                 request.expire, algorithm, bodyhash)
            with open(self.private_keyfile) as f:
                key = f.read()
            key = RSA.importKey(key)
            signature = DKIMUtils.sign(sign_this, key,
                                       DKIMUtils.hash_func(algorithm))

            self.assertEqual(result, signature)

            # Make sure header is updated in the request
            updated_header = "v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=private-exchange:http/well-known:dns/txt; c=ischedule-relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=%s" % (
                request.time,
                request.expire,
                algorithm,
                bodyhash,
                signature,
            )
            self.assertEqual(
                request.headers.getRawHeaders("DKIM-Signature")[0],
                updated_header)

            # Try to verify result using public key
            with open(self.public_keyfile) as f:
                pubkey = f.read()
            pubkey = RSA.importKey(pubkey)
            self.assertEqual(
                DKIMUtils.verify(sign_this, result, pubkey,
                                 DKIMUtils.hash_func(algorithm)), None)
Ejemplo n.º 3
0
        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)
Ejemplo n.º 4
0
    def test_get_attachment_data(self):
        """
        Test that action=get-all-attachments 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        remote_id = attachment.id()
        yield self.commitTransaction(0)

        home1 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name="puser01")
        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        attachment = yield ManagedAttachment._create(self.theTransactionUnderTest(1), None, home1.id())
        attachment._contentType = MimeType.fromString("text/plain")
        attachment._name = "test.txt"
        yield shared_object.ownerHome().readAttachmentData(remote_id, attachment)
        yield self.commitTransaction(1)
Ejemplo n.º 5
0
    def test_update_attachment(self):
        """
        Test that action=update-attachment 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        resourceID = object1.id()
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        managedID = attachment.managedID()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        data = "Here is some more text."
        attachment, location = yield shared_object.updateAttachment(managedID, MimeType.fromString("text/plain"), "test.txt", MemoryStream(data))
        managedID = attachment.managedID()
        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
        self.assertEqual(attachment.size(), len(data))
        self.assertTrue("user01/dropbox/" in location)
        yield self.commitTransaction(1)

        cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
        self.assertEqual(cobjs, set((resourceID,)))
        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
        self.assertEqual(attachment.name(), "test.txt")
        data = yield self.attachmentToString(attachment)
        self.assertEqual(data, "Here is some more text.")
        yield self.commitTransaction(0)
Ejemplo n.º 6
0
 def _defer(data):
     response = Response()
     response.stream = MemoryStream(str(data))
     response.headers.setHeader(
         "content-type", MimeType.fromString("text/calendar"))
     return response
Ejemplo n.º 7
0
    def addressbook_query(self, addressbook_uri, query, got_xml, data,
                          no_init):

        if not no_init:
            ''' FIXME: clear address book, possibly by removing
            mkcol = """<?xml version="1.0" encoding="utf-8" ?>
<D:mkcol xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<D:set>
<D:prop>
<D:resourcetype><D:collection/><C:addressbook/></D:resourcetype>
</D:prop>
</D:set>
</D:mkcol>
"""
            response = yield self.send(SimpleStoreRequest(self, "MKCOL", addressbook_uri, content=mkcol, authPrincipal=self.authPrincipal))

            response = IResponse(response)

            if response.code != responsecode.CREATED:
                self.fail("MKCOL failed: %s" % (response.code,))
            '''
            if data:
                for filename, icaldata in data.iteritems():
                    request = SimpleStoreRequest(
                        self,
                        "PUT",
                        joinURL(addressbook_uri, filename + ".vcf"),
                        headers=Headers({
                            "content-type":
                            MimeType.fromString("text/vcard")
                        }),
                        authPrincipal=self.authPrincipal)
                    request.stream = MemoryStream(icaldata)
                    yield self.send(request)
            else:
                # Add vcards to addressbook
                for child in FilePath(self.vcards_dir).children():
                    if os.path.splitext(child.basename())[1] != ".vcf":
                        continue
                    request = SimpleStoreRequest(
                        self,
                        "PUT",
                        joinURL(addressbook_uri, child.basename()),
                        headers=Headers({
                            "content-type":
                            MimeType.fromString("text/vcard")
                        }),
                        authPrincipal=self.authPrincipal)
                    request.stream = MemoryStream(child.getContent())
                    yield self.send(request)

        request = SimpleStoreRequest(self,
                                     "REPORT",
                                     addressbook_uri,
                                     authPrincipal=self.authPrincipal)
        request.stream = MemoryStream(query.toxml())
        response = yield self.send(request)

        response = IResponse(response)

        if response.code != responsecode.MULTI_STATUS:
            self.fail("REPORT failed: %s" % (response.code, ))

        returnValue((yield
                     davXMLFromStream(response.stream).addCallback(got_xml)))
Ejemplo n.º 8
0
 def __init__(self, code, headers, body):
     self.code = code
     self.headers = Headers(headers)
     self.body = body
     self.stream = MemoryStream(body)
Ejemplo n.º 9
0
    def initialState(self):
        """
        Setup the server with an initial set of data

        user01 - migrating user
        user02 - has a calendar shared with user01
        user03 - shared to by user01

        puser01 - user on other pod
        puser02 - has a calendar shared with user01
        puser03 - shared to by user01
        """

        # Data for user01
        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0),
                                        name="user01",
                                        create=True)
        self.stash["user01_pod0_home_id"] = home.id()
        calendar = yield home.childWithName("calendar")
        yield calendar.createCalendarObjectWithName(
            "01_1.ics", Component.fromString(self.data01_1))
        yield calendar.createCalendarObjectWithName(
            "01_2.ics", Component.fromString(self.data01_2))
        obj3 = yield calendar.createCalendarObjectWithName(
            "01_3.ics", Component.fromString(self.data01_3))
        attachment, _ignore_location = yield obj3.addAttachment(
            None, MimeType.fromString("text/plain"), "test.txt",
            MemoryStream("Here is some text #1."))
        self.stash["user01_attachment_id"] = attachment.id()
        self.stash["user01_attachment_md5"] = attachment.md5()
        self.stash["user01_attachment_mid"] = attachment.managedID()
        yield self.commitTransaction(0)

        # Data for user02
        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0),
                                        name="user02",
                                        create=True)
        calendar = yield home.childWithName("calendar")
        yield calendar.createCalendarObjectWithName(
            "02_1.ics", Component.fromString(self.data02_1))
        yield calendar.createCalendarObjectWithName(
            "02_2.ics", Component.fromString(self.data02_2))
        yield calendar.createCalendarObjectWithName(
            "02_3.ics", Component.fromString(self.data02_3))
        yield self.commitTransaction(0)

        # Data for puser02
        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1),
                                        name="puser02",
                                        create=True)
        calendar = yield home.childWithName("calendar")
        yield calendar.createCalendarObjectWithName(
            "p02_1.ics", Component.fromString(self.datap02_1))
        yield calendar.createCalendarObjectWithName(
            "p02_2.ics", Component.fromString(self.datap02_2))
        yield calendar.createCalendarObjectWithName(
            "p02_3.ics", Component.fromString(self.datap02_3))
        yield self.commitTransaction(1)

        # Share calendars
        self.stash["sharename_user01_to_user03"] = yield self._createShare(
            "user01", "user03")
        self.stash["sharename_user01_to_puser03"] = yield self._createShare(
            "user01", "puser03")
        self.stash["sharename_user02_to_user01"] = yield self._createShare(
            "user02", "user01")
        self.stash["sharename_puser02_to_user01"] = yield self._createShare(
            "puser02", "user01")

        # Add some delegates
        txn = self.theTransactionUnderTest(0)
        record01 = yield txn.directoryService().recordWithUID(u"user01")
        record02 = yield txn.directoryService().recordWithUID(u"user02")
        record03 = yield txn.directoryService().recordWithUID(u"user03")
        precord01 = yield txn.directoryService().recordWithUID(u"puser01")

        group02 = yield txn.directoryService().recordWithUID(u"group02")
        group03 = yield txn.directoryService().recordWithUID(u"group03")

        # Add user02 and user03 as individual delegates
        yield Delegates.addDelegate(txn, record01, record02, True)
        yield Delegates.addDelegate(txn, record01, record03, False)
        yield Delegates.addDelegate(txn, record01, precord01, False)

        # Add group delegates
        yield Delegates.addDelegate(txn, record01, group02, True)
        yield Delegates.addDelegate(txn, record01, group03, False)

        # Add external delegates
        yield txn.assignExternalDelegates(u"user01", None, None, u"external1",
                                          u"external2")

        yield self.commitTransaction(0)

        yield self.waitAllEmpty()
Ejemplo n.º 10
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 = 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)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
    def test_make_calendar_with_props(self):
        """
        Make calendar with properties (CalDAV-access-09, section 5.3.1.2)
        """
        uri = "/calendars/users/user01/calendar_prop/"
        path = os.path.join(self.docroot, uri[1:])

        if os.path.exists(path):
            rmdir(path)

        @inlineCallbacks
        def do_test(response):
            response = IResponse(response)

            if response.code != responsecode.CREATED:
                self.fail("MKCALENDAR failed: %s" % (response.code, ))

            resource = (yield request.locateResource(uri))
            if not resource.isCalendarCollection():
                self.fail("MKCALENDAR made non-calendar collection")

            for qname, value in (
                (davxml.DisplayName.qname(), "Lisa's Events"),
                (caldavxml.CalendarDescription.qname(),
                 "Calendar restricted to events."),
            ):
                stored = yield resource.readProperty(qname, None)
                stored = str(stored)
                if stored != value:
                    self.fail(
                        "MKCALENDAR failed to set property %s: %s != %s" %
                        (qname, stored, value))

            supported_components = yield resource.readProperty(
                caldavxml.SupportedCalendarComponentSet, None)
            supported_components = supported_components.children
            if len(supported_components) != 1:
                self.fail(
                    "MKCALENDAR failed to set property %s: len(%s) != 1" %
                    (caldavxml.SupportedCalendarComponentSet.qname(),
                     supported_components))
            if supported_components[0] != caldavxml.CalendarComponent(
                    name="VEVENT"):
                self.fail("MKCALENDAR failed to set property %s: %s != %s" %
                          (caldavxml.SupportedCalendarComponentSet.qname(),
                           supported_components[0].toxml(),
                           caldavxml.CalendarComponent(name="VEVENT").toxml()))

            tz = (yield resource.readProperty(caldavxml.CalendarTimeZone,
                                              None))
            tz = tz.calendar()
            self.failUnless(tz.resourceType() == "VTIMEZONE")
            self.failUnless(
                tuple(tz.subcomponents())[0].propertyValue("TZID") ==
                "US-Eastern")

        mk = caldavxml.MakeCalendar(
            davxml.Set(
                davxml.PropertyContainer(
                    davxml.DisplayName("Lisa's Events"),
                    caldavxml.CalendarDescription(
                        "Calendar restricted to events."),  # FIXME: lang=en
                    caldavxml.SupportedCalendarComponentSet(
                        caldavxml.CalendarComponent(name="VEVENT")),
                    caldavxml.CalendarTimeZone("""BEGIN:VCALENDAR
PRODID:-//Example Corp.//CalDAV Client//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:US-Eastern
LAST-MODIFIED:19870101T000000Z
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:Eastern Standard Time (US & Canada)
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:Eastern Daylight Time (US & Canada)
END:DAYLIGHT
END:VTIMEZONE
END:VCALENDAR
"""))))

        request = SimpleStoreRequest(self,
                                     "MKCALENDAR",
                                     uri,
                                     authPrincipal=self.authPrincipal)
        request.stream = MemoryStream(mk.toxml())
        return self.send(request, do_test)
Ejemplo n.º 13
0
    def finalState(self):
        """
        Setup the server with data changes appearing before the final sync
        """
        txn = self.theTransactionUnderTest(1)
        obj = yield self.calendarObjectUnderTest(txn, name="p02_2.ics", calendar_name="calendar", home="puser02")
        attachment, _ignore_location = yield obj.addAttachment(None, MimeType.fromString("text/plain"), "test_p02.txt", MemoryStream("Here is some text #p02."))
        self.stash["puser02_attachment_id"] = attachment.id()
        self.stash["puser02_attachment_mid"] = attachment.managedID()
        self.stash["puser02_attachment_md5"] = attachment.md5()

        yield self.commitTransaction(1)

        yield self.waitAllEmpty()
Ejemplo n.º 14
0
    def secondState(self):
        """
        Setup the server with data changes appearing after the first sync
        """
        txn = self.theTransactionUnderTest(0)
        obj = yield self.calendarObjectUnderTest(txn, name="01_1.ics", calendar_name="calendar", home="user01")
        yield obj.setComponent(self.data01_1_changed)

        obj = yield self.calendarObjectUnderTest(txn, name="02_2.ics", calendar_name="calendar", home="user02")
        attachment, _ignore_location = yield obj.addAttachment(None, MimeType.fromString("text/plain"), "test_02.txt", MemoryStream("Here is some text #02."))
        self.stash["user02_attachment_id"] = attachment.id()
        self.stash["user02_attachment_md5"] = attachment.md5()
        self.stash["user02_attachment_mid"] = attachment.managedID()

        yield self.commitTransaction(0)

        yield self.waitAllEmpty()
Ejemplo n.º 15
0
    def test_PROPFIND_basic(self):
        """
        PROPFIND request
        """
        def check_result(response):
            response = IResponse(response)

            if response.code != responsecode.MULTI_STATUS:
                self.fail("Incorrect response code for PROPFIND (%s != %s)" %
                          (response.code, responsecode.MULTI_STATUS))

            content_type = response.headers.getHeader("content-type")
            if content_type not in (http_headers.MimeType("text", "xml"),
                                    http_headers.MimeType(
                                        "application", "xml")):
                self.fail(
                    "Incorrect content-type for PROPFIND response (%r not in %r)"
                    % (content_type,
                       (http_headers.MimeType("text", "xml"),
                        http_headers.MimeType("application", "xml"))))

            return davXMLFromStream(response.stream).addCallback(check_xml)

        def check_xml(doc):
            multistatus = doc.root_element

            if not isinstance(multistatus, davxml.MultiStatus):
                self.fail(
                    "PROPFIND response XML root element is not multistatus: %r"
                    % (multistatus, ))

            for response in multistatus.childrenOfType(
                    davxml.PropertyStatusResponse):
                if response.childOfType(davxml.HRef) == "/":
                    for propstat in response.childrenOfType(
                            davxml.PropertyStatus):
                        status = propstat.childOfType(davxml.Status)
                        properties = propstat.childOfType(
                            davxml.PropertyContainer).children

                        if status.code != responsecode.OK:
                            self.fail(
                                "PROPFIND failed (status %s) to locate live properties: %s"
                                % (status.code, properties))

                        properties_to_find = [
                            p.qname() for p in self.liveProperties()
                        ]

                        for property in properties:
                            qname = property.qname()
                            if qname in properties_to_find:
                                properties_to_find.remove(qname)
                            else:
                                self.fail(
                                    "PROPFIND found property we didn't ask for: %r"
                                    % (property, ))

                        if properties_to_find:
                            self.fail(
                                "PROPFIND failed to find properties: %r" %
                                (properties_to_find, ))

                    break

            else:
                self.fail("No response for URI /")

        query = davxml.PropertyFind(
            davxml.PropertyContainer(*self.liveProperties()))

        request = SimpleRequest(self.site, "PROPFIND", "/")

        depth = "1"
        if depth is not None:
            request.headers.setHeader("depth", depth)

        request.stream = MemoryStream(query.toxml())

        return self.send(request, check_result)
Ejemplo n.º 16
0
        def mkcalendar_cb(response):
            response = IResponse(response)

            if response.code != responsecode.CREATED:
                self.fail("MKCALENDAR failed: %s" % (response.code, ))

            def propfind_cb(response):
                response = IResponse(response)

                if response.code != responsecode.MULTI_STATUS:
                    self.fail("Incorrect response to PROPFIND: %s" %
                              (response.code, ))

                def got_xml(doc):
                    if not isinstance(doc.root_element, davxml.MultiStatus):
                        self.fail(
                            "PROPFIND response XML root element is not multistatus: %r"
                            % (doc.root_element, ))

                    response = doc.root_element.childOfType(davxml.Response)
                    href = response.childOfType(davxml.HRef)
                    self.failUnless(str(href) == calendar_uri)

                    container = response.childOfType(
                        davxml.PropertyStatus).childOfType(
                            davxml.PropertyContainer)

                    #
                    # Check CalDAV:supported-calendar-component-set
                    #

                    supported_components = container.childOfType(
                        caldavxml.SupportedCalendarComponentSet)
                    if supported_components:
                        self.fail(
                            "CalDAV:supported-calendar-component-set element was returned; but should be hidden."
                        )

                    #
                    # Check CalDAV:supported-calendar-data
                    #

                    supported_calendar = container.childOfType(
                        caldavxml.SupportedCalendarData)
                    if supported_calendar:
                        self.fail(
                            "CalDAV:supported-calendar-data elementwas returned; but should be hidden."
                        )

                    #
                    # Check DAV:supported-report-set
                    #

                    supported_reports = container.childOfType(
                        davxml.SupportedReportSet)
                    if supported_reports:
                        self.fail(
                            "DAV:supported-report-set element was returned; but should be hidden.."
                        )

                return davXMLFromStream(response.stream).addCallback(got_xml)

            query = davxml.PropertyFind(davxml.AllProperties(), )

            request = SimpleStoreRequest(
                self,
                "PROPFIND",
                calendar_uri,
                headers=http_headers.Headers({"Depth": "0"}),
                authPrincipal=self.authPrincipal,
            )
            request.stream = MemoryStream(query.toxml())
            return self.send(request, propfind_cb)
Ejemplo n.º 17
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:
            # 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)
Ejemplo n.º 18
0
        def mkcalendar_cb(response):
            response = IResponse(response)

            if response.code != responsecode.CREATED:
                self.fail("MKCALENDAR failed: %s" % (response.code, ))

            def propfind_cb(response):
                response = IResponse(response)

                if response.code != responsecode.MULTI_STATUS:
                    self.fail("Incorrect response to PROPFIND: %s" %
                              (response.code, ))

                def got_xml(doc):
                    if not isinstance(doc.root_element, davxml.MultiStatus):
                        self.fail(
                            "PROPFIND response XML root element is not multistatus: %r"
                            % (doc.root_element, ))

                    response = doc.root_element.childOfType(davxml.Response)
                    href = response.childOfType(davxml.HRef)
                    self.failUnless(str(href) == calendar_uri)

                    for propstat in response.childrenOfType(
                            davxml.PropertyStatus):
                        status = propstat.childOfType(davxml.Status)
                        if status.code != responsecode.OK:
                            self.fail(
                                "Unable to read requested properties (%s): %r"
                                % (status,
                                   propstat.childOfType(
                                       davxml.PropertyContainer).toxml()))

                    container = propstat.childOfType(davxml.PropertyContainer)

                    #
                    # Check CalDAV:supported-calendar-component-set
                    #

                    supported_components = container.childOfType(
                        caldavxml.SupportedCalendarComponentSet)
                    if not supported_components:
                        self.fail(
                            "Expected CalDAV:supported-calendar-component-set element; but got none."
                        )

                    supported = set(("VEVENT", ))

                    for component in supported_components.children:
                        if component.type in supported:
                            supported.remove(component.type)

                    if supported:
                        self.fail(
                            "Expected supported calendar component types: %s" %
                            (tuple(supported), ))

                    #
                    # Check CalDAV:supported-calendar-data
                    #

                    supported_calendar = container.childOfType(
                        caldavxml.SupportedCalendarData)
                    if not supported_calendar:
                        self.fail(
                            "Expected CalDAV:supported-calendar-data element; but got none."
                        )

                    for calendar in supported_calendar.children:
                        if calendar.content_type not in (
                                "text/calendar", "application/calendar+json"):
                            self.fail(
                                "Expected a text/calendar calendar-data type restriction"
                            )
                        if calendar.version != "2.0":
                            self.fail(
                                "Expected a version 2.0 calendar-data restriction"
                            )

                    #
                    # Check DAV:supported-report-set
                    #

                    supported_reports = container.childOfType(
                        davxml.SupportedReportSet)
                    if not supported_reports:
                        self.fail(
                            "Expected DAV:supported-report-set element; but got none."
                        )

                    cal_query = False
                    cal_multiget = False
                    cal_freebusy = False
                    for supported in supported_reports.childrenOfType(
                            davxml.SupportedReport):
                        report = supported.childOfType(davxml.Report)
                        if report.childOfType(
                                caldavxml.CalendarQuery) is not None:
                            cal_query = True
                        if report.childOfType(
                                caldavxml.CalendarMultiGet) is not None:
                            cal_multiget = True
                        if report.childOfType(
                                caldavxml.FreeBusyQuery) is not None:
                            cal_freebusy = True

                    if not cal_query:
                        self.fail(
                            "Expected CalDAV:CalendarQuery element; but got none."
                        )
                    if not cal_multiget:
                        self.fail(
                            "Expected CalDAV:CalendarMultiGet element; but got none."
                        )
                    if not cal_freebusy:
                        self.fail(
                            "Expected CalDAV:FreeBusyQuery element; but got none."
                        )

                return davXMLFromStream(response.stream).addCallback(got_xml)

            query = davxml.PropertyFind(
                davxml.PropertyContainer(
                    caldavxml.SupportedCalendarData(),
                    caldavxml.SupportedCalendarComponentSet(),
                    davxml.SupportedReportSet(),
                ), )

            request = SimpleStoreRequest(
                self,
                "PROPFIND",
                calendar_uri,
                headers=http_headers.Headers({"Depth": "0"}),
                authPrincipal=self.authPrincipal,
            )
            request.stream = MemoryStream(query.toxml())
            return self.send(request, propfind_cb)
Ejemplo n.º 19
0
def storeResource(
    request,
    source=None, source_uri=None, data=None,
    destination=None, destination_uri=None,
    deletesource=False,
    depth="0"
):
    """
    Function that does common PUT/COPY/MOVE behaviour.
    
    @param request:           the L{txweb2.server.Request} for the current HTTP request.
    @param source:            the L{DAVFile} for the source resource to copy from, or None if source data
                              is to be read from the request.
    @param source_uri:        the URI for the source resource.
    @param data:              a C{str} to copy data from instead of the request stream.
    @param destination:       the L{DAVFile} for the destination resource to copy into.
    @param destination_uri:   the URI for the destination resource.
    @param deletesource:      True if the source resource is to be deleted on successful completion, False otherwise.
    @param depth:             a C{str} containing the COPY/MOVE Depth header value.
    @return:                  status response.
    """
    
    try:
        assert request is not None and destination is not None and destination_uri is not None
        assert (source is None) or (source is not None and source_uri is not None)
        assert not deletesource or (deletesource and source is not None)
    except AssertionError:
        log.error("Invalid arguments to storeResource():")
        log.error("request=%s\n" % (request,))
        log.error("source=%s\n" % (source,))
        log.error("source_uri=%s\n" % (source_uri,))
        log.error("data=%s\n" % (data,))
        log.error("destination=%s\n" % (destination,))
        log.error("destination_uri=%s\n" % (destination_uri,))
        log.error("deletesource=%s\n" % (deletesource,))
        log.error("depth=%s\n" % (depth,))
        raise

    class RollbackState(object):
        """
        This class encapsulates the state needed to rollback the entire PUT/COPY/MOVE
        transaction, leaving the server state the same as it was before the request was
        processed. The DoRollback method will actually execute the rollback operations.
        """
        
        def __init__(self):
            self.active = True
            self.source_copy = None
            self.destination_copy = None
            self.destination_created = False
            self.source_deleted = False
        
        def Rollback(self):
            """
            Rollback the server state. Do not allow this to raise another exception. If
            rollback fails then we are going to be left in an awkward state that will need
            to be cleaned up eventually.
            """
            if self.active:
                self.active = False
                log.error("Rollback: rollback")
                try:
                    if self.source_copy and self.source_deleted:
                        self.source_copy.moveTo(source.fp)
                        log.error("Rollback: source restored %s to %s" % (self.source_copy.path, source.fp.path))
                        self.source_copy = None
                        self.source_deleted = False
                    if self.destination_copy:
                        destination.fp.remove()
                        log.error("Rollback: destination restored %s to %s" % (self.destination_copy.path, destination.fp.path))
                        self.destination_copy.moveTo(destination.fp)
                        self.destination_copy = None
                    elif self.destination_created:
                        destination.fp.remove()
                        log.error("Rollback: destination removed %s" % (destination.fp.path,))
                        self.destination_created = False
                except:
                    log.error("Rollback: exception caught and not handled: %s" % Failure())

        def Commit(self):
            """
            Commit the resource changes by wiping the rollback state.
            """
            if self.active:
                log.error("Rollback: commit")
                self.active = False
                if self.source_copy:
                    self.source_copy.remove()
                    log.error("Rollback: removed source backup %s" % (self.source_copy.path,))
                    self.source_copy = None
                if self.destination_copy:
                    self.destination_copy.remove()
                    log.error("Rollback: removed destination backup %s" % (self.destination_copy.path,))
                    self.destination_copy = None
                self.destination_created = False
                self.source_deleted = False
    
    rollback = RollbackState()

    try:
        """
        Handle validation operations here.
        """

        """
        Handle rollback setup here.
        """

        # Do quota checks on destination and source before we start messing with adding other files
        destquota = waitForDeferred(destination.quota(request))
        yield destquota
        destquota = destquota.getResult()
        if destquota is not None and destination.exists():
            old_dest_size = waitForDeferred(destination.quotaSize(request))
            yield old_dest_size
            old_dest_size = old_dest_size.getResult()
        else:
            old_dest_size = 0
            
        if source is not None:
            sourcequota = waitForDeferred(source.quota(request))
            yield sourcequota
            sourcequota = sourcequota.getResult()
            if sourcequota is not None and source.exists():
                old_source_size = waitForDeferred(source.quotaSize(request))
                yield old_source_size
                old_source_size = old_source_size.getResult()
            else:
                old_source_size = 0
        else:
            sourcequota = None
            old_source_size = 0

        # We may need to restore the original resource data if the PUT/COPY/MOVE fails,
        # so rename the original file in case we need to rollback.
        overwrite = destination.exists()
        if overwrite:
            rollback.destination_copy = FilePath(destination.fp.path)
            rollback.destination_copy.path += ".rollback"
            destination.fp.copyTo(rollback.destination_copy)
        else:
            rollback.destination_created = True

        if deletesource:
            rollback.source_copy = FilePath(source.fp.path)
            rollback.source_copy.path += ".rollback"
            source.fp.copyTo(rollback.source_copy)
    
        """
        Handle actual store operations here.
        """

        # Do put or copy based on whether source exists
        if source is not None:
            response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, depth)
        else:
            datastream = request.stream
            if data is not None:
                datastream = MemoryStream(data)
            md5 = MD5Stream(datastream)
            response = maybeDeferred(put, md5, destination.fp)

        response = waitForDeferred(response)
        yield response
        response = response.getResult()

        # Update the MD5 value on the resource
        if source is not None:
            # Copy MD5 value from source to destination
            if source.hasDeadProperty(TwistedGETContentMD5):
                md5 = source.readDeadProperty(TwistedGETContentMD5)
                destination.writeDeadProperty(md5)
        else:
            # Finish MD5 calc and write dead property
            md5.close()
            md5 = md5.getMD5()
            destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))

        # Update the content-type value on the resource if it is not been copied or moved
        if source is None:
            content_type = request.headers.getHeader("content-type")
            if content_type is not None:
                destination.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(content_type)))

        response = IResponse(response)
        
        # Do quota check on destination
        if destquota is not None:
            # Get size of new/old resources
            new_dest_size = waitForDeferred(destination.quotaSize(request))
            yield new_dest_size
            new_dest_size = new_dest_size.getResult()
            diff_size = new_dest_size - old_dest_size
            if diff_size >= destquota[0]:
                log.error("Over quota: available %d, need %d" % (destquota[0], diff_size))
                raise HTTPError(ErrorResponse(
                    responsecode.INSUFFICIENT_STORAGE_SPACE,
                    (dav_namespace, "quota-not-exceeded")
                ))
            d = waitForDeferred(destination.quotaSizeAdjust(request, diff_size))
            yield d
            d.getResult()

        if deletesource:
            # Delete the source resource
            if sourcequota is not None:
                delete_size = 0 - old_source_size
                d = waitForDeferred(source.quotaSizeAdjust(request, delete_size))
                yield d
                d.getResult()

            delete(source_uri, source.fp, depth)
            rollback.source_deleted = True

        # Can now commit changes and forget the rollback details
        rollback.Commit()

        yield response
        return
        
    except:
        # Roll back changes to original server state. Note this may do nothing
        # if the rollback has already ocurred or changes already committed.
        rollback.Rollback()
        raise
Ejemplo n.º 20
0
    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)
Ejemplo n.º 21
0
    def test_get_all_attachments(self):
        """
        Test that action=get-all-attachments 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        attachments = yield shared_object.ownerHome().getAllAttachments()
        self.assertEqual(len(attachments), 1)
        self.assertTrue(isinstance(attachments[0], ManagedAttachment))
        self.assertEqual(attachments[0].contentType(), MimeType.fromString("text/plain"))
        self.assertEqual(attachments[0].name(), "test.txt")
        yield self.commitTransaction(1)
Ejemplo n.º 22
0
 def render(self, request):
     response = Response()
     response.stream = MemoryStream(self.renderOutput)
     return response
Ejemplo n.º 23
0
    def test_get_attachment_links(self):
        """
        Test that action=get-attachment-links works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        cobj1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        calobjID = cobj1.id()
        yield self.commitTransaction(0)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        attID = attachment.id()
        managedID = attachment.managedID()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        links = yield shared_object.ownerHome().getAttachmentLinks()
        self.assertEqual(len(links), 1)
        self.assertTrue(isinstance(links[0], AttachmentLink))
        self.assertEqual(links[0]._attachmentID, attID)
        self.assertEqual(links[0]._managedID, managedID)
        self.assertEqual(links[0]._calendarObjectID, calobjID)
        yield self.commitTransaction(1)
Ejemplo n.º 24
0
class TestDKIMVerifier (TestDKIMBase):
    """
    L{DKIMVerifier} support tests.
    """

    class StubRequest(object):

        def __init__(self, method, uri, headers, body):
            self.method = method
            self.uri = uri
            self.headers = Headers()
            for name, value in headers:
                self.headers.addRawHeader(name, value)
            self.stream = MemoryStream(body)


    def _makeHeaders(self, headers_pairs):
        headers = Headers()
        for name, value in headers_pairs:
            headers.addRawHeader(name, value)
        return headers


    def test_valid_dkim_headers(self):
        """
        L{DKIMVerifier.processDKIMHeader} correctly validates DKIM-Signature headers.
        """

        data = (
            # Bogus
            ((("DKIM-Signature", "v=1"),), False,),

            # More than one
            ((
                ("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),
                ("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha256; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),
            ), False,),

            # Valid
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), True,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def"),), True,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() + 30),)),), True,),

            # Invalid
            ((("DKIM-Signature", "v=2; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha512; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/relaxed; h=Originator:Recipient; bh=abc; b=def"),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,),
            ((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; c=ischedule-relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,),
        )

        for headers, result in data:
            verifier = DKIMVerifier(self._makeHeaders(headers), "")
            if result:
                verifier.processDKIMHeader()
            else:
                self.assertRaises(DKIMVerificationError, verifier.processDKIMHeader)


    def test_canonicalize_header(self):
        """
        L{DKIMVerifier.canonicalizeHeader} correctly canonicalizes headers.
        """

        data = (
            ("Content-Type", " text/calendar  ; charset =  \"utf-8\"  ", "content-type:text/calendar ; charset = \"utf-8\"\r\n"),
            ("Originator", "  mailto:[email protected]  ", "originator:mailto:[email protected]\r\n"),
            ("Recipient", "  mailto:[email protected]  ,\t mailto:[email protected]\t\t  ", "recipient:mailto:[email protected],mailto:[email protected]\r\n"),
            ("iSchedule-Version", " 1.0 ", "ischedule-version:1.0\r\n"),
            (
                "DKIM-Signature",
                "  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a b  c; b=d ef",
                "dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a b c; b=",
            ),
            (
                "DKIM-Signature",
                "  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; b= def ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a\t bc",
                "dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; b= ; c=ischedule-relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=a bc",
            ),
        )

        for name, value, result in data:
            verifier = DKIMVerifier(self._makeHeaders(((name, value,),)), "")
            if name == "DKIM-Signature":
                verifier.processDKIMHeader()
            canonicalized = DKIMUtils.canonicalizeHeader(name, value, verifier.dkim_tags if name == "DKIM-Signature" else None)
            self.assertEqual(canonicalized, result)


    def test_extract_headers(self):
        """
        L{DKIMVerifier.extractSignedHeaders} correctly extracts canonicalizes headers.
        """

        data = (
            # Count on Recipient
            (
                """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
iSchedule-Version: 1.0
DKIM-Signature:  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
""",
                """content-type:text/calendar ; charset = "utf-8"
originator:mailto:[email protected]
recipient:mailto:[email protected],mailto:[email protected]
ischedule-version:1.0
dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="""
            ),
            # Exact count on Recipient
            (
                """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Recipient:\t\t  mailto:[email protected]
iSchedule-Version: 1.0
DKIM-Signature:  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
""",
                """content-type:text/calendar ; charset = "utf-8"
originator:mailto:[email protected]
recipient:mailto:[email protected],mailto:[email protected],mailto:[email protected]
ischedule-version:1.0
dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="""
            ),
            # Re-ordered Content-Type
            (
                """Host:example.com
iSchedule-Version: 1.0
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
DKIM-Signature:  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Content-Type: text/calendar  ; charset =  "utf-8"
Cache-Control:no-cache
Connection:close
""",
                """content-type:text/calendar ; charset = "utf-8"
originator:mailto:[email protected]
recipient:mailto:[email protected],mailto:[email protected]
ischedule-version:1.0
dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="""
            ),
        )

        for hdrs, result in data:
            headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()]
            verifier = DKIMVerifier(self._makeHeaders(headers), "")
            verifier.processDKIMHeader()
            extracted = verifier.extractSignedHeaders()
            self.assertEqual(extracted, result.replace("\n", "\r\n"))


    def test_canonicalize_body(self):
        """
        L{DKIMUtils.canonicalizeBody} correctly canonicalizes bodies.
        """

        data = (
            (
                """Simple""",
                """Simple\n""",
            ),
            (
                """Simple\n""",
                """Simple\n""",
            ),
            (
                """Simple\n\n""",
                """Simple\n""",
            ),
        )

        for text, result in data:
            self.assertEqual(
                DKIMUtils.canonicalizeBody(text.replace("\n", "\r\n")),
                result.replace("\n", "\r\n"),
            )


    @inlineCallbacks
    def test_locate_public_key(self):
        """
        L{DKIMVerifier.locatePublicKey} correctly finds key matching headers.
        """

        data = (
            # Valid
            (
                """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
DKIM-Signature:  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
""",
                [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
                True,
            ),
            # Invalid - no method
            (
                """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
DKIM-Signature:  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
""",
                [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
                False,
            ),
            # Invalid - wrong algorithm
            (
                """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
DKIM-Signature:  v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; c=ischedule-relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
""",
                [DKIMUtils.extractTags("v=DKIM1; h=sha-1; p=%s" % (self.public_key_data,))],
                False,
            ),
        )

        for hdrs, keys, result in data:
            headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()]
            TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys
            TestPublicKeyLookup.PublicKeyLookup_Testing.flushCache()
            verifier = DKIMVerifier(self._makeHeaders(headers), "", key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
            verifier.processDKIMHeader()
            pkey = (yield verifier.locatePublicKey())
            if result:
                self.assertTrue(pkey is not None)
            else:
                self.assertTrue(pkey is None)


    @inlineCallbacks
    def test_verify(self):
        """
        L{DKIMVerifier.verify} correctly finds key matching headers.
        """

        @inlineCallbacks
        def _verify(hdrs, body, keys, result, sign_headers=("Originator", "Recipient", "Content-Type",), manipulate_request=None):
            for algorithm in ("rsa-sha1", "rsa-sha256",):
                # Create signature
                stream = MemoryStream(body)
                headers = Headers()
                for name, value in [hdr.split(":", 1) for hdr in hdrs.splitlines()]:
                    headers.addRawHeader(name, value)
                request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, sign_headers, True, True, True, 3600)
                yield request.sign()

                # Possibly munge the request after the signature is done
                if manipulate_request is not None:
                    manipulate_request(request)

                # Verify signature
                TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys
                data = (yield allDataFromStream(request.stream))
                verifier = DKIMVerifier(request.headers, data, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
                TestPublicKeyLookup.PublicKeyLookup_Testing.flushCache()
                try:
                    yield verifier.verify()
                except Exception, e:
                    if result:
                        self.fail("DKIMVerifier:verify failed: %s" % (e,))
                else:
                    if not result:
                        self.fail("DKIMVerifier:verify did not fail")

        # Valid
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            True,
        )

        # Invalid - key revoked
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=")],
            False,
        )

        # Invalid - missing header
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            False,
            manipulate_request=lambda request: request.headers.removeHeader("Originator")
        )

        # Invalid - changed header
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            False,
            manipulate_request=lambda request: request.headers.setRawHeaders("Originator", ("mailto:[email protected]",))
        )

        # Invalid - changed body
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            False,
            manipulate_request=lambda request: setattr(request, "stream", MemoryStream("BEGIN:DATA\n")),
        )

        # Invalid - extra header
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            False,
            manipulate_request=lambda request: request.headers.getRawHeaders("Recipient").insert(0, "mailto:[email protected]"),
        )

        # Valid - header
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            True,
            sign_headers=("Originator", "Recipient", "Content-Type",),
        )

        # Invalid - over sign header extra header
        yield _verify(
            """Host:example.com
Content-Type: text/calendar  ; charset =  "utf-8"
Originator:  mailto:[email protected]
Recipient:  mailto:[email protected]  ,\t mailto:[email protected]\t\t
Cache-Control:no-cache
Connection:close
""",
            """BEGIN:DATA
END:DATA
""",
            [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
            False,
            sign_headers=("Originator", "Recipient", "Content-Type",),
            manipulate_request=lambda request: request.headers.addRawHeader("Recipient", ("mailto:[email protected]",))
        )
Ejemplo n.º 25
0
    def test_remove_attachment(self):
        """
        Test that action=remove-attachment 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        resourceID = object1.id()
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        managedID = attachment.managedID()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        yield shared_object.removeAttachment(None, managedID)
        yield self.commitTransaction(1)

        cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
        self.assertEqual(cobjs, set())
        attachment = yield ManagedAttachment.load(self.theTransactionUnderTest(0), resourceID, managedID)
        self.assertTrue(attachment is None)
        yield self.commitTransaction(0)
Ejemplo n.º 26
0
            (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)