[CalendarServer-changes] [1967] CalendarServer/branches/users/cdaboo/server2server-1965

source_changes at macosforge.org source_changes at macosforge.org
Tue Oct 16 13:39:22 PDT 2007


Revision: 1967
          http://trac.macosforge.org/projects/calendarserver/changeset/1967
Author:   cdaboo at apple.com
Date:     2007-10-16 13:39:22 -0700 (Tue, 16 Oct 2007)

Log Message:
-----------
Merged forward.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd.plist
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/config.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/customxml.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/directory/calendar.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/logging.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/tap.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver-test.xml
    CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver.dtd
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/freebusyurl.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserver.py
    CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserverparser.py

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd-test.plist	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd-test.plist	2007-10-16 20:39:22 UTC (rev 1967)
@@ -330,7 +330,34 @@
   <key>EnableNotifications</key>
   <true/>
 
+  <!-- Server to server protocol -->
+  <key>ServerToServer</key>
+  <dict>
+  	<key>Enabled</key>
+  	<true/>
+  	<key>Email Domain</key>
+  	<string>example.com</string>
+  	<key>HTTP Domain</key>
+  	<string>example.com</string>
+  	<key>Local Addresses</key>
+  	<array>
+  	</array>
+  	<key>Remote Addresses</key>
+  	<array>
+  	</array>
+  	<key>Servers</key>
+  	<string>conf/servertoserver-test.xml</string>
+  </dict>
 
+  <!-- Free-busy URL protocol -->
+  <key>FreeBusyURL</key>
+  <dict>
+  	<key>Enabled</key>
+  	<true/>
+  	<key>Time Period</key>
+  	<integer>14</integer>
+  </dict>
+
   <!--
     Twisted
   -->

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd.plist	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/conf/caldavd.plist	2007-10-16 20:39:22 UTC (rev 1967)
@@ -264,6 +264,33 @@
   <key>EnableNotifications</key>
   <true/>
 
+  <!-- Server to server protocol -->
+  <key>ServerToServer</key>
+  <dict>
+  	<key>Enabled</key>
+  	<true/>
+  	<key>Email Domain</key>
+  	<string></string>
+  	<key>HTTP Domain</key>
+  	<string></string>
+  	<key>Local Addresses</key>
+  	<array>
+  	</array>
+  	<key>Remote Addresses</key>
+  	<array>
+  	</array>
+  	<key>Servers</key>
+  	<string>/etc/caldavd/servertoserver.xml</string>
+  </dict>
 
+  <!-- Free-busy URL protocol -->
+  <key>FreeBusyURL</key>
+  <dict>
+  	<key>Enabled</key>
+  	<true/>
+  	<key>Time Period</key>
+  	<integer>14</integer>
+  </dict>
+
 </dict>
 </plist>

Copied: CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver-test.xml (from rev 1966, CalendarServer/branches/users/cdaboo/server2server-1941/conf/servertoserver-test.xml)
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver-test.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver-test.xml	2007-10-16 20:39:22 UTC (rev 1967)
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE servers SYSTEM "servertoserver.dtd">
+
+<servers>
+  <server>
+    <uri>https://localhost:8543/inbox</uri>
+    <allow-requests-from/>
+    <allow-requests-to/>
+    <domains>
+    	<domain>example.org</domain>
+    </domains>
+  </server>
+</servers>

Copied: CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver.dtd (from rev 1966, CalendarServer/branches/users/cdaboo/server2server-1941/conf/servertoserver.dtd)
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver.dtd	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/conf/servertoserver.dtd	2007-10-16 20:39:22 UTC (rev 1967)
@@ -0,0 +1,33 @@
+<!--
+Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+DRI: Cyrus Daboo, cdaboo at apple.com
+ -->
+
+<!ELEMENT servers (server*) >
+
+	<!ELEMENT server (uri, authentication?, allow-requests-from, allow-requests-to, domains*) >
+
+		<!ELEMENT uri (#PCDATA) >
+		<!ELEMENT authentication (user, password) >
+		    <!ATTLIST authentication type (basic) "">
+		    <!ELEMENT user (#PCDATA) >
+		    <!ELEMENT password (#PCDATA) >
+
+		<!ELEMENT allow-requests-from EMPTY >
+		<!ELEMENT allow-requests-to EMPTY >
+		<!ELEMENT domains (domain*) >
+
+			<!ELEMENT domain (#PCDATA) >

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/config.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/config.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -145,9 +145,22 @@
     #
     # Non-standard CalDAV extensions
     #
-    "EnableDropBox"      : False, # Calendar Drop Box
-    "EnableNotifications": False, # Drop Box Notifications
+    "EnableDropBox"       : False, # Calendar Drop Box
+    "EnableNotifications" : False, # Drop Box Notifications
+    
+    "ServerToServer": {
+        "Enabled"          : False, # Server-to-server protocol
+        "Email Domain"     : "",    # Domain for mailto calendar user addresses on this server
+        "HTTP Domain"      : "",    # Domain for http calendar user addresses on this server
+        "Local Addresses"  : [],    # Reg-ex patterns to match local calendar user addresses
+        "Remote Addresses" : [],    # Reg-ex patterns to match remote calendar user addresses
+    },
 
+    "FreeBusyURL": {
+        "Enabled"         : False, # Per-user free-busy-url protocol
+        "Time Period"     : 14,    # Number of days into the future to generate f-b data if no explicit time-range is specified
+    },
+
     #
     # Implementation details
     #

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/customxml.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/customxml.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -254,6 +254,22 @@
 
         return found
 
+class ServerToServerInbox (davxml.WebDAVEmptyElement):
+    """
+    Denotes the resourcetype of a server-to-server Inbox.
+    (CalDAV-s2s-xx, section x.x.x)
+    """
+    namespace = calendarserver_namespace
+    name = "server-to-server-inbox"
+
+class FreeBusyURL (davxml.WebDAVEmptyElement):
+    """
+    Denotes the resourcetype of a free-busy URL resource.
+    (CalDAV-s2s-xx, section x.x.x)
+    """
+    namespace = calendarserver_namespace
+    name = "free-busy-url"
+
 ##
 # Extensions to davxml.ResourceType
 ##
@@ -263,3 +279,5 @@
 davxml.ResourceType.notifications = davxml.ResourceType(davxml.Collection(), Notifications())
 davxml.ResourceType.calendarproxyread = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyRead())
 davxml.ResourceType.calendarproxywrite = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyWrite())
+davxml.ResourceType.servertoserverinbox = davxml.ResourceType(ServerToServerInbox())
+davxml.ResourceType.freebusyurl = davxml.ResourceType(FreeBusyURL())

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/directory/calendar.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/directory/calendar.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -26,16 +26,15 @@
     "DirectoryCalendarHomeResource",
 ]
 
-from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
 from twisted.web2.dav.util import joinURL
 from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty
-from twisted.web2.http import HTTPError
 
 from twistedcaldav import caldavxml
 from twistedcaldav.config import config
 from twistedcaldav.dropbox import DropBoxHomeResource
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
+from twistedcaldav.freebusyurl import FreeBusyURLResource
 from twistedcaldav.notifications import NotificationsCollectionResource
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
@@ -205,6 +204,10 @@
             childlist += (
                 ("notifications", NotificationsCollectionResource),
             )
+        if config.FreeBusyURL["Enabled"]:
+            childlist += (
+                ("freebusy", FreeBusyURLResource),
+            )
         for name, cls in childlist:
             child = self.provisionChild(name)
             assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)

Copied: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/freebusyurl.py (from rev 1966, CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/freebusyurl.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/freebusyurl.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/freebusyurl.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -0,0 +1,237 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Free-busy-URL resources.
+"""
+
+__all__ = [
+    "FreeBusyURLResource",
+]
+
+from twisted.internet.defer import deferredGenerator
+from twisted.internet.defer import waitForDeferred
+from twisted.python import log
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http import StatusResponse
+from twisted.web2.http_headers import MimeType
+from twisted.web2.stream import MemoryStream
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.config import config
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.ical import Property
+from twistedcaldav.ical import parse_datetime
+from twistedcaldav.ical import parse_duration
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.schedule_common import Scheduler
+
+from vobject.icalendar import utc
+
+import datetime
+
+class FreeBusyURLResource (CalDAVResource):
+    """
+    Free-busy URL resource.
+
+    Extends L{DAVResource} to provide free-busy URL functionality.
+    """
+
+    def __init__(self, parent):
+        """
+        @param parent: the parent resource of this one.
+        """
+        assert parent is not None
+
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+        self.parent = parent
+
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # DAV:Read, CalDAV:schedule for all principals (does not include anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(caldavxml.Schedule()),
+                ),
+                davxml.Protected(),
+            ),
+        )
+
+    def resourceType(self):
+        return davxml.ResourceType.freebusyurl
+
+    def isCollection(self):
+        return False
+
+    def isCalendarCollection(self):
+        return False
+
+    def isPseudoCalendarCollection(self):
+        return False
+
+    def render(self, request):
+        output = """<html>
+<head>
+<title>Free-Busy URL Resource</title>
+</head>
+<body>
+<h1>Free-busy URL Resource.</h1>
+</body
+</html>"""
+
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response
+
+    def http_GET(self, request):
+        """
+        The free-busy URL POST method.
+        """
+        return self._processFBURL(request)
+
+    def http_POST(self, request):
+        """
+        The free-busy URL POST method.
+        """
+        return self._processFBURL(request)
+
+    @deferredGenerator
+    def _processFBURL(self, request):
+        
+        #
+        # Check authentication and access controls
+        #
+        x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+        yield x
+        x.getResult()
+        
+        # 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")))
+        
+        # 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")))
+        else:
+            self.format = "text/calendar"
+            
+        # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values
+        try:
+            if self.start:
+                self.start = parse_datetime(self.start)
+                if self.start.tzinfo != utc:
+                    raise ValueError()
+            if self.end:
+                self.end = parse_datetime(self.end)
+                if self.end.tzinfo != utc:
+                    raise ValueError()
+            if self.duration:
+                self.duration = parse_duration(self.duration)
+        except ValueError:
+            raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-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")))
+        
+        # Duration must be positive
+        if self.duration and self.duration.days < 0:
+            raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+        
+        # Now fill in the missing pieces
+        if self.start is None:
+            now = datetime.datetime.now()
+            self.start = now.replace(hour=0, minute=0, second=0, tzinfo=utc)
+        if self.duration:
+            self.end = self.start + self.duration
+        if self.end is None:
+            self.end = self.start + datetime.timedelta(days=config.FreeBusyURL["Time Period"])
+            
+        # End > start
+        if self.end <= self.start:
+            raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-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 targetted user
+        principal = self.parent.principalForRecord()
+        
+        # Pick the first mailto cu address or the first other type
+        cuaddr = None
+        for item in principal.calendarUserAddresses():
+            if cuaddr is None:
+                cuaddr = item
+            if item.startswith("mailto"):
+                cuaddr = item
+                break
+
+        # Get inbox details
+        inboxURL = principal.scheduleInboxURL()
+        if inboxURL is None:
+            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox URL for principal: %s" % (principal,)))
+        try:
+            inbox = waitForDeferred(request.locateResource(inboxURL))
+            yield inbox
+            inbox = inbox.getResult()
+        except:
+            log.err("No schedule inbox for principal: %s" % (principal,))
+            inbox = None
+        if inbox is None:
+            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox for principal: %s" % (principal,)))
+            
+        scheduler = Scheduler(request, self)
+        scheduler.timerange = TimeRange(start="20000101T000000Z", end="20070102T000000Z")
+        scheduler.timerange.start = self.start
+        scheduler.timerange.end = self.end
+        
+        scheduler.organizer = Scheduler.LocalCalendarUser(cuaddr, principal, inbox, inboxURL)
+        
+        attendeeProp = Property("ATTENDEE", scheduler.organizer.cuaddr)
+
+        d = waitForDeferred(scheduler.generateAttendeeFreeBusyResponse(
+            scheduler.organizer,
+            None,
+            None,
+            attendeeProp,
+            False,
+        ))
+        yield d
+        fbresult = d.getResult()
+        
+        response = Response()
+        response.stream = MemoryStream(str(fbresult))
+        response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,)))
+    
+        yield response

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -54,955 +54,904 @@
 __version__ = "0.0"
 
 __all__ = [
-    "handleRequest",
-    "canAutoRespond",
+    "iTipProcessor",
 ]
 
 class iTipException(Exception):
     pass
 
-def handleRequest(request, principal, inbox, calendar, child):
-    """
-    Handle an iTIP response automatically using a deferredGenerator.
-
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    @return: L{Deferred} that is a L{deferredGenerator}
-    """
+class iTipProcessor(object):
     
-    method = calendar.propertyValue("METHOD")
-    if method == "REQUEST":
-        f = processRequest
-    elif method == "ADD":
-        f = processAdd
-    elif method == "CANCEL":
-        f = processCancel
+    def handleRequest(self, request, principal, inbox, calendar, child):
+        """
+        Handle an iTIP response automatically using a deferredGenerator.
+    
+        @param request: the L{twisted.web2.server.Request} for the current request.
+        @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
+        @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
+        @param calendar: the L{Component} for the iTIP message we are processing.
+        @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
+        @return: L{Deferred} that is a L{deferredGenerator}
+        """
+        
+        method = calendar.propertyValue("METHOD")
+        if method == "REQUEST":
+            f = self.processRequest
+        elif method == "ADD":
+            f = self.processAdd
+        elif method == "CANCEL":
+            f = self.processCancel
 
-    return f(request, principal, inbox, calendar, child)
+        self.request = request
+        self.principal = principal
+        self.inbox = inbox
+        self.calendar = calendar
+        self.child = child
+        if self.child:
+            self.childname = self.child.fp.basename()
+        else:
+            self.childname = ""
+ 
+        return f()
 
-def processRequest(request, principal, inbox, calendar, child):
-    """
-    Process a METHOD=REQUEST.
-    This is a deferredGenerator function so use yield whenever we have a deferred.
-
-    Steps:
+    @deferredGenerator
+    def processRequest(self):
+        """
+        Process a METHOD=REQUEST.
+        This is a deferredGenerator function so use yield whenever we have a deferred.
     
-      1. See if this updates existing ones in Inbox.
-          1. If so,
-              1. Remove existing ones in Inbox.
-              2. See if this updates existing ones in free-busy-set calendars.
-              3. Remove existing ones in those calendars.
-              4. See if this fits into a free slot:
-                  1. If not, send REPLY with failure status
-                  2. If so
-                      1. send REPLY with success
-                      2. add to f-b-s calendar
-          2. If not,
-              1. remove the one we got - its 'stale'
-          3. Delete the request from the Inbox.
+        Steps:
+        
+          1. See if this updates existing ones in Inbox.
+              1. If so,
+                  1. Remove existing ones in Inbox.
+                  2. See if this updates existing ones in free-busy-set calendars.
+                  3. Remove existing ones in those calendars.
+                  4. See if this fits into a free slot:
+                      1. If not, send REPLY with failure status
+                      2. If so
+                          1. send REPLY with success
+                          2. add to f-b-s calendar
+              2. If not,
+                  1. remove the one we got - its 'stale'
+              3. Delete the request from the Inbox.
+        
+        """
+        
+        logging.info("Auto-processing iTIP REQUEST for: %s" % (str(self.principal),), system="iTIP")
+        processed = "ignored"
     
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    """
+        # First determine whether this is a full or partial update. A full update is one containing the master
+        # component in a recurrence set (or non-recurring event). Partial is one where overridden instances only are
+        # being changed.
+        
+        new_master = self.calendar.masterComponent()
     
-    logging.info("Auto-processing iTIP REQUEST for: %s" % (str(principal),), system="iTIP")
-    processed = "ignored"
-
-    # First determine whether this is a full or partial update. A full update is one containing the master
-    # component in a recurrence set (or non-recurring event). Partial is one where overridden instances only are
-    # being changed.
-    
-    new_master = calendar.masterComponent()
-
-    # Next we want to try and find a match to any components on existing calendars listed as contributing
-    # to free-busy as we will need to update those with the new one.
-    d = waitForDeferred(findCalendarMatch(request, principal, calendar))
-    yield d
-    calmatch, updatecal, calURL = d.getResult()
-    
-    if new_master:
-        # So we have a full update. That means we need to delete any existing events completely and
-        # replace with the ones provided so long as the new one is newer.
+        # Next we want to try and find a match to any components on existing calendars listed as contributing
+        # to free-busy as we will need to update those with the new one.
+        d = waitForDeferred(self.findCalendarMatch())
+        yield d
+        calmatch, updatecal, calURL = d.getResult()
         
-        # If we have a match then we need to check whether we are updating etc
-        check_reply = False
-        if calmatch:
-            # See whether the new component is older than any existing ones and throw it away if so
-            newinfo = (None,) + getComponentSyncInfo(new_master)
-            cal = updatecal.iCalendar(calmatch)
-            info = getSyncInfo(calmatch, cal)
-            if compareSyncInfo(info, newinfo) < 0:
-                # Existing resource is older and will be replaced
-                check_reply = True
+        if new_master:
+            # So we have a full update. That means we need to delete any existing events completely and
+            # replace with the ones provided so long as the new one is newer.
+            
+            # If we have a match then we need to check whether we are updating etc
+            check_reply = False
+            if calmatch:
+                # See whether the new component is older than any existing ones and throw it away if so
+                newinfo = (None,) + self.getComponentSyncInfo(new_master)
+                cal = updatecal.iCalendar(calmatch)
+                info = self.getSyncInfo(calmatch, cal)
+                if self.compareSyncInfo(info, newinfo) < 0:
+                    # Existing resource is older and will be replaced
+                    check_reply = True
+                else:
+                    processed = "older"
             else:
-                processed = "older"
+                # We have a new request which we can reply to
+                check_reply = True
+                
+            if check_reply:
+                # Process the reply by determining PARTSTAT and sending the reply and booking the event.
+                d = waitForDeferred(self.checkForReply())
+                yield d
+                doreply, replycal, accepted = d.getResult()
+                
+                try:
+                    if accepted:
+                        if calmatch:
+                            newchild = waitForDeferred(self.writeResource(calURL, updatecal, calmatch, self.calendar))
+                            yield newchild
+                            newchild = newchild.getResult()
+                            logging.info("Replaced calendar component %s with new iTIP message in %s." % (calmatch, calURL), system="iTIP")
+                        else:
+                            newchild = waitForDeferred(self.writeResource(calURL, updatecal, None, self.calendar))
+                            yield newchild
+                            newchild.getResult()
+                            logging.info("Added new calendar component in %s." % (calURL,), system="iTIP")
+                    else:
+                        if calmatch:
+                            d = waitForDeferred(self.deleteResource(updatecal, calmatch))
+                            yield d
+                            d.getResult()
+                            logging.info("Deleted calendar component %s in %s as update was not accepted." % (calmatch, calURL), system="iTIP")
+                            
+                    # Send a reply if needed. 
+                    if doreply:
+                        logging.info("Sending iTIP REPLY %s" % (("declined","accepted")[accepted],), system="iTIP")
+                        newchild = waitForDeferred(self.writeReply(replycal))
+                        yield newchild
+                        newchild = newchild.getResult()
+                    processed = "processed"
+                except:
+                    logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
+                    raise iTipException
+                
         else:
-            # We have a new request which we can reply to
-            check_reply = True
-            
-        if check_reply:
-            # Process the reply by determining PARTSTAT and sending the reply and booking the event.
-            d = waitForDeferred(checkForReply(request, principal, calendar))
-            yield d
-            doreply, replycal, accepted = d.getResult()
-            
-            try:
-                if accepted:
+            # So we have a partial update. That means we have to do partial updates to instances in
+            # the existing calendar component.
+    
+            # If we have a match then we need to check whether we are updating etc
+            check_reply = False
+            if calmatch:
+                # Check each component to see whether its new
+                cal = updatecal.iCalendar(calmatch)
+                old_master = cal.masterComponent()
+                processed = "older"
+                new_components = [component for component in self.calendar.subcomponents()]
+                for component in new_components:
+                    if component.name() == "VTIMEZONE":
+                        continue
+                    
+                    newinfo = (None,) + self.getComponentSyncInfo(component)
+                    old_component = self.findMatchingComponent(component, cal)
+                    if old_component:
+                        info = (None,) + self.getComponentSyncInfo(old_component)
+                    elif old_master:
+                        info = (None,) + self.getComponentSyncInfo(old_master)
+                    else:
+                        info = None
+                        
+                    if info is None or self.compareSyncInfo(info, newinfo) < 0:
+                        # Existing resource is older and will be replaced
+                        check_reply = True
+                        processed = "processed"
+                    else:
+                        self.calendar.removeComponent(component)
+            else:
+                # We have a new request which we can reply to
+                check_reply = True
+    
+            if check_reply:
+                # Process the reply by determining PARTSTAT and sending the reply and booking the event.
+                d = waitForDeferred(self.checkForReply())
+                yield d
+                doreply, replycal, accepted = d.getResult()
+                
+                try:
                     if calmatch:
-                        newchild = waitForDeferred(writeResource(request, calURL, updatecal, calmatch, calendar))
+                        # Merge the new instances with the old ones
+                        self.mergeComponents(self.calendar, cal)
+                        newchild = waitForDeferred(self.writeResource(calURL, updatecal, calmatch, cal))
                         yield newchild
                         newchild = newchild.getResult()
-                        logging.info("Replaced calendar component %s with new iTIP message in %s." % (calmatch, calURL), system="iTIP")
+                        logging.info("Merged calendar component %s with new iTIP message in %s." % (calmatch, calURL), system="iTIP")
                     else:
-                        newchild = waitForDeferred(writeResource(request, calURL, updatecal, None, calendar))
+                        if accepted:
+                            newchild = waitForDeferred(self.writeResource(calURL, updatecal, None, self.calendar))
+                            yield newchild
+                            newchild.getResult()
+                            logging.info("Added new calendar component in %s." % (calURL,), system="iTIP")
+                            
+                    # Do reply if needed. 
+                    if doreply:
+                        logging.info("Sending iTIP REPLY %s" % (("declined","accepted")[accepted],), system="iTIP")
+                        newchild = waitForDeferred(self.writeReply(replycal))
                         yield newchild
-                        newchild.getResult()
-                        logging.info("Added new calendar component in %s." % (calURL,), system="iTIP")
-                else:
-                    if calmatch:
-                        d = waitForDeferred(deleteResource(updatecal, calmatch))
-                        yield d
-                        d.getResult()
-                        logging.info("Deleted calendar component %s in %s as update was not accepted." % (calmatch, calURL), system="iTIP")
+                        newchild = newchild.getResult()
                         
-                # Send a reply if needed. 
-                if doreply:
-                    logging.info("Sending iTIP REPLY %s" % (("declined","accepted")[accepted],), system="iTIP")
-                    newchild = waitForDeferred(writeReply(request, principal, replycal, inbox))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    newInboxResource(child, newchild)
-                processed = "processed"
-            except:
-                logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
-                raise iTipException
-            
-    else:
-        # So we have a partial update. That means we have to do partial updates to instances in
-        # the existing calendar component.
-
-        # If we have a match then we need to check whether we are updating etc
-        check_reply = False
-        if calmatch:
-            # Check each component to see whether its new
-            cal = updatecal.iCalendar(calmatch)
-            old_master = cal.masterComponent()
-            processed = "older"
-            new_components = [component for component in calendar.subcomponents()]
-            for component in new_components:
-                if component.name() == "VTIMEZONE":
-                    continue
-                
-                newinfo = (None,) + getComponentSyncInfo(component)
-                old_component = findMatchingComponent(component, cal)
-                if old_component:
-                    info = (None,) + getComponentSyncInfo(old_component)
-                elif old_master:
-                    info = (None,) + getComponentSyncInfo(old_master)
-                else:
-                    info = None
-                    
-                if info is None or compareSyncInfo(info, newinfo) < 0:
-                    # Existing resource is older and will be replaced
-                    check_reply = True
                     processed = "processed"
-                else:
-                    calendar.removeComponent(component)
-        else:
-            # We have a new request which we can reply to
-            check_reply = True
-
-        if check_reply:
-            # Process the reply by determining PARTSTAT and sending the reply and booking the event.
-            d = waitForDeferred(checkForReply(request, principal, calendar))
-            yield d
-            doreply, replycal, accepted = d.getResult()
-            
-            try:
-                if calmatch:
-                    # Merge the new instances with the old ones
-                    mergeComponents(calendar, cal)
-                    newchild = waitForDeferred(writeResource(request, calURL, updatecal, calmatch, cal))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    logging.info("Merged calendar component %s with new iTIP message in %s." % (calmatch, calURL), system="iTIP")
-                else:
-                    if accepted:
-                        newchild = waitForDeferred(writeResource(request, calURL, updatecal, None, calendar))
-                        yield newchild
-                        newchild.getResult()
-                        logging.info("Added new calendar component in %s." % (calURL,), system="iTIP")
-                        
-                # Do reply if needed. 
-                if doreply:
-                    logging.info("Sending iTIP REPLY %s" % (("declined","accepted")[accepted],), system="iTIP")
-                    newchild = waitForDeferred(writeReply(request, principal, replycal, inbox))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    newInboxResource(child, newchild)
-                    
-                processed = "processed"
-            except:
-                logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
-                raise iTipException
-
-    # Remove the now processed incoming request.
-    try:
-        d = waitForDeferred(deleteResource(inbox, child.fp.basename()))
-        yield d
-        d.getResult()
-        logging.info("Deleted new iTIP message %s in Inbox because it has been %s." %
-            (child.fp.basename(),
-             {"processed":"processed",
-              "older":    "ignored: older",
-              "ignored":  "ignored: no match"}[processed],), system="iTIP")
-    except:
-        logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
-        raise iTipException
-    yield None
-    return
-
-processRequest = deferredGenerator(processRequest)
-
-def processAdd(request, principal, inbox, calendar, child):
-    """
-    Process a METHOD=ADD.
-    This is a deferredGenerator function so use yield whenever we have a deferred.
-
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    """
-    logging.info("Auto-processing iTIP ADD for: %s" % (str(principal),), system="iTIP")
-
-    raise NotImplementedError
-
-processAdd = deferredGenerator(processAdd)
-
-def processCancel(request, principal, inbox, calendar, child):
-    """
-    Process a METHOD=CANCEL.
-    This is a deferredGenerator function so use yield whenever we have a deferred.
-
-    Policy find all components that match UID, SEQ and R-ID and remove them.
-
-    Steps:
+                except:
+                    logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
+                    raise iTipException
     
-      1. See if this updates existing ones in Inbox.
-      2. Remove existing ones in Inbox.
-      3. See if this updates existing ones in free-busy-set calendars.
-      4. Remove existing ones in those calendars.
-      5. Remove the incoming request.
+        # Remove the now processed incoming request.
+        if self.inbox:
+            self.deleteInboxResource({
+                "processed":"processed",
+                "older":    "ignored: older",
+                "ignored":  "ignored: no match"
+            }[processed])
 
-    NB Removal can be complex as we need to take RECURRENCE-ID into account - i.e a single
-    instance may be cancelled. What we need to do for this is:
+        yield None
+        return
     
-      1. If the R-ID of iTIP component matches the R-ID of one in Inbox then it is an exact match, so
-         delete the old one.
-      2. If the R-ID of iTIP does not match an R-ID in Inbox, then we are adding a cancellation as an override, so
-         leave the new and existing ones in the Inbox.
-      3. If the R-ID of iTIP component matches the R-ID of an overridden component in an f-b-s calendar, then
-         remove the overridden component from the f-b-s resource.
-      4. Add an EXDATE to the f-b-s resource to 'cancel' that instance.
+    @deferredGenerator
+    def processAdd(self):
+        """
+        Process a METHOD=ADD.
+        This is a deferredGenerator function so use yield whenever we have a deferred.
+        """
+        logging.info("Auto-processing iTIP ADD for: %s" % (str(self.principal),), system="iTIP")
     
-    TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+        raise NotImplementedError
     
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    """
+    @deferredGenerator
+    def processCancel(self):
+        """
+        Process a METHOD=CANCEL.
+        This is a deferredGenerator function so use yield whenever we have a deferred.
     
-    logging.info("Auto-processing iTIP CANCEL for: %s" % (str(principal),), system="iTIP")
-    processed = "ignored"
-
-    # Get all component info for this iTIP message
-    newinfo = getSyncInfo(child.fp.basename(), calendar)
-    info = getAllInfo(inbox, calendar, child)
-
-    # First see if we have a recurrence id which will force extra work
-    has_rid = False
-    if newinfo[4] is not None:
-        has_rid = True
-    else:
-        for i in info:
-            if i[4] is not None:
-                has_rid = True
-                break
-            
-    if not has_rid:
-        # Compare the new one with each existing one.
-        d = waitForDeferred(processOthersInInbox(info, newinfo, inbox, child))
-        yield d
-        delete_child = d.getResult()
-
-        if delete_child:
-            yield None
-            return
-
+        Policy find all components that match UID, SEQ and R-ID and remove them.
+    
+        Steps:
+        
+            1. See if this updates existing ones in Inbox.
+            2. Remove existing ones in Inbox.
+            3. See if this updates existing ones in free-busy-set calendars.
+            4. Remove existing ones in those calendars.
+            5. Remove the incoming request.
+    
+        NB Removal can be complex as we need to take RECURRENCE-ID into account - i.e a single
+        instance may be cancelled. What we need to do for this is:
+        
+            1. If the R-ID of iTIP component matches the R-ID of one in Inbox then it is an exact match, so
+               delete the old one.
+            2. If the R-ID of iTIP does not match an R-ID in Inbox, then we are adding a cancellation as an override, so
+               leave the new and existing ones in the Inbox.
+            3. If the R-ID of iTIP component matches the R-ID of an overridden component in an f-b-s calendar, then
+               remove the overridden component from the f-b-s resource.
+            4. Add an EXDATE to the f-b-s resource to 'cancel' that instance.
+        
+        TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+        """
+        
+        logging.info("Auto-processing iTIP CANCEL for: %s" % (str(self.principal),), system="iTIP")
+        processed = "ignored"
+    
+        # Get all component info for this iTIP message
+        newinfo = self.getSyncInfo(self.childname, self.calendar)
+    
+        # First see if we have a recurrence id which will force extra work
+        has_rid = False
+        if newinfo[4] is not None:
+            has_rid = True
+        else:
+            for i in self.getAllInfo(self.inbox, self.calendar, self.child):
+                if i[4] is not None:
+                    has_rid = True
+                    break
+                
         # Next we want to try and find a match to any components on existing calendars listed as contributing
         # to free-busy as we will need to update those with the new one.
-        d = waitForDeferred(findCalendarMatch(request, principal, calendar))
+        d = waitForDeferred(self.findCalendarMatch())
         yield d
         calmatch, updatecal, calURL = d.getResult()
         
-        # If we have a match then we need to check whether we are updating etc
-        if calmatch:
-            # See whether the current component is older than any existing ones and throw it away if so
-            cal = updatecal.iCalendar(calmatch)
-            info = getSyncInfo(calmatch, cal)
-            if compareSyncInfo(info, newinfo) < 0:
-                # Delete existing resource which has been cancelled
-                try:
-                    d = waitForDeferred(deleteResource(updatecal, calmatch,))
-                    yield d
-                    d.getResult()
-                    logging.info("Delete calendar component %s in %s as it was cancelled." % (calmatch, calURL), system="iTIP")
-                except:
-                    logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
-                    raise iTipException
-                processed = "processed"
+        if not has_rid:
+            # If we have a match then we need to check whether we are updating etc
+            if calmatch:
+                # See whether the current component is older than any existing ones and throw it away if so
+                cal = updatecal.iCalendar(calmatch)
+                info = self.getSyncInfo(calmatch, cal)
+                if self.compareSyncInfo(info, newinfo) < 0:
+                    # Delete existing resource which has been cancelled
+                    try:
+                        d = waitForDeferred(self.deleteResource(updatecal, calmatch,))
+                        yield d
+                        d.getResult()
+                        logging.info("Delete calendar component %s in %s as it was cancelled." % (calmatch, calURL), system="iTIP")
+                    except:
+                        logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
+                        raise iTipException
+                    processed = "processed"
+                else:
+                    processed = "older"
             else:
-                processed = "older"
+                # Nothing to do except delete the inbox item as we have nothing to cancel.
+                processed = "ignored"
         else:
-            # Nothing to do except delete the inbox item as we have nothing to cancel.
-            processed = "ignored"
-    else:
-        # Try and find a match to any components on existing calendars listed as contributing
-        # to free-busy as we will need to update those with the new one.
-        d = waitForDeferred(findCalendarMatch(request, principal, calendar))
-        yield d
-        calmatch, updatecal, calURL = d.getResult()
-        
-        # If we have a match then we need to check whether we are updating etc
-        if calmatch:
-            # iTIP CANCEL can contain multiple components being cancelled in the RECURRENCE-ID case.
-            # So we need to iterate over each iTIP component.
-
-            # Get the existing calendar object
-            existing_calendar = updatecal.iCalendar(calmatch)
-            existing_master = existing_calendar.masterComponent()
-            exdates = []
-
-            for component in calendar.subcomponents():
-                if component.name() == "VTIMEZONE":
-                    continue
-            
-                # Find matching component in existing calendar
-                old_component = findMatchingComponent(component, existing_calendar)
+            # If we have a match then we need to check whether we are updating etc
+            if calmatch:
+                # iTIP CANCEL can contain multiple components being cancelled in the RECURRENCE-ID case.
+                # So we need to iterate over each iTIP component.
+    
+                # Get the existing calendar object
+                existing_calendar = updatecal.iCalendar(calmatch)
+                existing_master = existing_calendar.masterComponent()
+                exdates = []
+    
+                for component in self.calendar.subcomponents():
+                    if component.name() == "VTIMEZONE":
+                        continue
                 
-                if old_component:
-                    # We are cancelling an overridden component, so we need to check the
-                    # SEQUENCE/DTSAMP with the master.
-                    if compareComponents(old_component, component) < 0:
-                        # Exclude the cancelled instance
-                        exdates.append(component.getRecurrenceIDUTC())
-                        
-                        # Remove the existing component.
-                        existing_calendar.removeComponent(old_component)
-                elif existing_master:
-                    # We are trying to CANCEL a non-overridden instance, so we need to
-                    # check SEQUENCE/DTSTAMP with the master.
-                    if compareComponents(existing_master, component) < 0:
-                        # Exclude the cancelled instance
-                        exdates.append(component.getRecurrenceIDUTC())
-
-            # If we have any EXDATEs lets add them to the existing calendar object and write
-            # it back.
-            if exdates:
-                if existing_master:
-                    existing_master.addProperty(Property("EXDATE", exdates))
-
-                # See if there are still components in the calendar - we might have deleted the last overridden instance
-                # in which case the calendar object is empty (except for VTIMEZONEs).
-                if existing_calendar.mainType() is None:
-                    # Delete the now empty calendar object
-                    d = waitForDeferred(deleteResource(updatecal, calmatch))
-                    yield d
-                    d.getResult()
-                    logging.info("Deleted calendar component %s after cancellations from iTIP message in %s." % (calmatch, calURL), system="iTIP")
+                    # Find matching component in existing calendar
+                    old_component = self.findMatchingComponent(component, existing_calendar)
+                    
+                    if old_component:
+                        # We are cancelling an overridden component, so we need to check the
+                        # SEQUENCE/DTSAMP with the master.
+                        if self.compareComponents(old_component, component) < 0:
+                            # Exclude the cancelled instance
+                            exdates.append(component.getRecurrenceIDUTC())
+                            
+                            # Remove the existing component.
+                            existing_calendar.removeComponent(old_component)
+                    elif existing_master:
+                        # We are trying to CANCEL a non-overridden instance, so we need to
+                        # check SEQUENCE/DTSTAMP with the master.
+                        if self.compareComponents(existing_master, component) < 0:
+                            # Exclude the cancelled instance
+                            exdates.append(component.getRecurrenceIDUTC())
+    
+                # If we have any EXDATEs lets add them to the existing calendar object and write
+                # it back.
+                if exdates:
+                    if existing_master:
+                        existing_master.addProperty(Property("EXDATE", exdates))
+    
+                    # See if there are still components in the calendar - we might have deleted the last overridden instance
+                    # in which case the calendar object is empty (except for VTIMEZONEs).
+                    if existing_calendar.mainType() is None:
+                        # Delete the now empty calendar object
+                        d = waitForDeferred(self.deleteResource(updatecal, calmatch))
+                        yield d
+                        d.getResult()
+                        logging.info("Deleted calendar component %s after cancellations from iTIP message in %s." % (calmatch, calURL), system="iTIP")
+                    else:
+                        # Update the existing calendar object
+                        newchild = waitForDeferred(self.writeResource(calURL, updatecal, calmatch, existing_calendar))
+                        yield newchild
+                        newchild = newchild.getResult()
+                        logging.info("Updated calendar component %s with cancellations from iTIP message in %s." % (calmatch, calURL), system="iTIP")
+                    processed = "processed"
                 else:
-                    # Update the existing calendar object
-                    newchild = waitForDeferred(writeResource(request, calURL, updatecal, calmatch, existing_calendar))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    logging.info("Updated calendar component %s with cancellations from iTIP message in %s." % (calmatch, calURL), system="iTIP")
-                processed = "processed"
+                    processed = "older"
             else:
-                processed = "older"
-        else:
-            # Nothing to do except delete the inbox item as we have nothing to cancel.
-            processed = "ignored"
-
-    # Remove the now processed incoming request.
-    try:
-        d = waitForDeferred(deleteResource(inbox, child.fp.basename()))
-        yield d
-        d.getResult()
-        logging.info("Deleted new iTIP message %s in Inbox because it has been %s." %
-            (child.fp.basename(),
-             {"processed":"processed",
-              "older":    "ignored: older",
-              "ignored":  "ignored: no match"}[processed],), system="iTIP")
-    except:
-        logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
-        raise iTipException
-    yield None
-    return
-
-processCancel = deferredGenerator(processCancel)
-
-def checkForReply(request, principal, calendar):
-    """
-    Check whether a reply to the given iTIP message is needed. A reply will be needed if the
-    RSVP=TRUE. A reply will either be positive (accepted
-    invitation) or negative (denied invitation). In addition we will modify calendar to reflect
-    any new state (e.g. remove RSVP, set PARTSTAT to ACCEPTED or DECLINED).
+                # Nothing to do except delete the inbox item as we have nothing to cancel.
+                processed = "ignored"
     
-    BTW The incoming iTIP message may contain multiple components so we need to iterate over all those.
-    At the moment we will treat a failure on one isntances as a DECLINE of the entire set.
+        # Remove the now processed incoming request.
+        if self.inbox:
+            self.deleteInboxResource({
+                "processed":"processed",
+                "older":    "ignored: older",
+                "ignored":  "ignored: no match"
+            }[processed])
 
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @return: C{True} if a reply is needed, C{False} otherwise.
-    """
+        yield None
+        return
     
-    # We need to fugure out whether the specified component will clash with any others in the f-b-set calendars
-    accepted = True
+    @deferredGenerator
+    def checkForReply(self):
+        """
+        Check whether a reply to the given iTIP message is needed. A reply will be needed if the
+        RSVP=TRUE. A reply will either be positive (accepted
+        invitation) or negative (denied invitation). In addition we will modify calendar to reflect
+        any new state (e.g. remove RSVP, set PARTSTAT to ACCEPTED or DECLINED).
         
-    # First expand current one to get instances (only go 1 year into the future)
-    default_future_expansion_duration = datetime.timedelta(days=356*1)
-    expand_max = datetime.date.today() + default_future_expansion_duration
-    instances = calendar.expandTimeRanges(expand_max)
-    
-    # Extract UID from primary component as we want to ignore this one if we match it
-    # in any calendars.
-    comp = calendar.mainComponent(allow_multiple=True)
-    uid = comp.propertyValue("UID")
+        BTW The incoming iTIP message may contain multiple components so we need to iterate over all those.
+        At the moment we will treat a failure on one isntances as a DECLINE of the entire set.
 
-    # Now compare each instance time-range with the index and see if there is an overlap
-    fbset = waitForDeferred(principal.calendarFreeBusyURIs(request))
-    yield fbset
-    fbset = fbset.getResult()
-
-    for calURL in fbset:
-        testcal = waitForDeferred(request.locateResource(calURL))
-        yield testcal
-        testcal = testcal.getResult()
+        @return: C{True} if a reply is needed, C{False} otherwise.
+        """
         
-        # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
-        fbinfo = ([], [], [])
+        # We need to fugure out whether the specified component will clash with any others in the f-b-set calendars
+        accepted = True
+            
+        # First expand current one to get instances (only go 1 year into the future)
+        default_future_expansion_duration = datetime.timedelta(days=356*1)
+        expand_max = datetime.date.today() + default_future_expansion_duration
+        instances = self.calendar.expandTimeRanges(expand_max)
         
-        # Now do search for overlapping time-range
-        for instance in instances.instances.itervalues():
-            try:
-                tr = caldavxml.TimeRange(start="20000101", end="20000101")
-                tr.start = instance.start
-                tr.end = instance.end
-                d = waitForDeferred(report_common.generateFreeBusyInfo(request, testcal, fbinfo, tr, 0, uid))
-                yield d
-                d.getResult()
-                
-                # If any fbinfo entries exist we have an overlap
-                if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
+        # Extract UID from primary component as we want to ignore this one if we match it
+        # in any calendars.
+        comp = self.calendar.mainComponent(allow_multiple=True)
+        uid = comp.propertyValue("UID")
+    
+        # Now compare each instance time-range with the index and see if there is an overlap
+        fbset = waitForDeferred(self.principal.calendarFreeBusyURIs(self.request))
+        yield fbset
+        fbset = fbset.getResult()
+    
+        for calURL in fbset:
+            testcal = waitForDeferred(self.request.locateResource(calURL))
+            yield testcal
+            testcal = testcal.getResult()
+            
+            # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+            fbinfo = ([], [], [])
+            
+            # Now do search for overlapping time-range
+            for instance in instances.instances.itervalues():
+                try:
+                    tr = caldavxml.TimeRange(start="20000101", end="20000101")
+                    tr.start = instance.start
+                    tr.end = instance.end
+                    d = waitForDeferred(report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid))
+                    yield d
+                    d.getResult()
+                    
+                    # If any fbinfo entries exist we have an overlap
+                    if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
+                        accepted = False
+                        break
+                except NumberOfMatchesWithinLimits:
                     accepted = False
+                    logging.info("Exceeded number of matches whilst trying to find free-time.", system="iTIP")
                     break
-            except NumberOfMatchesWithinLimits:
-                accepted = False
-                logging.info("Exceeded number of matches whilst trying to find free-time.", system="iTIP")
+                
+            if not accepted:
                 break
-            
-        if not accepted:
-            break
-     
-    # Extract the ATTENDEE property matching current recipient from the calendar data
-    cuas = principal.calendarUserAddresses()
-    attendeeProps = calendar.getAttendeeProperties(cuas)
-    if not attendeeProps:
-        yield False, None, accepted
-        return
-
-    # Look for specific parameters
-    rsvp = False
-    for attendeeProp in attendeeProps:
-        if "RSVP" in attendeeProp.params():
-            if attendeeProp.params()["RSVP"][0] == "TRUE":
-                rsvp = True
+         
+        # Extract the ATTENDEE property matching current recipient from the calendar data
+        cuas = self.principal.calendarUserAddresses()
+        attendeeProps = self.calendar.getAttendeeProperties(cuas)
+        if not attendeeProps:
+            yield False, None, accepted
+            return
     
-            # Now modify the original component
-            del attendeeProp.params()["RSVP"]
-
-    if accepted:
-        partstat = "ACCEPTED"
-    else:
-        partstat = "DECLINED"
-    for attendeeProp in attendeeProps:
-        if "PARTSTAT" in attendeeProp.params():
-            attendeeProp.params()["PARTSTAT"][0] = partstat
-        else:
-            attendeeProp.params()["PARTSTAT"] = [partstat]
+        # Look for specific parameters
+        rsvp = False
+        for attendeeProp in attendeeProps:
+            if "RSVP" in attendeeProp.params():
+                if attendeeProp.params()["RSVP"][0] == "TRUE":
+                    rsvp = True
+        
+                # Now modify the original component
+                del attendeeProp.params()["RSVP"]
     
-    # Now create a new calendar object for the reply
-    
-    # First get useful props from the original
-    replycal = calendar.duplicate()
-    
-    # Change METHOD
-    replycal.getProperty("METHOD").setValue("REPLY")
-    
-    # Change PRODID to this server
-    replycal.getProperty("PRODID").setValue(iCalendarProductID)
-    
-    # Add REQUEST-STATUS
-    for component in replycal.subcomponents():
         if accepted:
-            component.addProperty(Property(name="REQUEST-STATUS", value="2.0; Success."))
+            partstat = "ACCEPTED"
         else:
-            component.addProperty(Property(name="REQUEST-STATUS", value="4.0; Event conflict. Date/time is busy."))
-
-    # Remove all attendees other than ourselves
-    for component in replycal.subcomponents():
-        if component.name() == "VTIMEZONE":
-            continue
-        attendeeProp = component.getAttendeeProperty(cuas)
-        attendees = tuple(component.properties("ATTENDEE"))
-        for attendee in attendees:
-            if attendeeProp is None or (attendee.value() != attendeeProp.value()):
-                replycal.mainComponent().removeProperty(attendee)
-
-    yield rsvp, replycal, accepted
-
-checkForReply = deferredGenerator(checkForReply)
-
-def writeReply(request, principal, replycal, ainbox):
-    """
-    Write an iTIP message reply into the specified Inbox.
+            partstat = "DECLINED"
+        for attendeeProp in attendeeProps:
+            if "PARTSTAT" in attendeeProp.params():
+                attendeeProp.params()["PARTSTAT"][0] = partstat
+            else:
+                attendeeProp.params()["PARTSTAT"] = [partstat]
+        
+        # Now create a new calendar object for the reply
+        
+        # First get useful props from the original
+        replycal = self.calendar.duplicate()
+        
+        # Change METHOD
+        replycal.getProperty("METHOD").setValue("REPLY")
+        
+        # Change PRODID to this server
+        replycal.getProperty("PRODID").setValue(iCalendarProductID)
+        
+        # Add REQUEST-STATUS
+        for component in replycal.subcomponents():
+            if accepted:
+                component.addProperty(Property(name="REQUEST-STATUS", value="2.0; Success."))
+            else:
+                component.addProperty(Property(name="REQUEST-STATUS", value="4.0; Event conflict. Date/time is busy."))
     
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param replycal: the L{Component} for the iTIP message reply.
-    @param ainbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    """
+        # Remove all attendees other than ourselves
+        for component in replycal.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            attendeeProp = component.getAttendeeProperty(cuas)
+            attendees = tuple(component.properties("ATTENDEE"))
+            for attendee in attendees:
+                if attendeeProp is None or (attendee.value() != attendeeProp.value()):
+                    replycal.mainComponent().removeProperty(attendee)
     
-    # Get the Inbox of the ORGANIZER
-    organizer = replycal.getOrganizer()
-    assert organizer is not None
-    inboxURL = ainbox.principalForCalendarUserAddress(organizer).scheduleInboxURL()
-    assert inboxURL
+        yield rsvp, replycal, accepted
     
-    # Determine whether current principal has CALDAV:schedule right on that Inbox
-    inbox = waitForDeferred(request.locateResource(inboxURL))
-    yield inbox
-    inbox = inbox.getResult()
+    @deferredGenerator
+    def writeReply(self, replycal):
+        """
+        Write an iTIP message reply into the specified Inbox.
 
-    try:
-        d = waitForDeferred(inbox.checkPrivileges(request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef.fromString(principal.principalURL()))))
-        yield d
-        d.getResult()
-    except AccessDeniedError:
-        logging.info("Could not send reply as %s does not have CALDAV:schedule permission on %s Inbox." % (principal.principalURL(), organizer), system="iTIP")
-        yield None
-        return
+        @param replycal: the L{Component} for the iTIP message reply.
+        """
+        
+        # Get the Inbox of the ORGANIZER
+        organizer = replycal.getOrganizer()
+        assert organizer is not None
+        inboxURL = self.inbox.principalForCalendarUserAddress(organizer).scheduleInboxURL()
+        assert inboxURL
+        
+        # Determine whether current principal has CALDAV:schedule right on that Inbox
+        writeinbox = waitForDeferred(self.request.locateResource(inboxURL))
+        yield writeinbox
+        writeinbox = writeinbox.getResult()
     
-    # Now deposit the new calendar into the inbox
-    d = waitForDeferred(writeResource(request, inboxURL, inbox, None, replycal))
-    yield d
-    yield d.getResult()
+        try:
+            d = waitForDeferred(writeinbox.checkPrivileges(self.request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef.fromString(self.principal.principalURL()))))
+            yield d
+            d.getResult()
+        except AccessDeniedError:
+            logging.info("Could not send reply as %s does not have CALDAV:schedule permission on %s Inbox." % (self.principal.principalURL(), organizer), system="iTIP")
+            yield None
+            return
+        
+        # Now deposit the new calendar into the inbox
+        newchild = waitForDeferred(self.writeResource(inboxURL, writeinbox, None, replycal))
+        yield newchild
+        newchild = newchild.getResult()
 
-writeReply = deferredGenerator(writeReply)
-
-def writeResource(request, collURL, collection, name, calendar):
-    """
-    Write out the calendar resource (iTIP) message to the specified calendar, either over-writing the named
-    resource or by creating a new one.
-    
-    @param request: the L{IRequest} for the current request.
-    @param collURL: the C{str} containing the URL of the calendar collection.
-    @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
-    @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
-    @param calendar: the L{Component} calendar to write.
-    @return: C{tuple} of L{Deferred}, L{CalDAVFile}
-    """
-    
-    # Create a new name if one was not provided
-    if name is None:
-        name =  md5.new(str(calendar) + str(time.time()) + collection.fp.path).hexdigest() + ".ics"
-
-    # Get a resource for the new item
-    newchildURL = joinURL(collURL, name)
-    newchild = waitForDeferred(request.locateResource(newchildURL))
-    yield newchild
-    newchild = newchild.getResult()
-    
-    # Modify the original calendar data by removing the METHOD property - everything else is left as-is,
-    # as any other needed changes (e.g. RSVP/PARTSTAT) will have been updated.
-    # NB Only do this when writing to something other than an Inbox or Outbox
-    itipper = True
-    if collection.isCalendarCollection():
-        method = calendar.getProperty("METHOD")
-        if method:
-            calendar.removeProperty(method)
-        itipper = False
-    
-    # Now write it to the resource
-    try:
-        d = waitForDeferred(storeCalendarObjectResource(
-                request=request,
-                sourcecal = False,
-                destination = newchild,
-                destination_uri = newchildURL,
-                calendardata = str(calendar),
-                destinationparent = collection,
-                destinationcal = True,
-                isiTIP = itipper
-            ))
-        yield d
-        d.getResult()
-    except:
-        yield None
+        self.newInboxResource(self.child, newchild)
+        
+        yield newchild
         return
     
-    yield newchild
-
-writeResource = deferredGenerator(writeResource)    
-
-def newInboxResource(child, newchild):
-    """
-    Copy recipient and organizer properties from one iTIP resource, to another,
-    switching them as appropriate for a reply, and also set the state.
+    @deferredGenerator
+    def writeResource(self, collURL, collection, name, calendar):
+        """
+        Write out the calendar resource (iTIP) message to the specified calendar, either over-writing the named
+        resource or by creating a new one.
+        
+        @param collURL: the C{str} containing the URL of the calendar collection.
+        @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
+        @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
+        @param calendar: the L{Component} calendar to write.
+        @return: C{tuple} of L{Deferred}, L{CalDAVFile}
+        """
+        
+        # Create a new name if one was not provided
+        if name is None:
+            name =  md5.new(str(calendar) + str(time.time()) + collection.fp.path).hexdigest() + ".ics"
     
-    @param child: the L{CalDAVFile} for the original iTIP message.
-    @param newchild: the L{CalDAVFile} for the iTIP message reply.
-    """
-    # Make previous Recipient the new Originator
-    if child.hasDeadProperty(caldavxml.Recipient):
-        recip = child.readDeadProperty(caldavxml.Recipient)
-        if recip.children:
-            # Store CALDAV:originator property
-            newchild.writeDeadProperty(caldavxml.Originator(davxml.HRef.fromString(str(recip.children[0]))))
+        # Get a resource for the new item
+        newchildURL = joinURL(collURL, name)
+        newchild = waitForDeferred(self.request.locateResource(newchildURL))
+        yield newchild
+        newchild = newchild.getResult()
+        
+        # Modify the original calendar data by removing the METHOD property - everything else is left as-is,
+        # as any other needed changes (e.g. RSVP/PARTSTAT) will have been updated.
+        # NB Only do this when writing to something other than an Inbox or Outbox
+        itipper = True
+        if collection.isCalendarCollection():
+            method = calendar.getProperty("METHOD")
+            if method:
+                calendar.removeProperty(method)
+            itipper = False
+        
+        # Now write it to the resource
+        try:
+            d = waitForDeferred(storeCalendarObjectResource(
+                    request=self.request,
+                    sourcecal = False,
+                    destination = newchild,
+                    destination_uri = newchildURL,
+                    calendardata = str(calendar),
+                    destinationparent = collection,
+                    destinationcal = True,
+                    isiTIP = itipper
+                ))
+            yield d
+            d.getResult()
+        except:
+            yield None
+            return
+        
+        yield newchild
     
-    # Make previous Originator the new Recipient
-    if child.hasDeadProperty(caldavxml.Originator):
-        orig = child.readDeadProperty(caldavxml.Originator)
-        if orig.children:
-            # Store CALDAV:originator property
-            newchild.writeDeadProperty(caldavxml.Recipient(davxml.HRef.fromString(str(orig.children[0]))))
-  
-def deleteResource(collection, name):
-    """
-    Delete the calendar resource in the specified calendar.
-    
-    @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
-    @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
-    @return: L{Deferred}
-    """
-    
-    delchild = collection.getChild(name)
-    index = collection.index()
-    index.deleteResource(delchild.fp.basename())
-    
-    def _deletedResourced(result):
-        # Change CTag on the parent calendar collection
-        collection.updateCTag()
+    def newInboxResource(self, child, newchild):
+        """
+        Copy recipient and organizer properties from one iTIP resource, to another,
+        switching them as appropriate for a reply, and also set the state.
         
-        return result
-
-    d = maybeDeferred(delete, "", delchild.fp, "0")
-    d.addCallback(_deletedResourced)
-    return d
-
-def canAutoRespond(calendar):
-    """
-    Check whether the METHOD of this iTIP calendar object is one we can process. Also,
-    we will only handle VEVENTs right now.
-
-    @param calendar: L{Component} for calendar to examine.
-    @return: C{True} if we can auto-respond, C{False} if not.
-    """
-
-    try:
-        method = calendar.propertyValue("METHOD")
-        if method not in ("REQUEST", "ADD", "CANCEL"):
-            return False
-        if calendar.mainType() not in ("VEVENT"):
-            return False
-    except ValueError:
-        return False
+        @param child: the L{CalDAVFile} for the original iTIP message.
+        @param newchild: the L{CalDAVFile} for the iTIP message reply.
+        """
+        # Make previous Recipient the new Originator
+        if child.hasDeadProperty(caldavxml.Recipient):
+            recip = child.readDeadProperty(caldavxml.Recipient)
+            if recip.children:
+                # Store CALDAV:originator property
+                newchild.writeDeadProperty(caldavxml.Originator(davxml.HRef.fromString(str(recip.children[0]))))
+        
+        # Make previous Originator the new Recipient
+        if child.hasDeadProperty(caldavxml.Originator):
+            orig = child.readDeadProperty(caldavxml.Originator)
+            if orig.children:
+                # Store CALDAV:originator property
+                newchild.writeDeadProperty(caldavxml.Recipient(davxml.HRef.fromString(str(orig.children[0]))))
     
-    return True
-
-def processOthersInInbox(info, newinfo, inbox, child):
-    # Compare the new one with each existing one.
-    delete_child = False
-    for i in info:
-        # For any that are older, delete them.
-        if compareSyncInfo(i, newinfo) < 0:
-            try:
-                d = waitForDeferred(deleteResource(inbox, i[0]))
-                yield d
-                d.getResult()
-                logging.info("Deleted iTIP message %s in Inbox that was older than the new one." % (i[0],), system="iTIP")
-            except:
-                logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
-                raise iTipException
-        else:
-            # For any that are newer or the same, mark the new one to be deleted.
-            delete_child = True
-
-    # Delete the new one if so marked.
-    if delete_child:
+    @deferredGenerator
+    def deleteInboxResource(self, processed_state):
+        # Remove the now processed incoming request.
         try:
-            d = waitForDeferred(deleteResource(inbox, child.fp.basename()))
+            d = waitForDeferred(self.deleteResource(self.inbox, self.childname))
             yield d
             d.getResult()
-            logging.info("Deleted new iTIP message %s in Inbox because it was older than existing ones." % (child.fp.basename(),), system="iTIP")
+            logging.info("Deleted new iTIP message %s in Inbox because it has been %s." %
+                (self.childname, processed_state,), system="iTIP")
         except:
             logging.err("Error while auto-processing iTIP: %s" % (failure.Failure(),), system="iTIP")
             raise iTipException
-    
-    yield delete_child
 
-processOthersInInbox = deferredGenerator(processOthersInInbox)    
-
-def findCalendarMatch(request, principal, calendar):
-    # Try and find a match to any components on existing calendars listed as contributing
-    # to free-busy as we will need to update those with the new one.
+    def deleteResource(self, collection, name):
+        """
+        Delete the calendar resource in the specified calendar.
+        
+        @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
+        @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
+        @return: L{Deferred}
+        """
+        
+        delchild = collection.getChild(name)
+        index = collection.index()
+        index.deleteResource(delchild.fp.basename())
+        
+        def _deletedResourced(result):
+            # Change CTag on the parent calendar collection
+            collection.updateCTag()
+            
+            return result
     
-    # Find the current recipients calendar-free-busy-set
-    fbset = waitForDeferred(principal.calendarFreeBusyURIs(request))
-    yield fbset
-    fbset = fbset.getResult()
-
-    # Find the first calendar in the list with a component matching the one we are processing
-    calmatch = None
-    updatecal = None
-    calURL = None
-    for calURL in fbset:
-        updatecal = waitForDeferred(request.locateResource(calURL))
-        yield updatecal
-        updatecal = updatecal.getResult()
-        if updatecal is None or not updatecal.exists() or not isCalendarCollectionResource(updatecal):
-            # We will ignore missing calendars. If the recipient has failed to
-            # properly manage the free busy set that should not prevent us from working.
-            continue
-        calmatch = matchComponentInCalendar(updatecal, calendar)
-        if calmatch:
-            logging.info("Found calendar component %s matching new iTIP message in %s." % (calmatch, calURL), system="iTIP")
-            break
+        d = maybeDeferred(delete, "", delchild.fp, "0")
+        d.addCallback(_deletedResourced)
+        return d
     
-    if calmatch is None and len(fbset):
-        calURL = fbset[0]
-        updatecal = waitForDeferred(request.locateResource(calURL))
-        yield updatecal
-        updatecal = updatecal.getResult()
-
-    yield calmatch, updatecal, calURL
-
-findCalendarMatch = deferredGenerator(findCalendarMatch)    
-
-def matchComponentInCalendar(collection, calendar):
-    """
-    See if the component in the provided iTIP calendar object matches any in the specified calendar
-    collection.
+    @staticmethod
+    def canAutoRespond(calendar):
+        """
+        Check whether the METHOD of this iTIP calendar object is one we can process. Also,
+        we will only handle VEVENTs right now.
     
-    @param collection: L{CalDAVFile} for the calendar collection to examine.
-    @param calendar: L{Component} for calendar to examine.
-    @return: C{list} of resource names found.
-    """
-
-    try:
-        # Extract UID from primary component (note we allow multiple components to be present
-        # because CANCEL requests can have multiple components).
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid = comp.propertyValue("UID")
+        @param calendar: L{Component} for calendar to examine.
+        @return: C{True} if we can auto-respond, C{False} if not.
+        """
+    
+        try:
+            method = calendar.propertyValue("METHOD")
+            if method not in ("REQUEST", "ADD", "CANCEL"):
+                return False
+            if calendar.mainType() not in ("VEVENT"):
+                return False
+        except ValueError:
+            return False
         
-        # Now use calendar collection index to find all other resources with the same UID
-        index = collection.index()
-        result = index.resourceNamesForUID(uid)
+        return True
+    
+    @deferredGenerator
+    def findCalendarMatch(self):
+        # Try and find a match to any components on existing calendars listed as contributing
+        # to free-busy as we will need to update those with the new one.
         
-        # There can be only one
-        if len(result) > 0: 
-            return result[0]
-        else:
-            return None
-    except ValueError:
-        return None
-
-def findMatchingComponent(component, calendar):
-    """
-    See if any overridden component in the provided iTIP calendar object matches the specified component.
+        # Find the current recipients calendar-free-busy-set
+        calendars = waitForDeferred(self.getCalendarsToMatch())
+        yield calendars
+        calendars = calendars.getResult()
     
-    @param component: the component to try and match.
-    @type component: L{Component}
-    @param calendar: the calendar to find a match in.
-    @type calendar: L{Component}
-    @return: L{Component} for matching component,
-        or C{None} if not found.
-    """
-
-    # Extract RECURRENCE-ID value from component
-    rid = component.getRecurrenceIDUTC()
+        # Find the first calendar in the list with a component matching the one we are processing
+        calmatch = None
+        updatecal = None
+        calURL = None
+        for calURL in calendars:
+            updatecal = waitForDeferred(self.request.locateResource(calURL))
+            yield updatecal
+            updatecal = updatecal.getResult()
+            if updatecal is None or not updatecal.exists() or not isCalendarCollectionResource(updatecal):
+                # We will ignore missing calendars. If the recipient has failed to
+                # properly manage the free busy set that should not prevent us from working.
+                continue
+            calmatch = self.matchComponentInCalendar(updatecal, self.calendar)
+            if calmatch:
+                logging.info("Found calendar component %s matching new iTIP message in %s." % (calmatch, calURL), system="iTIP")
+                break
+        
+        if calmatch is None and len(calendars):
+            calURL = calendars[0]
+            updatecal = waitForDeferred(self.request.locateResource(calURL))
+            yield updatecal
+            updatecal = updatecal.getResult()
     
-    # Return the one that matches in the calendar
-    return calendar.overriddenComponent(rid)
-
-def mergeComponents(newcal, oldcal):
-    """
-    Merge the overridden instance components in newcal into old cal replacing any
-    matching components there.
-
-    @param newcal: the new overridden instances to use.
-    @type newcal: L{Component}
-    @param oldcal: the component to merge into.
-    @type oldcal: L{Component}
-    """
+        yield calmatch, updatecal, calURL
     
-    # FIXME: going to ignore VTIMEZONE - i.e. will assume that the component being added
-    # use a TZID that is already specified in the old component set.
-
-    # We will update the SEQUENCE on the master to the highest value of the current one on the master
-    # or the ones in the components we are changing.
-
-    for component in newcal.subcomponents():
-        if component.name() == "VTIMEZONE":
-            continue
+    @deferredGenerator
+    def getCalendarsToMatch(self):
+        # Determine the set of calendar URIs for a principal need to be searched.
         
-        rid = component.getRecurrenceIDUTC()
-        old_component = oldcal.overriddenComponent(rid)
-        if old_component:
-            oldcal.removeComponent(old_component)
-        oldcal.addComponent(component)
+        # Find the current recipients calendar-free-busy-set
+        fbset = waitForDeferred(self.principal.calendarFreeBusyURIs(self.request))
+        yield fbset
+        fbset = fbset.getResult()
+    
+        yield fbset
 
-def getAllInfo(collection, calendar, ignore):
-    """
-    Find each component in the calendar collection that has a matching UID with
-    the supplied component, and get useful synchronization details from it, ignoring
-    the one with the supplied resource name.
-
-    @param collection: the L{CalDAVFile} for the calendar collection.
-    @param calendar: the L{Component} for the component being compared with.
-    @param ignore: the C{str} containing the name of a resource to ignore,
-        or C{None} if none to ignore.
-    @return: C{list} of synchronization information for each resource found.
-    """
-    names = []
-    try:
-        # Extract UID from primary component (note we allow multiple components to be present
-        # because CANCEL requests can have multiple components).
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid = comp.propertyValue("UID")
+    def matchComponentInCalendar(self, collection, calendar):
+        """
+        See if the component in the provided iTIP calendar object matches any in the specified calendar
+        collection.
         
-        # Now use calendar collection index to find all other resources with the same UID
-        index = collection.index()
-        names = index.resourceNamesForUID(uid)
+        @param collection: L{CalDAVFile} for the calendar collection to examine.
+        @param calendar: L{Component} for calendar to examine.
+        @return: C{list} of resource names found.
+        """
+    
+        try:
+            # Extract UID from primary component (note we allow multiple components to be present
+            # because CANCEL requests can have multiple components).
+            comp = calendar.mainComponent(allow_multiple=True)
+            uid = comp.propertyValue("UID")
+            
+            # Now use calendar collection index to find all other resources with the same UID
+            index = collection.index()
+            result = index.resourceNamesForUID(uid)
+            
+            # There can be only one
+            if len(result) > 0: 
+                return result[0]
+            else:
+                return None
+        except ValueError:
+            return None
+    
+    def findMatchingComponent(self, component, calendar):
+        """
+        See if any overridden component in the provided iTIP calendar object matches the specified component.
         
-        # Remove the one we want to ignore
-        if ignore is not None:
-            names = [name for name in names if name != ignore.fp.basename()]
-    except ValueError:
-        return []
+        @param component: the component to try and match.
+        @type component: L{Component}
+        @param calendar: the calendar to find a match in.
+        @type calendar: L{Component}
+        @return: L{Component} for matching component,
+            or C{None} if not found.
+        """
     
-    # Now get info for each name
-    result = []
-    for name in names:
-        cal = collection.iCalendar(name)
-        result.append(getSyncInfo(name, cal))
-
-    return result
+        # Extract RECURRENCE-ID value from component
+        rid = component.getRecurrenceIDUTC()
+        
+        # Return the one that matches in the calendar
+        return calendar.overriddenComponent(rid)
     
-def getSyncInfo(name, calendar):
-    """
-    Get property value details needed to synchronize iTIP components.
+    def mergeComponents(self, newcal, oldcal):
+        """
+        Merge the overridden instance components in newcal into old cal replacing any
+        matching components there.
     
-    @param calendar: L{Component} for calendar to check.
-    @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
-    """
-    try:
-        # Extract components from primary component (note we allow multiple components to be present
-        # because CANCEL requests can have multiple components).
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid, seq, dtstamp, rid = getComponentSyncInfo(comp)
+        @param newcal: the new overridden instances to use.
+        @type newcal: L{Component}
+        @param oldcal: the component to merge into.
+        @type oldcal: L{Component}
+        """
         
-    except ValueError:
-        return (name, None, None, None, None)
+        # FIXME: going to ignore VTIMEZONE - i.e. will assume that the component being added
+        # use a TZID that is already specified in the old component set.
     
-    return (name, uid, seq, dtstamp, rid)
-
-def getComponentSyncInfo(component):
-    """
-    Get property value details needed to synchronize iTIP components.
+        # We will update the SEQUENCE on the master to the highest value of the current one on the master
+        # or the ones in the components we are changing.
     
-    @param component: L{Component} to check.
-    @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
-    """
-    try:
-        # Extract items from component
-        uid = component.propertyValue("UID")
-        seq = component.propertyValue("SEQUENCE")
-        dtstamp = component.propertyValue("DTSTAMP")
-        rid = component.propertyValue("RECURRENCE-ID")
+        for component in newcal.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            
+            rid = component.getRecurrenceIDUTC()
+            old_component = oldcal.overriddenComponent(rid)
+            if old_component:
+                oldcal.removeComponent(old_component)
+            oldcal.addComponent(component)
+    
+    def getAllInfo(self, collection, calendar, ignore):
+        """
+        Find each component in the calendar collection that has a matching UID with
+        the supplied component, and get useful synchronization details from it, ignoring
+        the one with the supplied resource name.
+    
+        @param collection: the L{CalDAVFile} for the calendar collection.
+        @param calendar: the L{Component} for the component being compared with.
+        @param ignore: the C{str} containing the name of a resource to ignore,
+            or C{None} if none to ignore.
+        @return: C{list} of synchronization information for each resource found.
+        """
+        names = []
+        try:
+            # Extract UID from primary component (note we allow multiple components to be present
+            # because CANCEL requests can have multiple components).
+            comp = calendar.mainComponent(allow_multiple=True)
+            uid = comp.propertyValue("UID")
+            
+            # Now use calendar collection index to find all other resources with the same UID
+            index = collection.index()
+            names = index.resourceNamesForUID(uid)
+            
+            # Remove the one we want to ignore
+            if ignore is not None:
+                names = [name for name in names if name != ignore.fp.basename()]
+        except ValueError:
+            return []
         
-    except ValueError:
-        return (None, None, None, None)
+        # Now get info for each name
+        result = []
+        for name in names:
+            cal = collection.iCalendar(name)
+            result.append(self.getSyncInfo(name, cal))
     
-    return (uid, seq, dtstamp, rid)
-
-def compareComponents(component1, component2):
-    """
-    Compare synchronization information for two components to see if they match according to iTIP.
-
-    @param component1: first component to check.
-    @type component1: L{Component}
-    @param component2: second component to check.
-    @type component2: L{Component}
+        return result
+        
+    def getSyncInfo(self, name, calendar):
+        """
+        Get property value details needed to synchronize iTIP components.
+        
+        @param calendar: L{Component} for calendar to check.
+        @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
+        """
+        try:
+            # Extract components from primary component (note we allow multiple components to be present
+            # because CANCEL requests can have multiple components).
+            comp = calendar.mainComponent(allow_multiple=True)
+            uid, seq, dtstamp, rid = self.getComponentSyncInfo(comp)
+            
+        except ValueError:
+            return (name, None, None, None, None)
+        
+        return (name, uid, seq, dtstamp, rid)
     
-    @return: 0, 1, -1 as per compareSyncInfo.
-    """
-    info1 = (None,) + getComponentSyncInfo(component1)
-    info2 = (None,) + getComponentSyncInfo(component2)
-    return compareSyncInfo(info1, info2)
-
-def compareSyncInfo(info1, info2):
-    """
-    Compare two synchronization information records.
+    def getComponentSyncInfo(self, component):
+        """
+        Get property value details needed to synchronize iTIP components.
+        
+        @param component: L{Component} to check.
+        @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
+        """
+        try:
+            # Extract items from component
+            uid = component.propertyValue("UID")
+            seq = component.propertyValue("SEQUENCE")
+            dtstamp = component.propertyValue("DTSTAMP")
+            rid = component.propertyValue("RECURRENCE-ID")
+            
+        except ValueError:
+            return (None, None, None, None)
+        
+        return (uid, seq, dtstamp, rid)
     
-    @param info1: a C{tuple} as returned by L{getSyncInfo}.
-    @param info2: a C{tuple} as returned by L{getSyncInfo}.
-    @return: 1 if info1 > info2, 0 if info1 == info2, -1 if info1 < info2
-    """
-    # UIDs MUST match
-    assert info1[1] == info2[1]
+    def compareComponents(self, component1, component2):
+        """
+        Compare synchronization information for two components to see if they match according to iTIP.
     
-    # Look for sequence
-    if (info1[2] is not None) and (info2[2] is not None):
-        if info1[2] > info2[2]:
+        @param component1: first component to check.
+        @type component1: L{Component}
+        @param component2: second component to check.
+        @type component2: L{Component}
+        
+        @return: 0, 1, -1 as per compareSyncInfo.
+        """
+        info1 = (None,) + self.getComponentSyncInfo(component1)
+        info2 = (None,) + self.getComponentSyncInfo(component2)
+        return self.compareSyncInfo(info1, info2)
+    
+    def compareSyncInfo(self, info1, info2):
+        """
+        Compare two synchronization information records.
+        
+        @param info1: a C{tuple} as returned by L{getSyncInfo}.
+        @param info2: a C{tuple} as returned by L{getSyncInfo}.
+        @return: 1 if info1 > info2, 0 if info1 == info2, -1 if info1 < info2
+        """
+        # UIDs MUST match
+        assert info1[1] == info2[1]
+        
+        # Look for sequence
+        if (info1[2] is not None) and (info2[2] is not None):
+            if info1[2] > info2[2]:
+                return 1
+            if info1[2] < info2[2]:
+                return -1
+        elif (info1[2] is not None) and (info2[2] is None):
             return 1
-        if info1[2] < info2[2]:
+        elif (info1[2] is None) and (info2[2] is not None):
             return -1
-    elif (info1[2] is not None) and (info2[2] is None):
-        return 1
-    elif (info1[2] is None) and (info2[2] is not None):
-        return -1
-
-    # Look for DTSTAMP
-    if (info1[3] is not None) and (info2[3] is not None):
-        if info1[3] > info2[3]:
+    
+        # Look for DTSTAMP
+        if (info1[3] is not None) and (info2[3] is not None):
+            if info1[3] > info2[3]:
+                return 1
+            if info1[3] < info2[3]:
+                return -1
+        elif (info1[3] is not None) and (info2[3] is None):
             return 1
-        if info1[3] < info2[3]:
+        elif (info1[3] is None) and (info2[3] is not None):
             return -1
-    elif (info1[3] is not None) and (info2[3] is None):
-        return 1
-    elif (info1[3] is None) and (info2[3] is not None):
-        return -1
-
-    return 0
+    
+        return 0

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/logging.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/logging.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/logging.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -46,7 +46,7 @@
 
 logtypes = {"none": 0, "error": 1, "warning": 2, "info": 3, "debug": 4}
 
-currentLogLevel = logtypes["error"]
+currentLogLevel = logtypes["debug"]
 previousLogLevel = logtypes["debug"]
 
 def toggle():

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/report_common.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/report_common.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -264,7 +264,8 @@
 _namedPropertiesForResource = deferredGenerator(_namedPropertiesForResource)
     
 def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
-                         excludeuid=None, organizer=None, same_calendar_user=False):
+                         excludeuid=None, organizer=None, same_calendar_user=False,
+                         servertoserver=False):
     """
     Run a free busy report on the specified calendar collection
     accumulating the free busy info for later processing.
@@ -279,16 +280,19 @@
         This is used in conjunction with the UID value to process exclusions.
     @param same_calendar_user:   a C{bool} indicating whether the calendar user requesting tyhe free-busy information
         is the same as the calendar user being targeted.
+    @param servertoserver:       a C{bool} indicating whether we are doing a local or remote lookup request.
     """
     
     # First check the privilege on this collection
-    try:
-        d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),)))
-        yield d
-        d.getResult()
-    except AccessDeniedError:
-        yield matchtotal
-        return
+    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
+    if not servertoserver:
+        try:
+            d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),)))
+            yield d
+            d.getResult()
+        except AccessDeniedError:
+            yield matchtotal
+            return
 
     #
     # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
@@ -333,12 +337,14 @@
         yield child
         child = child.getResult()
 
-        try:
-            d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces))
-            yield d
-            d.getResult()
-        except AccessDeniedError:
-            continue
+        # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
+        if not servertoserver:
+            try:
+                d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces))
+                yield d
+                d.getResult()
+            except AccessDeniedError:
+                continue
 
         calendar = calresource.iCalendar(name)
         
@@ -567,7 +573,7 @@
     normalizePeriodList(periods)
     return periods
 
-def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None):
+def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None, method=None):
     """
     Generate a VCALENDAR object containing a single VFREEBUSY that is the
     aggregate of the free busy info passed in.
@@ -587,6 +593,8 @@
     # Now build a new calendar object with the free busy info we have
     fbcalendar = Component("VCALENDAR")
     fbcalendar.addProperty(Property("PRODID", iCalendarProductID))
+    if method:
+        fbcalendar.addProperty(Property("METHOD", method))
     fb = Component("VFREEBUSY")
     fbcalendar.addComponent(fb)
     if organizer is not None:

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -23,34 +23,27 @@
 __all__ = [
     "ScheduleInboxResource",
     "ScheduleOutboxResource",
+    "ScheduleServerToServerResource",
 ]
 
-from twisted.internet import reactor
-from twisted.internet.defer import deferredGenerator, maybeDeferred, succeed, waitForDeferred
-from twisted.python.failure import Failure
+from twisted.internet.defer import deferredGenerator, succeed, waitForDeferred
 from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, Response
-from twisted.web2.http_headers import MimeType
 from twisted.web2.dav import davxml
-from twisted.web2.dav.http import ErrorResponse, errorForFailure, messageForFailure, statusForFailure
-from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.util import joinURL
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http_headers import MimeType
 
 from twistedcaldav import caldavxml
-from twistedcaldav import itip
-from twistedcaldav import logging
-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.caldavxml import caldav_namespace, TimeRange
+from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.ical import Component
-from twistedcaldav.method import report_common
-from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.resource import isCalendarCollectionResource
+from twistedcaldav.schedule_common import CalDAVScheduler
+from twistedcaldav.schedule_common import ServerToServerScheduler
 
-import md5
-import time
-
 class CalendarSchedulingCollectionResource (CalDAVResource):
     """
     CalDAV principal resource.
@@ -168,6 +161,8 @@
     Extends L{DAVResource} to provide CalDAV functionality.
     """
 
+    _schedulerClass = CalDAVScheduler
+
     def defaultAccessControlList(self):
         if config.EnableProxyPrincipals:
             myPrincipal = self.parent.principalForRecord()
@@ -201,399 +196,88 @@
         yield x
         x.getResult()
 
-        # Must be content-type text/calendar
-        content_type = request.headers.getHeader("content-type")
-        if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
-            logging.err("MIME type %s not allowed in calendar collection" % (content_type,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
-    
-        # Must have Originator header
-        originator = request.headers.getRawHeaders("originator")
-        if originator is None or (len(originator) != 1):
-            logging.err("POST request must have Originator header", system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-specified")))
-        else:
-            originator = originator[0]
-    
-        # Verify that Originator is a valid calendar user (has an INBOX)
-        oprincipal = self.principalForCalendarUserAddress(originator)
-        if oprincipal is None:
-            logging.err("Could not find principal for originator: %s" % (originator,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        # This is a local CALDAV scheduling operation.
+        scheduler = ScheduleOutboxResource._schedulerClass(request, self)
 
-        inboxURL = oprincipal.scheduleInboxURL()
-        if inboxURL is None:
-            logging.err("Could not find inbox for originator: %s" % (originator,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
-    
-        # Verify that Originator matches the authenticated user
-        if davxml.Principal(davxml.HRef(oprincipal.principalURL())) != self.currentPrincipal(request):
-            logging.err("Originator: %s does not match authorized user: %s" % (originator, self.currentPrincipal(request).children[0],), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        # Do the POST processing treating
+        x = waitForDeferred(scheduler.doSchedulingViaPOST())
+        yield x
+        yield x.getResult()
 
-        # Get list of Recipient headers
-        rawrecipients = request.headers.getRawHeaders("recipient")
-        if rawrecipients is None or (len(rawrecipients) == 0):
-            logging.err("POST request must have at least one Recipient header", system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-specified")))
 
-        # Recipient header may be comma separated list
-        recipients = []
-        for rawrecipient in rawrecipients:
-            for r in rawrecipient.split(","):
-                r = r.strip()
-                if len(r):
-                    recipients.append(r)
+class ScheduleServerToServerResource (CalDAVResource):
+    """
+    Server-to-server schedule Inbox resource.
 
-        timerange = TimeRange(start="20000101", end="20000102")
-        recipients_state = {"OK":0, "BAD":0}
+    Extends L{DAVResource} to provide Server-to-server functionality.
+    """
 
-        # Parse the calendar object from the HTTP request stream
-        try:
-            d = waitForDeferred(Component.fromIStream(request.stream))
-            yield d
-            calendar = d.getResult()
-        except:
-            logging.err("Error while handling POST: %s" % (Failure(),), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- 
-        # Must be a valid calendar
-        try:
-            calendar.validCalendarForCalDAV()
-        except ValueError:
-            logging.err("POST request calendar component is not valid: %s" % (calendar,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+    _schedulerClass = ServerToServerScheduler
 
-        # Must have a METHOD
-        if not calendar.isValidMethod():
-            logging.err("POST request must have valid METHOD property in calendar component: %s" % (calendar,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-        
-        # Verify iTIP behaviour
-        if not calendar.isValidITIP():
-            logging.err("POST request must have a calendar component that satisfies iTIP requirements: %s" % (calendar,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-    
-        # Verify that the ORGANIZER's cu address maps to the request.uri
-        outboxURL = None
-        organizer = calendar.getOrganizer()
-        if organizer is not None:
-            oprincipal = self.principalForCalendarUserAddress(organizer)
-            if oprincipal is not None:
-                outboxURL = oprincipal.scheduleOutboxURL()
-        if outboxURL is None:
-            logging.err("ORGANIZER in calendar data is not valid: %s" % (calendar,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+    def __init__(self, parent):
+        """
+        @param parent: the parent resource of this one.
+        """
+        assert parent is not None
 
-        # Prevent spoofing of ORGANIZER with specific METHODs
-        if (calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER")) and (outboxURL != request.uri):
-            logging.err("ORGANIZER in calendar data does not match owner of Outbox: %s" % (calendar,), system="CalDAV Outbox POST")
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
 
-        # Prevent spoofing when doing reply-like METHODs
-        if calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
-            # Verify that there is a single ATTENDEE property and that the Originator has permission
-            # to send on behalf of that ATTENDEE
-            attendees = calendar.getAttendees()
-        
-            # Must have only one
-            if len(attendees) != 1:
-                logging.err("ATTENDEE list in calendar data is wrong: %s" % (calendar,), system="CalDAV Outbox POST")
-                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
-            
-            # Attendee's Outbox MUST be the request URI
-            aoutboxURL = None
-            aprincipal = self.principalForCalendarUserAddress(attendees[0])
-            if aprincipal is not None:
-                aoutboxURL = aprincipal.scheduleOutboxURL()
-            if aoutboxURL is None or aoutboxURL != request.uri:
-                logging.err("ATTENDEE in calendar data does not match owner of Outbox: %s" % (calendar,), system="CalDAV Outbox POST")
-                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+        self.parent = parent
 
-        # For free-busy do immediate determination of iTIP result rather than fan-out
-        logging.info("METHOD: %s, Component: %s" % (calendar.propertyValue("METHOD"), calendar.mainType(),), system="CalDAV Outbox POST")
-        if (calendar.propertyValue("METHOD") == "REQUEST") and (calendar.mainType() == "VFREEBUSY"):
-            # Extract time range from VFREEBUSY object
-            vfreebusies = [v for v in calendar.subcomponents() if v.name() == "VFREEBUSY"]
-            if len(vfreebusies) != 1:
-                logging.err("iTIP data is not valid for a VFREEBUSY request: %s" % (calendar,), system="CalDAV Outbox POST")
-                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-            dtstart = vfreebusies[0].getStartDateUTC()
-            dtend = vfreebusies[0].getEndDateUTC()
-            if dtstart is None or dtend is None:
-                logging.err("VFREEBUSY start/end not valid: %s" % (calendar,), system="CalDAV Outbox POST")
-                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-            timerange.start = dtstart
-            timerange.end = dtend
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # DAV:Read, CalDAV:schedule for all principals (includes anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.All()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(caldavxml.Schedule()),
+                ),
+                davxml.Protected(),
+            ),
+        )
 
-            # Look for maksed UID
-            excludeuid = calendar.getMaskUID()
+    def resourceType(self):
+        return davxml.ResourceType.servertoserverinbox
 
-            # Do free busy operation
-            freebusy = True
-        else:
-            # Do regular invite (fan-out)
-            freebusy = False
+    def isCollection(self):
+        return False
 
-        # Prepare for multiple responses
-        responses = ScheduleResponseQueue("POST", responsecode.OK)
-    
-        # Extract the ORGANIZER property and UID value from the calendar data for use later
-        organizerProp = calendar.getOrganizerProperty()
-        uid = calendar.resourceUID()
+    def isCalendarCollection(self):
+        return False
 
-        # Loop over each recipient and do appropriate action.
-        autoresponses = []
-        for recipient in recipients:
-            # Get the principal resource for this recipient
-            principal = self.principalForCalendarUserAddress(recipient)
+    def isPseudoCalendarCollection(self):
+        return False
 
-            # Map recipient to their inbox
-            inbox = None
-            if principal is None:
-                logging.err("No principal for calendar user address: %s" % (recipient,), system="CalDAV Outbox POST")
-            else:
-                inboxURL = principal.scheduleInboxURL()
-                if inboxURL:
-                    inbox = waitForDeferred(request.locateResource(inboxURL))
-                    yield inbox
-                    inbox = inbox.getResult()
-                else:
-                    logging.err("No schedule inbox for principal: %s" % (principal,), system="CalDAV Outbox POST")
+    def render(self, request):
+        output = """<html>
+<head>
+<title>Server To Server Inbox Resource</title>
+</head>
+<body>
+<h1>Server To Server Inbox Resource.</h1>
+</body
+</html>"""
 
-            if inbox is None:
-                err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-exists")))
-                responses.add(recipient, Failure(exc_value=err), reqstatus="3.7;Invalid Calendar User")
-                recipients_state["BAD"] += 1
-            
-                # Process next recipient
-                continue
-            else:
-                #
-                # Check access controls
-                #
-                try:
-                    d = waitForDeferred(inbox.checkPrivileges(request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef(oprincipal.principalURL()))))
-                    yield d
-                    d.getResult()
-                except AccessDeniedError:
-                    logging.err("Could not access Inbox for recipient: %s" % (recipient,), system="CalDAV Outbox POST")
-                    err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-permisions")))
-                    responses.add(recipient, Failure(exc_value=err), reqstatus="3.8;No authority")
-                    recipients_state["BAD"] += 1
-                
-                    # Process next recipient
-                    continue
-    
-                # Different behaviour for free-busy vs regular invite
-                if freebusy:
-                    # Extract the ATTENDEE property matching current recipient from the calendar data
-                    cuas = principal.calendarUserAddresses()
-                    attendeeProp = calendar.getAttendeeProperty(cuas)
-            
-                    # Find the current recipients calendar-free-busy-set
-                    fbset = waitForDeferred(principal.calendarFreeBusyURIs(request))
-                    yield fbset
-                    fbset = fbset.getResult()
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response
 
-                    # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
-                    fbinfo = ([], [], [])
-                
-                    try:
-                        # Process the availability property from the Inbox.
-                        has_prop = waitForDeferred(inbox.hasProperty((calendarserver_namespace, "calendar-availability"), request))
-                        yield has_prop
-                        has_prop = has_prop.getResult()
-                        if has_prop:
-                            availability = waitForDeferred(inbox.readProperty((calendarserver_namespace, "calendar-availability"), request))
-                            yield availability
-                            availability = availability.getResult()
-                            availability = availability.calendar()
-                            report_common.processAvailabilityFreeBusy(availability, fbinfo, timerange)
-
-                        # Check to see if the recipient is the same calendar user as the organizer.
-                        # Needed for masked UID stuff.
-                        same_calendar_user = oprincipal.principalURL() == principal.principalURL()
-
-                        # Now process free-busy set calendars
-                        matchtotal = 0
-                        for calURL in fbset:
-                            cal = waitForDeferred(request.locateResource(calURL))
-                            yield cal
-                            cal = cal.getResult()
-                            if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
-                                # We will ignore missing calendars. If the recipient has failed to
-                                # properly manage the free busy set that should not prevent us from working.
-                                continue
-                         
-                            matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
-                                request,
-                                cal,
-                                fbinfo,
-                                timerange,
-                                matchtotal,
-                                excludeuid=excludeuid,
-                                organizer=organizer,
-                                same_calendar_user=same_calendar_user))
-                            yield matchtotal
-                            matchtotal = matchtotal.getResult()
-                    
-                        # Build VFREEBUSY iTIP reply for this recipient
-                        fbresult = report_common.buildFreeBusyResult(fbinfo, timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
-
-                        responses.add(recipient, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
-                        recipients_state["OK"] += 1
-                
-                    except:
-                        logging.err("Could not determine free busy information: %s" % (recipient,), system="CalDAV Outbox POST")
-                        err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
-                        responses.add(recipient, Failure(exc_value=err), reqstatus="3.8;No authority")
-                        recipients_state["BAD"] += 1
-                
-                else:
-                    # Hash the iCalendar data for use as the last path element of the URI path
-                    name = md5.new(str(calendar) + str(time.time()) + inbox.fp.path).hexdigest() + ".ics"
-                
-                    # Get a resource for the new item
-                    childURL = joinURL(inboxURL, name)
-                    child = waitForDeferred(request.locateResource(childURL))
-                    yield child
-                    child = child.getResult()
-            
-                    # Copy calendar to inbox (doing fan-out)
-                    d = waitForDeferred(
-                            maybeDeferred(
-                                storeCalendarObjectResource,
-                                request=request,
-                                sourcecal = False,
-                                destination = child,
-                                destination_uri = childURL,
-                                calendardata = str(calendar),
-                                destinationparent = inbox,
-                                destinationcal = True,
-                                isiTIP = True
-                            )
-                         )
-                    yield d
-                    try:
-                        d.getResult()
-                        responses.add(recipient, responsecode.OK, reqstatus="2.0;Success")
-                        recipients_state["OK"] += 1
-        
-                        # Store CALDAV:originator property
-                        child.writeDeadProperty(caldavxml.Originator(davxml.HRef(originator)))
-                    
-                        # Store CALDAV:recipient property
-                        child.writeDeadProperty(caldavxml.Recipient(davxml.HRef(recipient)))
-                    
-                        # Look for auto-schedule option
-                        if principal.autoSchedule():
-                            autoresponses.append((principal, inbox, child))
-                    except:
-                        logging.err("Could not store data in Inbox : %s" % (inbox,), system="CalDAV Outbox POST")
-                        err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
-                        responses.add(recipient, Failure(exc_value=err), reqstatus="3.8;No authority")
-                        recipients_state["BAD"] += 1
-
-        # Now we have to do auto-respond
-        if len(autoresponses) != 0:
-            # First check that we have a method that we can auto-respond to
-            if not itip.canAutoRespond(calendar):
-                autoresponses = []
-            
-        # Now do the actual auto response
-        for principal, inbox, child in autoresponses:
-            # Add delayed reactor task to handle iTIP responses
-            reactor.callLater(0.0, itip.handleRequest, *(request, principal, inbox, calendar.duplicate(), child)) #@UndefinedVariable
-            #reactor.callInThread(itip.handleRequest, *(request, principal, inbox, calendar.duplicate(), child)) #@UndefinedVariable
-
-        # Return with final response if we are done
-        yield responses.response()
-
-class ScheduleResponseResponse (Response):
-    """
-    ScheduleResponse L{Response} object.
-    Renders itself as a CalDAV:schedule-response XML document.
-    """
-    def __init__(self, xml_responses, location=None):
+    @deferredGenerator
+    def http_POST(self, request):
         """
-        @param xml_responses: an interable of davxml.Response objects.
-        @param location:      the value of the location header to return in the response,
-            or None.
+        The server-to-server POST method.
         """
 
-        Response.__init__(self, code=responsecode.OK,
-                          stream=caldavxml.ScheduleResponse(*xml_responses).toxml())
+        # Check authentication and access controls
+        x = waitForDeferred(self.authorize(request, (caldavxml.Schedule(),)))
+        yield x
+        x.getResult()
 
-        self.headers.setHeader("content-type", MimeType("text", "xml"))
-    
-        if location is not None:
-            self.headers.setHeader("location", location)
+        # This is a server-to-server scheduling operation.
+        scheduler = ScheduleServerToServerResource._schedulerClass(request, self)
 
-class ScheduleResponseQueue (object):
-    """
-    Stores a list of (typically error) responses for use in a
-    L{ScheduleResponse}.
-    """
-    def __init__(self, method, success_response):
-        """
-        @param method: the name of the method generating the queue.
-        @param success_response: the response to return in lieu of a
-            L{ScheduleResponse} if no responses are added to this queue.
-        """
-        self.responses         = []
-        self.method            = method
-        self.success_response  = success_response
-        self.location          = None
-
-    def setLocation(self, location):
-        """
-        @param location:      the value of the location header to return in the response,
-            or None.
-        """
-        self.location = location
-
-    def add(self, recipient, what, reqstatus=None, calendar=None):
-        """
-        Add a response.
-        @param recipient: the recipient for this response.
-        @param what: a status code or a L{Failure} for the given recipient.
-        @param status: the iTIP request-status for the given recipient.
-        @param calendar: the calendar data for the given recipient response.
-        """
-        if type(what) is int:
-            code    = what
-            error   = None
-            message = responsecode.RESPONSES[code]
-        elif isinstance(what, Failure):
-            code    = statusForFailure(what)
-            error   = errorForFailure(what)
-            message = messageForFailure(what)
-        else:
-            raise AssertionError("Unknown data type: %r" % (what,))
-
-        if code > 400: # Error codes only
-            logging.err("Error during %s for %s: %s" % (self.method, recipient, message), system="CalDAV Outbox POST")
-
-        children = []
-        children.append(caldavxml.Recipient(davxml.HRef.fromString(recipient)))
-        children.append(caldavxml.RequestStatus(reqstatus))
-        if calendar is not None:
-            children.append(caldavxml.CalendarData.fromCalendar(calendar))
-        if error is not None:
-            children.append(error)
-        if message is not None:
-            children.append(davxml.ResponseDescription(message))
-        self.responses.append(caldavxml.Response(*children))
-
-    def response(self):
-        """
-        Generate a L{ScheduleResponseResponse} with the responses contained in the
-        queue or, if no such responses, return the C{success_response} provided
-        to L{__init__}.
-        @return: the response.
-        """
-        if self.responses:
-            return ScheduleResponseResponse(self.responses, self.location)
-        else:
-            return self.success_response
+        # Do the POST processing treating this as a non-local schedule
+        x = waitForDeferred(scheduler.doSchedulingViaPOST())
+        yield x
+        yield x.getResult()

Copied: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py (from rev 1966, CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/schedule_common.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -0,0 +1,977 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+CalDAV/Server-to-Server scheduling behavior.
+"""
+
+__all__ = [
+    "Scheduler",
+    "CalDAVScheduler",
+    "ServerToServerScheduler",
+]
+
+from twisted.internet import reactor
+from twisted.internet.defer import DeferredList
+from twisted.internet.defer import deferredGenerator, maybeDeferred, waitForDeferred
+from twisted.python.failure import Failure
+from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError, Response
+from twisted.web2.http_headers import MimeType
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse, errorForFailure, messageForFailure, statusForFailure
+from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.dav.util import joinURL
+
+from twistedcaldav import caldavxml
+from twistedcaldav import logging
+from twistedcaldav.caldavxml import caldav_namespace, TimeRange
+from twistedcaldav.config import config
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.ical import Component
+from twistedcaldav.itip import iTipProcessor
+from twistedcaldav.method import report_common
+from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.resource import isCalendarCollectionResource
+from twistedcaldav.servertoserver import ServerToServer
+from twistedcaldav.servertoserver import ServerToServerRequest
+
+import itertools
+import md5
+import re
+import socket
+import time
+
+class Scheduler(object):
+    
+    class CalendarUser(object):
+        def __init__(self, cuaddr):
+            self.cuaddr = cuaddr
+
+    class LocalCalendarUser(CalendarUser):
+        def __init__(self, cuaddr, principal, inbox=None, inboxURL=None):
+            self.cuaddr = cuaddr
+            self.principal = principal
+            self.inbox = inbox
+            self.inboxURL = inboxURL
+        
+        def __str__(self):
+            return "Local calendar user: %s" % (self.cuaddr,)
+
+    class RemoteCalendarUser(CalendarUser):
+        def __init__(self, cuaddr):
+            self.cuaddr = cuaddr
+            self.extractDomain()
+
+        def __str__(self):
+            return "Remote calendar user: %s" % (self.cuaddr,)
+        
+        def extractDomain(self):
+            if self.cuaddr.startswith("mailto:"):
+                splits = self.cuaddr[7:].split("?")
+                self.domain = splits[0].split("@")[1]
+            elif self.cuaddr.startswith("http://") or self.cuaddr.startswith("https://"):
+                splits = self.cuaddr.split(":")[1][2:].split("/")
+                self.domain = splits[0]
+            else:
+                self.domain = ""
+
+    class InvalidCalendarUser(CalendarUser):
+        
+        def __str__(self):
+            return "Invalid calendar user: %s" % (self.cuaddr,)
+
+            
+    def __init__(self, request, resource):
+        self.request = request
+        self.resource = resource
+        self.originator = None
+        self.recipients = None
+        self.calendar = None
+        self.organizer = None
+        self.timerange = None
+        self.excludeuid = None
+    
+    @deferredGenerator
+    def doSchedulingViaPOST(self):
+        """
+        The Scheduling POST operation.
+        """
+    
+        # Do some extra authorization checks
+        self.checkAuthorization()
+
+        # Load various useful bits doing some basic checks on those
+        self.loadOriginator()
+        self.loadRecipients()
+        d = waitForDeferred(self.loadCalendar())
+        yield d
+        d.getResult()
+
+        # Check validity of Originator header.
+        self.checkOriginator()
+    
+        # Get recipient details.
+        d = waitForDeferred(self.checkRecipients())
+        yield d
+        d.getResult()
+    
+        # Check calendar data.
+        self.checkCalendarData()
+    
+        # Check validity of ORGANIZER
+        self.checkOrganizer()
+    
+        # Do security checks (e.g. spoofing)
+        self.securityChecks()
+    
+        # Do scheduling tasks
+        d = waitForDeferred(self.generateSchedulingResponse())
+        yield d
+        yield d.getResult()
+
+    def loadOriginator(self):
+        # Must have Originator header
+        originator = self.request.headers.getRawHeaders("originator")
+        if originator is None or (len(originator) != 1):
+            logging.err("POST request must have Originator header", system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-specified")))
+        else:
+            self.originator = originator[0]
+    
+    def loadRecipients(self):
+        # Get list of Recipient headers
+        rawrecipients = self.request.headers.getRawHeaders("recipient")
+        if rawrecipients is None or (len(rawrecipients) == 0):
+            logging.err("POST request must have at least one Recipient header", system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-specified")))
+    
+        # Recipient header may be comma separated list
+        self.recipients = []
+        for rawrecipient in rawrecipients:
+            for r in rawrecipient.split(","):
+                r = r.strip()
+                if len(r):
+                    self.recipients.append(r)
+        
+    @deferredGenerator
+    def loadCalendar(self):
+        # Must be content-type text/calendar
+        content_type = self.request.headers.getHeader("content-type")
+        if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
+            logging.err("MIME type %s not allowed in calendar collection" % (content_type,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
+    
+        # Parse the calendar object from the HTTP request stream
+        try:
+            d = waitForDeferred(Component.fromIStream(self.request.stream))
+            yield d
+            self.calendar = d.getResult()
+        except:
+            logging.err("Error while handling POST: %s" % (Failure(),), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+    def checkAuthorization(self):
+        raise NotImplementedError
+
+    def checkOriginator(self):
+        raise NotImplementedError
+
+    def checkRecipient(self):
+        raise NotImplementedError
+
+    def checkOrganizer(self):
+        raise NotImplementedError
+
+    def checkOrganizerAsOriginator(self):
+        raise NotImplementedError
+
+    def checkAttendeeAsOriginator(self):
+        raise NotImplementedError
+
+    def checkCalendarData(self):
+        # Must be a valid calendar
+        try:
+            self.calendar.validCalendarForCalDAV()
+        except ValueError:
+            logging.err("POST request calendar component is not valid: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+    
+        # Must have a METHOD
+        if not self.calendar.isValidMethod():
+            logging.err("POST request must have valid METHOD property in calendar component: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+        
+        # Verify iTIP behaviour
+        if not self.calendar.isValidITIP():
+            logging.err("POST request must have a calendar component that satisfies iTIP requirements: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+    def checkForFreeBusy(self):
+        if (self.calendar.propertyValue("METHOD") == "REQUEST") and (self.calendar.mainType() == "VFREEBUSY"):
+            # Extract time range from VFREEBUSY object
+            vfreebusies = [v for v in self.calendar.subcomponents() if v.name() == "VFREEBUSY"]
+            if len(vfreebusies) != 1:
+                logging.err("iTIP data is not valid for a VFREEBUSY request: %s" % (self.calendar,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+            dtstart = vfreebusies[0].getStartDateUTC()
+            dtend = vfreebusies[0].getEndDateUTC()
+            if dtstart is None or dtend is None:
+                logging.err("VFREEBUSY start/end not valid: %s" % (self.calendar,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+            self.timerange = TimeRange(start="20000101T000000Z", end="20070102T000000Z")
+            self.timerange.start = dtstart
+            self.timerange.end = dtend
+    
+            # Look for maksed UID
+            self.excludeuid = self.calendar.getMaskUID()
+    
+            # Do free busy operation
+            return True
+        else:
+            # Do regular invite (fan-out)
+            return False
+    
+    def securityChecks(self):
+        raise NotImplementedError
+
+    @staticmethod
+    def isCalendarUserAddressInMyDomain(cuaddr):
+        """
+        Check whether the supplied calendar user address corresponds to one that ought to be within
+        this server's domain.
+        
+        For now we will try to match email and http domains against ones in our config.
+         
+        @param cuaddr: the calendar user address to check.
+        @type cuaddr: C{str}
+        
+        @return: C{True} if the address is within the server's domain,
+            C{False} otherwise.
+        """
+        
+        if config.ServerToServer["Email Domain"] and cuaddr.startswith("mailto:"):
+            splits = cuaddr[7:].split("?")
+            domain = config.ServerToServer["Email Domain"]
+            if not domain:
+                domain = config.ServerHostName
+            return splits[0].endswith(domain)
+        elif config.ServerToServer["HTTP Domain"] and (cuaddr.startswith("http://") or cuaddr.startswith("https://")):
+            splits = cuaddr.split(":")[0][2:].split("?")
+            domain = config.ServerToServer["HTTP Domain"]
+            if not domain:
+                domain = config.ServerHostName
+            return splits[0].endswith(domain)
+        
+        result = False
+        
+        for pattern in config.ServerToServer["Local Addresses"]:
+            if re.match(pattern, cuaddr) is not None:
+                result = True
+        
+        for pattern in config.ServerToServer["Remote Addresses"]:
+            if re.match(pattern, cuaddr) is not None:
+                result = False
+        
+        return result
+    
+    @deferredGenerator
+    def generateSchedulingResponse(self):
+
+        logging.info("METHOD: %s, Component: %s" % (self.calendar.propertyValue("METHOD"), self.calendar.mainType(),), system="Scheduling")
+
+        # For free-busy do immediate determination of iTIP result rather than fan-out
+        freebusy = self.checkForFreeBusy()
+    
+        # Prepare for multiple responses
+        responses = ScheduleResponseQueue("POST", responsecode.OK)
+    
+        # Extract the ORGANIZER property and UID value from the calendar data for use later
+        organizerProp = self.calendar.getOrganizerProperty()
+        uid = self.calendar.resourceUID()
+    
+        # Loop over each recipient and do appropriate action.
+        remote_recipients = []
+        autoresponses = []
+        for recipient in self.recipients:
+    
+            if isinstance(recipient, Scheduler.InvalidCalendarUser):
+                err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-exists")))
+                responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.7;Invalid Calendar User")
+            
+                # Process next recipient
+                continue
+            elif isinstance(recipient, Scheduler.RemoteCalendarUser):
+                # Pool remote recipients into a seperate list for processing after the local ones.
+                remote_recipients.append(recipient)
+            
+                # Process next recipient
+                continue
+            elif isinstance(recipient, Scheduler.LocalCalendarUser):
+                #
+                # Check access controls
+                #
+                if isinstance(self.organizer, Scheduler.LocalCalendarUser):
+                    try:
+                        d = waitForDeferred(recipient.inbox.checkPrivileges(self.request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef(self.organizer.principal.principalURL()))))
+                        yield d
+                        d.getResult()
+                    except AccessDeniedError:
+                        logging.err("Could not access Inbox for recipient: %s" % (recipient.cuaddr,), system="Scheduling")
+                        err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-permisions")))
+                        responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+                    
+                        # Process next recipient
+                        continue
+                else:
+                    # TODO: need to figure out how best to do server-to-server authorization.
+                    # First thing would be to checkk for DAV:unauthenticated privilege.
+                    # Next would be to allow the calendar user address of the organizer/originator to be used
+                    # as a principal. 
+                    pass
+    
+                # Different behaviour for free-busy vs regular invite
+                if freebusy:
+                    d = waitForDeferred(self.generateLocalFreeBusyResponse(recipient, responses, organizerProp, uid))
+                else:
+                    d = waitForDeferred(self.generateLocalResponse(recipient, responses, autoresponses))
+                yield d
+                d.getResult()
+    
+        # Now process remote recipients
+        if remote_recipients:
+            d = waitForDeferred(self.generateRemoteSchedulingResponses(remote_recipients, responses))
+            yield d
+            d.getResult()
+
+        # Now we have to do auto-respond
+        if len(autoresponses) != 0:
+            # First check that we have a method that we can auto-respond to
+            if not iTipProcessor.canAutoRespond(self.calendar):
+                autoresponses = []
+            
+        # Now do the actual auto response
+        for principal, inbox, child in autoresponses:
+            # Add delayed reactor task to handle iTIP responses
+            itip = iTipProcessor()
+            reactor.callLater(0.0, itip.handleRequest, *(self.request, principal, inbox, self.calendar.duplicate(), child)) #@UndefinedVariable
+    
+        # Return with final response if we are done
+        yield responses.response()
+    
+    @deferredGenerator
+    def generateRemoteSchedulingResponses(self, recipients, responses):
+        """
+        Generate scheduling responses for remote recipients.
+        """
+        
+        # Group recipients by server so that we can do a single request with multiple recipients
+        # to each different server.
+        groups = {}
+        servermgr = ServerToServer()
+        for recipient in recipients:
+            # Map the recipient's domain to a server
+            server = servermgr.mapDomain(recipient.domain)
+            if not server:
+                # Cannot do server-to-server for this recipient.
+                err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-allowed")))
+                responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="5.3;No scheduling support for user")
+            
+                # Process next recipient
+                continue
+            
+            if not server.allow_to:
+                # Cannot do server-to-server outgoing requests for this server.
+                err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-allowed")))
+                responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="5.1;Service unavailable")
+            
+                # Process next recipient
+                continue
+            
+            groups.setdefault(server, []).append(recipient)
+        
+        if len(groups) == 0:
+            yield None
+            return
+
+        # Now we process each server: let's use a DeferredList to aggregate all the Deferred's
+        # we will generate for each request. That way we can have parallel requests in progress
+        # rather than serialize them.
+        deferreds = []
+        for server, recipients in groups.iteritems():
+            requestor = ServerToServerRequest(self, server, recipients, responses)
+            deferreds.append(requestor.doRequest())
+
+        d = waitForDeferred(DeferredList(deferreds))
+        yield d
+        d.getResult()
+
+    @deferredGenerator
+    def generateLocalResponse(self, recipient, responses, autoresponses):
+        # Hash the iCalendar data for use as the last path element of the URI path
+        calendar_str = str(self.calendar)
+        name = md5.new(calendar_str + str(time.time()) + recipient.inbox.fp.path).hexdigest() + ".ics"
+    
+        # Get a resource for the new item
+        childURL = joinURL(recipient.inboxURL, name)
+        child = waitForDeferred(self.request.locateResource(childURL))
+        yield child
+        child = child.getResult()
+
+        # Copy calendar to inbox (doing fan-out)
+        try:
+            d = waitForDeferred(
+                    maybeDeferred(
+                        storeCalendarObjectResource,
+                        request=self.request,
+                        sourcecal = False,
+                        destination = child,
+                        destination_uri = childURL,
+                        calendardata = calendar_str,
+                        destinationparent = recipient.inbox,
+                        destinationcal = True,
+                        isiTIP = True
+                    )
+                 )
+            yield d
+            d.getResult()
+            responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success")
+
+            # Store CALDAV:originator property
+            child.writeDeadProperty(caldavxml.Originator(davxml.HRef(self.originator.cuaddr)))
+        
+            # Store CALDAV:recipient property
+            child.writeDeadProperty(caldavxml.Recipient(davxml.HRef(recipient.cuaddr)))
+        
+            # Look for auto-schedule option
+            if recipient.principal.autoSchedule():
+                autoresponses.append((recipient.principal, recipient.inbox, child))
+                
+            yield True
+        except:
+            logging.err("Could not store data in Inbox : %s" % (recipient.inbox,), system="Scheduling")
+            err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
+            responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+            yield False
+    
+    @deferredGenerator
+    def generateLocalFreeBusyResponse(self, recipient, responses, organizerProp, uid):
+
+        # Extract the ATTENDEE property matching current recipient from the calendar data
+        cuas = recipient.principal.calendarUserAddresses()
+        attendeeProp = self.calendar.getAttendeeProperty(cuas)
+
+        remote = isinstance(self.organizer, Scheduler.RemoteCalendarUser)
+
+        try:
+            d = waitForDeferred(self.generateAttendeeFreeBusyResponse(
+                recipient,
+                organizerProp,
+                uid,
+                attendeeProp,
+                remote,
+            ))
+            yield d
+            fbresult = d.getResult()
+
+            responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
+            
+            yield True
+        except:
+            logging.err("Could not determine free busy information: %s" % (recipient.cuaddr,), system="Scheduling")
+            err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
+            responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+            
+            yield False
+    
+    @deferredGenerator
+    def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, uid, attendeeProp, remote):
+
+        # Find the current recipients calendar-free-busy-set
+        fbset = waitForDeferred(recipient.principal.calendarFreeBusyURIs(self.request))
+        yield fbset
+        fbset = fbset.getResult()
+
+        # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+        fbinfo = ([], [], [])
+    
+        # Process the availability property from the Inbox.
+        has_prop = waitForDeferred(recipient.inbox.hasProperty((calendarserver_namespace, "calendar-availability"), self.request))
+        yield has_prop
+        has_prop = has_prop.getResult()
+        if has_prop:
+            availability = waitForDeferred(recipient.inbox.readProperty((calendarserver_namespace, "calendar-availability"), self.request))
+            yield availability
+            availability = availability.getResult()
+            availability = availability.calendar()
+            report_common.processAvailabilityFreeBusy(availability, fbinfo, self.timerange)
+
+        # Check to see if the recipient is the same calendar user as the organizer.
+        # Needed for masked UID stuff.
+        if isinstance(self.organizer, Scheduler.LocalCalendarUser):
+            same_calendar_user = self.organizer.principal.principalURL() == recipient.principal.principalURL()
+        else:
+            same_calendar_user = False
+
+        # Now process free-busy set calendars
+        matchtotal = 0
+        for calURL in fbset:
+            cal = waitForDeferred(self.request.locateResource(calURL))
+            yield cal
+            cal = cal.getResult()
+            if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+                # We will ignore missing calendars. If the recipient has failed to
+                # properly manage the free busy set that should not prevent us from working.
+                continue
+         
+            matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
+                self.request,
+                cal,
+                fbinfo,
+                self.timerange,
+                matchtotal,
+                excludeuid=self.excludeuid,
+                organizer=self.organizer.cuaddr,
+                same_calendar_user=same_calendar_user,
+                servertoserver=remote))
+            yield matchtotal
+            matchtotal = matchtotal.getResult()
+    
+        # Build VFREEBUSY iTIP reply for this recipient
+        fbresult = report_common.buildFreeBusyResult(fbinfo, self.timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid, method="REPLY")
+
+        yield fbresult
+    
+    def generateRemoteResponse(self):
+        raise NotImplementedError
+    
+    def generateRemoteFreeBusyResponse(self):
+        raise NotImplementedError
+        
+class CalDAVScheduler(Scheduler):
+
+    def checkAuthorization(self):
+        # Must have an authenticated user
+        if self.resource.currentPrincipal(self.request) == davxml.Principal(davxml.Unauthenticated()):
+            logging.err("Unauthenticated originators not allowed: %s" % (self.originator,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+    def checkOriginator(self):
+        """
+        Check the validity of the Originator header. Extract the corresponding principal.
+        """
+    
+        # Verify that Originator is a valid calendar user
+        originator_principal = self.resource.principalForCalendarUserAddress(self.originator)
+        if originator_principal is None:
+            # Local requests MUST have a principal.
+            logging.err("Could not find principal for originator: %s" % (self.originator,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        else:
+            # Must have a valid Inbox.
+            inboxURL = originator_principal.scheduleInboxURL()
+            if inboxURL is None:
+                logging.err("Could not find inbox for originator: %s" % (self.originator,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        
+            # Verify that Originator matches the authenticated user.
+            authn_principal = self.resource.currentPrincipal(self.request)
+            if davxml.Principal(davxml.HRef(originator_principal.principalURL())) != authn_principal:
+                logging.err("Originator: %s does not match authorized user: %s" % (self.originator, authn_principal.children[0],), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+            self.originator = Scheduler.LocalCalendarUser(self.originator, originator_principal)
+
+    @deferredGenerator
+    def checkRecipients(self):
+        """
+        Check the validity of the Recipient header values. Map these into local or
+        remote CalendarUsers.
+        """
+        
+        results = []
+        for recipient in self.recipients:
+            # Get the principal resource for this recipient
+            principal = self.resource.principalForCalendarUserAddress(recipient)
+            
+            # If no principal we may have a remote recipient but we should check whether
+            # the address is one that ought to be on our server and treat that as a missing
+            # user. Also if server-to-server is not enabled then remote addresses are not allowed.
+            if principal is None:
+                if self.isCalendarUserAddressInMyDomain(recipient):
+                    logging.err("No principal for calendar user address: %s" % (recipient,), system="Scheduling")
+                    results.append(Scheduler.InvalidCalendarUser(recipient))
+                elif not config.ServerToServer["Enabled"]:
+                    logging.err("Unknown calendar user address: %s" % (recipient,), system="Scheduling")
+                    results.append(Scheduler.InvalidCalendarUser(recipient))
+                else:
+                    results.append(Scheduler.RemoteCalendarUser(recipient))
+            else:
+                # Map recipient to their inbox
+                inbox = None
+                inboxURL = principal.scheduleInboxURL()
+                if inboxURL:
+                    inbox = waitForDeferred(self.request.locateResource(inboxURL))
+                    yield inbox
+                    inbox = inbox.getResult()
+
+                if inbox:
+                    results.append(Scheduler.LocalCalendarUser(recipient, principal, inbox, inboxURL))
+                else:
+                    logging.err("No schedule inbox for principal: %s" % (principal,), system="Scheduling")
+                    results.append(Scheduler.InvalidCalendarUser(recipient))
+        
+        self.recipients = results
+
+    def checkOrganizer(self):
+        """
+        Check the validity of the ORGANIZER value. ORGANIZER must be local.
+        """
+        
+        # Verify that the ORGANIZER's cu address maps to a valid user
+        organizer = self.calendar.getOrganizer()
+        if organizer:
+            orgprincipal = self.resource.principalForCalendarUserAddress(organizer)
+            if orgprincipal:
+                outboxURL = orgprincipal.scheduleOutboxURL()
+                if outboxURL:
+                    self.organizer = Scheduler.LocalCalendarUser(organizer, orgprincipal)
+                else:
+                    logging.err("No outbox for ORGANIZER in calendar data: %s" % (self.calendar,), system="Scheduling")
+                    raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+            elif self.isCalendarUserAddressInMyDomain(organizer):
+                logging.err("No principal for ORGANIZER in calendar data: %s" % (self.calendar,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+            else:
+                self.organizer = Scheduler.RemoteCalendarUser(organizer) 
+        else:
+            logging.err("ORGANIZER missing in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+
+    def checkOrganizerAsOriginator(self):
+        # Make sure that the ORGANIZER's Outbox is the request URI
+        if self.organizer.principal.scheduleOutboxURL() != self.request.uri:
+            logging.err("Wrong outbox for ORGANIZER in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+
+    def checkAttendeeAsOriginator(self):
+        """
+        Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
+        Only local attendees are allowed for message originating from this server.
+        """
+        
+        # Verify that there is a single ATTENDEE property
+        attendees = self.calendar.getAttendees()
+    
+        # Must have only one
+        if len(attendees) != 1:
+            logging.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+        attendee = attendees[0]
+    
+        # Attendee's Outbox MUST be the request URI
+        aprincipal = self.resource.principalForCalendarUserAddress(attendee)
+        if aprincipal:
+            aoutboxURL = aprincipal.scheduleOutboxURL()
+            if aoutboxURL is None or aoutboxURL != self.request.uri:
+                logging.err("ATTENDEE in calendar data does not match owner of Outbox: %s" % (self.calendar,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+        else:
+            logging.err("Unkown ATTENDEE in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+    
+    def securityChecks(self):
+        """
+        Check that the orginator has the appropriate rights to send this type of iTIP message.
+        """
+    
+        # Prevent spoofing of ORGANIZER with specific METHODs when local
+        if self.calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER"):
+            self.checkOrganizerAsOriginator()
+    
+        # Prevent spoofing when doing reply-like METHODs
+        elif self.calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
+            self.checkAttendeeAsOriginator()
+            
+        else:
+            logging.err("Unknown iTIP METHOD for security checks: %s" % (self.calendar.propertyValue("METHOD"),), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+class ServerToServerScheduler(Scheduler):
+
+    def checkAuthorization(self):
+        # Must have an unauthenticated user
+        if self.resource.currentPrincipal(self.request) != davxml.Principal(davxml.Unauthenticated()):
+            logging.err("Authenticated originators not allowed: %s" % (self.originator,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+    def checkOriginator(self):
+        """
+        Check the validity of the Originator header.
+        """
+    
+        # For remote requests we do not allow the originator to be a local user or one within our domain.
+        originator_principal = self.resource.principalForCalendarUserAddress(self.originator)
+        if originator_principal or self.isCalendarUserAddressInMyDomain(self.originator):
+            logging.err("Cannot use originator that is on this server: %s" % (self.originator,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        else:
+            self.originator = Scheduler.RemoteCalendarUser(self.originator)
+            
+        # We will only accept originator in known domains.
+        servermgr = ServerToServer()
+        server = servermgr.mapDomain(self.originator.domain)
+        if not server or not server.allow_from:
+            logging.err("Originator not on recognized server: %s" % (self.originator,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        else:
+            # Get the request IP and map to hostname.
+            clientip = self.request.remoteAddr.host
+            
+            # First compare as dotted IP
+            if clientip != server.host:
+                # Now do hostname lookup
+                host, aliases, _ignore_ips = socket.gethostbyaddr(clientip)
+                for host in itertools.chain((host,), aliases):
+                    if host == server.host:
+                        break
+                else:
+                    logging.err("Originator not on allowed server: %s" % (self.originator,), system="Scheduling")
+                    raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+    @deferredGenerator
+    def checkRecipients(self):
+        """
+        Check the validity of the Recipient header values. These must all be local as there
+        is no concept of server-to-server relaying.
+        """
+        
+        results = []
+        for recipient in self.recipients:
+            # Get the principal resource for this recipient
+            principal = self.resource.principalForCalendarUserAddress(recipient)
+            
+            # If no principal we may have a remote recipient but we should check whether
+            # the address is one that ought to be on our server and treat that as a missing
+            # user. Also if server-to-server is not enabled then remote addresses are not allowed.
+            if principal is None:
+                if self.isCalendarUserAddressInMyDomain(recipient):
+                    logging.err("No principal for calendar user address: %s" % (recipient,), system="Scheduling")
+                    results.append(Scheduler.InvalidCalendarUser(recipient))
+                else:
+                    logging.err("Unknown calendar user address: %s" % (recipient,), system="Scheduling")
+                    results.append(Scheduler.InvalidCalendarUser(recipient))
+            else:
+                # Map recipient to their inbox
+                inbox = None
+                inboxURL = principal.scheduleInboxURL()
+                if inboxURL:
+                    inbox = waitForDeferred(self.request.locateResource(inboxURL))
+                    yield inbox
+                    inbox = inbox.getResult()
+
+                if inbox:
+                    results.append(Scheduler.LocalCalendarUser(recipient, principal, inbox, inboxURL))
+                else:
+                    logging.err("No schedule inbox for principal: %s" % (principal,), system="Scheduling")
+                    results.append(Scheduler.InvalidCalendarUser(recipient))
+        
+        self.recipients = results
+
+    def checkOrganizer(self):
+        """
+        Delay ORGANIZER check until we know what their role is.
+        """
+        pass
+
+    def checkOrganizerAsOriginator(self):
+        """
+        Check the validity of the ORGANIZER value. ORGANIZER must not be local.
+        """
+        
+        # Verify that the ORGANIZER's cu address does not map to a valid user
+        organizer = self.calendar.getOrganizer()
+        if organizer:
+            orgprincipal = self.resource.principalForCalendarUserAddress(organizer)
+            if orgprincipal:
+                logging.err("Invalid ORGANIZER in calendar data: %s" % (self.calendar,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+            elif self.isCalendarUserAddressInMyDomain(organizer):
+                logging.err("Unsupported ORGANIZER in calendar data: %s" % (self.calendar,), system="Scheduling")
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+            else:
+                self.organizer = Scheduler.RemoteCalendarUser(organizer)
+        else:
+            logging.err("ORGANIZER missing in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+
+    def checkAttendeeAsOriginator(self):
+        """
+        Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
+        Only local attendees are allowed for message originating from this server.
+        """
+        
+        # Verify that there is a single ATTENDEE property
+        attendees = self.calendar.getAttendees()
+    
+        # Must have only one
+        if len(attendees) != 1:
+            logging.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+        attendee = attendees[0]
+    
+        # Attendee cannot be local.
+        aprincipal = self.resource.principalForCalendarUserAddress(attendee)
+        if aprincipal:
+            logging.err("Invalid ATTENDEE in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+        elif self.isCalendarUserAddressInMyDomain(attendee):
+            logging.err("Unkown ATTENDEE in calendar data: %s" % (self.calendar,), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+    
+        # TODO: in this case we should check that the ORGANIZER is the sole recipient.
+
+    def securityChecks(self):
+        """
+        Check that the orginator has the appropriate rights to send this type of iTIP message.
+        """
+
+        # Prevent spoofing of ORGANIZER with specific METHODs when local
+        if self.calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER"):
+            self.checkOrganizerAsOriginator()
+    
+        # Prevent spoofing when doing reply-like METHODs
+        elif self.calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
+            self.checkAttendeeAsOriginator()
+            
+        else:
+            logging.err("Unknown iTIP METHOD for security checks: %s" % (self.calendar.propertyValue("METHOD"),), system="Scheduling")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+class ScheduleResponseResponse (Response):
+    """
+    ScheduleResponse L{Response} object.
+    Renders itself as a CalDAV:schedule-response XML document.
+    """
+    def __init__(self, xml_responses, location=None):
+        """
+        @param xml_responses: an interable of davxml.Response objects.
+        @param location:      the value of the location header to return in the response,
+            or None.
+        """
+
+        Response.__init__(self, code=responsecode.OK,
+                          stream=caldavxml.ScheduleResponse(*xml_responses).toxml())
+
+        self.headers.setHeader("content-type", MimeType("text", "xml"))
+    
+        if location is not None:
+            self.headers.setHeader("location", location)
+
+class ScheduleResponseQueue (object):
+    """
+    Stores a list of (typically error) responses for use in a
+    L{ScheduleResponse}.
+    """
+    def __init__(self, method, success_response):
+        """
+        @param method: the name of the method generating the queue.
+        @param success_response: the response to return in lieu of a
+            L{ScheduleResponse} if no responses are added to this queue.
+        """
+        self.responses         = []
+        self.method            = method
+        self.success_response  = success_response
+        self.location          = None
+
+    def setLocation(self, location):
+        """
+        @param location:      the value of the location header to return in the response,
+            or None.
+        """
+        self.location = location
+
+    def add(self, recipient, what, reqstatus=None, calendar=None):
+        """
+        Add a response.
+        @param recipient: the recipient for this response.
+        @param what: a status code or a L{Failure} for the given recipient.
+        @param status: the iTIP request-status for the given recipient.
+        @param calendar: the calendar data for the given recipient response.
+        """
+        if type(what) is int:
+            code    = what
+            error   = None
+            message = responsecode.RESPONSES[code]
+        elif isinstance(what, Failure):
+            code    = statusForFailure(what)
+            error   = errorForFailure(what)
+            message = messageForFailure(what)
+        else:
+            raise AssertionError("Unknown data type: %r" % (what,))
+
+        if code > 400: # Error codes only
+            logging.err("Error during %s for %s: %s" % (self.method, recipient, message), system="Scheduling")
+
+        children = []
+        children.append(caldavxml.Recipient(davxml.HRef.fromString(recipient)))
+        children.append(caldavxml.RequestStatus(reqstatus))
+        if calendar is not None:
+            children.append(caldavxml.CalendarData.fromCalendar(calendar))
+        if error is not None:
+            children.append(error)
+        if message is not None:
+            children.append(davxml.ResponseDescription(message))
+        self.responses.append(caldavxml.Response(*children))
+
+    def clone(self, clone):
+        """
+        Add a response cloned from an existing caldavxml.Response element.
+        @param clone: the response to clone.
+        """
+        if not isinstance(clone, caldavxml.Response):
+            raise AssertionError("Incorrect element type: %r" % (clone,))
+
+        recipient = clone.childOfType(caldavxml.Recipient)
+        request_status = clone.childOfType(caldavxml.RequestStatus)
+        calendar_data = clone.childOfType(caldavxml.CalendarData)
+        error = clone.childOfType(davxml.Error)
+        desc = clone.childOfType(davxml.ResponseDescription)
+
+        children = []
+        children.append(recipient)
+        children.append(request_status)
+        if calendar_data is not None:
+            children.append(calendar_data)
+        if error is not None:
+            children.append(error)
+        if desc is not None:
+            children.append(desc)
+        self.responses.append(caldavxml.Response(*children))
+
+    def response(self):
+        """
+        Generate a L{ScheduleResponseResponse} with the responses contained in the
+        queue or, if no such responses, return the C{success_response} provided
+        to L{__init__}.
+        @return: the response.
+        """
+        if self.responses:
+            return ScheduleResponseResponse(self.responses, self.location)
+        else:
+            return self.success_response

Copied: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserver.py (from rev 1966, CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/servertoserver.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserver.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserver.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -0,0 +1,178 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+from twisted.web2.dav.util import allDataFromStream
+from twisted.web2.stream import MemoryStream
+import logging
+
+"""
+Server to server utility functions and client requests.
+"""
+
+__all__ = [
+    "ServerToServer",
+    "ServerToServerRequest",
+]
+
+
+from twisted.internet.defer import deferredGenerator
+from twisted.internet.defer import waitForDeferred
+from twisted.internet.protocol import ClientCreator
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.python.filepath import FilePath
+from twisted.web2 import responsecode
+from twisted.web2.client.http import ClientRequest
+from twisted.web2.client.http import HTTPClientProtocol
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.dav.util import davXMLFromStream
+from twisted.web2.http import HTTPError
+from twisted.web2.http_headers import MimeType
+from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.config import config
+from twistedcaldav.servertoserverparser import ServerToServerParser
+from twisted.web2.http_headers import Headers
+from twistedcaldav import caldavxml
+
+class ServerToServer(object):
+    
+    _fileInfo = None
+    _xmlFile = None
+    _servers = None
+    _domainMap = None
+    
+    def __init__(self):
+        
+        self._loadConfig()
+
+    def _loadConfig(self):
+        if ServerToServer._servers is None:
+            ServerToServer._xmlFile = FilePath(config.ServerToServer["Servers"])
+        ServerToServer._xmlFile.restat()
+        fileInfo = (ServerToServer._xmlFile.getmtime(), ServerToServer._xmlFile.getsize())
+        if fileInfo != ServerToServer._fileInfo:
+            parser = ServerToServerParser(ServerToServer._xmlFile)
+            ServerToServer._servers = parser.servers
+            self._mapDomains()
+            ServerToServer._fileInfo = fileInfo
+        
+    def _mapDomains(self):
+        ServerToServer._domainMap = {}
+        for server in ServerToServer._servers:
+            for domain in server.domains:
+                ServerToServer._domainMap[domain] = server
+    
+    def mapDomain(self, domain):
+        """
+        Map a calendar user address domain to a suitable server that can
+        handle server-to-server requests for that user.
+        """
+        return ServerToServer._domainMap.get(domain)
+
+class ServerToServerRequest(object):
+    
+    def __init__(self, scheduler, server, recipients, responses):
+
+        self.scheduler = scheduler
+        self.server = server
+        self.recipients = recipients
+        self.responses = responses
+        
+        self._generateHeaders()
+        self._prepareData()
+        
+    @deferredGenerator
+    def doRequest(self):
+        
+        # Generate an HTTP client request
+        try:
+            from twisted.internet import reactor
+            if self.server.ssl:
+                from tap import ChainingOpenSSLContextFactory
+                context = ChainingOpenSSLContextFactory(config.SSLPrivateKey, config.SSLCertificate, certificateChainFile=config.SSLAuthorityChain)
+                d = waitForDeferred(ClientCreator(reactor, HTTPClientProtocol).connectSSL(self.server.host, self.server.port, context))
+            else:
+                d = waitForDeferred(ClientCreator(reactor, HTTPClientProtocol).connectTCP(self.server.host, self.server.port))
+            yield d
+            proto = d.getResult()
+            
+            log.msg("Sending server-to-server POST request: %s" % (self.server.path,))
+            if logging.canLog("debug"):
+                logging.debug(self.headers, system="Server-to-server Send")
+                logging.debug(self.data, system="Server-to-server Send")
+            d = waitForDeferred(proto.submitRequest(ClientRequest("POST", self.server.path, self.headers, self.data)))
+            yield d
+            response = d.getResult()
+    
+            log.msg("Received server-to-server POST response: %s" % (response.code,))
+            if logging.canLog("debug"):
+                logging.debug(response.headers, system="Server-to-server Send")
+                d = waitForDeferred(allDataFromStream(response.stream))
+                yield d
+                data = d.getResult()
+                logging.debug(data, system="Server-to-server Send")
+                response.stream = MemoryStream(data)
+            d = waitForDeferred(davXMLFromStream(response.stream))
+            yield d
+            xml = d.getResult()
+    
+            self._parseResponse(xml)
+        except Exception, e:
+            # Generated failed responses for each recipient
+            log.err("Could not do server-to-server request : %s %s" % (self, e))
+            for recipient in self.recipients:
+                err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-failed")))
+                self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="5.1;Service unavailable")
+
+    def _generateHeaders(self):
+        self.headers = Headers()
+        self.headers.setHeader('Host', self.server.host + ":%s" % (self.server.port,))
+        self.headers.addRawHeader('Originator', self.scheduler.originator.cuaddr)
+        self._doAuthentication()
+        for recipient in self.recipients:
+            self.headers.addRawHeader('Recipient', recipient.cuaddr)
+        self.headers.setHeader('Content-Type', MimeType("text", "calendar", params={"charset":"utf-8"}))
+
+    def _doAuthentication(self):
+        if self.server.authentication and self.server.authentication[0] == "basic":
+            self.headers.setHeader(
+                'Authorization',
+                ('Basic', ("%s:%s" % (self.server.authentication[1], self.server.authentication[2],)).encode('base64')[:-1])
+            )
+
+    def _prepareData(self):
+        self.data = str(self.scheduler.calendar)
+
+    def _parseResponse(self, xml):
+
+        # Check for correct root element
+        schedule_response = xml.root_element
+        if not isinstance(schedule_response, caldavxml.ScheduleResponse) or not schedule_response.children:
+            raise HTTPError(responsecode.BAD_REQUEST)
+        
+        # Parse each response - do this twice: once looking for errors that will
+        # result in all recipients shown as failures; the second loop adds all the
+        # valid responses to the actual result.
+        for response in schedule_response.children:
+            if not isinstance(response, caldavxml.Response) or not response.children:
+                raise HTTPError(responsecode.BAD_REQUEST)
+            recipient = response.childOfType(caldavxml.Recipient)
+            request_status = response.childOfType(caldavxml.RequestStatus)
+            if not recipient or not request_status:
+                raise HTTPError(responsecode.BAD_REQUEST)
+        for response in schedule_response.children:
+            self.responses.clone(response)

Copied: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserverparser.py (from rev 1966, CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/servertoserverparser.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserverparser.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/servertoserverparser.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -0,0 +1,158 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+
+"""
+XML based server-to-server configuration file handling.
+"""
+
+__all__ = [
+    "ServerToServerParser",
+    "ServerToServerRecord",
+]
+
+import xml.dom.minidom
+
+ELEMENT_SERVERS                 = "servers"
+ELEMENT_SERVER                  = "server"
+ELEMENT_URI                     = "uri"
+ELEMENT_ALLOW_REQUESTS_FROM     = "allow-requests-from"
+ELEMENT_ALLOW_REQUESTS_TO       = "allow-requests-to"
+ELEMENT_DOMAINS                 = "domains"
+ELEMENT_DOMAIN                  = "domain"
+ELEMENT_AUTHENTICATION          = "authentication"
+ATTRIBUTE_TYPE                  = "type"
+ATTRIBUTE_BASICAUTH             = "basic"
+ELEMENT_USER                    = "user"
+ELEMENT_PASSWORD                = "password"
+
+class ServerToServerParser(object):
+    """
+    Server-to-server configuration file parser.
+    """
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+    def __init__(self, xmlFile):
+
+        self.servers = []
+        
+        # Read in XML
+        fd = open(xmlFile.path, "r")
+        doc = xml.dom.minidom.parse(fd)
+        fd.close()
+
+        # Verify that top-level element is correct
+        servers_node = doc._get_documentElement()
+        if servers_node._get_localName() != ELEMENT_SERVERS:
+            self.log("Ignoring file %r because it is not a server-to-server config file" % (self.xmlFile,))
+            return
+        self._parseXML(servers_node)
+        
+    def _parseXML(self, node):
+        """
+        Parse the XML root node from the server-to-server configuration document.
+        @param node: the L{Node} to parse.
+        """
+
+        for child in node._get_childNodes():
+            child_name = child._get_localName()
+            if child_name is None:
+                continue
+            elif child_name == ELEMENT_SERVER:
+                self.servers.append(ServerToServerRecord())
+                self.servers[-1].parseXML(child)
+                
+class ServerToServerRecord (object):
+    """
+    Contains server-to-server details.
+    """
+    def __init__(self):
+        """
+        @param recordType: record type for directory entry.
+        """
+        self.uri = ""
+        self.allow_from = False
+        self.allow_to = True
+        self.domains = []
+        self.authentication = None
+
+    def parseXML(self, node):
+        for child in node._get_childNodes():
+            child_name = child._get_localName()
+            if child_name is None:
+                continue
+            elif child_name == ELEMENT_URI:
+                if child.firstChild is not None:
+                    self.uri = child.firstChild.data.encode("utf-8")
+            elif child_name == ELEMENT_ALLOW_REQUESTS_FROM:
+                self.allow_from = True
+            elif child_name == ELEMENT_ALLOW_REQUESTS_TO:
+                self.allow_to = True
+            elif child_name == ELEMENT_DOMAINS:
+                self._parseDomains(child)
+            elif child_name == ELEMENT_AUTHENTICATION:
+                self._parseAuthentication(child)
+            else:
+                raise RuntimeError("[%s] Unknown attribute: %s" % (self.__class__, child_name,))
+        
+        self._parseDetails()
+
+    def _parseDomains(self, node):
+        for child in node._get_childNodes():
+            if child._get_localName() == ELEMENT_DOMAIN:
+                if child.firstChild is not None:
+                    self.domains.append(child.firstChild.data.encode("utf-8"))
+
+    def _parseAuthentication(self, node):
+        if node.hasAttribute(ATTRIBUTE_TYPE):
+            type = node.getAttribute(ATTRIBUTE_TYPE).encode("utf-8")
+            if type != ATTRIBUTE_BASICAUTH:
+                return
+        else:
+            return
+
+        for child in node._get_childNodes():
+            if child._get_localName() == ELEMENT_USER:
+                if child.firstChild is not None:
+                    user = child.firstChild.data.encode("utf-8")
+            elif child._get_localName() == ELEMENT_PASSWORD:
+                if child.firstChild is not None:
+                    password = child.firstChild.data.encode("utf-8")
+        
+        self.authentication = ("basic", user, password,)
+
+    def _parseDetails(self):
+        # Extract scheme, host, port and path
+        if self.uri.startswith("http://"):
+            self.ssl = False
+            rest = self.uri[7:]
+        elif self.uri.startswith("https://"):
+            self.ssl = True
+            rest = self.uri[8:]
+        
+        splits = rest.split("/", 1)
+        hostport = splits[0].split(":")
+        self.host = hostport[0]
+        if len(hostport) > 1:
+            self.port = int(hostport[1])
+        else:
+            self.port = {False:80, True:443}[self.ssl]
+        self.path = "/"
+        if len(splits) > 1:
+            self.path += splits[1]

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/static.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/static.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -56,14 +56,14 @@
 from twistedcaldav import customxml
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.extensions import DAVFile
+from twistedcaldav.freebusyurl import FreeBusyURLResource
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import Property as iProperty
 from twistedcaldav.index import Index, IndexSchedule
 from twistedcaldav.notifications import NotificationsCollectionResource, NotificationResource
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
-from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
+from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, ScheduleServerToServerResource
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource, DropBoxChildResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeTypeProvisioningResource
@@ -504,12 +504,17 @@
             NotificationsCollectionFileClass = NotificationsCollectionFile
         else:
             NotificationsCollectionFileClass = None
+        if config.FreeBusyURL["Enabled"]:
+            FreeBusyURLFileClass = FreeBusyURLFile
+        else:
+            FreeBusyURLFileClass = None
             
         cls = {
             "inbox"        : ScheduleInboxFile,
             "outbox"       : ScheduleOutboxFile,
             "dropbox"      : DropBoxHomeFileClass,
             "notifications": NotificationsCollectionFileClass,
+            "freebusy"     : FreeBusyURLFileClass,
         }.get(name, None)
 
         if cls is not None:
@@ -607,6 +612,80 @@
     def __repr__(self):
         return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
 
+class ServerToServerInboxFile (ScheduleServerToServerResource, CalDAVFile):
+    """
+    Server-to-server scheduling inbox resource.
+    """
+    def __init__(self, path, parent):
+        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+        ScheduleServerToServerResource.__init__(self, parent)
+        
+        self.fp.open("w").close()
+        self.fp.restat(False)
+
+    def __repr__(self):
+        return "<%s (server-to-server inbox resource): %s>" % (self.__class__.__name__, self.fp.path)
+
+    def isCollection(self):
+        return False
+
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            return responsecode.NOT_FOUND
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
+class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
+    """
+    Free-busy URL resource.
+    """
+    def __init__(self, path, parent):
+        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+        FreeBusyURLResource.__init__(self, parent)
+
+    def __repr__(self):
+        return "<%s (free-busy URL resource): %s>" % (self.__class__.__name__, self.fp.path)
+
+    def isCollection(self):
+        return False
+
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            return responsecode.NOT_FOUND
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(schedulePrivilegeSet)
+
 class DropBoxHomeFile (AutoProvisioningFileMixIn, DropBoxHomeResource, CalDAVFile):
     def __init__(self, path, parent):
         DropBoxHomeResource.__init__(self)

Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/tap.py	2007-10-16 20:12:45 UTC (rev 1966)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/tap.py	2007-10-16 20:39:22 UTC (rev 1967)
@@ -52,6 +52,7 @@
 
 from twistedcaldav import pdmonster
 from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.static import ServerToServerInboxFile
 
 try:
     from twistedcaldav.authkerb import NegotiateCredentialFactory
@@ -341,9 +342,10 @@
     # default resource classes
     #
 
-    rootResourceClass      = RootResource
-    principalResourceClass = DirectoryPrincipalProvisioningResource
-    calendarResourceClass  = CalendarHomeProvisioningFile
+    rootResourceClass           = RootResource
+    principalResourceClass      = DirectoryPrincipalProvisioningResource
+    calendarResourceClass       = CalendarHomeProvisioningFile
+    servertoserverResourceClass = ServerToServerInboxFile
 
     def makeService_Slave(self, options):
         
@@ -419,6 +421,15 @@
         root.putChild('principals', principalCollection)
         root.putChild('calendars', calendarCollection)
 
+        if config.ServerToServer["Enabled"]:
+            log.msg("Setting up server-to-server resource: %r" % (self.servertoserverResourceClass,))
+    
+            servertoserver = self.servertoserverResourceClass(
+                os.path.join(config.DocumentRoot, 'inbox'),
+                root,
+            )
+            root.putChild('inbox', servertoserver)
+
         # Configure default ACLs on the root resource
 
         logging.info("Setting up default ACEs on root resource", system="startup")

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20071016/74c4a2bd/attachment-0001.html


More information about the calendarserver-changes mailing list