~ubuntu-branches/ubuntu/maverick/calendarserver/maverick

« back to all changes in this revision

Viewing changes to twistedcaldav/resource.py

  • Committer: Bazaar Package Importer
  • Author(s): Guido Guenther
  • Date: 2008-04-27 10:36:57 UTC
  • Revision ID: james.westby@ubuntu.com-20080427103657-4v6lwkgekk98d3j6
Tags: upstream-1.2.dfsg
ImportĀ upstreamĀ versionĀ 1.2.dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
##
 
2
# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
 
3
#
 
4
# Licensed under the Apache License, Version 2.0 (the "License");
 
5
# you may not use this file except in compliance with the License.
 
6
# You may obtain a copy of the License at
 
7
#
 
8
# http://www.apache.org/licenses/LICENSE-2.0
 
9
#
 
10
# Unless required by applicable law or agreed to in writing, software
 
11
# distributed under the License is distributed on an "AS IS" BASIS,
 
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
# See the License for the specific language governing permissions and
 
14
# limitations under the License.
 
15
#
 
16
# DRI: Wilfredo Sanchez, wsanchez@apple.com
 
17
##
 
18
 
 
19
"""
 
20
CalDAV-aware resources.
 
21
"""
 
22
 
 
23
__all__ = [
 
24
    "CalDAVComplianceMixIn",
 
25
    "CalDAVResource",
 
26
    "CalendarPrincipalCollectionResource",
 
27
    "CalendarPrincipalResource",
 
28
    "isCalendarCollectionResource",
 
29
    "isPseudoCalendarCollectionResource",
 
30
]
 
31
 
 
32
from zope.interface import implements
 
33
 
 
34
from twisted.internet import reactor
 
35
from twisted.internet.defer import Deferred, maybeDeferred, succeed
 
36
from twisted.internet.defer import waitForDeferred
 
37
from twisted.internet.defer import deferredGenerator
 
38
from twisted.web2 import responsecode
 
39
from twisted.web2.dav import davxml
 
40
from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
 
41
from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource
 
42
from twisted.web2.dav.davxml import dav_namespace
 
43
from twisted.web2.dav.http import ErrorResponse
 
44
from twisted.web2.dav.resource import TwistedACLInheritable
 
45
from twisted.web2.dav.util import joinURL, parentForURL, unimplemented
 
46
from twisted.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
 
47
from twisted.web2.http_headers import MimeType
 
48
from twisted.web2.iweb import IResponse
 
49
from twisted.web2.stream import MemoryStream
 
50
import twisted.web2.server
 
51
 
 
52
import twistedcaldav
 
53
from twistedcaldav import caldavxml, customxml
 
54
from twistedcaldav.config import config
 
55
from twistedcaldav.customxml import TwistedCalendarAccessProperty
 
56
from twistedcaldav.extensions import DAVResource, DAVPrincipalResource
 
57
from twistedcaldav.ical import Component
 
58
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
 
59
from twistedcaldav.caldavxml import caldav_namespace
 
60
from twistedcaldav.customxml import calendarserver_namespace
 
61
from twistedcaldav.ical import allowedComponents
 
62
from twistedcaldav.ical import Component as iComponent
 
63
 
 
64
if twistedcaldav.__version__:
 
65
    serverVersion = twisted.web2.server.VERSION + " TwistedCalDAV/" + twistedcaldav.__version__
 
66
else:
 
67
    serverVersion = twisted.web2.server.VERSION + " TwistedCalDAV/?"
 
68
 
 
69
class CalDAVComplianceMixIn(object):
 
70
 
 
71
    def davComplianceClasses(self):
 
72
        extra_compliance = caldavxml.caldav_compliance
 
73
        if config.EnableProxyPrincipals:
 
74
            extra_compliance += customxml.calendarserver_proxy_compliance
 
75
        if config.EnablePrivateEvents:
 
76
            extra_compliance += customxml.calendarserver_private_events_compliance
 
77
        return tuple(super(CalDAVComplianceMixIn, self).davComplianceClasses()) + extra_compliance
 
78
 
 
79
 
 
80
class CalDAVResource (CalDAVComplianceMixIn, DAVResource):
 
81
    """
 
82
    CalDAV resource.
 
83
 
 
84
    Extends L{DAVResource} to provide CalDAV functionality.
 
85
    """
 
86
    implements(ICalDAVResource)
 
87
 
 
88
    ##
 
89
    # HTTP
 
90
    ##
 
91
 
 
92
    def render(self, request):
 
93
        # Send listing instead of iCalendar data to HTML agents
 
94
        # This is mostly useful for debugging...
 
95
        # FIXME: Add a self-link to the dirlist with a query string so
 
96
        #     users can still download the actual iCalendar data?
 
97
        agent = request.headers.getHeader("user-agent")
 
98
        if agent is not None and agent.startswith("Mozilla/") and agent.find("Gecko") != -1:
 
99
            html_agent = True
 
100
        else:
 
101
            html_agent = False
 
102
 
 
103
        if not html_agent and self.isPseudoCalendarCollection():
 
104
            # Render a monolithic iCalendar file
 
105
            if request.uri[-1] != "/":
 
106
                # Redirect to include trailing '/' in URI
 
107
                return RedirectResponse(request.unparseURL(path=request.path+"/"))
 
108
 
 
109
            def _defer(data):
 
110
                response = Response()
 
111
                response.stream = MemoryStream(str(data))
 
112
                response.headers.setHeader("content-type", MimeType.fromString("text/calendar"))
 
113
                return response
 
114
 
 
115
            d = self.iCalendarRolledup(request)
 
116
            d.addCallback(_defer)
 
117
            return d
 
118
        else:
 
119
            return super(CalDAVResource, self).render(request)
 
120
 
 
121
    def renderHTTP(self, request):
 
122
        response = maybeDeferred(super(CalDAVResource, self).renderHTTP, request)
 
123
 
 
124
        def setHeaders(response):
 
125
            response = IResponse(response)
 
126
            response.headers.setHeader("server", serverVersion)
 
127
 
 
128
            return response
 
129
 
 
130
        response.addCallback(setHeaders)
 
131
 
 
132
        return response
 
133
 
 
134
    ##
 
135
    # WebDAV
 
136
    ##
 
137
 
 
138
    liveProperties = DAVResource.liveProperties + (
 
139
        (dav_namespace,    "owner"),               # Private Events needs this but it is also OK to return empty
 
140
        (caldav_namespace, "supported-calendar-component-set"),
 
141
        (caldav_namespace, "supported-calendar-data"         ),
 
142
    )
 
143
 
 
144
    supportedCalendarComponentSet = caldavxml.SupportedCalendarComponentSet(
 
145
        *[caldavxml.CalendarComponent(name=item) for item in allowedComponents]
 
146
    )
 
147
 
 
148
    def readProperty(self, property, request):
 
149
        if type(property) is tuple:
 
150
            qname = property
 
151
        else:
 
152
            qname = property.qname()
 
153
 
 
154
        namespace, name = qname
 
155
 
 
156
        if namespace == dav_namespace:
 
157
            if name == "owner":
 
158
                d = self.owner(request)
 
159
                d.addCallback(lambda x: davxml.Owner(x))
 
160
                return d
 
161
            
 
162
        elif namespace == caldav_namespace:
 
163
            if name == "supported-calendar-component-set":
 
164
                # CalDAV-access-09, section 5.2.3
 
165
                if self.hasDeadProperty(qname):
 
166
                    return succeed(self.readDeadProperty(qname))
 
167
                return succeed(self.supportedCalendarComponentSet)
 
168
            elif name == "supported-calendar-data":
 
169
                # CalDAV-access-09, section 5.2.4
 
170
                return succeed(caldavxml.SupportedCalendarData(
 
171
                    caldavxml.CalendarData(**{
 
172
                        "content-type": "text/calendar",
 
173
                        "version"     : "2.0",
 
174
                    }),
 
175
                ))
 
176
            elif name == "max-resource-size":
 
177
                # CalDAV-access-15, section 5.2.5
 
178
                if config.MaximumAttachmentSize:
 
179
                    return succeed(caldavxml.MaxResourceSize.fromString(
 
180
                        str(config.MaximumAttachmentSize)
 
181
                    ))
 
182
 
 
183
        return super(CalDAVResource, self).readProperty(property, request)
 
184
 
 
185
    def writeProperty(self, property, request):
 
186
        assert isinstance(property, davxml.WebDAVElement)
 
187
 
 
188
        if property.qname() == (caldav_namespace, "supported-calendar-component-set"):
 
189
            if not self.isPseudoCalendarCollection():
 
190
                raise HTTPError(StatusResponse(
 
191
                    responsecode.FORBIDDEN,
 
192
                    "Property %s may only be set on calendar collection." % (property,)
 
193
                ))
 
194
            for component in property.children:
 
195
                if component not in self.supportedCalendarComponentSet:
 
196
                    raise HTTPError(StatusResponse(
 
197
                        responsecode.NOT_IMPLEMENTED,
 
198
                        "Component %s is not supported by this server" % (component.toxml(),)
 
199
                    ))
 
200
                    
 
201
        # Strictly speaking CalDAV:timezone is a live property in the sense that the
 
202
        # server enforces what can be stored, however it need not actually
 
203
        # exist so we cannot list it in liveProperties on this resource, since its
 
204
        # its presence there means that hasProperty will always return True for it.
 
205
        elif property.qname() == (caldav_namespace, "calendar-timezone"):
 
206
            if not self.isCalendarCollection():
 
207
                raise HTTPError(StatusResponse(
 
208
                    responsecode.FORBIDDEN,
 
209
                    "Property %s may only be set on calendar collection." % (property,)
 
210
                ))
 
211
            if not property.valid():
 
212
                raise HTTPError(ErrorResponse(
 
213
                    responsecode.CONFLICT,
 
214
                    (caldav_namespace, "valid-calendar-data")
 
215
                ))
 
216
 
 
217
        return super(CalDAVResource, self).writeProperty(property, request)
 
218
 
 
219
    ##
 
220
    # ACL
 
221
    ##
 
222
 
 
223
    def disable(self, disabled=True):
 
224
        """
 
225
        Completely disables all access to this resource, regardless of ACL
 
226
        settings.
 
227
        @param disabled: If true, disabled all access. If false, enables access.
 
228
        """
 
229
        if disabled:
 
230
            self.writeDeadProperty(AccessDisabled())
 
231
        else:
 
232
            self.removeDeadProperty(AccessDisabled())
 
233
 
 
234
    def isDisabled(self):
 
235
        """
 
236
        @return: C{True} if access to this resource is disabled, C{False}
 
237
            otherwise.
 
238
        """
 
239
        return self.hasDeadProperty(AccessDisabled)
 
240
 
 
241
    # FIXME: Perhaps this is better done in authorize() instead.
 
242
    @deferredGenerator
 
243
    def accessControlList(self, request, *args, **kwargs):
 
244
        if self.isDisabled():
 
245
            yield None
 
246
            return
 
247
 
 
248
        d = waitForDeferred(super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
 
249
        yield d
 
250
        acls = d.getResult()
 
251
 
 
252
        # Look for private events access classification
 
253
        if self.hasDeadProperty(TwistedCalendarAccessProperty):
 
254
            access = self.readDeadProperty(TwistedCalendarAccessProperty)
 
255
            if access.getValue() in (Component.ACCESS_PRIVATE, Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED,):
 
256
                # Need to insert ACE to prevent non-owner principals from seeing this resource
 
257
                d = waitForDeferred(self.owner(request))
 
258
                yield d
 
259
                owner = d.getResult()
 
260
                if access.getValue() == Component.ACCESS_PRIVATE:
 
261
                    ace = davxml.ACE(
 
262
                        davxml.Invert(
 
263
                            davxml.Principal(owner),
 
264
                        ),
 
265
                        davxml.Deny(
 
266
                            davxml.Privilege(
 
267
                                davxml.Read(),
 
268
                            ),
 
269
                            davxml.Privilege(
 
270
                                davxml.Write(),
 
271
                            ),
 
272
                        ),
 
273
                        davxml.Protected(),
 
274
                    )
 
275
                else:
 
276
                    ace = davxml.ACE(
 
277
                        davxml.Invert(
 
278
                            davxml.Principal(owner),
 
279
                        ),
 
280
                        davxml.Deny(
 
281
                            davxml.Privilege(
 
282
                                davxml.Write(),
 
283
                            ),
 
284
                        ),
 
285
                        davxml.Protected(),
 
286
                    )
 
287
 
 
288
                acls = davxml.ACL(ace, *acls.children)
 
289
        yield acls
 
290
 
 
291
    @deferredGenerator
 
292
    def owner(self, request):
 
293
        """
 
294
        Return the DAV:owner property value (MUST be a DAV:href or None).
 
295
        """
 
296
        d = waitForDeferred(self.locateParent(request, request.urlForResource(self)))
 
297
        yield d
 
298
        parent = d.getResult()
 
299
        if parent and isinstance(parent, CalDAVResource):
 
300
            d = waitForDeferred(parent.owner(request))
 
301
            yield d
 
302
            yield d.getResult()
 
303
        else:
 
304
            yield None
 
305
 
 
306
    @deferredGenerator
 
307
    def isOwner(self, request):
 
308
        """
 
309
        Determine whether the DAV:owner of this resource matches the currently authorized principal
 
310
        in the request.
 
311
        """
 
312
 
 
313
        d = waitForDeferred(self.owner(request))
 
314
        yield d
 
315
        owner = d.getResult()
 
316
        result = (davxml.Principal(owner) == self.currentPrincipal(request))
 
317
        yield result
 
318
 
 
319
    ##
 
320
    # CalDAV
 
321
    ##
 
322
 
 
323
    def isCalendarCollection(self):
 
324
        """
 
325
        See L{ICalDAVResource.isCalendarCollection}.
 
326
        """
 
327
        return self.isSpecialCollection(caldavxml.Calendar)
 
328
 
 
329
    def isSpecialCollection(self, collectiontype):
 
330
        """
 
331
        See L{ICalDAVResource.isSpecialCollection}.
 
332
        """
 
333
        if not self.isCollection(): return False
 
334
 
 
335
        try:
 
336
            resourcetype = self.readDeadProperty((dav_namespace, "resourcetype"))
 
337
            return bool(resourcetype.childrenOfType(collectiontype))
 
338
        except HTTPError, e:
 
339
            assert e.response.code == responsecode.NOT_FOUND
 
340
            return False
 
341
 
 
342
    def isPseudoCalendarCollection(self):
 
343
        """
 
344
        See L{ICalDAVResource.isPseudoCalendarCollection}.
 
345
        """
 
346
        return self.isCalendarCollection()
 
347
 
 
348
    def findCalendarCollections(self, depth, request, callback, privileges=None):
 
349
        """
 
350
        See L{ICalDAVResource.findCalendarCollections}.
 
351
        """
 
352
        assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
 
353
 
 
354
        def checkPrivilegesError(failure):
 
355
            failure.trap(AccessDeniedError)
 
356
            
 
357
            reactor.callLater(0, getChild)
 
358
 
 
359
        def checkPrivileges(child):
 
360
            if privileges is None:
 
361
                return child
 
362
   
 
363
            ca = child.checkPrivileges(request, privileges)
 
364
            ca.addCallback(lambda ign: child)
 
365
            return ca
 
366
 
 
367
        def gotChild(child, childpath):
 
368
            if child.isCalendarCollection():
 
369
                callback(child, childpath)
 
370
            elif child.isCollection():
 
371
                if depth == "infinity": 
 
372
                    fc = child.findCalendarCollections(depth, request, callback, privileges)
 
373
                    fc.addCallback(lambda x: reactor.callLater(0, getChild))
 
374
                    return fc
 
375
 
 
376
            reactor.callLater(0, getChild)
 
377
 
 
378
        def getChild():
 
379
            try:
 
380
                childname = children.pop()
 
381
            except IndexError:
 
382
                completionDeferred.callback(None)
 
383
            else:
 
384
                childpath = joinURL(basepath, childname)
 
385
                child = request.locateResource(childpath)
 
386
                child.addCallback(checkPrivileges)
 
387
                child.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
 
388
                child.addErrback(completionDeferred.errback)
 
389
 
 
390
        completionDeferred = Deferred()
 
391
 
 
392
        if depth != "0" and self.isCollection():
 
393
            basepath = request.urlForResource(self)
 
394
            children = self.listChildren()
 
395
            getChild()
 
396
        else:
 
397
            completionDeferred.callback(None)
 
398
 
 
399
        return completionDeferred
 
400
 
 
401
    def createCalendar(self, request):
 
402
        """
 
403
        See L{ICalDAVResource.createCalendar}.
 
404
        This implementation raises L{NotImplementedError}; a subclass must
 
405
        override it.
 
406
        """
 
407
        unimplemented(self)
 
408
 
 
409
    def iCalendar(self, name=None):
 
410
        """
 
411
        See L{ICalDAVResource.iCalendar}.
 
412
 
 
413
        This implementation returns the an object created from the data returned
 
414
        by L{iCalendarText} when given the same arguments.
 
415
 
 
416
        Note that L{iCalendarText} by default calls this method, which creates
 
417
        an infinite loop.  A subclass must override one of both of these
 
418
        methods.
 
419
        """
 
420
        calendar_data = self.iCalendarText(name)
 
421
 
 
422
        if calendar_data is None: return None
 
423
 
 
424
        try:
 
425
            return iComponent.fromString(calendar_data)
 
426
        except ValueError:
 
427
            return None
 
428
 
 
429
    def iCalendarRolledup(self, request):
 
430
        """
 
431
        See L{ICalDAVResource.iCalendarRolledup}.
 
432
 
 
433
        This implementation raises L{NotImplementedError}; a subclass must
 
434
        override it.
 
435
        """
 
436
        unimplemented(self)
 
437
 
 
438
    def iCalendarText(self, name=None):
 
439
        """
 
440
        See L{ICalDAVResource.iCalendarText}.
 
441
 
 
442
        This implementation returns the string representation (according to
 
443
        L{str}) of the object returned by L{iCalendar} when given the same
 
444
        arguments.
 
445
 
 
446
        Note that L{iCalendar} by default calls this method, which creates
 
447
        an infinite loop.  A subclass must override one of both of these
 
448
        methods.
 
449
        """
 
450
        return str(self.iCalendar(name))
 
451
 
 
452
    def iCalendarXML(self, name=None):
 
453
        """
 
454
        See L{ICalDAVResource.iCalendarXML}.
 
455
        This implementation returns an XML element constructed from the object
 
456
        returned by L{iCalendar} when given the same arguments.
 
457
        """
 
458
        return caldavxml.CalendarData.fromCalendar(self.iCalendar(name))
 
459
 
 
460
    def principalForCalendarUserAddress(self, address):
 
461
        for principalCollection in self.principalCollections():
 
462
            principal = principalCollection.principalForCalendarUserAddress(address)
 
463
            if principal is not None:
 
464
                return principal
 
465
        return None
 
466
 
 
467
    def supportedReports(self):
 
468
        result = super(CalDAVResource, self).supportedReports()
 
469
        result.append(davxml.Report(caldavxml.CalendarQuery(),))
 
470
        result.append(davxml.Report(caldavxml.CalendarMultiGet(),))
 
471
        if (self.isCollection()):
 
472
            # Only allowed on collections
 
473
            result.append(davxml.Report(caldavxml.FreeBusyQuery(),))
 
474
        return result
 
475
 
 
476
    def writeNewACEs(self, newaces):
 
477
        """
 
478
        Write a new ACL to the resource's property store. We override this for calendar collections
 
479
        and force all the ACEs to be inheritable so that all calendar object resources within the
 
480
        calendar collection have the same privileges unless explicitly overridden. The same applies
 
481
        to drop box collections as we want all resources (attachments) to have the same privileges as
 
482
        the drop box collection.
 
483
        
 
484
        @param newaces: C{list} of L{ACE} for ACL being set.
 
485
        """
 
486
        
 
487
        # Do this only for regular calendar collections and Inbox/Outbox
 
488
        if self.isPseudoCalendarCollection():
 
489
            edited_aces = []
 
490
            for ace in newaces:
 
491
                if TwistedACLInheritable() not in ace.children:
 
492
                    children = list(ace.children)
 
493
                    children.append(TwistedACLInheritable())
 
494
                    edited_aces.append(davxml.ACE(*children))
 
495
                else:
 
496
                    edited_aces.append(ace)
 
497
        else:
 
498
            edited_aces = newaces
 
499
        
 
500
        # Do inherited with possibly modified set of aces
 
501
        super(CalDAVResource, self).writeNewACEs(edited_aces)
 
502
 
 
503
    ##
 
504
    # Utilities
 
505
    ##
 
506
 
 
507
    def locateParent(self, request, uri):
 
508
        """
 
509
        Locates the parent resource of the resource with the given URI.
 
510
        @param request: an L{IRequest} object for the request being processed.
 
511
        @param uri: the URI whose parent resource is desired.
 
512
        """
 
513
        return request.locateResource(parentForURL(uri))
 
514
 
 
515
class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
 
516
    """
 
517
    CalDAV principal collection.
 
518
    """
 
519
    implements(IDAVPrincipalCollectionResource)
 
520
 
 
521
    def isCollection(self):
 
522
        return True
 
523
 
 
524
    def isCalendarCollection(self):
 
525
        return False
 
526
 
 
527
    def isPseudoCalendarCollection(self):
 
528
        return False
 
529
 
 
530
    def principalForCalendarUserAddress(self, address):
 
531
        return None
 
532
 
 
533
    def supportedReports(self):
 
534
        """
 
535
        Principal collections are the only resources supporting the
 
536
        principal-search-property-set report.
 
537
        """
 
538
        result = super(CalendarPrincipalCollectionResource, self).supportedReports()
 
539
        result.append(davxml.Report(davxml.PrincipalSearchPropertySet(),))
 
540
        return result
 
541
 
 
542
    def principalSearchPropertySet(self):
 
543
        return davxml.PrincipalSearchPropertySet(
 
544
            davxml.PrincipalSearchProperty(
 
545
                davxml.PropertyContainer(
 
546
                    davxml.DisplayName()
 
547
                ),
 
548
                davxml.Description(
 
549
                    davxml.PCDATAElement("Display Name"),
 
550
                    **{"xml:lang":"en"}
 
551
                ),
 
552
            ),
 
553
            davxml.PrincipalSearchProperty(
 
554
                davxml.PropertyContainer(
 
555
                    caldavxml.CalendarUserAddressSet()
 
556
                ),
 
557
                davxml.Description(
 
558
                    davxml.PCDATAElement("Calendar User Addresses"),
 
559
                    **{"xml:lang":"en"}
 
560
                ),
 
561
            ),
 
562
        )
 
563
 
 
564
class CalendarPrincipalResource (CalDAVComplianceMixIn, DAVPrincipalResource):
 
565
    """
 
566
    CalDAV principal resource.
 
567
 
 
568
    Extends L{DAVPrincipalResource} to provide CalDAV functionality.
 
569
    """
 
570
    implements(ICalendarPrincipalResource)
 
571
 
 
572
    liveProperties = tuple(DAVPrincipalResource.liveProperties) + (
 
573
        (caldav_namespace, "calendar-home-set"        ),
 
574
        (caldav_namespace, "calendar-user-address-set"),
 
575
        (caldav_namespace, "schedule-inbox-URL"       ),
 
576
        (caldav_namespace, "schedule-outbox-URL"      ),
 
577
    )
 
578
 
 
579
    @classmethod
 
580
    def enableDropBox(clz, enable):
 
581
        qname = (calendarserver_namespace, "dropbox-home-URL" )
 
582
        if enable and qname not in clz.liveProperties:
 
583
            clz.liveProperties += (qname,)
 
584
        elif not enable and qname in clz.liveProperties:
 
585
            clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
 
586
 
 
587
    @classmethod
 
588
    def enableNotifications(clz, enable):
 
589
        qname = (calendarserver_namespace, "notifications-URL" )
 
590
        if enable and qname not in clz.liveProperties:
 
591
            clz.liveProperties += (qname,)
 
592
        elif not enable and qname in clz.liveProperties:
 
593
            clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
 
594
 
 
595
    def isCollection(self):
 
596
        return True
 
597
 
 
598
    def readProperty(self, property, request):
 
599
        def defer():
 
600
            if type(property) is tuple:
 
601
                qname = property
 
602
            else:
 
603
                qname = property.qname()
 
604
 
 
605
            namespace, name = qname
 
606
 
 
607
            if namespace == caldav_namespace:
 
608
                if name == "calendar-home-set":
 
609
                    return caldavxml.CalendarHomeSet(
 
610
                        *[davxml.HRef(url) for url in self.calendarHomeURLs()]
 
611
                    )
 
612
 
 
613
                if name == "calendar-user-address-set":
 
614
                    return succeed(caldavxml.CalendarUserAddressSet(
 
615
                        *[davxml.HRef(uri) for uri in self.calendarUserAddresses()]
 
616
                    ))
 
617
 
 
618
                if name == "schedule-inbox-URL":
 
619
                    url = self.scheduleInboxURL()
 
620
                    if url is None:
 
621
                        return None
 
622
                    else:
 
623
                        return caldavxml.ScheduleInboxURL(davxml.HRef(url))
 
624
 
 
625
                if name == "schedule-outbox-URL":
 
626
                    url = self.scheduleOutboxURL()
 
627
                    if url is None:
 
628
                        return None
 
629
                    else:
 
630
                        return caldavxml.ScheduleOutboxURL(davxml.HRef(url))
 
631
 
 
632
            elif namespace == calendarserver_namespace:
 
633
                if name == "dropbox-home-URL" and config.EnableDropBox:
 
634
                    url = self.dropboxURL()
 
635
                    if url is None:
 
636
                        return None
 
637
                    else:
 
638
                        return customxml.DropBoxHomeURL(davxml.HRef(url))
 
639
 
 
640
                if name == "notifications-URL" and config.EnableNotifications:
 
641
                    url = self.notificationsURL()
 
642
                    if url is None:
 
643
                        return None
 
644
                    else:
 
645
                        return customxml.NotificationsURL(davxml.HRef(url))
 
646
 
 
647
            return super(CalendarPrincipalResource, self).readProperty(property, request)
 
648
 
 
649
        return maybeDeferred(defer)
 
650
 
 
651
    def groupMembers(self):
 
652
        return ()
 
653
 
 
654
    def groupMemberships(self):
 
655
        return ()
 
656
 
 
657
    def calendarHomeURLs(self):
 
658
        if self.hasDeadProperty((caldav_namespace, "calendar-home-set")):
 
659
            home_set = self.readDeadProperty((caldav_namespace, "calendar-home-set"))
 
660
            return [str(h) for h in home_set.children]
 
661
        else:
 
662
            return ()
 
663
 
 
664
    def calendarUserAddresses(self):
 
665
        if self.hasDeadProperty((caldav_namespace, "calendar-user-address-set")):
 
666
            addresses = self.readDeadProperty((caldav_namespace, "calendar-user-address-set"))
 
667
            return [str(h) for h in addresses.children]
 
668
        else:
 
669
            # Must have a valid address of some kind so use the principal uri
 
670
            return (self.principalURL(),)
 
671
 
 
672
    def calendarFreeBusyURIs(self, request):
 
673
        def gotInbox(inbox):
 
674
            if inbox is None:
 
675
                return ()
 
676
 
 
677
            def getFreeBusy(has):
 
678
                if not has:
 
679
                    return ()
 
680
    
 
681
                def parseFreeBusy(freeBusySet):
 
682
                    return tuple(str(href) for href in freeBusySet.children)
 
683
        
 
684
                d = inbox.readProperty((caldav_namespace, "calendar-free-busy-set"), request)
 
685
                d.addCallback(parseFreeBusy)
 
686
                return d
 
687
    
 
688
            d = inbox.hasProperty((caldav_namespace, "calendar-free-busy-set"), request)
 
689
            d.addCallback(getFreeBusy)
 
690
            return d
 
691
 
 
692
        d = self.scheduleInbox(request)
 
693
        d.addCallback(gotInbox)
 
694
        return d
 
695
 
 
696
    def scheduleInbox(self, request):
 
697
        """
 
698
        @return: the deferred schedule inbox for this principal.
 
699
        """
 
700
        return request.locateResource(self.scheduleInboxURL())
 
701
 
 
702
    def scheduleInboxURL(self):
 
703
        if self.hasDeadProperty((caldav_namespace, "schedule-inbox-URL")):
 
704
            inbox = self.readDeadProperty((caldav_namespace, "schedule-inbox-URL"))
 
705
            return str(inbox.children[0])
 
706
        else:
 
707
            return None
 
708
 
 
709
    def scheduleOutboxURL(self):
 
710
        """
 
711
        @return: the schedule outbox URL for this principal.
 
712
        """
 
713
        if self.hasDeadProperty((caldav_namespace, "schedule-outbox-URL")):
 
714
            outbox = self.readDeadProperty((caldav_namespace, "schedule-outbox-URL"))
 
715
            return str(outbox.children[0])        
 
716
        else:
 
717
            return None
 
718
        
 
719
    def dropboxURL(self):
 
720
        """
 
721
        @return: the drop box home collection URL for this principal.
 
722
        """
 
723
        if self.hasDeadProperty((calendarserver_namespace, "dropbox-home-URL")):
 
724
            inbox = self.readDeadProperty((caldav_namespace, "dropbox-home-URL"))
 
725
            return str(inbox.children[0])
 
726
        else:
 
727
            return None
 
728
        
 
729
    def notificationsURL(self):
 
730
        """
 
731
        @return: the notifications collection URL for this principal.
 
732
        """
 
733
        if self.hasDeadProperty((calendarserver_namespace, "notifications-URL")):
 
734
            inbox = self.readDeadProperty((caldav_namespace, "notifications-URL"))
 
735
            return str(inbox.children[0])
 
736
        else:
 
737
            return None
 
738
 
 
739
##
 
740
# Utilities
 
741
##
 
742
 
 
743
class AccessDisabled (davxml.WebDAVEmptyElement):
 
744
    namespace = davxml.twisted_private_namespace
 
745
    name = "caldav-access-disabled"
 
746
 
 
747
davxml.registerElement(AccessDisabled)
 
748
 
 
749
 
 
750
def isCalendarCollectionResource(resource):
 
751
    try:
 
752
        resource = ICalDAVResource(resource)
 
753
    except TypeError:
 
754
        return False
 
755
    else:
 
756
        return resource.isCalendarCollection()
 
757
 
 
758
def isPseudoCalendarCollectionResource(resource):
 
759
    try:
 
760
        resource = ICalDAVResource(resource)
 
761
    except TypeError:
 
762
        return False
 
763
    else:
 
764
        return resource.isPseudoCalendarCollection()