[CalendarServer-changes] [4136] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri May 1 14:12:23 PDT 2009


Revision: 4136
          http://trac.macosforge.org/projects/calendarserver/changeset/4136
Author:   wsanchez at apple.com
Date:     2009-05-01 14:12:23 -0700 (Fri, 01 May 2009)
Log Message:
-----------
Back out 4134 for now

Modified Paths:
--------------
    CalendarServer/trunk/lib-patches/Twisted/twisted.python.util.patch
    CalendarServer/trunk/run
    CalendarServer/trunk/twistedcaldav/notify.py
    CalendarServer/trunk/twistedcaldav/test/test_index.py
    CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
    CalendarServer/trunk/twistedcaldav/test/util.py

Added Paths:
-----------
    CalendarServer/trunk/lib-patches/Twisted/twisted.application.app.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.conch.test.test_keys.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.internet._sslverify.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.internet.defer.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.mail.imap4.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.mail.pop3client.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.persisted.sob.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.plugins.__init__.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.python.filepath.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.runner.procmon.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.spread.pb.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_plugin.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_tcp.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web.test.test_webclient.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.__init__.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.extensions.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.parser.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc2518.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.fileop.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.http.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.prop_common.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_acl_principal_prop_set.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_match.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.stream.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.data.quota_100.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_copy.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_pipeline.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_quota.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_static.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_stream.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_xml.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.tworequest_client.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.util.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.util.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.http.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.static.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_http.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.words.protocols.jabber.sasl_mechanisms.patch

Removed Paths:
-------------
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.application.app.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.application.app.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.application.app.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.application.app.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,13 @@
+Index: twisted/application/app.py
+===================================================================
+--- twisted/application/app.py	(revision 19773)
++++ twisted/application/app.py	(working copy)
+@@ -18,7 +18,7 @@
+ def runWithProfiler(reactor, config):
+     """Run reactor under standard profiler."""
+     try:
+-        import profile
++        import cProfile as profile
+     except ImportError, e:
+         s = "Failed to import module profile: %s" % e
+         s += """

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.conch.test.test_keys.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.conch.test.test_keys.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.conch.test.test_keys.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.conch.test.test_keys.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,16 @@
+Index: twisted/conch/test/test_keys.py
+===================================================================
+--- twisted/conch/test/test_keys.py	(revision 19773)
++++ twisted/conch/test/test_keys.py	(working copy)
+@@ -4,10 +4,10 @@
+ 
+ try:
+     import Crypto
++    from twisted.conch.ssh import keys
+ except ImportError:
+     Crypto = None
+ 
+-from twisted.conch.ssh import keys
+ from twisted.trial import unittest
+ 
+ publicRSA_openssh = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHRivcJSkbh/C+BR3utDS555mV comment"

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.internet._sslverify.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.internet._sslverify.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.internet._sslverify.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.internet._sslverify.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,35 @@
+Index: twisted/internet/_sslverify.py
+===================================================================
+--- twisted/internet/_sslverify.py	(revision 19773)
++++ twisted/internet/_sslverify.py	(working copy)
+@@ -1,7 +1,11 @@
+ # -*- test-case-name: twisted.test.test_sslverify -*-
+ # Copyright 2005 Divmod, Inc.  See LICENSE file for details
+ 
+-import itertools, md5
++import itertools
++try:
++    from hashlib import md5
++except ImportError:
++    from md5 import new as md5
+ from OpenSSL import SSL, crypto
+ 
+ from twisted.python import reflect, util
+@@ -666,7 +670,7 @@
+         MD5 hex digest of signature on an empty certificate request with this
+         key.
+         """
+-        return md5.md5(self._emptyReq).hexdigest()
++        return md5(self._emptyReq).hexdigest()
+ 
+ 
+     def inspect(self):
+@@ -942,7 +946,7 @@
+             ctx.set_options(self._OP_ALL)
+ 
+         if self.enableSessions:
+-            sessionName = md5.md5("%s-%d" % (reflect.qual(self.__class__), _sessionCounter())).hexdigest()
++            sessionName = md5("%s-%d" % (reflect.qual(self.__class__), _sessionCounter())).hexdigest()
+             ctx.set_session_id(sessionName)
+ 
+         return ctx

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.internet.defer.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.internet.defer.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.internet.defer.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.internet.defer.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,12 @@
+Index: twisted/internet/defer.py
+===================================================================
+--- twisted/internet/defer.py	(revision 19773)
++++ twisted/internet/defer.py	(working copy)
+@@ -998,6 +998,6 @@
+ __all__ = ["Deferred", "DeferredList", "succeed", "fail", "FAILURE", "SUCCESS",
+            "AlreadyCalledError", "TimeoutError", "gatherResults",
+            "maybeDeferred",
+-           "waitForDeferred", "deferredGenerator", "inlineCallbacks",
++           "waitForDeferred", "deferredGenerator", "returnValue", "inlineCallbacks",
+            "DeferredLock", "DeferredSemaphore", "DeferredQueue",
+           ]

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.mail.imap4.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.mail.imap4.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.mail.imap4.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.mail.imap4.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,65 @@
+Index: twisted/mail/imap4.py
+===================================================================
+--- twisted/mail/imap4.py	(revision 19773)
++++ twisted/mail/imap4.py	(working copy)
+@@ -363,16 +363,11 @@
+         for L in self.lines:
+             names = parseNestedParens(L)
+             N = len(names)
++            # This section is patched as described in http://twistedmatrix.com/trac/ticket/1105
+             if (N >= 1 and names[0] in self._1_RESPONSES or
++                N >= 2 and names[1] in self._2_RESPONSES or
+                 N >= 2 and names[0] == 'OK' and isinstance(names[1], types.ListType) and names[1][0] in self._OK_RESPONSES):
+                 send.append(L)
+-            elif N >= 3 and names[1] in self._2_RESPONSES:
+-                if isinstance(names[2], list) and len(names[2]) >= 1 and names[2][0] == 'FLAGS' and 'FLAGS' not in self.args:
+-                    unuse.append(L)
+-                else:
+-                    send.append(L)
+-            elif N >= 2 and names[1] in self._2_RESPONSES:
+-                send.append(L)
+             else:
+                 unuse.append(L)
+         d, self.defer = self.defer, None
+@@ -2245,10 +2240,12 @@
+                     for f in fetched.get('FLAGS', []):
+                         sum.append(f)
+                     flags.setdefault(mId, []).extend(sum)
++            elif L.find('BYE LOGOUT') != -1:
++                pass
+             else:
+                 log.msg('Unhandled unsolicited response: ' + repr(L))
+-        if flags:
+-            self.flagsChanged(flags)
++        #if flags:
++        #    self.flagsChanged(flags)
+         if recent is not None or exists is not None:
+             self.newMessages(exists, recent)
+ 
+@@ -3336,6 +3333,8 @@
+                             if len(data) < 2:
+                                 raise IllegalServerResponse("Not enough arguments", data)
+                             flags.setdefault(id, {})[data[0]] = data[1]
++                            if data[0] == 'FLAGS':
++                                self.flagsChanged({id: data[1]})
+                             del data[:2]
+                 else:
+                     print '(2)Ignoring ', parts
+@@ -3431,7 +3430,16 @@
+                     except ValueError:
+                         raise IllegalServerResponse, line
+                     else:
+-                        info[id] = parseNestedParens(parts[2])
++                        data = parseNestedParens(parts[2])[0]
++                        # This section is patched as described in http://twistedmatrix.com/trac/ticket/1105
++                        # XXX this will fail if 'FLAGS' is a retrieved part
++                        for i in range(len(data) -1):
++                            if data[i] == 'FLAGS':
++                                self.flagsChanged({id: data[i+1]})
++                                del data[i:i+2]
++                                break
++                        if data:
++                            info.setdefault(id, []).append(data)
+         return info
+ 
+     def _fetch(self, messages, useUID=0, **terms):

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.mail.pop3client.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.mail.pop3client.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.mail.pop3client.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.mail.pop3client.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,28 @@
+Index: twisted/mail/pop3client.py
+===================================================================
+--- twisted/mail/pop3client.py	(revision 19773)
++++ twisted/mail/pop3client.py	(working copy)
+@@ -11,8 +11,13 @@
+ API Stability: Unstable
+ """
+ 
+-import re, md5
++import re
+ 
++try:
++    from hashlib import md5
++except ImportError:
++    from md5 import new as md5
++
+ from twisted.python import log
+ from twisted.internet import defer
+ from twisted.protocols import basic
+@@ -486,7 +491,7 @@
+     def _apop(self, username, password, challenge):
+         # Internal helper.  Computes and sends an APOP response.  Returns
+         # a Deferred that fires when the server responds to the response.
+-        digest = md5.new(challenge + password).hexdigest()
++        digest = md5(challenge + password).hexdigest()
+         return self.apop(username, digest)
+ 
+     def apop(self, username, digest):

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.persisted.sob.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.persisted.sob.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.persisted.sob.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.persisted.sob.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,32 @@
+Index: twisted/persisted/sob.py
+===================================================================
+--- twisted/persisted/sob.py	(revision 19773)
++++ twisted/persisted/sob.py	(working copy)
+@@ -10,8 +10,12 @@
+ Maintainer: U{Moshe Zadka<mailto:moshez at twistedmatrix.com>}
+ """
+ 
+-import os, md5, sys
++import os, sys
+ try:
++    from hashlib import md5
++except ImportError:
++    from md5 import new as md5
++try:
+     import cPickle as pickle
+ except ImportError:
+     import pickle
+@@ -32,11 +36,11 @@
+     leftover = len(data) % cipher.block_size
+     if leftover:
+         data += ' '*(cipher.block_size - leftover)
+-    return cipher.new(md5.new(passphrase).digest()[:16]).encrypt(data)
++    return cipher.new(md5(passphrase).digest()[:16]).encrypt(data)
+ 
+ def _decrypt(passphrase, data):
+     from Crypto.Cipher import AES
+-    return AES.new(md5.new(passphrase).digest()[:16]).decrypt(data)
++    return AES.new(md5(passphrase).digest()[:16]).decrypt(data)
+ 
+ 
+ class IPersistable(Interface):

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.plugins.__init__.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.plugins.__init__.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.plugins.__init__.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.plugins.__init__.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,12 @@
+Index: twisted/plugins/__init__.py
+===================================================================
+--- twisted/plugins/__init__.py	(revision 19773)
++++ twisted/plugins/__init__.py	(working copy)
+@@ -12,6 +12,6 @@
+ """
+ 
+ import os, sys
+-__path__ = [os.path.abspath(os.path.join(x, 'twisted', 'plugins')) for x in sys.path]
++__path__ = [os.path.abspath(os.path.join(x, 'twisted', 'plugins')) for x in sys.path if not x.startswith('/System')]
+ 
+ __all__ = []                    # nothing to see here, move along, move along

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.python.filepath.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.python.filepath.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.python.filepath.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.python.filepath.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,28 @@
+Index: twisted/python/filepath.py
+===================================================================
+--- twisted/python/filepath.py	(revision 19773)
++++ twisted/python/filepath.py	(working copy)
+@@ -9,9 +9,13 @@
+ import os
+ import errno
+ import random
+-import sha
+ import base64
+ 
++try:
++    from hashlib import sha1
++except ImportError:
++    from sha import new as sha1
++
+ from os.path import isabs, exists, normpath, abspath, splitext
+ from os.path import basename, dirname
+ from os.path import join as joinpath
+@@ -109,7 +113,7 @@
+     """
+     Create a pseudorandom, 16-character string for use in secure filenames.
+     """
+-    return armor(sha.new(randomBytes(64)).digest())[:16]
++    return armor(sha1(randomBytes(64)).digest())[:16]
+ 
+ class _PathHelper:
+     """

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.python.util.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.python.util.patch	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.python.util.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -1,36 +1,180 @@
 Index: twisted/python/util.py
 ===================================================================
---- twisted/python/util.py	(revision 26741)
+--- twisted/python/util.py	(revision 19773)
 +++ twisted/python/util.py	(working copy)
-@@ -569,9 +569,30 @@
+@@ -561,83 +561,107 @@
      L2.sort()
      return [e[2] for e in L2]
  
--if pwd is None or grp is None or setgroups is None or getgroups is None:
 +
-+try:
-+    from ctypes import c_int, c_char_p
-+    from ctypes.cdll import LoadLibrary
-+    from ctypes.util import find_library
++# The python implementation of initgroups below, which iterates all groups,
++# doesn't scale, so use the libc version if available:
++
+ try:
+-    import pwd, grp
+-    from os import setgroups, getgroups
+-    
+-    def _setgroups_until_success(l):
+-        while(1):
+-            # NASTY NASTY HACK (but glibc does it so it must be okay):
+-            # In case sysconfig didn't give the right answer, find the limit
+-            # on max groups by just looping, trying to set fewer and fewer
+-            # groups each time until it succeeds.
++    from ctypes import *
++    import ctypes.util
 +    hasCtypes = True
 +except ImportError:
 +    hasCtypes = False
 +
 +if sys.platform == "darwin" and hasCtypes:
 +    import pwd
-+    libc = LoadLibrary(find_library("libc"))
-     def initgroups(uid, primaryGid):
-         """
-+        Call initgroups with ctypes.
-+        """
++
++    libc = cdll.LoadLibrary(ctypes.util.find_library("libc"))
++
++    def initgroups(uid, primaryGid):
 +        c_gid = c_int(primaryGid)
 +        username = pwd.getpwuid(uid)[0]
 +        c_username = c_char_p(username)
 +        return libc.initgroups(c_username, c_gid)
 +
-+elif pwd is None or grp is None or setgroups is None or getgroups is None:
-+    def initgroups(uid, primaryGid):
-+        """
-         Do nothing.
++else:
++    # Original twisted implementation
++    try:
++        import pwd, grp
++        from os import setgroups, getgroups
++        
++        def _setgroups_until_success(l):
++            while(1):
++                # NASTY NASTY HACK (but glibc does it so it must be okay):
++                # In case sysconfig didn't give the right answer, find the limit
++                # on max groups by just looping, trying to set fewer and fewer
++                # groups each time until it succeeds.
++                try:
++                    setgroups(l)
++                except ValueError:
++                    # This exception comes from python itself restricting
++                    # number of groups allowed.
++                    if len(l) > 1:
++                        del l[-1]
++                    else:
++                        raise
++                except OSError, e:
++                    if e.errno == errno.EINVAL and len(l) > 1:
++                        # This comes from the OS saying too many groups
++                        del l[-1]
++                    else:
++                        raise
++                else:
++                    # Success, yay!
++                    return
++                
++        def initgroups(uid, primaryGid):
++            """Initializes the group access list.
++
++            This is done by reading the group database /etc/group and using all
++            groups of which C{uid} is a member.  The additional group
++            C{primaryGid} is also added to the list.
++
++            If the given user is a member of more than C{NGROUPS}, arbitrary
++            groups will be silently discarded to bring the number below that
++            limit.
++            """       
+             try:
+-                setgroups(l)
+-            except ValueError:
+-                # This exception comes from python itself restricting
+-                # number of groups allowed.
+-                if len(l) > 1:
+-                    del l[-1]
+-                else:
+-                    raise
++                # Try to get the maximum number of groups
++                max_groups = os.sysconf("SC_NGROUPS_MAX")
++            except:
++                # No predefined limit
++                max_groups = 0
++            
++            username = pwd.getpwuid(uid)[0]
++            l = []
++            if primaryGid is not None:
++                l.append(primaryGid)
++            for groupname, password, gid, userlist in grp.getgrall():
++                if username in userlist:
++                    l.append(gid)
++                    if len(l) == max_groups:
++                        break # No more groups, ignore any more
++            try:
++                _setgroups_until_success(l)
+             except OSError, e:
+-                if e.errno == errno.EINVAL and len(l) > 1:
+-                    # This comes from the OS saying too many groups
+-                    del l[-1]
++                # We might be able to remove this code now that we
++                # don't try to setgid/setuid even when not asked to.
++                if e.errno == errno.EPERM:
++                    for g in getgroups():
++                        if g not in l:
++                            raise
+                 else:
+                     raise
+-            else:
+-                # Success, yay!
+-                return
+-            
+-    def initgroups(uid, primaryGid):
+-        """Initializes the group access list.
++                                        
  
-         Underlying platform support require to manipulate groups is missing.
+-        This is done by reading the group database /etc/group and using all
+-        groups of which C{uid} is a member.  The additional group
+-        C{primaryGid} is also added to the list.
++    except:
++        def initgroups(uid, primaryGid):
++            """Do nothing.
+ 
+-        If the given user is a member of more than C{NGROUPS}, arbitrary
+-        groups will be silently discarded to bring the number below that
+-        limit.
+-        """       
+-        try:
+-            # Try to get the maximum number of groups
+-            max_groups = os.sysconf("SC_NGROUPS_MAX")
+-        except:
+-            # No predefined limit
+-            max_groups = 0
+-        
+-        username = pwd.getpwuid(uid)[0]
+-        l = []
+-        if primaryGid is not None:
+-            l.append(primaryGid)
+-        for groupname, password, gid, userlist in grp.getgrall():
+-            if username in userlist:
+-                l.append(gid)
+-                if len(l) == max_groups:
+-                    break # No more groups, ignore any more
+-        try:
+-            _setgroups_until_success(l)
+-        except OSError, e:
+-            # We might be able to remove this code now that we
+-            # don't try to setgid/setuid even when not asked to.
+-            if e.errno == errno.EPERM:
+-                for g in getgroups():
+-                    if g not in l:
+-                        raise
+-            else:
+-                raise
+-                                    
++            Underlying platform support require to manipulate groups is missing.
++            """
+ 
+-except:
+-    def initgroups(uid, primaryGid):
+-        """Do nothing.
+ 
+-        Underlying platform support require to manipulate groups is missing.
+-        """
+-
+-
+ def switchUID(uid, gid, euid=False):
+     if euid:
+         setuid = os.seteuid

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.runner.procmon.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.runner.procmon.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.runner.procmon.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.runner.procmon.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,39 @@
+Index: twisted/runner/procmon.py
+===================================================================
+--- twisted/runner/procmon.py	(revision 19773)
++++ twisted/runner/procmon.py	(working copy)
+@@ -59,6 +59,9 @@
+ 
+     disconnecting = 0
+ 
++    def loseConnection(self):
++        pass
++
+ transport = DummyTransport() 
+ 
+ class LineLogger(basic.LineReceiver):
+@@ -130,10 +133,10 @@
+         self.consistency = reactor.callLater(self.consistencyDelay,
+                                              self._checkConsistency)
+ 
+-    def addProcess(self, name, args, uid=None, gid=None):
++    def addProcess(self, name, args, uid=None, gid=None, env={}):
+         if self.processes.has_key(name):
+             raise KeyError("remove %s first" % name)
+-        self.processes[name] = args, uid, gid
++        self.processes[name] = args, uid, gid, env
+         if self.active:
+             self.startProcess(name)
+ 
+@@ -175,9 +178,9 @@
+         p = self.protocols[name] = LoggingProtocol()
+         p.service = self
+         p.name = name
+-        args, uid, gid = self.processes[name]
++        args, uid, gid, env = self.processes[name]
+         self.timeStarted[name] = time.time()
+-        reactor.spawnProcess(p, args[0], args, uid=uid, gid=gid)
++        reactor.spawnProcess(p, args[0], args, uid=uid, gid=gid, env=env)
+ 
+     def _forceStopProcess(self, proc):
+         try:

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.spread.pb.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.spread.pb.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.spread.pb.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.spread.pb.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,53 @@
+Index: twisted/spread/pb.py
+===================================================================
+--- twisted/spread/pb.py	(revision 19773)
++++ twisted/spread/pb.py	(working copy)
+@@ -64,7 +64,11 @@
+ except ImportError:
+     import StringIO
+ 
+-import md5
++try:
++    from hashlib import md5
++except ImportError:
++    from md5 import new as md5
++
+ import random
+ import new
+ import types
+@@ -1003,10 +1007,10 @@
+ 
+     This is useful for challenge/response authentication.
+     """
+-    m = md5.new()
++    m = md5()
+     m.update(password)
+     hashedPassword = m.digest()
+-    m = md5.new()
++    m = md5()
+     m.update(hashedPassword)
+     m.update(challenge)
+     doubleHashedPassword = m.digest()
+@@ -1017,7 +1021,7 @@
+     crap = ''
+     for x in range(random.randrange(15,25)):
+         crap = crap + chr(random.randint(65,90))
+-    crap = md5.new(crap).digest()
++    crap = md5(crap).digest()
+     return crap
+ 
+ 
+@@ -1226,11 +1230,11 @@
+ 
+     # IUsernameHashedPassword:
+     def checkPassword(self, password):
+-        return self.checkMD5Password(md5.md5(password).digest())
++        return self.checkMD5Password(md5(password).digest())
+ 
+     # IUsernameMD5Password
+     def checkMD5Password(self, md5Password):
+-        md = md5.new()
++        md = md5()
+         md.update(md5Password)
+         md.update(self.challenge)
+         correct = md.digest()

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_plugin.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_plugin.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_plugin.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_plugin.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,11 @@
+Index: twisted/test/test_plugin.py
+===================================================================
+--- twisted/test/test_plugin.py	(revision 19773)
++++ twisted/test/test_plugin.py	(working copy)
+@@ -518,3 +518,6 @@
+         self.assertEqual(len(self.flushLoggedErrors()), 0)
+         self.assertIn('one', self.getAllPlugins())
+         self.assertEqual(len(self.flushLoggedErrors()), 1)
++
++    
++    test_newPluginsOnReadOnlyPath.skip = "Seems not to work on OS X 10.4 buildbot machine."

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_tcp.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_tcp.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_tcp.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.test.test_tcp.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,13 @@
+Index: twisted/test/test_tcp.py
+===================================================================
+--- twisted/test/test_tcp.py	(revision 19773)
++++ twisted/test/test_tcp.py	(working copy)
+@@ -1294,6 +1294,8 @@
+             self.client.transport.loseConnection()
+             log.flushErrors(RuntimeError)
+         return d.addCallback(check)
++
++    testReadNotificationRaises.todo = "self.f.protocol is None"
+     
+     def testWriteNotificationRaises(self):
+         self.client.writeConnectionLost = self.aBug

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web.test.test_webclient.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web.test.test_webclient.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web.test.test_webclient.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web.test.test_webclient.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,13 @@
+Index: twisted/web/test/test_webclient.py
+===================================================================
+--- twisted/web/test/test_webclient.py	(revision 19773)
++++ twisted/web/test/test_webclient.py	(working copy)
+@@ -206,6 +206,8 @@
+         d.addBoth(self._cleanupDownloadPageError3)
+         return d
+ 
++    testDownloadPageError3.skip = "Seems not to work on OS X."
++
+     def _cleanupDownloadPageError3(self, ignored):
+         os.chmod("unwritable", 0700)
+         os.unlink("unwritable")

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,29 @@
+Index: twisted/web2/auth/basic.py
+===================================================================
+--- twisted/web2/auth/basic.py	(revision 19773)
++++ twisted/web2/auth/basic.py	(working copy)
+@@ -1,6 +1,7 @@
+ # -*- test-case-name: twisted.web2.test.test_httpauth -*-
+ 
+ from twisted.cred import credentials, error
++from twisted.internet.defer import succeed
+ from twisted.web2.auth.interfaces import ICredentialFactory
+ 
+ from zope.interface import implements
+@@ -18,7 +19,7 @@
+         self.realm = realm
+ 
+     def getChallenge(self, peer):
+-        return {'realm': self.realm}
++        return succeed({'realm': self.realm})
+ 
+     def decode(self, response, request):
+         try:
+@@ -28,6 +29,6 @@
+ 
+         creds = creds.split(':', 1)
+         if len(creds) == 2:
+-            return credentials.UsernamePassword(*creds)
++            return succeed(credentials.UsernamePassword(*creds))
+         else:
+             raise error.LoginFailed('Invalid credentials')

Deleted: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -1,22 +0,0 @@
-Index: twisted/web2/auth/digest.py
-===================================================================
---- twisted/web2/auth/digest.py	(revision 26741)
-+++ twisted/web2/auth/digest.py	(working copy)
-@@ -158,6 +158,17 @@
-             algo, nonce, nc, cnonce, qop, self.method, uri, None
-         )
- 
-+        if expected == response:
-+            return True
-+
-+        # IE7 sends cnonce and nc values, but auth fails if they are used.
-+        # So try again without them...
-+        # They can be omitted for backwards compatibility [RFC 2069].
-+        expected = calcResponse(
-+            calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
-+            algo, nonce, None, None, qop, self.method, uri, None
-+        )
-+
-         return expected == response
- 
-     def checkHash(self, digestHash):

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,135 @@
+Index: twisted/web2/auth/digest.py
+===================================================================
+--- twisted/web2/auth/digest.py	(revision 19773)
++++ twisted/web2/auth/digest.py	(working copy)
+@@ -8,19 +8,28 @@
+ import time
+ 
+ from twisted.cred import credentials, error
++from twisted.internet.defer import succeed
+ from zope.interface import implements, Interface
+ 
+ from twisted.web2.auth.interfaces import ICredentialFactory
++from twisted.web2.http_headers import tokenize
++from twisted.web2.http_headers import Token
++from twisted.web2.http_headers import split
++from twisted.web2.http_headers import parseKeyValue
+ 
+-import md5, sha
++try:
++    from hashlib import md5, sha1
++except ImportError:
++    from md5 import new as md5
++    from sha import new as sha1
+ import random, sys
+ 
+ # The digest math
+ 
+ algorithms = {
+-    'md5': md5.new,
+-    'md5-sess': md5.new,
+-    'sha': sha.new,
++    'md5': md5,
++    'md5-sess': md5,
++    'sha': sha1,
+ }
+ 
+ # DigestCalcHA1
+@@ -153,7 +162,18 @@
+             calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
+             algo, nonce, nc, cnonce, qop, self.method, uri, None
+         )
++        
++        if expected == response:
++            return True
+ 
++        # IE7 sends cnonce and nc values, but auth fails if they are used.
++        # So try again without them...
++        # They can be omitted for backwards compatibility [RFC 2069].
++        expected = calcResponse(
++            calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
++            algo, nonce, None, None, qop, self.method, uri, None
++        )
++
+         return expected == response
+ 
+     def checkHash(self, digestHash):
+@@ -228,9 +248,9 @@
+         # Now, what we do is encode the nonce, client ip and a timestamp
+         # in the opaque value with a suitable digest
+         key = "%s,%s,%s" % (nonce, clientip, str(int(self._getTime())))
+-        digest = md5.new(key + self.privateKey).hexdigest()
++        digest = md5(key + self.privateKey).hexdigest()
+         ekey = key.encode('base64')
+-        return "%s-%s" % (digest, ekey.strip('\n'))
++        return "%s-%s" % (digest, ekey.replace('\n', ''))
+ 
+     def verifyOpaque(self, opaque, nonce, clientip):
+         """
+@@ -274,7 +294,7 @@
+                 'Invalid response, incompatible opaque/nonce too old')
+ 
+         # Verify the digest
+-        digest = md5.new(key + self.privateKey).hexdigest()
++        digest = md5(key + self.privateKey).hexdigest()
+         if digest != opaqueParts[0]:
+             raise error.LoginFailed('Invalid response, invalid opaque value')
+ 
+@@ -293,11 +313,12 @@
+         c = self.generateNonce()
+         o = self.generateOpaque(c, peer.host)
+ 
+-        return {'nonce': c,
+-                'opaque': o,
+-                'qop': 'auth',
+-                'algorithm': self.algorithm,
+-                'realm': self.realm}
++        return succeed({'nonce': c,
++            'opaque': o,
++            'qop': 'auth',
++            'algorithm': self.algorithm,
++            'realm': self.realm,
++        })
+ 
+     def decode(self, response, request):
+         """
+@@ -315,18 +336,18 @@
+         @raise: L{error.LoginFailed} if the response does not contain a
+             username, a nonce, an opaque, or if the opaque is invalid.
+         """
+-        def unq(s):
+-            if s[0] == s[-1] == '"':
+-                return s[1:-1]
+-            return s
+         response = ' '.join(response.splitlines())
+-        parts = response.split(',')
+-
+-        auth = {}
+-
+-        for (k, v) in [p.split('=', 1) for p in parts]:
+-            auth[k.strip()] = unq(v.strip())
+-
++        
++        try:
++            parts = split(tokenize((response,), foldCase=False), Token(","))
++    
++            auth = {}
++    
++            for (k, v) in [parseKeyValue(p) for p in parts]:
++                auth[k.strip()] = v.strip()
++        except ValueError:
++            raise error.LoginFailed('Invalid response.')
++            
+         username = auth.get('username')
+         if not username:
+             raise error.LoginFailed('Invalid response, no username given.')
+@@ -342,7 +363,7 @@
+                              auth.get('nonce'),
+                              request.remoteAddr.host):
+ 
+-            return DigestedCredentials(username,
++            return succeed(DigestedCredentials(username,
+                                        request.method,
+                                        self.realm,
+-                                       auth)
++                                       auth))

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,22 @@
+Index: twisted/web2/auth/interfaces.py
+===================================================================
+--- twisted/web2/auth/interfaces.py	(revision 19773)
++++ twisted/web2/auth/interfaces.py	(working copy)
+@@ -18,7 +18,7 @@
+         @param peer: The client's address
+ 
+         @rtype: C{dict}
+-        @return: dictionary of challenge arguments
++        @return: deferred returning dictionary of challenge arguments
+         """
+ 
+     def decode(response, request):
+@@ -32,7 +32,7 @@
+         @type request: L{twisted.web2.server.Request}
+         @param request: the request being processed
+ 
+-        @return: ICredentials
++        @return: deferred returning ICredentials
+         """
+ 
+ 

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,205 @@
+Index: twisted/web2/auth/wrapper.py
+===================================================================
+--- twisted/web2/auth/wrapper.py	(revision 19773)
++++ twisted/web2/auth/wrapper.py	(working copy)
+@@ -5,32 +5,45 @@
+ """
+ from zope.interface import implements, directlyProvides
+ from twisted.cred import error, credentials
+-from twisted.python import failure
+ from twisted.web2 import responsecode
+ from twisted.web2 import http
+ from twisted.web2 import iweb
+ from twisted.web2.auth.interfaces import IAuthenticatedRequest
++from twisted.internet.defer import inlineCallbacks, returnValue
+ 
+ class UnauthorizedResponse(http.StatusResponse):
+     """A specialized response class for generating www-authenticate headers
+     from the given L{CredentialFactory} instances
+     """
+ 
+-    def __init__(self, factories, remoteAddr=None):
++    @staticmethod
++    def makeResponse(factories, remoteAddr=None):
++        
++        response = UnauthorizedResponse()
++        d = response.generateHeaders(factories, remoteAddr)
++        d.addCallback(lambda _:response)
++        return d
++
++    def __init__(self):
++
++        super(UnauthorizedResponse, self).__init__(
++            responsecode.UNAUTHORIZED,
++            "You are not authorized to access this resource."
++    )
++
++    @inlineCallbacks
++    def generateHeaders(self, factories, remoteAddr=None):
+         """
+         @param factories: A L{dict} of {'scheme': ICredentialFactory}
+ 
+         @param remoteAddr: An L{IAddress} for the connecting client.
+         """
+ 
+-        super(UnauthorizedResponse, self).__init__(
+-            responsecode.UNAUTHORIZED,
+-            "You are not authorized to access this resource.")
+-
+         authHeaders = []
+         for factory in factories.itervalues():
+-            authHeaders.append((factory.scheme,
+-                                factory.getChallenge(remoteAddr)))
++            scheme = factory.scheme
++            challenge = (yield factory.getChallenge(remoteAddr))
++            authHeaders.append((scheme, challenge,))
+ 
+         self.headers.setHeader('www-authenticate', authHeaders)
+ 
+@@ -71,8 +84,6 @@
+ 
+     def _loginSucceeded(self, avatar, request):
+         """
+-        Callback for successful login.
+-
+         @param avatar: A tuple of the form (interface, avatar) as
+             returned by your realm.
+ 
+@@ -85,6 +96,7 @@
+ 
+         directlyProvides(request, IAuthenticatedRequest)
+ 
++        @inlineCallbacks
+         def _addAuthenticateHeaders(request, response):
+             """
+             A response filter that adds www-authenticate headers
+@@ -93,14 +105,16 @@
+             """
+             if response.code == responsecode.UNAUTHORIZED:
+                 if not response.headers.hasHeader('www-authenticate'):
+-                    newResp = UnauthorizedResponse(self.credentialFactories,
+-                                                   request.remoteAddr)
++                    newResp = (yield UnauthorizedResponse.makeResponse(
++                        self.credentialFactories,
++                        request.remoteAddr
++                    ))
+ 
+                     response.headers.setHeader(
+                         'www-authenticate',
+                         newResp.headers.getHeader('www-authenticate'))
+ 
+-            return response
++            returnValue(response)
+ 
+         _addAuthenticateHeaders.handleErrors = True
+ 
+@@ -108,27 +122,22 @@
+ 
+         return self.wrappedResource
+ 
+-    def _loginFailed(self, result, request):
++    @inlineCallbacks
++    def _loginFailed(self, request):
+         """
+-        Errback for failed login.
+-
+-        @param result: L{Failure} returned by portal.login
+-
+         @param request: L{IRequest} that encapsulates this auth
+             attempt.
+ 
+-        @return: A L{Failure} containing an L{HTTPError} containing the
+-            L{UnauthorizedResponse} if C{result} is an L{UnauthorizedLogin}
+-            or L{UnhandledCredentials} error
++        @raise: always rais HTTPError
+         """
+-        result.trap(error.UnauthorizedLogin, error.UnhandledCredentials)
+ 
+-        return failure.Failure(
+-            http.HTTPError(
+-                UnauthorizedResponse(
+-                self.credentialFactories,
+-                request.remoteAddr)))
++        response = (yield UnauthorizedResponse.makeResponse(
++            self.credentialFactories,
++            request.remoteAddr
++        ))
++        raise http.HTTPError(response)
+ 
++    @inlineCallbacks
+     def login(self, factory, response, request):
+         """
+         @param factory: An L{ICredentialFactory} that understands the given
+@@ -142,50 +151,48 @@
+             or a failure containing an L{UnauthorizedResponse}
+         """
+         try:
+-            creds = factory.decode(response, request)
++            creds = (yield factory.decode(response, request))
+         except error.LoginFailed:
+-            raise http.HTTPError(UnauthorizedResponse(
+-                                    self.credentialFactories,
+-                                    request.remoteAddr))
++            yield self._loginFailed(request)
+ 
++        try:
++            avatar = (yield self.portal.login(creds, None, *self.interfaces))
++        except (error.UnauthorizedLogin, error.UnhandledCredentials):
++            yield self._loginFailed(request)
++        resource = self._loginSucceeded(avatar, request)
++        returnValue(resource)
+ 
+-        return self.portal.login(creds, None, *self.interfaces
+-                                ).addCallbacks(self._loginSucceeded,
+-                                               self._loginFailed,
+-                                               (request,), None,
+-                                               (request,), None)
+-
++    @inlineCallbacks
+     def authenticate(self, request):
+         """
+-        Attempt to authenticate the givin request
++        Attempt to authenticate the giving request
+ 
+         @param request: An L{IRequest} to be authenticated.
+         """
+         authHeader = request.headers.getHeader('authorization')
+ 
+         if authHeader is None:
+-            return self.portal.login(credentials.Anonymous(),
+-                                     None,
+-                                     *self.interfaces
+-                                     ).addCallbacks(self._loginSucceeded,
+-                                                    self._loginFailed,
+-                                                    (request,), None,
+-                                                    (request,), None)
++            try:
++                avatar = (yield self.portal.login(credentials.Anonymous(), None, *self.interfaces))
++            except:
++                yield self._loginFailed(request)
++            resource = self._loginSucceeded(avatar, request)
++            returnValue(resource)
+ 
+         elif authHeader[0] not in self.credentialFactories:
+-            raise http.HTTPError(UnauthorizedResponse(
+-                                    self.credentialFactories,
+-                                    request.remoteAddr))
++            yield self._loginFailed(request)
+         else:
+-            return self.login(self.credentialFactories[authHeader[0]],
+-                              authHeader[1], request)
++            result = (yield self.login(self.credentialFactories[authHeader[0]], authHeader[1], request))
++            returnValue(result)
+ 
+     def locateChild(self, request, seg):
+         """
+         Authenticate the request then return the C{self.wrappedResource}
+         and the unmodified segments.
+         """
+-        return self.authenticate(request), seg
++        d = self.authenticate(request)
++        d.addCallback(lambda result:(result, seg,))
++        return d
+ 
+     def renderHTTP(self, request):
+         """

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.__init__.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.__init__.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.__init__.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.__init__.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,12 @@
+Index: twisted/web2/dav/__init__.py
+===================================================================
+--- twisted/web2/dav/__init__.py	(revision 19773)
++++ twisted/web2/dav/__init__.py	(working copy)
+@@ -45,6 +45,7 @@
+     "noneprops",
+     "resource",
+     "static",
++    "stream",
+     "util",
+     "xattrprops",
+ ]

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,83 @@
+Index: twisted/web2/dav/auth.py
+===================================================================
+--- twisted/web2/dav/auth.py	(revision 19773)
++++ twisted/web2/dav/auth.py	(working copy)
+@@ -5,7 +5,13 @@
+ from twisted.web2.dav import davxml
+ from twisted.web2.dav.davxml import twisted_private_namespace
+ 
+-__all__ = ["PrincipalCredentials", "AuthenticationWrapper"]
++__all__ = [
++    "IPrincipal",
++    "DavRealm",
++    "IPrincipalCredentials",
++    "PrincipalCredentials",
++    "AuthenticationWrapper",
++]
+ 
+ class AuthenticationWrapper(WrapperResource):
+     def __init__(self, resource, portal, credentialFactories, loginInterfaces):
+@@ -40,7 +46,7 @@
+ 
+     def requestAvatar(self, avatarId, mind, *interfaces):
+         if IPrincipal in interfaces:
+-            return IPrincipal, davxml.Principal(davxml.HRef(avatarId))
++            return IPrincipal, davxml.Principal(davxml.HRef(avatarId[0])), davxml.Principal(davxml.HRef(avatarId[1]))
+         
+         raise NotImplementedError("Only IPrincipal interface is supported")
+ 
+@@ -52,33 +58,44 @@
+ class PrincipalCredentials(object):
+     implements(IPrincipalCredentials)
+ 
+-    def __init__(self, principal, principalURI, credentials):
+-        self.principal = principal
+-        self.principalURI = principalURI
++    def __init__(self, authnPrincipal, authzPrincipal, credentials):
++        """
++        Initialize with both authentication and authorization values. Note that in most cases theses will be the same
++        since HTTP auth makes no distinction between the two - but we may be layering some addition auth on top of this
++        (.e.g.. proxy auth, cookies, forms etc) that make result in authentication and authorization being different.
++
++        @param authnPrincipal: L{IDAVPrincipalResource} for the authenticated principal.
++        @param authnURI: C{str} containing the URI of the authenticated principal.
++        @param authzPrincipal: L{IDAVPrincipalResource} for the authorized principal.
++        @param authzURI: C{str} containing the URI of the authorized principal.
++        @param credentials: L{ICredentials} for the authentication credentials.
++        """
++        self.authnPrincipal = authnPrincipal
++        self.authzPrincipal = authzPrincipal
+         self.credentials = credentials
+ 
+     def checkPassword(self, password):
+         return self.credentials.checkPassword(password)
+ 
+ 
+-class TwistedPropertyChecker:
++class TwistedPropertyChecker(object):
+     implements(checkers.ICredentialsChecker)
+ 
+     credentialInterfaces = (IPrincipalCredentials,)
+ 
+-    def _cbPasswordMatch(self, matched, principalURI):
++    def _cbPasswordMatch(self, matched, principalURIs):
+         if matched:
+-            return principalURI
++            # We return both URIs
++            return principalURIs
+         else:
+-            raise error.UnauthorizedLogin(
+-                "Bad credentials for: %s" % (principalURI,))
++            raise error.UnauthorizedLogin("Bad credentials for: %s" % (principalURIs[0],))
+ 
+     def requestAvatarId(self, credentials):
+         pcreds = IPrincipalCredentials(credentials)
+-        pswd = str(pcreds.principal.readDeadProperty(TwistedPasswordProperty))
++        pswd = str(pcreds.authnPrincipal.readDeadProperty(TwistedPasswordProperty))
+ 
+         d = defer.maybeDeferred(credentials.checkPassword, pswd)
+-        d.addCallback(self._cbPasswordMatch, pcreds.principalURI)
++        d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
+         return d
+ 
+ ##

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,30 @@
+Index: twisted/web2/dav/davxml.py
+===================================================================
+--- twisted/web2/dav/davxml.py	(revision 19773)
++++ twisted/web2/dav/davxml.py	(working copy)
+@@ -45,6 +45,8 @@
+ from twisted.web2.dav.element.rfc2518 import *
+ from twisted.web2.dav.element.rfc3253 import *
+ from twisted.web2.dav.element.rfc3744 import *
++from twisted.web2.dav.element.rfc4331 import *
++from twisted.web2.dav.element.extensions import *
+ 
+ #
+ # Register all XML elements with the parser
+@@ -56,11 +58,15 @@
+ import twisted.web2.dav.element.rfc2518
+ import twisted.web2.dav.element.rfc3253
+ import twisted.web2.dav.element.rfc3744
++import twisted.web2.dav.element.rfc4331
++import twisted.web2.dav.element.extensions
+ 
+ __all__ = (
+     registerElements(twisted.web2.dav.element.base   ) +
+     registerElements(twisted.web2.dav.element.parser ) +
+     registerElements(twisted.web2.dav.element.rfc2518) +
+     registerElements(twisted.web2.dav.element.rfc3253) +
+-    registerElements(twisted.web2.dav.element.rfc3744)
++    registerElements(twisted.web2.dav.element.rfc3744) +
++    registerElements(twisted.web2.dav.element.rfc4331) +
++    registerElements(twisted.web2.dav.element.extensions)
+ )

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,11 @@
+Index: twisted/web2/dav/element/__init__.py
+===================================================================
+--- twisted/web2/dav/element/__init__.py	(revision 19773)
++++ twisted/web2/dav/element/__init__.py	(working copy)
+@@ -35,4 +35,6 @@
+     "rfc2518",
+     "rfc3253",
+     "rfc3744",
++    "rfc4331",
++    "extensions",
+ ]

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,267 @@
+Index: twisted/web2/dav/element/base.py
+===================================================================
+--- twisted/web2/dav/element/base.py	(revision 19773)
++++ twisted/web2/dav/element/base.py	(working copy)
+@@ -45,7 +45,7 @@
+ ]
+ 
+ import string
+-import StringIO
++import cStringIO as StringIO
+ import xml.dom.minidom
+ 
+ import datetime
+@@ -90,6 +90,35 @@
+             raise NotImplementedError("WebDAVElement subclass %s is not implemented."
+                                       % (self.__class__.__name__,))
+ 
++        my_children = []
++
++        allowPCDATA = self.allowed_children.has_key(PCDATAElement)
++
++        for child in children:
++            if child is None:
++                continue
++
++            if isinstance(child, (str, unicode)):
++                child = PCDATAElement(child)
++
++            if isinstance(child, PCDATAElement) and not allowPCDATA:
++                continue
++
++            my_children.append(child)
++
++        self.children = tuple(my_children)
++
++        self.attributes = attributes
++
++    def validate(self):
++
++        children = self.children
++        attributes = self.attributes
++
++        if self.allowed_children is None:
++            raise NotImplementedError("WebDAVElement subclass %s is not implemented."
++                                      % (self.__class__.__name__,))
++
+         #
+         # Validate that children are of acceptable types
+         #
+@@ -102,13 +131,10 @@
+         my_children = []
+ 
+         for child in children:
+-            if child is None:
+-                continue
+ 
+-            if isinstance(child, (str, unicode)):
+-                child = PCDATAElement(child)
+-
+             assert isinstance(child, (WebDAVElement, PCDATAElement)), "Not an element: %r" % (child,)
++            
++            child.validate()
+ 
+             for allowed, (min, max) in allowed_children.items():
+                 if type(allowed) == type and isinstance(child, allowed):
+@@ -145,24 +171,26 @@
+ 
+         if self.allowed_attributes:
+             for name in attributes:
+-                if name in self.allowed_attributes:
+-                    my_attributes[name] = attributes[name]
+-                else:
+-                    log.msg("Attribute %s is unexpected and therefore ignored in %s element"
+-                            % (name, self.sname()))
++                if name not in self.allowed_attributes:
++                    log.msg("Attribute %s is unexpected in %s element" % (name, self.sname()))
++                my_attributes[name] = attributes[name]
+     
+             for name, required in self.allowed_attributes.items():
+                 if required and name not in my_attributes:
+                     raise ValueError("Attribute %s is required in %s element"
+                                      % (name, self.sname()))
+ 
+-        elif not isinstance(self, WebDAVUnknownElement):
+-            if attributes:
+-                log.msg("Attributes %s are unexpected and therefore ignored in %s element"
++        else:
++            if not isinstance(self, WebDAVUnknownElement) and attributes:
++                log.msg("Attributes %s are unexpected in %s element"
+                         % (attributes.keys(), self.sname()))
++            my_attributes.update(attributes)
+ 
+         self.attributes = my_attributes
+ 
++    def emptyCopy(self):
++        return self.__class__()
++
+     def __str__(self):
+         return self.sname()
+ 
+@@ -190,14 +218,93 @@
+         return child in self.children
+ 
+     def writeXML(self, output):
+-        document = xml.dom.minidom.Document()
+-        self.addToDOM(document, None)
+-        PrintXML(document, stream=output)
++        # FIXME: Now have a 'fast' write implementation as well as previous PyXML-based one.
++        # For now the fast one is the default and we will test to see if its good enough.
++        
++        usePyXML = False
++        if usePyXML:
++            document = xml.dom.minidom.Document()
++            self.addToDOM(document, None)
++            PrintXML(document, stream=output)
++        else:
++            output.write("<?xml version='1.0' encoding='UTF-8'?>\r\n")
++            self.writeToStream(output, "", 0, True)
++            output.write("\r\n")
++    
++    def writeToStream(self, output, ns, level, pretty):
++        """
++        Fast XML output.
+ 
++        @param output: C{stream} to write to.
++        @param ns: C{str} containing the namespace of the enclosing element.
++        @param level: C{int} containing the element nesting level (starts at 0).
++        @param pretty: C{bool} whether to use 'pretty' formatted output or not.
++        """
++        
++        # Do pretty indent
++        if pretty and level:
++            output.write("  " * level)
++        
++        # Check for empty element (one with either no children or a single PCDATA that is itself empty)
++        if (len(self.children) == 0 or
++            (len(self.children) == 1 and isinstance(self.children[0], PCDATAElement) and len(str(self.children[0])) == 0)):
++
++            # Write out any attributes or the namespace if difference from enclosing element.
++            if self.attributes or (ns != self.namespace):
++                output.write("<%s" % (self.name,))
++                for name, value in self.attributes.iteritems():
++                    self.writeAttributeToStream(output, name, value)
++                if ns != self.namespace:
++                    output.write(" xmlns='%s'" % (self.namespace,))
++                output.write("/>")
++            else:
++                output.write("<%s/>" % (self.name,))
++        else:
++            # Write out any attributes or the namespace if difference from enclosing element.
++            if self.attributes or (ns != self.namespace):
++                output.write("<%s" % (self.name,))
++                for name, value in self.attributes.iteritems():
++                    self.writeAttributeToStream(output, name, value)
++                if ns != self.namespace:
++                    output.write(" xmlns='%s'" % (self.namespace,))
++                    ns = self.namespace
++                output.write(">")
++            else:
++                output.write("<%s>" % (self.name,))
++                
++            # Determine nature of children when doing pretty print: we do
++            # not want to insert CRLFs or any other whitespace in PCDATA.
++            hasPCDATA = False
++            for child in self.children:
++                if isinstance(child, PCDATAElement):
++                    hasPCDATA = True
++                    break
++            
++            # Write out the children.
++            if pretty and not hasPCDATA:
++                output.write("\r\n")
++            for child in self.children:
++                child.writeToStream(output, ns, level+1, pretty)
++                
++            # Close the element.
++            if pretty and not hasPCDATA and level:
++                output.write("  " * level)
++            output.write("</%s>" % (self.name,))
++
++        if pretty and level:
++            output.write("\r\n")
++
++    def writeAttributeToStream(self, output, name, value):
++        
++        # Quote any single quotes. We do not need to be any smarter than this.
++        value = value.replace("'", "&apos;")
++
++        output.write(" %s='%s'" % (name, value,))  
++      
+     def toxml(self):
+         output = StringIO.StringIO()
+         self.writeXML(output)
+-        return output.getvalue()
++        return str(output.getvalue())
+ 
+     def element(self, document):
+         element = document.createElementNS(self.namespace, self.name)
+@@ -285,6 +392,9 @@
+ 
+         self.data = data
+ 
++    def validate(self):
++        pass
++
+     def __str__(self):
+         return str(self.data)
+ 
+@@ -324,6 +434,22 @@
+             log.err("Invalid PCDATA: %r" % (self.data,))
+             raise
+ 
++    def writeToStream(self, output, ns, level, pretty):
++        # Do escaping/CDATA behavior
++        if "\r" in self.data or "\n" in self.data:
++            # Do CDATA
++            cdata = "<![CDATA[%s]]>" % (self.data.replace("]]>", "]]&gt;"),)
++        else:
++            cdata = self.data
++            if "&" in cdata:
++                cdata = cdata.replace("&", "&amp;")
++            if "<" in cdata:
++                cdata = cdata.replace("<", "&lt;")
++            if ">" in cdata:
++                cdata = cdata.replace(">", "&gt;")
++
++        output.write(cdata)
++
+ class WebDAVOneShotElement (WebDAVElement):
+     """
+     Element with exactly one WebDAVEmptyElement child and no attributes.
+@@ -364,6 +490,18 @@
+         PCDATAElement: (0, None),
+     }
+ 
++    def qname(self):
++        return (self.namespace, self.name)
++
++    def sname(self):
++        return "{%s}%s" % (self.namespace, self.name)
++
++    def emptyCopy(self):
++        copied = self.__class__()
++        copied.name = self.name
++        copied.namespace = self.namespace
++        return copied
++
+ class WebDAVEmptyElement (WebDAVElement):
+     """
+     WebDAV element with no contents.
+@@ -388,6 +526,7 @@
+     """
+     WebDAV element containing PCDATA.
+     """
++    @classmethod
+     def fromString(clazz, string):
+         if string is None:
+             return clazz()
+@@ -396,8 +535,6 @@
+         else:
+             return clazz(PCDATAElement(str(string)))
+ 
+-    fromString = classmethod(fromString)
+-
+     allowed_children = { PCDATAElement: (0, None) }
+ 
+     def __str__(self):

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.extensions.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.extensions.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.extensions.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.extensions.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,44 @@
+Index: twisted/web2/dav/element/extensions.py
+===================================================================
+--- twisted/web2/dav/element/extensions.py	(revision 0)
++++ twisted/web2/dav/element/extensions.py	(revision 0)
+@@ -0,0 +1,39 @@
++##
++# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++##
++
++from twisted.web2.dav.element.base import *
++
++##
++# draft-sanchez-webdav-current-principal
++##
++
++class CurrentUserPrincipal(WebDAVElement):
++    """
++    Current principal information
++    """
++
++    name = "current-user-principal"
++    allowed_children = {
++        (dav_namespace, "href" )                : (0, 1),
++        (dav_namespace, "unauthenticated" )     : (0, 1),
++    }

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.parser.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.parser.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.parser.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.parser.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,81 @@
+Index: twisted/web2/dav/element/parser.py
+===================================================================
+--- twisted/web2/dav/element/parser.py	(revision 19773)
++++ twisted/web2/dav/element/parser.py	(working copy)
+@@ -37,7 +37,7 @@
+     "WebDAVDocument",
+ ]
+ 
+-import StringIO
++import cStringIO as StringIO
+ import xml.dom.minidom
+ import xml.sax
+ 
+@@ -106,6 +106,12 @@
+             "children"   : [],
+         }]
+ 
++        # Keep a cache of the subclasses we create for unknown XML
++        # elements, so that we don't create multiple classes for the
++        # same element; it's fairly typical for elements to appear
++        # multiple times in a document.
++        self.unknownElementClasses = {}
++
+     def endDocument(self):
+         top = self.stack[-1]
+ 
+@@ -115,6 +121,7 @@
+         assert len(top["children"]) is 1, "Must have exactly one root element, got %d" % len(top["children"])
+ 
+         self.dom = WebDAVDocument(top["children"][0])
++        del(self.unknownElementClasses)
+ 
+     def startElementNS(self, name, qname, attributes):
+         attributes_dict = {}
+@@ -125,13 +132,17 @@
+ 
+         tag_namespace, tag_name = name
+ 
+-        if (name not in elements_by_tag_name):
+-            class UnknownElement (WebDAVUnknownElement):
+-                namespace = tag_namespace
+-                name      = tag_name
+-            element_class = UnknownElement
++        if name in elements_by_tag_name:
++            element_class = elements_by_tag_name[name]
++        elif name in self.unknownElementClasses:
++            element_class = self.unknownElementClasses[name]
+         else:
+-            element_class = elements_by_tag_name[name]
++            def element_class(*args, **kwargs):
++                element = WebDAVUnknownElement(*args, **kwargs)
++                element.namespace = tag_namespace
++                element.name      = tag_name
++                return element
++            self.unknownElementClasses[name] = element_class
+ 
+         self.stack.append({
+             "name"       : name,
+@@ -158,7 +169,12 @@
+         self.stack[-1]["children"].append(element)
+ 
+     def characters(self, content):
+-        self.stack[-1]["children"].append(PCDATAElement(content))
++        # Coalesce adjacent PCDATAElements
++        pcdata = PCDATAElement(content)
++        if len(self.stack[-1]["children"]) and isinstance(self.stack[-1]["children"][-1], PCDATAElement):
++            self.stack[-1]["children"][-1] = self.stack[-1]["children"][-1] + pcdata
++        else:
++            self.stack[-1]["children"].append(pcdata)
+ 
+     def ignorableWhitespace(self, whitespace):
+         self.characters(self, whitespace)
+@@ -194,6 +210,8 @@
+             except xml.sax.SAXParseException, e:
+                 raise ValueError(e)
+ 
++            #handler.dom.root_element.validate()
++
+             return handler.dom
+ 
+         return parse

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc2518.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc2518.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc2518.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc2518.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,37 @@
+Index: twisted/web2/dav/element/rfc2518.py
+===================================================================
+--- twisted/web2/dav/element/rfc2518.py	(revision 19773)
++++ twisted/web2/dav/element/rfc2518.py	(working copy)
+@@ -59,8 +59,8 @@
+     """
+     name = "depth"
+ 
+-    def __init__(self, *children, **attributes):
+-        super(Depth, self).__init__(*children, **attributes)
++    def validate(self):
++        super(Depth, self).validate()
+ 
+         depth = str(self)
+         if depth not in ("0", "1", "infinity"):
+@@ -382,8 +382,8 @@
+         PCDATAElement: (0, 1),
+     }
+ 
+-    def __init__(self, *children, **attributes):
+-        super(KeepAlive, self).__init__(*children, **attributes)
++    def validate(self):
++        super(KeepAlive, self).validate()
+ 
+         type = None
+ 
+@@ -450,8 +450,8 @@
+         (dav_namespace, "prop"    ): (0, 1),
+     }
+ 
+-    def __init__(self, *children, **attributes):
+-        super(PropertyFind, self).__init__(*children, **attributes)
++    def validate(self):
++        super(PropertyFind, self).validate()
+ 
+         if len(self.children) != 1:
+             raise ValueError(

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,102 @@
+Index: twisted/web2/dav/element/rfc3744.py
+===================================================================
+--- twisted/web2/dav/element/rfc3744.py	(revision 19773)
++++ twisted/web2/dav/element/rfc3744.py	(working copy)
+@@ -131,8 +131,8 @@
+         (dav_namespace, "self"           ): (0, 1),
+     }
+ 
+-    def __init__(self, *children, **attributes):
+-        super(Principal, self).__init__(*children, **attributes)
++    def validate(self):
++        super(Principal, self).validate()
+ 
+         if len(self.children) > 1:
+             raise ValueError(
+@@ -385,9 +385,14 @@
+         self.inherited  = None
+         self.protected  = False
+ 
++        my_children = []
++
+         for child in self.children:
+             namespace, name = child.qname()
+ 
++            if isinstance(child, PCDATAElement):
++                continue
++
+             if (namespace == dav_namespace):
+                 if name in ("principal", "invert"):
+                     if self.principal is not None:
+@@ -417,6 +422,10 @@
+                 elif name == "protected":
+                     self.protected = True
+ 
++            my_children.append(child)
++
++        self.children = tuple(my_children)
++
+         if self.principal is None:
+             raise ValueError(
+                 "One of DAV:principal or DAV:invert is required in %s, got: %s"
+@@ -456,7 +465,7 @@
+     """
+     name = "self"
+ 
+-class Invert (WebDAVEmptyElement):
++class Invert (WebDAVElement):
+     """
+     Principal which matches a user if the user does not match the principal
+     contained by this principal. (RFC 3744, section 5.5.1)
+@@ -551,8 +560,8 @@
+         (dav_namespace, "property"       ): (0, None),
+     }
+ 
+-    def __init__(self, *children, **attributes):
+-        super(RequiredPrincipal, self).__init__(*children, **attributes)
++    def validate(self):
++        super(RequiredPrincipal, self).validate()
+ 
+         type = None
+ 
+@@ -628,8 +637,8 @@
+ 
+     allowed_children = { WebDAVElement: (0, None) }
+ 
+-    def __init__(self, *children, **attributes):
+-        super(ACLPrincipalPropSet, self).__init__(*children, **attributes)
++    def validate(self):
++        super(ACLPrincipalPropSet, self).validate()
+ 
+         prop = False
+         
+@@ -656,8 +665,8 @@
+         (dav_namespace, "prop"              ): (0, 1),
+     }
+ 
+-    def __init__(self, *children, **attributes):
+-        super(PrincipalMatch, self).__init__(*children, **attributes)
++    def validate(self):
++        super(PrincipalMatch, self).validate()
+ 
+         # This element can be empty when uses in supported-report-set
+         if not len(self.children):
+@@ -705,6 +714,7 @@
+         (dav_namespace, "prop"                             ): (0, 1),
+         (dav_namespace, "apply-to-principal-collection-set"): (0, 1),
+     }
++    allowed_attributes = { "test": False }
+ 
+ class PropertySearch (WebDAVElement):
+     """
+@@ -745,4 +755,10 @@
+         (dav_namespace, "description"): (1, 1),
+     }
+ 
++class NumberOfMatchesWithinLimits (WebDAVEmptyElement):
++    """
++    Error which indicates too many results
++    """
++    name = "number-of-matches-within-limits"
++
+ # For DAV:description element (RFC 3744, section 9.5) see Description class above.

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,60 @@
+Index: twisted/web2/dav/element/rfc4331.py
+===================================================================
+--- twisted/web2/dav/element/rfc4331.py	(revision 0)
++++ twisted/web2/dav/element/rfc4331.py	(revision 0)
+@@ -0,0 +1,55 @@
++##
++# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++RFC 4331 (Quota and Size Properties for WebDAV Collections) XML Elements
++
++This module provides XML element definitions for use with WebDAV.
++
++See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt
++"""
++
++from twisted.web2.dav.element.base import WebDAVTextElement
++
++##
++# Section 3 & 4 (Quota Properties)
++##
++
++class QuotaAvailableBytes (WebDAVTextElement):
++    """
++    Property which contains the the number of bytes available under the
++    current quota to store data in a collection (RFC 4331, section 3)
++    """
++    name = "quota-available-bytes"
++    hidden = True
++    protected = True
++
++class QuotaUsedBytes (WebDAVTextElement):
++    """
++    Property which contains the the number of bytes used under the
++    current quota to store data in a collection (RFC 4331, section 4)
++    """
++    name = "quota-used-bytes"
++    hidden = True
++    protected = True

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.fileop.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.fileop.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.fileop.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.fileop.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,31 @@
+Index: twisted/web2/dav/fileop.py
+===================================================================
+--- twisted/web2/dav/fileop.py	(revision 19773)
++++ twisted/web2/dav/fileop.py	(working copy)
+@@ -35,6 +35,7 @@
+     "move",
+     "put",
+     "mkcollection",
++    "rmdir",
+ ]
+ 
+ import os
+@@ -287,7 +288,7 @@
+                     response = waitForDeferred(copy(FilePath(source_path), FilePath(destination_path), destination_uri, depth))
+                     yield response
+                     response = response.getResult()
+-                    checkResponse(response, "copy", responsecode.NO_CONTENT)
++                    checkResponse(response, "copy", responsecode.CREATED, responsecode.NO_CONTENT)
+ 
+             for subdir in subdirs:
+                 source_path, destination_path = paths(dir, subdir)
+@@ -509,7 +510,5 @@
+     os.rmdir(dirname)
+ 
+ def checkResponse(response, method, *codes):
+-    assert (
+-        response in codes,
+-        "%s() should have raised, but returned one of %r instead" % (method, codes)
+-    )
++    assert  response in codes, \
++        "%s() returned %r, but should have returned one of %r instead" % (method, response, codes)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.http.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.http.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.http.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.http.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,41 @@
+Index: twisted/web2/dav/http.py
+===================================================================
+--- twisted/web2/dav/http.py	(revision 19773)
++++ twisted/web2/dav/http.py	(working copy)
+@@ -28,10 +28,13 @@
+ 
+ __all__ = [
+     "ErrorResponse",
++    "NeedPrivilegesResponse",
+     "MultiStatusResponse",
+     "ResponseQueue",
+     "PropertyStatusResponseQueue",
+     "statusForFailure",
++    "errorForFailure",
++    "messageForFailure",
+ ]
+ 
+ import errno
+@@ -69,10 +72,9 @@
+         """
+         if type(error) is tuple:
+             xml_namespace, xml_name = error
+-            class EmptyError (davxml.WebDAVEmptyElement):
+-                namespace = xml_namespace
+-                name      = xml_name
+-            error = EmptyError()
++            error = davxml.WebDAVUnknownElement()
++            error.namespace = xml_namespace
++            error.name = xml_name
+ 
+         output = davxml.Error(error).toxml()
+ 
+@@ -227,7 +229,7 @@
+ 
+         if len(property.children) > 0:
+             # Re-instantiate as empty element.
+-            property = property.__class__()
++            property = property.emptyCopy()
+ 
+         if code > 400: # Error codes only
+             log.err("Error during %s for %s: %s" % (self.method, property, message))

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,157 @@
+Index: twisted/web2/dav/idav.py
+===================================================================
+--- twisted/web2/dav/idav.py	(revision 19773)
++++ twisted/web2/dav/idav.py	(working copy)
+@@ -26,7 +26,7 @@
+ web2.dav interfaces.
+ """
+ 
+-__all__ = [ "IDAVResource", "IDAVPrincipalResource" ]
++__all__ = [ "IDAVResource", "IDAVPrincipalResource", "IDAVPrincipalCollectionResource", ]
+ 
+ from twisted.web2.iweb import IResource
+ 
+@@ -41,7 +41,7 @@
+             otherwise.
+         """
+ 
+-    def findChildren(depth, request, callback, privileges):
++    def findChildren(depth, request, callback, privileges, inherited_aces):
+         """
+         Returns an iterable of child resources for the given depth.
+         Because resources do not know their request URIs, chidren are returned
+@@ -52,6 +52,8 @@
+         @param callback: C{callable} that will be called for each child found
+         @param privileges: the list of L{Privilege}s to test for.  This should 
+             default to None.
++        @param inherited_aces: a list of L{Privilege}s for aces being inherited from
++            the parent collection used to bypass inheritance lookup.
+         @return: An L{Deferred} that fires when all the children have been found
+         """
+ 
+@@ -125,15 +127,10 @@
+             L{responsecode.UNAUTHORIZED}) if not authorized.
+         """
+ 
+-    def principalCollections(request):
++    def principalCollections():
+         """
+-        Provides the DAV:HRef's of collection resources which contain principal
+-        resources which may be used in access control entries on this resource.
+-        (RFC 3744, section 5.8)
+-        @param request: the request being processed.
+-        @return: a deferred sequence of L{davxml.HRef}s referring to
+-            collection resources which implement the
+-            C{DAV:principal-property-search} C{REPORT}.
++        @return: an interable of L{IDAVPrincipalCollectionResource}s which
++            contain principals used in ACLs for this resource.
+         """
+ 
+     def setAccessControlList(acl):
+@@ -180,6 +177,80 @@
+             the specified principal.
+         """
+ 
++    ##
++    # Quota
++    ##
++    
++    def quota(request):
++        """
++        Get current available & used quota values for this resource's quota root
++        collection.
++
++        @return: a C{tuple} containing two C{int}'s the first is 
++            quota-available-bytes, the second is quota-used-bytes, or
++            C{None} if quota is not defined on the resource.
++        """
++    
++    def hasQuota(request):
++        """
++        Check whether this resource is undre quota control by checking each parent to see if
++        it has a quota root.
++        
++        @return: C{True} if under quota control, C{False} if not.
++        """
++        
++    def hasQuotaRoot(request):
++        """
++        Determine whether the resource has a quota root.
++
++        @return: a C{True} if this resource has quota root, C{False} otherwise.
++        """
++    
++
++    def quotaRoot(request):
++        """
++        Get the quota root (max. allowed bytes) value for this collection.
++
++        @return: a C{int} containing the maximum allowed bytes if this collection
++            is quota-controlled, or C{None} if not quota controlled.
++        """
++    
++    def setQuotaRoot(request, maxsize):
++        """
++        Set the quota root (max. allowed bytes) value for this collection.
++
++        @param maxsize: a C{int} containing the maximum allowed bytes for the contents
++            of this collection.
++        """
++    
++    def quotaSize(request):
++        """
++        Get the size of this resource (if its a collection get total for all children as well).
++        TODO: Take into account size of dead-properties.
++
++        @return: a L{Deferred} with a C{int} result containing the size of the resource.
++        """
++        
++    def currentQuotaUse(request):
++        """
++        Get the cached quota use value, or if not present (or invalid) determine
++        quota use by brute force.
++
++        @return: an L{Deferred} with a C{int} result containing the current used byte count if
++            this collection is quota-controlled, or C{None} if not quota controlled.
++        """
++        
++    def updateQuotaUse(request, adjust):
++        """
++        Adjust current quota use on this all all parent collections that also
++        have quota roots.
++
++        @param adjust: a C{int} containing the number of bytes added (positive) or
++        removed (negative) that should be used to adjust the cached total.
++        @return: an L{Deferred} with a C{int} result containing the current used byte if this collection
++            is quota-controlled, or C{None} if not quota controlled.
++        """
++
+ class IDAVPrincipalResource (IDAVResource):
+     """
+     WebDAV principal resource.  (RFC 3744, section 2)
+@@ -203,12 +274,23 @@
+         """
+         Provides the principal URLs of principals that are direct members of
+         this (group) principal.  (RFC 3744, section 4.3)
+-        @return: a iterable of principal URLs.
++        @return: a deferred returning an iterable of principal URLs.
+         """
+ 
+     def groupMemberships():
+         """
+         Provides the URLs of the group principals in which the principal is
+         directly a member.  (RFC 3744, section 4.4)
+-        @return: a iterable of group principal URLs.
++        @return: a deferred containing an iterable of group principal URLs.
+         """
++
++class IDAVPrincipalCollectionResource (IDAVResource):
++    """
++    WebDAV principal collection resource.  (RFC 3744, section 5.8)
++    """
++    def principalCollectionURL():
++        """
++        Provides a URL for this resource which may be used to identify this
++        resource in ACL requests.  (RFC 3744, section 5.8)
++        @return: a URL.
++        """

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,12 @@
+Index: twisted/web2/dav/method/__init__.py
+===================================================================
+--- twisted/web2/dav/method/__init__.py	(revision 19773)
++++ twisted/web2/dav/method/__init__.py	(working copy)
+@@ -40,6 +40,7 @@
+     "proppatch",
+     "prop_common",
+     "put",
++    "put_common",
+     "report",
+     "report_acl_principal_prop_set",
+     "report_expand",

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,76 @@
+Index: twisted/web2/dav/method/copymove.py
+===================================================================
+--- twisted/web2/dav/method/copymove.py	(revision 19773)
++++ twisted/web2/dav/method/copymove.py	(working copy)
+@@ -34,11 +34,12 @@
+ from twisted.python import log
+ from twisted.internet.defer import waitForDeferred, deferredGenerator
+ from twisted.web2 import responsecode
++from twisted.web2.dav.fileop import move
+ from twisted.web2.http import HTTPError, StatusResponse
+ from twisted.web2.filter.location import addLocation
+ from twisted.web2.dav import davxml
+ from twisted.web2.dav.idav import IDAVResource
+-from twisted.web2.dav.fileop import copy, move
++from twisted.web2.dav.method import put_common
+ from twisted.web2.dav.util import parentForURL
+ 
+ # FIXME: This is circular
+@@ -81,7 +82,15 @@
+         # May need to add a location header
+         addLocation(request, destination_uri)
+ 
+-    x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth))
++    #x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth))
++    x = waitForDeferred(put_common.storeResource(request,
++                                                 source=self,
++                                                 source_uri=request.uri,
++                                                 destination=destination,
++                                                 destination_uri=destination_uri,
++                                                 deletesource=False,
++                                                 depth=depth
++                                                 ))
+     yield x
+     yield x.getResult()
+ 
+@@ -100,7 +109,8 @@
+     #
+     # Check authentication and access controls
+     #
+-    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
++    parentURL = parentForURL(request.uri)
++    parent = waitForDeferred(request.locateResource(parentURL))
+     yield parent
+     parent = parent.getResult()
+ 
+@@ -117,7 +127,8 @@
+         yield x
+         x.getResult()
+     else:
+-        destparent = waitForDeferred(request.locateResource(parentForURL(destination_uri)))
++        destparentURL = parentForURL(destination_uri)
++        destparent = waitForDeferred(request.locateResource(destparentURL))
+         yield destparent
+         destparent = destparent.getResult()
+ 
+@@ -144,7 +155,19 @@
+         log.err(msg)
+         raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
+ 
+-    x = waitForDeferred(move(self.fp, request.uri, destination.fp, destination_uri, depth))
++    # Lets optimise a move within the same directory to a new resource as a simple move
++    # rather than using the full transaction based storeResource api. This allows simple
++    # "rename" operations to work quickly.
++    if (not destination.exists()) and destparent == parent:
++        x = waitForDeferred(move(self.fp, request.uri, destination.fp, destination_uri, depth))
++    else:
++        x = waitForDeferred(put_common.storeResource(request,
++                                                     source=self,
++                                                     source_uri=request.uri,
++                                                     destination=destination,
++                                                     destination_uri=destination_uri,
++                                                     deletesource=True,
++                                                     depth=depth))
+     yield x
+     yield x.getResult()
+ 

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,34 @@
+Index: twisted/web2/dav/method/delete.py
+===================================================================
+--- twisted/web2/dav/method/delete.py	(revision 19773)
++++ twisted/web2/dav/method/delete.py	(working copy)
+@@ -58,8 +58,28 @@
+     yield x
+     x.getResult()
+ 
++    # Do quota checks before we start deleting things
++    myquota = waitForDeferred(self.quota(request))
++    yield myquota
++    myquota = myquota.getResult()
++    if myquota is not None:
++        old_size = waitForDeferred(self.quotaSize(request))
++        yield old_size
++        old_size = old_size.getResult()
++    else:
++        old_size = 0
++
++    # Do delete
+     x = waitForDeferred(delete(request.uri, self.fp, depth))
+     yield x
+-    yield x.getResult()
++    result = x.getResult()
+ 
++    # Adjust quota
++    if myquota is not None:
++        d = waitForDeferred(self.quotaSizeAdjust(request, -old_size))
++        yield d
++        d.getResult()
++    
++    yield result
++
+ http_DELETE = deferredGenerator(http_DELETE)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.prop_common.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.prop_common.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.prop_common.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.prop_common.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,36 @@
+Index: twisted/web2/dav/method/prop_common.py
+===================================================================
+--- twisted/web2/dav/method/prop_common.py	(revision 19773)
++++ twisted/web2/dav/method/prop_common.py	(working copy)
+@@ -23,19 +23,21 @@
+         properties_by_status = waitForDeferred(propertiesForResource(request, propertyreq, resource))
+         yield properties_by_status
+         properties_by_status = properties_by_status.getResult()
+-        
++
++        propstats = []
++
+         for status in properties_by_status:
+             properties = properties_by_status[status]
+             if properties:
+-                responses.append(
+-                    davxml.PropertyStatusResponse(
+-                        href,
+-                        davxml.PropertyStatus(
+-                            davxml.PropertyContainer(*properties),
+-                            davxml.Status.fromResponseCode(status)
+-                        )
+-                    )
+-                )
++                xml_status = davxml.Status.fromResponseCode(status)
++                xml_container = davxml.PropertyContainer(*properties)
++                xml_propstat = davxml.PropertyStatus(xml_container, xml_status)
++
++                propstats.append(xml_propstat)
++
++        if propstats:
++            responses.append(davxml.PropertyStatusResponse(href, *propstats))
++
+     else:
+         responses.append(
+             davxml.StatusResponse(

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,28 @@
+Index: twisted/web2/dav/method/propfind.py
+===================================================================
+--- twisted/web2/dav/method/propfind.py	(revision 19773)
++++ twisted/web2/dav/method/propfind.py	(working copy)
+@@ -27,7 +27,10 @@
+ WebDAV PROPFIND method
+ """
+ 
+-__all__ = ["http_PROPFIND"]
++__all__ = [
++    "http_PROPFIND",
++    "propertyName",
++]
+ 
+ from twisted.python import log
+ from twisted.python.failure import Failure
+@@ -200,7 +203,7 @@
+ 
+ def propertyName(name):
+     property_namespace, property_name = name
+-    class PropertyName (davxml.WebDAVEmptyElement):
+-        namespace = property_namespace
+-        name = property_name
+-    return PropertyName()
++    pname = davxml.WebDAVUnknownElement()
++    pname.namespace = property_namespace
++    pname.name = property_name
++    return pname

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,20 @@
+Index: twisted/web2/dav/method/put.py
+===================================================================
+--- twisted/web2/dav/method/put.py	(revision 19773)
++++ twisted/web2/dav/method/put.py	(working copy)
+@@ -34,7 +34,7 @@
+ from twisted.web2 import responsecode
+ from twisted.web2.http import HTTPError, StatusResponse
+ from twisted.web2.dav import davxml
+-from twisted.web2.dav.fileop import put
++from twisted.web2.dav.method import put_common
+ from twisted.web2.dav.util import parentForURL
+ 
+ def preconditions_PUT(self, request):
+@@ -107,4 +107,5 @@
+     # to return a MULTI_STATUS response, which is WebDAV-specific (and PUT is
+     # not).
+     #
+-    return put(request.stream, self.fp)
++    #return put(request.stream, self.fp)
++    return put_common.storeResource(request, destination=self, destination_uri=request.uri)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,270 @@
+Index: twisted/web2/dav/method/put_common.py
+===================================================================
+--- twisted/web2/dav/method/put_common.py	(revision 0)
++++ twisted/web2/dav/method/put_common.py	(revision 0)
+@@ -0,0 +1,265 @@
++##
++# 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
++##
++
++"""
++PUT/COPY/MOVE common behavior.
++"""
++
++__version__ = "0.0"
++
++__all__ = ["storeCalendarObjectResource"]
++
++from twisted.internet.defer import deferredGenerator, maybeDeferred, waitForDeferred
++from twisted.python import failure, log
++from twisted.python.filepath import FilePath
++from twisted.web2 import responsecode
++from twisted.web2.dav import davxml
++from twisted.web2.dav.element.base import dav_namespace
++from twisted.web2.dav.fileop import copy, delete, put
++from twisted.web2.dav.http import ErrorResponse
++from twisted.web2.dav.resource import TwistedGETContentMD5
++from twisted.web2.dav.stream import MD5StreamWrapper
++from twisted.web2.http import HTTPError
++from twisted.web2.http_headers import generateContentType
++from twisted.web2.iweb import IResponse
++from twisted.web2.stream import MemoryStream
++
++def storeResource(
++    request,
++    source=None, source_uri=None, data=None,
++    destination=None, destination_uri=None,
++    deletesource=False,
++    depth="0"
++):
++    """
++    Function that does common PUT/COPY/MOVE behaviour.
++    
++    @param request:           the L{twisted.web2.server.Request} for the current HTTP request.
++    @param source:            the L{DAVFile} for the source resource to copy from, or None if source data
++                              is to be read from the request.
++    @param source_uri:        the URI for the source resource.
++    @param data:              a C{str} to copy data from instead of the request stream.
++    @param destination:       the L{DAVFile} for the destination resource to copy into.
++    @param destination_uri:   the URI for the destination resource.
++    @param deletesource:      True if the source resource is to be deleted on successful completion, False otherwise.
++    @param depth:             a C{str} containing the COPY/MOVE Depth header value.
++    @return:                  status response.
++    """
++    
++    try:
++        assert request is not None and destination is not None and destination_uri is not None
++        assert (source is None) or (source is not None and source_uri is not None)
++        assert not deletesource or (deletesource and source is not None)
++    except AssertionError:
++        log.err("Invalid arguments to storeResource():")
++        log.err("request=%s\n" % (request,))
++        log.err("source=%s\n" % (source,))
++        log.err("source_uri=%s\n" % (source_uri,))
++        log.err("data=%s\n" % (data,))
++        log.err("destination=%s\n" % (destination,))
++        log.err("destination_uri=%s\n" % (destination_uri,))
++        log.err("deletesource=%s\n" % (deletesource,))
++        log.err("depth=%s\n" % (depth,))
++        raise
++
++    class RollbackState(object):
++        """
++        This class encapsulates the state needed to rollback the entire PUT/COPY/MOVE
++        transaction, leaving the server state the same as it was before the request was
++        processed. The DoRollback method will actually execute the rollback operations.
++        """
++        
++        def __init__(self):
++            self.active = True
++            self.source_copy = None
++            self.destination_copy = None
++            self.destination_created = False
++            self.source_deleted = False
++        
++        def Rollback(self):
++            """
++            Rollback the server state. Do not allow this to raise another exception. If
++            rollback fails then we are going to be left in an awkward state that will need
++            to be cleaned up eventually.
++            """
++            if self.active:
++                self.active = False
++                log.err("Rollback: rollback")
++                try:
++                    if self.source_copy and self.source_deleted:
++                        self.source_copy.moveTo(source.fp)
++                        log.err("Rollback: source restored %s to %s" % (self.source_copy.path, source.fp.path))
++                        self.source_copy = None
++                        self.source_deleted = False
++                    if self.destination_copy:
++                        destination.fp.remove()
++                        log.err("Rollback: destination restored %s to %s" % (self.destination_copy.path, destination.fp.path))
++                        self.destination_copy.moveTo(destination.fp)
++                        self.destination_copy = None
++                    elif self.destination_created:
++                        destination.fp.remove()
++                        log.err("Rollback: destination removed %s" % (destination.fp.path,))
++                        self.destination_created = False
++                except:
++                    log.err("Rollback: exception caught and not handled: %s" % failure.Failure())
++
++        def Commit(self):
++            """
++            Commit the resource changes by wiping the rollback state.
++            """
++            if self.active:
++                log.err("Rollback: commit")
++                self.active = False
++                if self.source_copy:
++                    self.source_copy.remove()
++                    log.err("Rollback: removed source backup %s" % (self.source_copy.path,))
++                    self.source_copy = None
++                if self.destination_copy:
++                    self.destination_copy.remove()
++                    log.err("Rollback: removed destination backup %s" % (self.destination_copy.path,))
++                    self.destination_copy = None
++                self.destination_created = False
++                self.source_deleted = False
++    
++    rollback = RollbackState()
++
++    try:
++        """
++        Handle validation operations here.
++        """
++
++        """
++        Handle rollback setup here.
++        """
++
++        # Do quota checks on destination and source before we start messing with adding other files
++        destquota = waitForDeferred(destination.quota(request))
++        yield destquota
++        destquota = destquota.getResult()
++        if destquota is not None and destination.exists():
++            old_dest_size = waitForDeferred(destination.quotaSize(request))
++            yield old_dest_size
++            old_dest_size = old_dest_size.getResult()
++        else:
++            old_dest_size = 0
++            
++        if source is not None:
++            sourcequota = waitForDeferred(source.quota(request))
++            yield sourcequota
++            sourcequota = sourcequota.getResult()
++            if sourcequota is not None and source.exists():
++                old_source_size = waitForDeferred(source.quotaSize(request))
++                yield old_source_size
++                old_source_size = old_source_size.getResult()
++            else:
++                old_source_size = 0
++        else:
++            sourcequota = None
++            old_source_size = 0
++
++        # We may need to restore the original resource data if the PUT/COPY/MOVE fails,
++        # so rename the original file in case we need to rollback.
++        overwrite = destination.exists()
++        if overwrite:
++            rollback.destination_copy = FilePath(destination.fp.path)
++            rollback.destination_copy.path += ".rollback"
++            destination.fp.copyTo(rollback.destination_copy)
++        else:
++            rollback.destination_created = True
++
++        if deletesource:
++            rollback.source_copy = FilePath(source.fp.path)
++            rollback.source_copy.path += ".rollback"
++            source.fp.copyTo(rollback.source_copy)
++    
++        """
++        Handle actual store operations here.
++        """
++
++        # Do put or copy based on whether source exists
++        if source is not None:
++            response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, depth)
++        else:
++            datastream = request.stream
++            if data is not None:
++                datastream = MemoryStream(data)
++            md5 = MD5StreamWrapper(datastream)
++            response = maybeDeferred(put, md5, destination.fp)
++
++        response = waitForDeferred(response)
++        yield response
++        response = response.getResult()
++
++        # Update the MD5 value on the resource
++        if source is not None:
++            # Copy MD5 value from source to destination
++            if source.hasDeadProperty(TwistedGETContentMD5):
++                md5 = source.readDeadProperty(TwistedGETContentMD5)
++                destination.writeDeadProperty(md5)
++        else:
++            # Finish MD5 calc and write dead property
++            md5.close()
++            md5 = md5.getMD5()
++            destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
++
++        # Update the content-type value on the resource if it is not been copied or moved
++        if source is None:
++            content_type = request.headers.getHeader("content-type")
++            if content_type is not None:
++                destination.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(content_type)))
++
++        response = IResponse(response)
++        
++        # Do quota check on destination
++        if destquota is not None:
++            # Get size of new/old resources
++            new_dest_size = waitForDeferred(destination.quotaSize(request))
++            yield new_dest_size
++            new_dest_size = new_dest_size.getResult()
++            diff_size = new_dest_size - old_dest_size
++            if diff_size >= destquota[0]:
++                log.err("Over quota: available %d, need %d" % (destquota[0], diff_size))
++                raise HTTPError(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
++            d = waitForDeferred(destination.quotaSizeAdjust(request, diff_size))
++            yield d
++            d.getResult()
++
++        if deletesource:
++            # Delete the source resource
++            if sourcequota is not None:
++                delete_size = 0 - old_source_size
++                d = waitForDeferred(source.quotaSizeAdjust(request, delete_size))
++                yield d
++                d.getResult()
++
++            delete(source_uri, source.fp, depth)
++            rollback.source_deleted = True
++
++        # Can now commit changes and forget the rollback details
++        rollback.Commit()
++
++        yield response
++        return
++        
++    except:
++        # Roll back changes to original server state. Note this may do nothing
++        # if the rollback has already ocurred or changes already committed.
++        rollback.Rollback()
++        raise
++
++storeResource = deferredGenerator(storeResource)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,33 @@
+Index: twisted/web2/dav/method/report.py
+===================================================================
+--- twisted/web2/dav/method/report.py	(revision 19773)
++++ twisted/web2/dav/method/report.py	(working copy)
+@@ -27,7 +27,11 @@
+ WebDAV REPORT method
+ """
+ 
+-__all__ = ["http_REPORT"]
++__all__ = [
++    "max_number_of_matches",
++    "NumberOfMatchesWithinLimits",
++    "http_REPORT",
++]
+ 
+ import string
+ 
+@@ -43,7 +47,14 @@
+ max_number_of_matches = 500
+ 
+ class NumberOfMatchesWithinLimits(Exception):
+-    pass
++    
++    def __init__(self, limit):
++        
++        super(NumberOfMatchesWithinLimits, self).__init__()
++        self.limit = limit
++        
++    def maxLimit(self):
++        return self.limit
+ 
+ def http_REPORT(self, request):
+     """

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_acl_principal_prop_set.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_acl_principal_prop_set.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_acl_principal_prop_set.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_acl_principal_prop_set.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,22 @@
+Index: twisted/web2/dav/method/report_acl_principal_prop_set.py
+===================================================================
+--- twisted/web2/dav/method/report_acl_principal_prop_set.py	(revision 19773)
++++ twisted/web2/dav/method/report_acl_principal_prop_set.py	(working copy)
+@@ -103,7 +103,7 @@
+             # Check size of results is within limit
+             matchcount += 1
+             if matchcount > max_number_of_matches:
+-                raise NumberOfMatchesWithinLimits
++                raise NumberOfMatchesWithinLimits(max_number_of_matches)
+ 
+             resource = waitForDeferred(request.locateResource(str(principal)))
+             yield resource
+@@ -144,7 +144,7 @@
+         log.err("Too many matching components")
+         raise HTTPError(ErrorResponse(
+             responsecode.FORBIDDEN,
+-            (dav_namespace, "number-of-matches-within-limits")
++            davxml.NumberOfMatchesWithinLimits()
+         ))
+ 
+     yield MultiStatusResponse(responses)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,214 @@
+Index: twisted/web2/dav/method/report_expand.py
+===================================================================
+--- twisted/web2/dav/method/report_expand.py	(revision 19773)
++++ twisted/web2/dav/method/report_expand.py	(working copy)
+@@ -1,6 +1,6 @@
+ # -*- test-case-name: twisted.web2.dav.test.test_report_expand -*-
+ ##
+-# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++# Copyright (c) 2005-2008 Apple Computer, Inc. All rights reserved.
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
+ # of this software and associated documentation files (the "Software"), to deal
+@@ -19,8 +19,6 @@
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ # SOFTWARE.
+-#
+-# DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
+ 
+ """
+@@ -29,86 +27,143 @@
+ 
+ __all__ = ["report_DAV__expand_property"]
+ 
++from twisted.internet.defer import inlineCallbacks, returnValue
+ from twisted.python import log
+ from twisted.python.failure import Failure
+-from twisted.internet.defer import deferredGenerator, waitForDeferred
+ from twisted.web2 import responsecode
+ from twisted.web2.dav import davxml
+-from twisted.web2.dav.http import statusForFailure
+ from twisted.web2.dav.davxml import dav_namespace
++from twisted.web2.dav.http import statusForFailure, MultiStatusResponse
++from twisted.web2.dav.method import prop_common
++from twisted.web2.dav.method.propfind import propertyName
++from twisted.web2.dav.resource import AccessDeniedError
++from twisted.web2.dav.util import parentForURL
++from twisted.web2.http import HTTPError, StatusResponse
+ 
++ at inlineCallbacks
+ def report_DAV__expand_property(self, request, expand_property):
+     """
+     Generate an expand-property REPORT. (RFC 3253, section 3.8)
++    
++    TODO: for simplicity we will only support one level of expansion.
+     """
+-    # FIXME: Handle depth header
+-
++    # Verify root element
+     if not isinstance(expand_property, davxml.ExpandProperty):
+         raise ValueError("%s expected as root element, not %s."
+                          % (davxml.ExpandProperty.sname(), expand_property.sname()))
+ 
++    # Only handle Depth: 0
++    depth = request.headers.getHeader("depth", "0")
++    if depth != "0":
++        log.err("Non-zero depth is not allowed: %s" % (depth,))
++        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
++    
+     #
+-    # Expand DAV:allprop
++    # Get top level properties to expand and make sure we only have one level
+     #
+     properties = {}
+ 
+     for property in expand_property.children:
+-        namespace = property.getAttribute("namespace")
+-        name      = property.getAttribute("name")
++        namespace = property.attributes.get("namespace", dav_namespace)
++        name      = property.attributes.get("name", "")
++        
++        # Make sure children have no children
++        props_to_find = []
++        for child in property.children:
++            if child.children:
++                log.err("expand-property REPORT only supports single level expansion")
++                raise HTTPError(StatusResponse(
++                    responsecode.NOT_IMPLEMENTED,
++                    "expand-property REPORT only supports single level expansion"
++                ))
++            child_namespace = child.attributes.get("namespace", dav_namespace)
++            child_name      = child.attributes.get("name", "")
++            props_to_find.append((child_namespace, child_name))
+ 
+-        if not namespace: namespace = dav_namespace
++        properties[(namespace, name)] = props_to_find
+ 
+-        if (namespace, name) == (dav_namespace, "allprop"):
+-            all_properties = waitForDeferred(self.listAllProp(request))
+-            yield all_properties
+-            all_properties = all_properties.getResult()
+-
+-            for all_property in all_properties:
+-                properties[all_property.qname()] = property
+-        else:
+-            properties[(namespace, name)] = property
+-
+     #
+-    # Look up the requested properties
++    # Generate the expanded responses status for each top-level property
+     #
+     properties_by_status = {
+         responsecode.OK        : [],
+         responsecode.NOT_FOUND : [],
+     }
++    
++    filteredaces = None
++    lastParent = None
+ 
+-    for property in properties:
+-        my_properties = waitForDeferred(self.listProperties(request))
+-        yield my_properties
+-        my_properties = my_properties.getResult()
++    for qname in properties.iterkeys():
++        try:
++            prop = (yield self.readProperty(qname, request))
++            
++            # Form the PROPFIND-style DAV:prop element we need later
++            props_to_return = davxml.PropertyContainer(*properties[qname])
+ 
+-        if property in my_properties:
+-            try:
+-                value = waitForDeferred(self.readProperty(property, request))
+-                yield value
+-                value = value.getResult()
++            # Now dereference any HRefs
++            responses = []
++            for href in prop.children:
++                if isinstance(href, davxml.HRef):
++                    
++                    # Locate the Href resource and its parent
++                    resource_uri = str(href)
++                    child = (yield request.locateResource(resource_uri))
++    
++                    if not child or not child.exists():
++                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
++                        continue
++                    parent = (yield request.locateResource(parentForURL(resource_uri)))
++    
++                    # Check privileges on parent - must have at least DAV:read
++                    try:
++                        yield parent.checkPrivileges(request, (davxml.Read(),))
++                    except AccessDeniedError:
++                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
++                        continue
++                    
++                    # Cache the last parent's inherited aces for checkPrivileges optimization
++                    if lastParent != parent:
++                        lastParent = parent
++                
++                        # Do some optimisation of access control calculation by determining any inherited ACLs outside of
++                        # the child resource loop and supply those to the checkPrivileges on each child.
++                        filteredaces = (yield parent.inheritedACEsforChildren(request))
+ 
+-                if isinstance(value, davxml.HRef):
+-                    raise NotImplementedError()
+-                else:
+-                    raise NotImplementedError()
+-            except:
+-                f = Failure()
++                    # Check privileges - must have at least DAV:read
++                    try:
++                        yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
++                    except AccessDeniedError:
++                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
++                        continue
++            
++                    # Now retrieve all the requested properties on the HRef resource
++                    yield prop_common.responseForHref(
++                        request,
++                        responses,
++                        href,
++                        child,
++                        prop_common.propertyListForResource,
++                        props_to_return,
++                    )
++            
++            prop.children = responses
++            properties_by_status[responsecode.OK].append(prop)
++        except:
++            f = Failure()
+ 
+-                log.err("Error reading property %r for resource %s: %s"
+-                        % (property, self, f.value))
++            log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
+ 
+-                status = statusForFailure(f, "getting property: %s" % (property,))
+-                if status not in properties_by_status:
+-                    properties_by_status[status] = []
++            status = statusForFailure(f, "getting property: %s" % (qname,))
++            if status not in properties_by_status: properties_by_status[status] = []
++            properties_by_status[status].append(propertyName(qname))
+ 
+-                raise NotImplementedError()
++    # Build the overall response
++    propstats = [
++        davxml.PropertyStatus(
++            davxml.PropertyContainer(*properties_by_status[status]),
++            davxml.Status.fromResponseCode(status)
++        )
++        for status in properties_by_status if properties_by_status[status]
++    ]
+ 
+-                #properties_by_status[status].append(
+-                #    ____propertyName(property)
+-                #)
+-        else:
+-            properties_by_status[responsecode.NOT_FOUND].append(property)
+-
+-    raise NotImplementedError()
+-
+-report_DAV__expand_property = deferredGenerator(report_DAV__expand_property)
++    returnValue(MultiStatusResponse((davxml.PropertyStatusResponse(davxml.HRef(request.uri), *propstats),)))

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_match.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_match.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_match.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_match.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,150 @@
+Index: twisted/web2/dav/method/report_principal_match.py
+===================================================================
+--- twisted/web2/dav/method/report_principal_match.py	(revision 19773)
++++ twisted/web2/dav/method/report_principal_match.py	(working copy)
+@@ -89,40 +89,64 @@
+         responses = []
+         matchcount = 0
+ 
+-        selfPrincipal = self.currentPrincipal(request).children[0]
++        selfPrincipalURL = self.currentPrincipal(request).children[0]
+ 
+-        # Do some optimisation of access control calculation by determining any inherited ACLs outside of
+-        # the child resource loop and supply those to the checkPrivileges on each child.
+-        filteredaces = waitForDeferred(self.inheritedACEsforChildren(request))
+-        yield filteredaces
+-        filteredaces = filteredaces.getResult()
+-    
+-        children = []
+-        d = waitForDeferred(self.findChildren("infinity", request, lambda x, y: children.append((x,y)),
+-                                              privileges=(davxml.Read(),), inherited_aces=filteredaces))
+-        yield d
+-        d.getResult()
+-
+         if lookForPrincipals:
+ 
+-            for child, uri in children:
+-                if isPrincipalResource(child) and child.principalMatch(selfPrincipal):
+-                    # Check size of results is within limit
+-                    matchcount += 1
+-                    if matchcount > max_number_of_matches:
+-                        raise NumberOfMatchesWithinLimits
++            # Find the set of principals that represent "self".
++            
++            # First add "self"
++            principal = waitForDeferred(request.locateResource(str(selfPrincipalURL)))
++            yield principal
++            principal = principal.getResult()
++            selfItems = [principal,]
++            
++            # Get group memberships for "self" and add each of those
++            d = waitForDeferred(principal.groupMemberships())
++            yield d
++            memberships = d.getResult()
++            selfItems.extend(memberships)
++            
++            # Now add each principal found to the response provided the principal resource is a child of
++            # the current resource.
++            for principal in selfItems:
++                # Get all the URIs that point to the principal resource
++                # FIXME: making the assumption that the principalURL() is the URL of the resource we found
++                principal_uris = [principal.principalURL()]
++                principal_uris.extend(principal.alternateURIs())
++                
++                # Compare each one to the request URI and return at most one that matches
++                for uri in principal_uris:
++                    if uri.startswith(request.uri):
++                        # Check size of results is within limit
++                        matchcount += 1
++                        if matchcount > max_number_of_matches:
++                            raise NumberOfMatchesWithinLimits(max_number_of_matches)
++        
++                        d = waitForDeferred(prop_common.responseForHref(
++                            request,
++                            responses,
++                            davxml.HRef.fromString(uri),
++                            principal,
++                            propertiesForResource,
++                            propElement
++                        ))
++                        yield d
++                        d.getResult()
++                        break
++        else:
++            # Do some optimisation of access control calculation by determining any inherited ACLs outside of
++            # the child resource loop and supply those to the checkPrivileges on each child.
++            filteredaces = waitForDeferred(self.inheritedACEsforChildren(request))
++            yield filteredaces
++            filteredaces = filteredaces.getResult()
++        
++            children = []
++            d = waitForDeferred(self.findChildren("infinity", request, lambda x, y: children.append((x,y)),
++                                                  privileges=(davxml.Read(),), inherited_aces=filteredaces))
++            yield d
++            d.getResult()
+ 
+-                    d = waitForDeferred(prop_common.responseForHref(
+-                        request,
+-                        responses,
+-                        davxml.HRef.fromString(uri),
+-                        child,
+-                        propertiesForResource,
+-                        propElement
+-                    ))
+-                    yield d
+-                    d.getResult()
+-        else:
+             for child, uri in children:
+                 # Try to read the requested property from this resource
+                 try:
+@@ -137,22 +161,26 @@
+                         yield principal
+                         principal = principal.getResult()
+ 
+-                        if principal and isPrincipalResource(principal) and principal.principalMatch(selfPrincipal):
+-                            # Check size of results is within limit
+-                            matchcount += 1
+-                            if matchcount > max_number_of_matches:
+-                                raise NumberOfMatchesWithinLimits
+-
+-                            d = waitForDeferred(prop_common.responseForHref(
+-                                request,
+-                                responses,
+-                                davxml.HRef.fromString(uri),
+-                                child,
+-                                propertiesForResource,
+-                                propElement
+-                            ))
++                        if principal and isPrincipalResource(principal):
++                            d = waitForDeferred(principal.principalMatch(selfPrincipalURL))
+                             yield d
+-                            d.getResult()
++                            matched = d.getResult()
++                            if matched:
++                                # Check size of results is within limit
++                                matchcount += 1
++                                if matchcount > max_number_of_matches:
++                                    raise NumberOfMatchesWithinLimits(max_number_of_matches)
++    
++                                d = waitForDeferred(prop_common.responseForHref(
++                                    request,
++                                    responses,
++                                    davxml.HRef.fromString(uri),
++                                    child,
++                                    propertiesForResource,
++                                    propElement
++                                ))
++                                yield d
++                                d.getResult()
+                 except HTTPError:
+                     # Just ignore a failure to access the property. We treat this like a property that does not exist
+                     # or does not match the principal.
+@@ -162,7 +190,7 @@
+         log.err("Too many matching components in principal-match report")
+         raise HTTPError(ErrorResponse(
+             responsecode.FORBIDDEN,
+-            (dav_namespace, "number-of-matches-within-limits")
++            davxml.NumberOfMatchesWithinLimits()
+         ))
+ 
+     yield MultiStatusResponse(responses)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,22 @@
+Index: twisted/web2/dav/method/report_principal_property_search.py
+===================================================================
+--- twisted/web2/dav/method/report_principal_property_search.py	(revision 19773)
++++ twisted/web2/dav/method/report_principal_property_search.py	(working copy)
+@@ -166,7 +166,7 @@
+                         # Check size of results is within limit
+                         matchcount += 1
+                         if matchcount > max_number_of_matches:
+-                            raise NumberOfMatchesWithinLimits
++                            raise NumberOfMatchesWithinLimits(max_number_of_matches)
+     
+                         d = waitForDeferred(prop_common.responseForHref(
+                             request,
+@@ -183,7 +183,7 @@
+         log.err("Too many matching components in prinicpal-property-search report")
+         raise HTTPError(ErrorResponse(
+             responsecode.FORBIDDEN,
+-            (dav_namespace, "number-of-matches-within-limits")
++            davxml.NumberOfMatchesWithinLimits()
+         ))
+ 
+     yield MultiStatusResponse(responses)

Deleted: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -1,13 +0,0 @@
-Index: twisted/web2/dav/resource.py
-===================================================================
---- twisted/web2/dav/resource.py	(revision 26741)
-+++ twisted/web2/dav/resource.py	(working copy)
-@@ -1888,7 +1888,7 @@
-         # If this is a collection and the URI doesn't end in "/", redirect.
-         #
-         if self.isCollection() and request.path[-1:] != "/":
--            return RedirectResponse(request.unparseURL(path=request.path+'/'))
-+            return RedirectResponse(request.unparseURL(path=urllib.quote(request.path, safe=':/')+'/'))
- 
-         def setHeaders(response):
-             response = IResponse(response)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,1238 @@
+Index: twisted/web2/dav/resource.py
+===================================================================
+--- twisted/web2/dav/resource.py	(revision 19773)
++++ twisted/web2/dav/resource.py	(working copy)
+@@ -31,20 +31,31 @@
+     "DAVResource",
+     "DAVLeafResource",
+     "DAVPrincipalResource",
++    "DAVPrincipalCollectionResource",
+     "AccessDeniedError",
+     "isPrincipalResource",
+     "TwistedACLInheritable",
++    "TwistedGETContentMD5",
++    "TwistedQuotaRootProperty",
+     "allACL",
+     "readonlyACL",
+     "davPrivilegeSet",
+     "unauthenticatedPrincipal",
+ ]
+ 
+ import urllib
++import __builtin__
++if not hasattr(__builtin__, "set"):
++    import sets.Set as set
++if not hasattr(__builtin__, "frozenset"):
++    import sets.ImmutableSet as frozenset
+ 
+ from zope.interface import implements
+ from twisted.python import log
+-from twisted.internet.defer import Deferred, maybeDeferred, succeed
++from twisted.python.failure import Failure
++from twisted.cred.error import LoginFailed, UnauthorizedLogin
++from twisted.internet.defer import Deferred, maybeDeferred, succeed,\
++    inlineCallbacks, returnValue
+ from twisted.internet.defer import waitForDeferred, deferredGenerator
+ from twisted.internet import reactor
+ from twisted.web2 import responsecode
+@@ -52,12 +62,13 @@
+ from twisted.web2.http_headers import generateContentType
+ from twisted.web2.iweb import IResponse
+ from twisted.web2.resource import LeafResource
++from twisted.web2.server import NoURLForResourceError
+ from twisted.web2.static import MetaDataMixin, StaticRenderMixin
+ from twisted.web2.auth.wrapper import UnauthorizedResponse
+ from twisted.web2.dav import davxml
+ from twisted.web2.dav.davxml import dav_namespace, lookupElement
+ from twisted.web2.dav.davxml import twisted_dav_namespace, twisted_private_namespace
+-from twisted.web2.dav.idav import IDAVResource, IDAVPrincipalResource
++from twisted.web2.dav.idav import IDAVResource, IDAVPrincipalResource, IDAVPrincipalCollectionResource
+ from twisted.web2.dav.http import NeedPrivilegesResponse
+ from twisted.web2.dav.noneprops import NonePropertyStore
+ from twisted.web2.dav.util import unimplemented, parentForURL, joinURL
+@@ -126,10 +137,13 @@
+        #(dav_namespace, "group"                     ), # RFC 3744, section 5.2
+         (dav_namespace, "supported-privilege-set"   ), # RFC 3744, section 5.3
+         (dav_namespace, "current-user-privilege-set"), # RFC 3744, section 5.4
++        (dav_namespace, "current-user-principal"    ), # draft-sanchez-webdav-current-principal
+         (dav_namespace, "acl"                       ), # RFC 3744, section 5.5
+         (dav_namespace, "acl-restrictions"          ), # RFC 3744, section 5.6
+         (dav_namespace, "inherited-acl-set"         ), # RFC 3744, section 5.7
+         (dav_namespace, "principal-collection-set"  ), # RFC 3744, section 5.8
++        (dav_namespace, "quota-available-bytes"     ), # RFC 4331, section 3
++        (dav_namespace, "quota-used-bytes"          ), # RFC 4331, section 4
+ 
+         (twisted_dav_namespace, "resource-class"),
+     )
+@@ -166,6 +180,14 @@
+         if qname[0] == twisted_private_namespace:
+             return succeed(False)
+ 
++        # Need to special case the dynamic live properties
++        namespace, name = qname
++        if namespace == dav_namespace:
++            if name in ("quota-available-bytes", "quota-used-bytes"):
++                d = self.hasQuota(request)
++                d.addCallback(lambda result: result)
++                return d
++        
+         return succeed(qname in self.liveProperties or self.deadProperties().contains(qname))
+ 
+     def readProperty(self, property, request):
+@@ -201,7 +223,6 @@
+                     mimeType = self.contentType()
+                     if mimeType is None:
+                         return None
+-                    mimeType.params = None # WebDAV getcontenttype property does not include parameters
+                     return davxml.GETContentType(generateContentType(mimeType))
+ 
+                 if name == "getcontentlength":
+@@ -239,8 +260,10 @@
+                     )
+ 
+                 if name == "supported-report-set":
+-                    supported = [davxml.SupportedReport(report,) for report in self.supportedReports()]
+-                    return davxml.SupportedReportSet(*supported)
++                    return davxml.SupportedReportSet(*[
++                        davxml.SupportedReport(report,)
++                        for report in self.supportedReports()
++                    ])
+ 
+                 if name == "supported-privilege-set":
+                     return self.supportedPrivileges(request)
+@@ -252,9 +275,10 @@
+                     return davxml.InheritedACLSet(*self.inheritedACLSet())
+ 
+                 if name == "principal-collection-set":
+-                    d = self.principalCollections(request)
+-                    d.addCallback(lambda collections: davxml.PrincipalCollectionSet(*collections))
+-                    return d
++                    return davxml.PrincipalCollectionSet(*[
++                        davxml.HRef(principalCollection.principalCollectionURL())
++                        for principalCollection in self.principalCollections()
++                    ])
+ 
+                 def ifAllowed(privileges, callback):
+                     def onError(failure):
+@@ -286,7 +310,36 @@
+                         d.addCallback(gotACL)
+                         return d
+                     return ifAllowed((davxml.ReadACL(),), callback)
++                
++                if name == "current-user-principal":
++                    return davxml.CurrentUserPrincipal(self.currentPrincipal(request).children[0])
+ 
++                if name == "quota-available-bytes":
++                    def callback(qvalue):
++                        if qvalue is None:
++                            raise HTTPError(StatusResponse(
++                                responsecode.NOT_FOUND,
++                                "Property %s does not exist." % (sname,)
++                            ))
++                        else:
++                            return davxml.QuotaAvailableBytes(str(qvalue[0]))
++                    d = self.quota(request)
++                    d.addCallback(callback)
++                    return d
++
++                if name == "quota-used-bytes":
++                    def callback(qvalue):
++                        if qvalue is None:
++                            raise HTTPError(StatusResponse(
++                                responsecode.NOT_FOUND,
++                                "Property %s does not exist." % (sname,)
++                            ))
++                        else:
++                            return davxml.QuotaUsedBytes(str(qvalue[1]))
++                    d = self.quota(request)
++                    d.addCallback(callback)
++                    return d
++
+             elif namespace == twisted_dav_namespace:
+                 if name == "resource-class":
+                     class ResourceClass (davxml.WebDAVTextElement):
+@@ -309,10 +362,7 @@
+         """
+         See L{IDAVResource.writeProperty}.
+         """
+-        assert (
+-            isinstance(property, davxml.WebDAVElement),
+-            "Not a property: %r" % (property,)
+-        )
++        assert isinstance(property, davxml.WebDAVElement), "Not a property: %r" % (property,)
+ 
+         def defer():
+             if property.protected:
+@@ -363,15 +413,28 @@
+         """
+         See L{IDAVResource.listProperties}.
+         """
+-        # FIXME: A set would be better here, that that's a python 2.4+ feature.
+-        qnames = list(self.liveProperties)
++        qnames = set(self.liveProperties)
+ 
++        # Add dynamic live properties that exist
++        dynamicLiveProperties = (
++            (dav_namespace, "quota-available-bytes"     ),
++            (dav_namespace, "quota-used-bytes"          ),
++        )
++        for dqname in dynamicLiveProperties:
++            has = waitForDeferred(self.hasProperty(dqname, request))
++            yield has
++            has = has.getResult()
++            if not has:
++                qnames.remove(dqname)
++
+         for qname in self.deadProperties().list():
+             if (qname not in qnames) and (qname[0] != twisted_private_namespace):
+-                qnames.append(qname)
++                qnames.add(qname)
+ 
+-        return succeed(qnames)
++        yield qnames
+ 
++    listProperties = deferredGenerator(listProperties)
++
+     def listAllprop(self, request):
+         """
+         Some DAV properties should not be returned to a C{DAV:allprop} query.
+@@ -465,8 +528,22 @@
+             return super(DAVPropertyMixIn, self).displayName()
+ 
+ class DAVResource (DAVPropertyMixIn, StaticRenderMixin):
++    """
++    WebDAV resource.
++    """
+     implements(IDAVResource)
+ 
++    def __init__(self, principalCollections=None):
++        """
++        @param principalCollections: an iterable of L{IDAVPrincipalCollectionResource}s
++            which contain principals to be used in ACLs for this resource.
++        """
++        if principalCollections is not None:
++            self._principalCollections = frozenset([
++                IDAVPrincipalCollectionResource(principalCollection)
++                for principalCollection in principalCollections
++            ])
++
+     ##
+     # DAV
+     ##
+@@ -553,69 +630,65 @@
+     def supportedReports(self):
+         """
+         See L{IDAVResource.supportedReports}.
+-        This implementation lists the three main ACL reports.
++        This implementation lists the three main ACL reports and expand-property.
+         """
+         result = []
+         result.append(davxml.Report(davxml.ACLPrincipalPropSet(),))
+         result.append(davxml.Report(davxml.PrincipalMatch(),))
+         result.append(davxml.Report(davxml.PrincipalPropertySearch(),))
++        result.append(davxml.Report(davxml.ExpandProperty(),))
+         return result
+ 
+     ##
+     # Authentication
+     ##
+ 
++    @inlineCallbacks
+     def authorize(self, request, privileges, recurse=False):
+         """
+         See L{IDAVResource.authorize}.
+         """
+-        def onError(failure):
+-            log.err("Invalid authentication details: %s" % (request,))
+-            raise HTTPError(UnauthorizedResponse(
++
++        try:
++            yield self.authenticate(request)
++        except (UnauthorizedLogin, LoginFailed), e:
++            log.msg("Authentication failed: %s" % (e,))
++            response = (yield UnauthorizedResponse.makeResponse(
+                 request.credentialFactories,
+                 request.remoteAddr
+             ))
++            raise HTTPError(response)
+ 
+-        def onAuth(result):
+-            def onErrors(failure):
+-                failure.trap(AccessDeniedError)
+-                
+-                # If we were unauthorized to start with (no Authorization header from client) then
+-                # we should return an unauthorized response instead to force the client to login if it can
+-                if request.user == davxml.Principal(davxml.Unauthenticated()):
+-                    response = UnauthorizedResponse(request.credentialFactories,
+-                                                    request.remoteAddr)
+-                else:
+-                    response = NeedPrivilegesResponse(request.uri,
+-                                                      failure.value.errors)
+-                #
+-                # We're not adding the headers here because this response
+-                # class is supposed to be a FORBIDDEN status code and
+-                # "Authorization will not help" according to RFC2616
+-                #
+-                raise HTTPError(response)
++        try:
++            yield self.checkPrivileges(request, privileges, recurse)
++        except AccessDeniedError, e:
++            # If we were unauthenticated to start with (no Authorization header from client) then
++            # we should return an unauthorized response instead to force the client to login if it can
++            if request.authnUser == davxml.Principal(davxml.Unauthenticated()):
++                response = (yield UnauthorizedResponse.makeResponse(
++                    request.credentialFactories,
++                    request.remoteAddr
++                ))
++            else:
++                response = NeedPrivilegesResponse(request.uri, e.errors)
++            #
++            # We're not adding the headers here because this response
++            # class is supposed to be a FORBIDDEN status code and
++            # "Authorization will not help" according to RFC2616
++            #
++            raise HTTPError(response)
+ 
+-            d = self.checkPrivileges(request, privileges, recurse)
+-            d.addErrback(onErrors)
+-            return d
+-
+-        d = maybeDeferred(self.authenticate, request)
+-        d.addCallbacks(onAuth, onError)
+-
+-        return d
+-
++    @inlineCallbacks
+     def authenticate(self, request):
+-        def loginSuccess(result):
+-            request.user = result[1]
+-            return request.user
+ 
+         if not (
+             hasattr(request, 'portal') and 
+             hasattr(request, 'credentialFactories') and
+             hasattr(request, 'loginInterfaces')
+         ):
+-            request.user = davxml.Principal(davxml.Unauthenticated())
+-            return request.user
++            request.authnUser = davxml.Principal(davxml.Unauthenticated())
++            request.authzUser = davxml.Principal(davxml.Unauthenticated())
++            returnValue((request.authnUser, request.authzUser,))
+ 
+         authHeader = request.headers.getHeader('authorization')
+ 
+@@ -623,31 +696,32 @@
+             if authHeader[0] not in request.credentialFactories:
+                 log.err("Client authentication scheme %s is not provided by server %s"
+                         % (authHeader[0], request.credentialFactories.keys()))
+-                raise HTTPError(responsecode.FORBIDDEN)
++
++                response = (yield UnauthorizedResponse.makeResponse(
++                    request.credentialFactories,
++                    request.remoteAddr
++                ))
++                raise HTTPError(response)
+             else:
+                 factory = request.credentialFactories[authHeader[0]]
+ 
+-                creds = factory.decode(authHeader[1], request)
++                creds = (yield factory.decode(authHeader[1], request))
+ 
+                 # Try to match principals in each principal collection on the resource
+-                def gotDetails(details):
+-                    principal = IDAVPrincipalResource(details[0])
+-                    principalURI = details[1]
+-                    return PrincipalCredentials(principal, principalURI, creds)
++                authnPrincipal, authzPrincipal = (yield self.principalsForAuthID(request, creds.username))
++                authnPrincipal = IDAVPrincipalResource(authnPrincipal)
++                authzPrincipal = IDAVPrincipalResource(authzPrincipal)
+ 
+-                def login(pcreds):
+-                    d = request.portal.login(pcreds, None, *request.loginInterfaces)
+-                    d.addCallback(loginSuccess)
++                pcreds = PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
+ 
+-                    return d
+-
+-                d = self.findPrincipalForAuthID(request, creds.username)
+-                d.addCallback(gotDetails).addCallback(login)
+-
+-                return d
++                result = (yield request.portal.login(pcreds, None, *request.loginInterfaces))
++                request.authnUser = result[1]
++                request.authzUser = result[2]
++                returnValue((request.authnUser, request.authzUser,))
+         else:
+-            request.user = davxml.Principal(davxml.Unauthenticated())
+-            return request.user
++            request.authnUser = davxml.Principal(davxml.Unauthenticated())
++            request.authzUser = davxml.Principal(davxml.Unauthenticated())
++            returnValue((request.authnUser, request.authzUser,))
+ 
+     ##
+     # ACL
+@@ -656,49 +730,23 @@
+     def currentPrincipal(self, request):
+         """
+         @param request: the request being processed.
+-        @return: the current principal, as derived from the given request.
++        @return: the current authorized principal, as derived from the given request.
+         """
+-        if hasattr(request, "user"):
+-            return request.user
++        if hasattr(request, "authzUser"):
++            return request.authzUser
+         else:
+             return unauthenticatedPrincipal
+ 
+-    def principalCollections(self, request):
++    def principalCollections(self):
+         """
+         See L{IDAVResource.accessControlList}.
+-
+-        This implementation tries to read the L{davxml.PrincipalCollectionSet}
+-        from the dead property store of this resource and uses that. If not
+-        present on this resource, it tries to get it from the parent, unless it
+-        is the root or has no parent.
+         """
+-        try:
+-            principalCollections = self.readDeadProperty(davxml.PrincipalCollectionSet).childrenOfType(davxml.HRef)
+-        except HTTPError, e:
+-            if e.response.code != responsecode.NOT_FOUND:
+-                raise
++        if hasattr(self, "_principalCollections"):
++            return self._principalCollections
++        else:
++            return ()
+ 
+-            principalCollections = []
+-
+-            # Try the parent
+-            myURL = request.urlForResource(self)
+-            if myURL != "/":
+-                parentURL = parentForURL(myURL)
+-
+-                parent = waitForDeferred(request.locateResource(parentURL))
+-                yield parent
+-                parent = parent.getResult()
+-
+-                if parent:
+-                    principalCollections = waitForDeferred(parent.principalCollections(request))
+-                    yield principalCollections
+-                    principalCollections = principalCollections.getResult()
+-
+-        yield principalCollections
+-
+-    principalCollections = deferredGenerator(principalCollections)
+-
+-    def defaultAccessControlList(self):
++    def defaultRootAccessControlList(self):
+         """
+         @return: the L{davxml.ACL} element containing the default access control
+             list for this resource.
+@@ -710,6 +758,17 @@
+         #
+         return readonlyACL
+ 
++    def defaultAccessControlList(self):
++        """
++        @return: the L{davxml.ACL} element containing the default access control
++            list for this resource.
++        """
++        #
++        # The default behaviour is no ACL; we should inherrit from the parent
++        # collection.
++        #
++        return davxml.ACL()
++
+     def setAccessControlList(self, acl):
+         """
+         See L{IDAVResource.setAccessControlList}.
+@@ -748,13 +807,16 @@
+         # 10. Verify that new acl is not in conflict with itself
+         # 11. Update acl on the resource
+ 
+-        old_acl = waitForDeferred(self.accessControlList(request))
++        # Get the current access control list, preserving any private properties on the ACEs as
++        # we will need to keep those when we change the ACL.
++        old_acl = waitForDeferred(self.accessControlList(request, expanding=True))
+         yield old_acl
+         old_acl = old_acl.getResult()
+ 
+         # Check disabled
+         if old_acl is None:
+             yield None
++            return
+ 
+         # Need to get list of supported privileges
+         supported = []
+@@ -773,10 +835,7 @@
+         yield supportedPrivs
+         supportedPrivs = supportedPrivs.getResult()
+         for item in supportedPrivs.children:
+-            assert (
+-                isinstance(item, davxml.SupportedPrivilege),
+-                "Not a SupportedPrivilege: %r" % (item,)
+-            )
++            assert isinstance(item, davxml.SupportedPrivilege), "Not a SupportedPrivilege: %r" % (item,)
+             addSupportedPrivilege(item)
+ 
+         # Steps 1 - 6
+@@ -910,8 +969,7 @@
+         supportedPrivs = supportedPrivs.getResult()
+ 
+         # Other principals types don't make sense as actors.
+-        assert (
+-            principal.children[0].name in ("unauthenticated", "href"),
++        assert principal.children[0].name in ("unauthenticated", "href"), (
+             "Principal is not an actor: %r" % (principal,)
+         )
+ 
+@@ -1019,15 +1077,16 @@
+         def getMyURL():
+             url = request.urlForResource(self)
+ 
+-            assert url is not None, "urlForResource(self) returned None for resource %s" % (self,)
++            assert url is not None, (
++                "urlForResource(self) returned None for resource %s" % (self,)
++            )
+ 
+             return url
+ 
+         try:
+             acl = self.readDeadProperty(davxml.ACL)
+         except HTTPError, e:
+-            assert (
+-                e.response.code == responsecode.NOT_FOUND,
++            assert e.response.code == responsecode.NOT_FOUND, (
+                 "Expected %s response from readDeadProperty() exception, not %s"
+                 % (responsecode.NOT_FOUND, e.response.code)
+             )
+@@ -1038,9 +1097,9 @@
+ 
+             if myURL == "/":
+                 # If we get to the root without any ACLs, then use the default.
++                acl = self.defaultRootAccessControlList()
++            else:
+                 acl = self.defaultAccessControlList()
+-            else:
+-                acl = davxml.ACL()
+ 
+         # Dynamically update privileges for those ace's that are inherited.
+         if inheritance:
+@@ -1076,7 +1135,7 @@
+                                 # Adjust ACE for inherit on this resource
+                                 children = list(ace.children)
+                                 children.remove(TwistedACLInheritable())
+-                                children.append(davxml.Inherited(davxml.HRef.fromString(parentURL)))
++                                children.append(davxml.Inherited(davxml.HRef(parentURL)))
+                                 aces.append(davxml.ACE(*children))
+             else:
+                 aces.extend(inherited_aces)
+@@ -1105,8 +1164,7 @@
+         the child resource loop and supply those to the checkPrivileges on each child.
+ 
+         @param request: the L{IRequest} for the request in progress.
+-        @return:        a C{list} of L{Ace}s that child resources of this one will
+-            inherit and which will match the currently authenticated principal.
++        @return:        a C{list} of L{Ace}s that child resources of this one will inherit.
+         """
+         
+         # Get the parent ACLs with inheritance and preserve the <inheritable> element.
+@@ -1128,21 +1186,9 @@
+                 # Adjust ACE for inherit on this resource
+                 children = list(ace.children)
+                 children.remove(TwistedACLInheritable())
+-                children.append(davxml.Inherited(davxml.HRef.fromString(request.urlForResource(self))))
++                children.append(davxml.Inherited(davxml.HRef(request.urlForResource(self))))
+                 aces.append(davxml.ACE(*children))
+-                
+-        # Filter out those that do not have a principal match with the current principal
+-        principal = self.currentPrincipal(request)
+-        filteredaces = []
+-        for ace in aces:
+-            if self.matchPrincipal(principal, ace.principal, request):
+-                if ace.invert:
+-                    continue
+-            else:
+-                if not ace.invert:
+-                    continue
+-            filteredaces.append(ace)
+-        yield filteredaces
++        yield aces
+ 
+     inheritedACEsforChildren = deferredGenerator(inheritedACEsforChildren)
+ 
+@@ -1152,49 +1198,69 @@
+ 
+         This implementation returns an empty set.
+         """
+-
+         return []
+ 
+-    def findPrincipalForAuthID(self, request, authid):
++    def principalsForAuthID(self, request, authid):
+         """
++        Return authentication and authorization prinicipal identifiers for the
++        authentication identifer passed in. In this implementation authn and authz
++        principals are the same.
++
+         @param request: the L{IRequest} for the request in progress.
+         @param authid: a string containing the
+             authentication/authorization identifier for the principal
+             to lookup.
+-        @return: a deferred tuple of C{(principal, principalURI)}
+-            where: C{principal} is the L{Principal} that is found;
+-            C{principalURI} is the C{str} URI of the principal. 
++        @return: a deferred tuple of two tuples. Each tuple is
++            C{(principal, principalURI)} where: C{principal} is the L{Principal}
++            that is found; {principalURI} is the C{str} URI of the principal.
++            The first tuple corresponds to authentication identifiers,
++            the second to authorization identifiers.
+             It will errback with an HTTPError(responsecode.FORBIDDEN) if
+             the principal isn't found.
+         """
+-        # Try to match principals in each principal collection on the resource
+-        collections = waitForDeferred(self.principalCollections(request))
+-        yield collections
+-        collections = collections.getResult()
++        authnPrincipal = self.findPrincipalForAuthID(authid)
+ 
+-        for collection in collections:
+-            principalURI = joinURL(str(collection), authid)
++        if authnPrincipal is None:
++            log.msg("Could not find the principal resource for user id: %s" % (authid,))
++            raise HTTPError(responsecode.FORBIDDEN)
+ 
+-            principal = waitForDeferred(request.locateResource(principalURI))
+-            yield principal
+-            principal = principal.getResult()
++        d = self.authorizationPrincipal(request, authid, authnPrincipal)
++        d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
++        return d
+ 
+-            if isPrincipalResource(principal):
+-                yield (principal, principalURI)
+-                return
+-        else:
+-            principalCollections = waitForDeferred(self.principalCollections(request))
+-            yield principalCollections
+-            principalCollections = principalCollections.getResult()
++    def findPrincipalForAuthID(self, authid):
++        """
++        Return authentication and authoirization prinicipal identifiers for the
++        authentication identifer passed in. In this implementation authn and authz
++        principals are the same.
+ 
+-            if len(principalCollections) == 0:
+-                log.msg("DAV:principal-collection-set property cannot be found on the resource being authorized: %s" % self)
+-            else:
+-                log.msg("Could not find principal matching user id: %s" % authid)
+-            raise HTTPError(responsecode.FORBIDDEN)
++        @param authid: a string containing the
++            authentication/authorization identifier for the principal
++            to lookup.
++        @return: a tuple of C{(principal, principalURI)} where: C{principal} is the L{Principal}
++            that is found; {principalURI} is the C{str} URI of the principal.
++            If not found return None.
++        """
++        for collection in self.principalCollections():
++            principal = collection.principalForUser(authid)
++            if principal is not None:
++                return principal
++        return None
+ 
+-    findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
+-
++    def authorizationPrincipal(self, request, authid, authnPrincipal):
++        """
++        Determine the authorization principal for the given request and authentication principal.
++        This implementation simply uses aht authentication principalk as the authoization principal.
++        
++        @param request: the L{IRequest} for the request in progress.
++        @param authid: a string containing the uthentication/authorization identifier
++            for the principal to lookup.
++        @param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
++         @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
++            resource and URI respectively.
++        """
++        return succeed(authnPrincipal)
++        
+     def samePrincipal(self, principal1, principal2):
+         """
+         Check whether the two prinicpals are exactly the same in terms of
+@@ -1219,7 +1285,6 @@
+             return False
+                 
+     def matchPrincipal(self, principal1, principal2, request):
+-
+         """
+         Check whether the principal1 is a principal in the set defined by
+         principal2.
+@@ -1244,6 +1309,9 @@
+             if isinstance(principal1, davxml.Unauthenticated):
+                 yield False
+                 return
++            elif isinstance(principal1, davxml.All):
++                yield False
++                return
+             else:
+                 yield True
+                 return
+@@ -1260,10 +1328,7 @@
+             yield False
+             return
+ 
+-        assert (
+-            isinstance(principal1, davxml.HRef),
+-            "Not an HRef: %r" % (principal1,)
+-        )
++        assert isinstance(principal1, davxml.HRef), "Not an HRef: %r" % (principal1,)
+ 
+         principal2 = waitForDeferred(self.resolvePrincipal(principal2, request))
+         yield principal2
+@@ -1271,7 +1336,6 @@
+ 
+         assert principal2 is not None, "principal2 is None"
+ 
+-
+         # Compare two HRefs and do group membership test as well
+         if principal1 == principal2:
+             yield True
+@@ -1289,6 +1353,7 @@
+ 
+     matchPrincipal = deferredGenerator(matchPrincipal)
+ 
++    @deferredGenerator
+     def principalIsGroupMember(self, principal1, principal2, request):
+         """
+         Check whether one principal is a group member of another.
+@@ -1299,18 +1364,21 @@
+         @return: L{Deferred} with result C{True} if principal1 is a member of principal2, C{False} otherwise
+         """
+         
+-        def testGroup(group):
+-            # Get principal resource for principal2
+-            if group and isinstance(group, DAVPrincipalResource):
+-                members = group.groupMembers()
+-                if principal1 in members:
+-                    return True
+-                
+-            return False
++        d = waitForDeferred(request.locateResource(principal2))
++        yield d
++        group = d.getResult()
+ 
+-        d = request.locateResource(principal2)
+-        d.addCallback(testGroup)
+-        return d
++        # Get principal resource for principal2
++        if group and isinstance(group, DAVPrincipalResource):
++            d = waitForDeferred(group.expandedGroupMembers())
++            yield d
++            members = d.getResult()
++            for member in members:
++                if member.principalURL() == principal1:
++                    yield True
++                    return
++            
++        yield False
+         
+     def validPrincipal(self, ace_principal, request):
+         """
+@@ -1351,11 +1419,16 @@
+         @return C{True} if C{href_principal} is valid, C{False} otherwise.
+ 
+         This implementation tests for a href element that corresponds to
+-        a principal resource.
++        a principal resource and matches the principal-URL.
+         """
+-        # Must have the principal resource type
++
++        # Must have the principal resource type and must match the principal-URL
++        
++        def _matchPrincipalURL(resource):
++            return isPrincipalResource(resource) and resource.principalURL() == str(href_principal)
++
+         d = request.locateResource(str(href_principal))
+-        d.addCallback(isPrincipalResource)
++        d.addCallback(_matchPrincipalURL)
+         return d
+ 
+     def resolvePrincipal(self, principal, request):
+@@ -1404,8 +1477,7 @@
+             try:
+                 principal = principal.getResult()
+             except HTTPError, e:
+-                assert (
+-                    e.response.code == responsecode.NOT_FOUND,
++                assert e.response.code == responsecode.NOT_FOUND, (
+                     "Expected %s response from readProperty() exception, not %s"
+                     % (responsecode.NOT_FOUND, e.response.code)
+                 )
+@@ -1432,15 +1504,15 @@
+                 log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
+                 yield None
+                 return
+-            principal = davxml.HRef.fromString(self.principalURL())
++            principal = davxml.HRef(self.principalURL())
+ 
+         if isinstance(principal, davxml.HRef):
+             yield principal
++            return
+         else:
+             yield None
+ 
+-        assert (
+-            isinstance(principal, (davxml.All, davxml.Authenticated, davxml.Unauthenticated)),
++        assert isinstance(principal, (davxml.All, davxml.Authenticated, davxml.Unauthenticated)), (
+             "Not a meta-principal: %r" % (principal,)
+         )
+ 
+@@ -1517,6 +1589,280 @@
+         return None
+ 
+     ##
++    # Quota
++    ##
++    
++    """
++    The basic policy here is to define a private 'quota-root' property on a collection.
++    That property will contain the maximum allowed bytes for the collections and all
++    its contents.
++    
++    In order to determine the quota property values on a resource, the server must look
++    for the private property on that resource and any of its parents. If found on a parent,
++    then that parent should be queried for quota information. If not found, no quota
++    exists for the resource.
++    
++    To determine tha actual quota in use we will cache the used byte count on the quota-root
++    collection in another private property. It is the servers responsibility to
++    keep that property up to date by adjusting it after every PUT, DELETE, COPY,
++    MOVE, MKCOL, PROPPATCH, ACL, POST or any other method that may affect the size of
++    stored data. If the private property is not present, the server will fall back to
++    getting the size by iterating over all resources (this is done in static.py).
++    
++    """
++
++    def quota(self, request):
++        """
++        Get current available & used quota values for this resource's quota root
++        collection.
++
++        @return: an L{Defered} with result C{tuple} containing two C{int}'s the first is 
++            quota-available-bytes, the second is quota-used-bytes, or
++            C{None} if quota is not defined on the resource.
++        """
++        
++        # See if already cached
++        if not hasattr(request, "quota"):
++            request.quota = {}
++        if request.quota.has_key(self):
++            yield request.quota[self]
++            return
++
++        # Check this resource first
++        if self.isCollection():
++            qroot = self.quotaRoot(request)
++            if qroot is not None:
++                used = waitForDeferred(self.currentQuotaUse(request))
++                yield used
++                used = used.getResult()
++                available = qroot - used
++                if available < 0:
++                    available = 0
++                request.quota[self] = (available, used)
++                yield request.quota[self]
++                return
++        
++        # Check the next parent
++        url = request.urlForResource(self)
++        if url != "/":
++            parent = waitForDeferred(request.locateResource(parentForURL(url)))
++            yield parent
++            parent = parent.getResult()
++            d = waitForDeferred(parent.quota(request))
++            yield d
++            request.quota[self] = d.getResult()
++        else:
++            request.quota[self] = None
++
++        yield request.quota[self]
++        return
++    
++    quota = deferredGenerator(quota)
++
++    def hasQuota(self, request):
++        """
++        Check whether this resource is undre quota control by checking each parent to see if
++        it has a quota root.
++        
++        @return: C{True} if under quota control, C{False} if not.
++        """
++        
++        # Check this one first
++        if self.hasQuotaRoot(request):
++            yield True
++            return
++        
++        # Look at each parent
++        try:
++            url = request.urlForResource(self)
++            if url != "/":
++                parent = waitForDeferred(request.locateResource(parentForURL(url)))
++                yield parent
++                parent = parent.getResult()
++                d = waitForDeferred(parent.hasQuota(request))
++                yield d
++                yield d.getResult()
++            else:
++                yield False
++        except NoURLForResourceError:
++            yield False
++    
++    hasQuota = deferredGenerator(hasQuota)
++        
++    def hasQuotaRoot(self, request):
++        """
++        @return: a C{True} if this resource has quota root, C{False} otherwise.
++        """
++        return self.hasDeadProperty(TwistedQuotaRootProperty)
++    
++    def quotaRoot(self, request):
++        """
++        @return: a C{int} containing the maximum allowed bytes if this collection
++            is quota-controlled, or C{None} if not quota controlled.
++        """
++        if self.hasDeadProperty(TwistedQuotaRootProperty):
++            return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
++        else:
++            return None
++    
++    def quotaRootParent(self, request):
++        """
++        Return the next quota root above this resource.
++        
++        @return: L{DAVResource} or C{None}
++        """
++
++        # Check the next parent
++        url = request.urlForResource(self)
++        while (url != "/"):
++            url = parentForURL(url)
++            parent = waitForDeferred(request.locateResource(url))
++            yield parent
++            parent = parent.getResult()
++            if parent.hasQuotaRoot(request):
++                yield parent
++                return
++
++        yield None
++    
++    quotaRootParent = deferredGenerator(quotaRootParent)
++        
++    def setQuotaRoot(self, request, maxsize):
++        """
++        @param maxsize: a C{int} containing the maximum allowed bytes for the contents
++            of this collection, or C{None} tp remove quota restriction.
++        """
++        assert self.isCollection(), "Only collections can have a quota root"
++        assert maxsize is None or isinstance(maxsize, int), "maxsize must be an int or None"
++        
++        if maxsize is not None:
++            self.writeDeadProperty(TwistedQuotaRootProperty(str(maxsize)))
++        else:
++            # Remove both the root and the cached used value
++            self.removeDeadProperty(TwistedQuotaRootProperty)
++            self.removeDeadProperty(TwistedQuotaUsedProperty)
++    
++    def quotaSize(self, request):
++        """
++        Get the size of this resource (if its a collection get total for all children as well).
++        TODO: Take into account size of dead-properties.
++
++        @return: a C{int} containing the size of the resource.
++        """
++        unimplemented(self)
++
++    def checkQuota(self, request, available):
++        """
++        Check to see whether all quota roots have sufficient available bytes.
++        We currently do not use hierarchical quota checks - i.e. only the most
++        immediate quota root parent is checked for quota.
++        
++        @param available: a C{int} containing the additional quota required.
++        @return: C{True} if there is sufficient quota remaining on all quota roots,
++            C{False} otherwise.
++        """
++        
++        quotaroot = self
++        while(quotaroot is not None):
++            # Check quota on this root (if it has one)
++            quota = quotaroot.quotaRoot(request)
++            if quota is not None:
++                if available > quota[0]:
++                    yield False
++                    return
++
++            # Check the next parent with a quota root
++            quotaroot = waitForDeferred(quotaroot.quotaRootParent(request))
++            yield quotaroot
++            quotaroot = quotaroot.getResult()
++
++        yield True
++
++    checkQuota = deferredGenerator(checkQuota)
++
++    def quotaSizeAdjust(self, request, adjust):
++        """
++        Update the quota used value on all quota root parents of this resource.
++
++        @param adjust: a C{int} containing the number of bytes added (positive) or
++            removed (negative) that should be used to adjust the cached total.
++        """
++        
++        # Check this resource first
++        if self.isCollection():
++            if self.hasQuotaRoot(request):
++                d = waitForDeferred(self.updateQuotaUse(request, adjust))
++                yield d
++                d.getResult()
++                yield None
++                return
++        
++        # Check the next parent
++        url = request.urlForResource(self)
++        if url != "/":
++            parent = waitForDeferred(request.locateResource(parentForURL(url)))
++            yield parent
++            parent = parent.getResult()
++            d = waitForDeferred(parent.quotaSizeAdjust(request, adjust))
++            yield d
++            d.getResult()
++
++        yield None
++
++    quotaSizeAdjust = deferredGenerator(quotaSizeAdjust)
++
++    def currentQuotaUse(self, request):
++        """
++        Get the cached quota use value, or if not present (or invalid) determine
++        quota use by brute force.
++
++        @return: an L{Deferred} with a C{int} result containing the current used byte if this collection
++            is quota-controlled, or C{None} if not quota controlled.
++        """
++        assert self.isCollection(), "Only collections can have a quota root"
++        assert self.hasQuotaRoot(request), "Quota use only on quota root collection"
++        
++        # Try to get the cached value property
++        if self.hasDeadProperty(TwistedQuotaUsedProperty):
++            return succeed(int(str(self.readDeadProperty(TwistedQuotaUsedProperty))))
++        else:
++            # Do brute force size determination and cache the result in the private property
++            def _defer(result):
++                self.writeDeadProperty(TwistedQuotaUsedProperty(str(result)))
++                return result
++            d = self.quotaSize(request)
++            d.addCallback(_defer)
++            return d
++
++    def updateQuotaUse(self, request, adjust):
++        """
++        Update the quota used value on this resource.
++
++        @param adjust: a C{int} containing the number of bytes added (positive) or
++        removed (negative) that should be used to adjust the cached total.
++        @return: an L{Deferred} with a C{int} result containing the current used byte if this collection
++            is quota-controlled, or C{None} if not quota controlled.
++        """
++        assert self.isCollection(), "Only collections can have a quota root"
++        
++        # Get current value
++        def _defer(size):
++            size += adjust
++            
++            # Sanity check the resulting size
++            if size >= 0:
++                self.writeDeadProperty(TwistedQuotaUsedProperty(str(size)))
++            else:
++                # Remove the dead property and re-read to do brute force quota calc
++                log.msg("Attempt to set quota used to a negative value: %s (adjustment: %s)" % (size, adjust,))
++                self.removeDeadProperty(TwistedQuotaUsedProperty)
++                return self.currentQuotaUse(request)
++
++        d = self.currentQuotaUse(request)
++        d.addCallback(_defer)
++        return d
++        
++    ##
+     # HTTP
+     ##
+ 
+@@ -1525,15 +1871,11 @@
+         #litmus = request.headers.getRawHeaders("x-litmus")
+         #if litmus: log.msg("*** Litmus test: %s ***" % (litmus,))
+ 
+-        # FIXME: Learn how to use twisted logging facility, wsanchez
+-        protocol = "HTTP/%s.%s" % request.clientproto
+-        log.msg("%s %s %s" % (request.method, urllib.unquote(request.uri), protocol))
+-
+         #
+         # If this is a collection and the URI doesn't end in "/", redirect.
+         #
+-        if self.isCollection() and request.uri[-1:] != "/":
+-            return RedirectResponse(request.uri + "/")
++        if self.isCollection() and request.path[-1:] != "/":
++            return RedirectResponse(request.unparseURL(path=urllib.quote(request.path, safe=':/')+'/'))
+ 
+         def setHeaders(response):
+             response = IResponse(response)
+@@ -1567,7 +1909,7 @@
+     def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
+         return succeed(None)
+ 
+-class DAVPrincipalResource (DAVLeafResource):
++class DAVPrincipalResource (DAVResource):
+     """
+     Resource representing a WebDAV principal.  (RFC 3744, section 2)
+     """
+@@ -1577,7 +1919,7 @@
+     # WebDAV
+     ##
+ 
+-    liveProperties = DAVLeafResource.liveProperties + (
++    liveProperties = DAVResource.liveProperties + (
+         (dav_namespace, "alternate-URI-set"),
+         (dav_namespace, "principal-URL"    ),
+         (dav_namespace, "group-member-set" ),
+@@ -1585,14 +1927,11 @@
+     )
+ 
+     def davComplianceClasses(self):
+-        return ("1",)
++        return ("1", "access-control",)
+ 
+     def isCollection(self):
+         return False
+ 
+-    def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
+-        return succeed(None)
+-
+     def readProperty(self, property, request):
+         def defer():
+             if type(property) is tuple:
+@@ -1610,10 +1949,20 @@
+                     return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
+ 
+                 if name == "group-member-set":
+-                    return davxml.GroupMemberSet(*[davxml.HRef(p) for p in self.groupMembers()])
++                    def callback(members):
++                        return davxml.GroupMemberSet(*[davxml.HRef(p.principalURL()) for p in members])
++                    
++                    d = self.groupMembers()
++                    d.addCallback(callback)
++                    return d
+ 
+                 if name == "group-membership":
+-                    return davxml.GroupMembership(*[davxml.HRef(g) for g in self.groupMemberships()])
++                    def callback(memberships):
++                        return davxml.GroupMembership(*[davxml.HRef(g.principalURL()) for g in memberships])
++                    
++                    d = self.groupMemberships()
++                    d.addCallback(callback)
++                    return d
+ 
+                 if name == "resourcetype":
+                     if self.isCollection():
+@@ -1655,7 +2004,7 @@
+         principals.  Subclasses should override this method to provide member
+         URLs for this resource if appropriate.
+         """
+-        return ()
++        return succeed(())
+ 
+     def groupMemberships(self):
+         """
+@@ -1666,6 +2015,7 @@
+         """
+         unimplemented(self)
+ 
++    @deferredGenerator
+     def principalMatch(self, href):
+         """
+         Check whether the supplied principal matches this principal or is a
+@@ -1675,10 +2025,33 @@
+         """
+         uri = str(href)
+         if self.principalURL() == uri:
+-            return True
++            yield True
++            return
+         else:
+-            return uri in self.groupMembers()
++            d = waitForDeferred(self.expandedGroupMembers())
++            yield d
++            members = d.getResult()
++            member_uris = [member.principalURL() for member in members]
++            yield uri in member_uris
+ 
++class DAVPrincipalCollectionResource (DAVResource):
++    """
++    WebDAV principal collection resource.  (RFC 3744, section 5.8)
++    """
++    implements(IDAVPrincipalCollectionResource)
++
++    def __init__(self, url, principalCollections=()):
++        """
++        @param url: This resource's URL.
++        """
++        DAVResource.__init__(self, principalCollections=principalCollections)
++
++        assert url.endswith("/"), "Collection URL must end in '/'"
++        self._url = url
++
++    def principalCollectionURL(self):
++        return self._url
++
+ class AccessDeniedError(Exception):
+     def __init__(self, errors):
+         """ 
+@@ -1718,6 +2091,37 @@
+ davxml.registerElement(TwistedACLInheritable)
+ davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
+ 
++class TwistedGETContentMD5 (davxml.WebDAVTextElement):
++    """
++    MD5 hash of the resource content.
++    """
++    namespace = twisted_dav_namespace
++    name = "getcontentmd5"
++
++davxml.registerElement(TwistedGETContentMD5)
++
++"""
++When set on a collection, this property indicates that the collection has a quota limit for
++the size of all resources stored in the collection (and any associate meta-data such as properties).
++The value is a number - the maximum size in bytes allowed.
++"""
++class TwistedQuotaRootProperty (davxml.WebDAVTextElement):
++    namespace = twisted_private_namespace
++    name = "quota-root"
++
++davxml.registerElement(TwistedQuotaRootProperty)
++
++"""
++When set on a collection, this property contains the cached running total of the size of all
++resources stored in the collection (and any associate meta-data such as properties).
++The value is a number - the size in bytes used.
++"""
++class TwistedQuotaUsedProperty (davxml.WebDAVTextElement):
++    namespace = twisted_private_namespace
++    name = "quota-used"
++
++davxml.registerElement(TwistedQuotaUsedProperty)
++
+ allACL = davxml.ACL(
+     davxml.ACE(
+         davxml.Principal(davxml.All()),

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,161 @@
+Index: twisted/web2/dav/static.py
+===================================================================
+--- twisted/web2/dav/static.py	(revision 19773)
++++ twisted/web2/dav/static.py	(working copy)
+@@ -28,16 +28,17 @@
+ 
+ __all__ = ["DAVFile"]
+ 
+-import os
+-
++from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
++from twisted.python.filepath import InsecurePath
+ from twisted.python import log
+-from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
+-from twisted.web2.static import File
++from twisted.web2 import http_headers
+ from twisted.web2 import responsecode, dirlist
+-from twisted.web2.http import RedirectResponse
+ from twisted.web2.dav import davxml
+ from twisted.web2.dav.resource import DAVResource, davPrivilegeSet
++from twisted.web2.dav.resource import TwistedGETContentMD5
+ from twisted.web2.dav.util import bindMethods
++from twisted.web2.http import HTTPError, StatusResponse, RedirectResponse
++from twisted.web2.static import File
+ 
+ try:
+     from twisted.web2.dav.xattrprops import xattrPropertyStore as DeadPropertyStore
+@@ -52,9 +53,11 @@
+ 
+     Extends twisted.web2.static.File to handle WebDAV methods.
+     """
+-    def __init__(self, path,
+-                 defaultType="text/plain",
+-                 indexNames=None):
++    def __init__(
++        self, path,
++        defaultType="text/plain", indexNames=None,
++        principalCollections=()
++    ):
+         """
+         @param path: the path of the file backing this resource.
+         @param defaultType: the default mime type (as a string) for this
+@@ -62,11 +65,14 @@
+         @param indexNames: a sequence of index file names.
+         @param acl: an L{IDAVAccessControlList} with the .
+         """
+-        super(DAVFile, self).__init__(path,
+-                                      defaultType = defaultType,
+-                                      ignoredExts = (),
+-                                      processors  = None,
+-                                      indexNames  = indexNames)
++        File.__init__(
++            self, path,
++            defaultType = defaultType,
++            ignoredExts = (),
++            processors = None,
++            indexNames = indexNames,
++        )
++        DAVResource.__init__(self, principalCollections=principalCollections)
+ 
+     def __repr__(self):
+         return "<%s: %s>" % (self.__class__.__name__, self.fp.path)
+@@ -75,6 +81,13 @@
+     # WebDAV
+     ##
+ 
++    def etag(self):
++        if not self.fp.exists(): return None
++        if self.hasDeadProperty(TwistedGETContentMD5):
++            return http_headers.ETag(str(self.readDeadProperty(TwistedGETContentMD5)))
++        else:
++            return super(DAVFile, self).etag()
++
+     def davComplianceClasses(self):
+         return ("1", "access-control") # Add "2" when we have locking
+ 
+@@ -87,7 +100,6 @@
+         """
+         See L{IDAVResource.isCollection}.
+         """
+-        for child in self.listChildren(): return True
+         return self.fp.isdir()
+ 
+     ##
+@@ -98,6 +110,50 @@
+         return succeed(davPrivilegeSet)
+ 
+     ##
++    # Quota
++    ##
++
++    def quotaSize(self, request):
++        """
++        Get the size of this resource.
++        TODO: Take into account size of dead-properties. Does stat
++            include xattrs size?
++
++        @return: an L{Deferred} with a C{int} result containing the size of the resource.
++        """
++        if self.isCollection():
++            def walktree(top):
++                """
++                Recursively descend the directory tree rooted at top,
++                calling the callback function for each regular file
++                
++                @param top: L{FilePath} for the directory to walk.
++                """
++            
++                total = 0
++                for f in top.listdir():
++                    child = top.child(f)
++                    if child.isdir():
++                        # It's a directory, recurse into it
++                        result = waitForDeferred(walktree(child))
++                        yield result
++                        total += result.getResult()
++                    elif child.isfile():
++                        # It's a file, call the callback function
++                        total += child.getsize()
++                    else:
++                        # Unknown file type, print a message
++                        pass
++            
++                yield total
++            
++            walktree = deferredGenerator(walktree)
++    
++            return walktree(self.fp)
++        else:
++            return succeed(self.fp.getsize())
++
++    ##
+     # Workarounds for issues with File
+     ##
+ 
+@@ -112,8 +168,12 @@
+         See L{IResource}C{.locateChild}.
+         """
+         # If getChild() finds a child resource, return it
+-        child = self.getChild(segments[0])
+-        if child is not None: return (child, segments[1:])
++        try:
++            child = self.getChild(segments[0])
++            if child is not None:
++                return (child, segments[1:])
++        except InsecurePath:
++            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Invalid URL path"))
+         
+         # If we're not backed by a directory, we have no children.
+         # But check for existance first; we might be a collection resource
+@@ -132,7 +192,9 @@
+         return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
+ 
+     def createSimilarFile(self, path):
+-        return self.__class__(path, defaultType=self.defaultType, indexNames=self.indexNames[:])
++        return self.__class__(
++            path, defaultType=self.defaultType, indexNames=self.indexNames[:],
++            principalCollections=self.principalCollections())
+ 
+ #
+ # Attach method handlers to DAVFile

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.stream.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.stream.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.stream.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.stream.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,84 @@
+Index: twisted/web2/dav/stream.py
+===================================================================
+--- twisted/web2/dav/stream.py	(revision 0)
++++ twisted/web2/dav/stream.py	(revision 0)
+@@ -0,0 +1,79 @@
++##
++# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++Class that implements a stream that calculates the MD5 hash of the data
++as the data is read.
++"""
++
++__all__ = ["MD5StreamWrapper"]
++
++from twisted.internet.defer import Deferred
++from twisted.web2.stream import SimpleStream
++
++try:
++    from hashlib import md5
++except ImportError:
++    from md5 import new as md5
++
++class MD5StreamWrapper(SimpleStream):
++ 
++    def __init__(self, wrap):
++        
++        assert wrap is not None, "Must have a stream to wrap."
++
++        self.stream = wrap
++
++        # Init MD5
++        self.md5 = md5()
++    
++    def read(self):
++        assert self.md5 is not None, "Cannot call read after close."
++
++        # Read from wrapped stream first
++        b = self.stream.read()
++        
++        if isinstance(b, Deferred):
++            def _gotData(data):
++                if data is not None:
++                    self.md5.update(data)
++                return data
++            b.addCallback(_gotData)
++        else:
++            # Update current MD5 state
++            if b is not None:
++                self.md5.update(str(b))
++    
++        return b
++    
++    def close(self):
++        # Close out the MD5 hash
++        self.md5value = self.md5.hexdigest()
++        self.md5 = None
++
++        SimpleStream.close(self)
++    
++    def getMD5(self):
++        assert hasattr(self, "md5value"), "Stream has to be closed first"
++        return self.md5value

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.data.quota_100.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.data.quota_100.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.data.quota_100.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.data.quota_100.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,15 @@
+Index: twisted/web2/dav/test/data/quota_100.txt
+===================================================================
+--- twisted/web2/dav/test/data/quota_100.txt	(revision 0)
++++ twisted/web2/dav/test/data/quota_100.txt	(revision 0)
+@@ -0,0 +1,10 @@
++123456789
++123456789
++123456789
++123456789
++123456789
++123456789
++123456789
++123456789
++123456789
++123456789

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,108 @@
+Index: twisted/web2/dav/test/test_acl.py
+===================================================================
+--- twisted/web2/dav/test/test_acl.py	(revision 19773)
++++ twisted/web2/dav/test/test_acl.py	(working copy)
+@@ -30,6 +30,7 @@
+ from twisted.web2.auth import basic
+ from twisted.web2.stream import MemoryStream
+ from twisted.web2.dav import davxml
++from twisted.web2.dav.resource import DAVPrincipalCollectionResource
+ from twisted.web2.dav.util import davXMLFromStream
+ from twisted.web2.dav.auth import TwistedPasswordProperty, IPrincipal, DavRealm, TwistedPropertyChecker, AuthenticationWrapper
+ 
+@@ -38,6 +39,25 @@
+ from twisted.web2.dav.test.util import Site, serialize
+ from twisted.web2.dav.test.test_resource import TestResource, TestDAVPrincipalResource
+ 
++class TestPrincipalsCollection(DAVPrincipalCollectionResource, TestResource):
++    def __init__(self, url, children):
++        DAVPrincipalCollectionResource.__init__(self, url)
++        TestResource.__init__(self, url, children, principalCollections=(self,))
++    
++    def principalForUser(self, user):
++        return self.principalForShortName('users', user)
++
++    def principalForAuthID(self, creds):
++        return self.principalForShortName('users', creds.username)
++
++    def principalForShortName(self, type, shortName):
++        typeResource = self.children.get(type, None)
++        user = None
++        if typeResource:
++            user = typeResource.children.get(shortName, None)
++
++        return user
++
+ class ACL(twisted.web2.dav.test.util.TestCase):
+     """
+     RFC 3744 (WebDAV ACL) tests.
+@@ -46,8 +66,18 @@
+         if not hasattr(self, "docroot"):
+             self.docroot = self.mktemp()
+             os.mkdir(self.docroot)
+-            rootresource = self.resource_class(self.docroot)
+ 
++            userResource = TestDAVPrincipalResource("/principals/users/user01")
++            userResource.writeDeadProperty(TwistedPasswordProperty("user01"))
++
++            principalCollection = TestPrincipalsCollection(
++                "/principals/", 
++                children={"users": TestPrincipalsCollection(
++                        "/principals/users/",
++                        children={"user01": userResource})})
++
++            rootResource = self.resource_class(self.docroot, principalCollections=(principalCollection,))
++
+             portal = Portal(DavRealm())
+             portal.registerChecker(TwistedPropertyChecker())
+ 
+@@ -56,26 +86,14 @@
+             loginInterfaces = (IPrincipal,)
+ 
+             self.site = Site(AuthenticationWrapper(
+-                rootresource, 
++                rootResource, 
+                 portal,
+                 credentialFactories,
+                 loginInterfaces
+             ))
+ 
+-            rootresource.setAccessControlList(self.grant(davxml.All()))
++            rootResource.setAccessControlList(self.grant(davxml.All()))
+ 
+-            userresource = TestDAVPrincipalResource("/principals/user01")
+-            userresource.writeDeadProperty(TwistedPasswordProperty.fromString("user01"))
+-
+-            rootresource.putChild(
+-                "principals",
+-                TestResource("/principals", {"user01": userresource})
+-            )
+-
+-            rootresource.writeDeadProperty(
+-                davxml.PrincipalCollectionSet(davxml.HRef("/principals/"))
+-            )
+-
+         for name, acl in (
+             ("none"       , self.grant()),
+             ("read"       , self.grant(davxml.Read())),
+@@ -361,9 +379,7 @@
+                 if method == "GET":
+                     ok = responsecode.OK
+                 elif method == "REPORT":
+-                    # BAD_REQUEST in the allowed case, because we're not actually
+-                    # including the required XML in the request body.
+-                    ok = responsecode.BAD_REQUEST
++                    ok = responsecode.MULTI_STATUS
+                 else:
+                     raise AssertionError("We shouldn't be here.  (method = %r)" % (method,))
+ 
+@@ -377,6 +393,9 @@
+                     path = os.path.join(self.docroot, name)
+ 
+                     request = SimpleRequest(self.site, method, "/" + name)
++                    if method == "REPORT":
++                        request.stream = MemoryStream(davxml.PrincipalPropertySearch().toxml())
++
+                     _add_auth_header(request)
+ 
+                     def test(response, code=code, path=path):

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_copy.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_copy.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_copy.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_copy.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,24 @@
+Index: twisted/web2/dav/test/test_copy.py
+===================================================================
+--- twisted/web2/dav/test/test_copy.py	(revision 19773)
++++ twisted/web2/dav/test/test_copy.py	(working copy)
+@@ -22,9 +22,9 @@
+ # DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
+ 
++from hashlib import md5
+ import os
+ import urllib
+-import md5
+ 
+ import twisted.web2.dav.test.util
+ from twisted.web2 import responsecode
+@@ -161,7 +161,7 @@
+             yield (request, do_test)
+ 
+ def sumFile(path):
+-    m = md5.new()
++    m = md5()
+ 
+     if os.path.isfile(path):
+         f = file(path)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_pipeline.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_pipeline.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_pipeline.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_pipeline.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,74 @@
+Index: twisted/web2/dav/test/test_pipeline.py
+===================================================================
+--- twisted/web2/dav/test/test_pipeline.py	(revision 0)
++++ twisted/web2/dav/test/test_pipeline.py	(revision 0)
+@@ -0,0 +1,69 @@
++##
++# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Wilfredo Sanchez, wsanchez at apple.com
++##
++from twisted.internet import utils
++from twisted.web2.test import test_server
++from twisted.web2 import resource
++from twisted.web2 import http
++from twisted.web2.test import test_http
++import sys
++
++from twisted.internet.defer import waitForDeferred, deferredGenerator
++
++from twisted.python import util
++
++class Pipeline(test_server.BaseCase):
++    """
++    Pipelined request
++    """
++    class TestResource(resource.LeafResource):
++        def render(self, req):
++            return http.Response(stream="Host:%s, Path:%s"%(req.host, req.path))
++            
++    def setUp(self):
++        self.root = self.TestResource()
++
++    def chanrequest(self, root, uri, length, headers, method, version, prepath, content):
++        self.cr = super(Pipeline, self).chanrequest(root, uri, length, headers, method, version, prepath, content)
++        return self.cr
++
++    def test_root(self):
++        
++        def _testStreamRead(x):
++            self.assertTrue(self.cr.request.stream.length == 0)
++
++        return self.assertResponse(
++            (self.root, 'http://host/path', {"content-type":"text/plain",}, "PUT", None, '', "This is some text."),
++            (405, {}, None)).addCallback(_testStreamRead)
++
++class SSLPipeline(test_http.SSLServerTest):
++
++    @deferredGenerator
++    def testAdvancedWorkingness(self):
++        args = ('-u', util.sibpath(__file__, "tworequest_client.py"), "basic",
++                str(self.port), self.type)
++        d = waitForDeferred(utils.getProcessOutputAndValue(sys.executable, args=args))
++        yield d; out,err,code = d.getResult()
++
++        self.assertEquals(code, 0, "Error output:\n%s" % (err,))
++        self.assertEquals(out, "HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\nHTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n")

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,69 @@
+Index: twisted/web2/dav/test/test_prop.py
+===================================================================
+--- twisted/web2/dav/test/test_prop.py	(revision 19773)
++++ twisted/web2/dav/test/test_prop.py	(working copy)
+@@ -21,6 +21,8 @@
+ #
+ # DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
++from twisted.web2.dav.element.rfc4331 import QuotaUsedBytes
++from twisted.web2.dav.element.rfc4331 import QuotaAvailableBytes
+ 
+ import random
+ 
+@@ -37,8 +39,13 @@
+ from twisted.web2.dav.test.util import serialize
+ import twisted.web2.dav.test.util
+ 
+-live_properties = [lookupElement(qname)() for qname in DAVResource.liveProperties if qname[0] == dav_namespace]
++# Remove dynamic live properties that exist
++dynamicLiveProperties = (
++    (dav_namespace, "quota-available-bytes"     ),
++    (dav_namespace, "quota-used-bytes"          ),
++)
+ 
++
+ #
+ # See whether dead properties are available
+ #
+@@ -49,6 +56,10 @@
+     """
+     PROPFIND, PROPPATCH requests
+     """
++
++    def liveProperties(self):
++        return [lookupElement(qname)() for qname in self.resource_class.liveProperties if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]
++
+     def test_PROPFIND_basic(self):
+         """
+         PROPFIND request
+@@ -85,7 +96,7 @@
+                             self.fail("PROPFIND failed (status %s) to locate live properties: %s"
+                                       % (status.code, properties))
+ 
+-                        properties_to_find = [p.qname() for p in live_properties]
++                        properties_to_find = [p.qname() for p in self.liveProperties()]
+ 
+                         for property in properties:
+                             qname = property.qname()
+@@ -102,7 +113,7 @@
+             else:
+                 self.fail("No response for URI /")
+ 
+-        query = davxml.PropertyFind(davxml.PropertyContainer(*live_properties))
++        query = davxml.PropertyFind(davxml.PropertyContainer(*self.liveProperties()))
+ 
+         request = SimpleRequest(self.site, "PROPFIND", "/")
+ 
+@@ -146,9 +157,9 @@
+                               % (status.code, properties))
+ 
+                 if which.name == "allprop":
+-                    properties_to_find = [p.qname() for p in live_properties if not p.hidden]
++                    properties_to_find = [p.qname() for p in self.liveProperties() if not p.hidden]
+                 else:
+-                    properties_to_find = [p.qname() for p in live_properties]
++                    properties_to_find = [p.qname() for p in self.liveProperties()]
+ 
+                 for property in properties:
+                     qname = property.qname()

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_quota.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_quota.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_quota.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_quota.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,206 @@
+Index: twisted/web2/dav/test/test_quota.py
+===================================================================
+--- twisted/web2/dav/test/test_quota.py	(revision 0)
++++ twisted/web2/dav/test/test_quota.py	(revision 0)
+@@ -0,0 +1,201 @@
++##
++# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Wilfredo Sanchez, wsanchez at apple.com
++##
++
++from twisted.web2 import responsecode
++from twisted.web2.iweb import IResponse
++from twisted.web2.stream import FileStream
++
++import twisted.web2.dav.test.util
++from twisted.web2.test.test_server import SimpleRequest
++from twisted.web2.dav.test.util import Site
++from twisted.web2.dav import davxml
++import os
++
++class QuotaBase(twisted.web2.dav.test.util.TestCase):
++
++    def setUp(self):
++        
++        #super(Quota, self).setUp()
++        self.docroot = self.mktemp()
++        os.mkdir(self.docroot)
++        rootresource = self.resource_class(self.docroot)
++        rootresource.setAccessControlList(self.grantInherit(davxml.All()))
++        self.site = Site(rootresource)
++        self.site.resource.setQuotaRoot(None, 100000)
++
++    def checkQuota(self, value):
++        def _defer(quota):
++            self.assertEqual(quota, value)
++            
++        d = self.site.resource.currentQuotaUse(None)
++        d.addCallback(_defer)
++        return d
++        
++class QuotaEmpty(QuotaBase):
++
++    def test_Empty_Quota(self):
++        
++        return self.checkQuota(0)
++
++class QuotaPUT(QuotaBase):
++
++    def test_Quota_PUT(self):
++        """
++        Quota change on PUT
++        """
++        dst_uri = "/dst"
++
++        def checkResult(response):
++            response = IResponse(response)
++
++            if response.code != responsecode.CREATED:
++                self.fail("Incorrect response code for PUT (%s != %s)"
++                          % (response.code, responsecode.CREATED))
++                
++            return self.checkQuota(100)
++
++        request = SimpleRequest(self.site, "PUT", dst_uri)
++        request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))        
++        return self.send(request, checkResult)
++
++class QuotaDELETE(QuotaBase):
++
++    def test_Quota_DELETE(self):
++        """
++        Quota change on DELETE
++        """
++        dst_uri = "/dst"
++
++        def checkPUTResult(response):
++            response = IResponse(response)
++
++            if response.code != responsecode.CREATED:
++                self.fail("Incorrect response code for PUT (%s != %s)"
++                          % (response.code, responsecode.CREATED))
++            
++            def doDelete(_ignore):
++                def checkDELETEResult(response):
++                    response = IResponse(response)
++        
++                    if response.code != responsecode.NO_CONTENT:
++                        self.fail("Incorrect response code for PUT (%s != %s)"
++                                  % (response.code, responsecode.NO_CONTENT))
++
++                    return self.checkQuota(0)
++                    
++                request = SimpleRequest(self.site, "DELETE", dst_uri)
++                return self.send(request, checkDELETEResult)
++                
++            d = self.checkQuota(100)
++            d.addCallback(doDelete)
++            return d
++
++        request = SimpleRequest(self.site, "PUT", dst_uri)
++        request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))        
++        return self.send(request, checkPUTResult)
++
++class OverQuotaPUT(QuotaBase):
++
++    def test_Quota_PUT(self):
++        """
++        Quota change on PUT
++        """
++        dst_uri = "/dst"
++
++        self.site.resource.setQuotaRoot(None, 90)
++
++        def checkResult(response):
++            response = IResponse(response)
++
++            if response.code != responsecode.INSUFFICIENT_STORAGE_SPACE:
++                self.fail("Incorrect response code for PUT (%s != %s)"
++                          % (response.code, responsecode.INSUFFICIENT_STORAGE_SPACE))
++                
++            return self.checkQuota(0)
++
++        request = SimpleRequest(self.site, "PUT", dst_uri)
++        request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))        
++        return self.send(request, checkResult)
++
++class QuotaOKAdjustment(QuotaBase):
++
++    def test_Quota_OK_Adjustment(self):
++        """
++        Quota adjustment OK
++        """
++        dst_uri = "/dst"
++
++        def checkPUTResult(response):
++            response = IResponse(response)
++
++            if response.code != responsecode.CREATED:
++                self.fail("Incorrect response code for PUT (%s != %s)"
++                          % (response.code, responsecode.CREATED))
++            
++            def doOKAdjustment(_ignore):
++                def checkAdjustmentResult(_ignore):
++                    return self.checkQuota(10)
++                
++                d = self.site.resource.quotaSizeAdjust(None, -90)
++                d.addCallback(checkAdjustmentResult)
++                return d
++                
++            d = self.checkQuota(100)
++            d.addCallback(doOKAdjustment)
++            return d
++
++        request = SimpleRequest(self.site, "PUT", dst_uri)
++        request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))        
++        return self.send(request, checkPUTResult)
++
++class QuotaBadAdjustment(QuotaBase):
++
++    def test_Quota_Bad_Adjustment(self):
++        """
++        Quota adjustment too much
++        """
++        dst_uri = "/dst"
++
++        def checkPUTResult(response):
++            response = IResponse(response)
++
++            if response.code != responsecode.CREATED:
++                self.fail("Incorrect response code for PUT (%s != %s)"
++                          % (response.code, responsecode.CREATED))
++            
++            def doBadAdjustment(_ignore):
++                def checkAdjustmentResult(_ignore):
++                    return self.checkQuota(100)
++                
++                d = self.site.resource.quotaSizeAdjust(None, -200)
++                d.addCallback(checkAdjustmentResult)
++                return d
++                
++            d = self.checkQuota(100)
++            d.addCallback(doBadAdjustment)
++            return d
++
++        request = SimpleRequest(self.site, "PUT", dst_uri)
++        request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))        
++        return self.send(request, checkPUTResult)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,102 @@
+Index: twisted/web2/dav/test/test_resource.py
+===================================================================
+--- twisted/web2/dav/test/test_resource.py	(revision 19773)
++++ twisted/web2/dav/test/test_resource.py	(working copy)
+@@ -192,13 +192,10 @@
+ class AccessTests(TestCase):
+     def setUp(self):
+         gooduser = TestDAVPrincipalResource('/users/gooduser')
++        gooduser.writeDeadProperty(TwistedPasswordProperty('goodpass'))
+ 
+-        gooduser.writeDeadProperty(
+-            TwistedPasswordProperty.fromString('goodpass'))
+-
+         baduser = TestDAVPrincipalResource('/users/baduser')
+-        baduser.writeDeadProperty(
+-            TwistedPasswordProperty.fromString('badpass'))
++        baduser.writeDeadProperty(TwistedPasswordProperty('badpass'))
+ 
+         protected = TestResource('/protected')
+         protected.setAccessControlList(davxml.ACL(
+@@ -282,7 +279,8 @@
+         # Has auth; should allow
+ 
+         request = SimpleRequest(site, "GET", "/")
+-        request.user = davxml.Principal(davxml.HRef("/users/d00d"))
++        request.authnUser = davxml.Principal(davxml.HRef("/users/d00d"))
++        request.authzUser = davxml.Principal(davxml.HRef("/users/d00d"))
+         d = request.locateResource('/')
+         d.addCallback(_checkPrivileges)
+         d.addCallback(expectOK)
+@@ -301,6 +299,8 @@
+                       
+         return self.checkSecurity(request)
+ 
++    test_authorize.todo = "Needs refactoring"
++
+     def test_badUsernameOrPassword(self):
+         request = SimpleRequest(self.site, 'GET', '/protected')
+ 
+@@ -316,6 +316,8 @@
+ 
+         return d
+ 
++    test_badUsernameOrPassword.todo = "Needs refactoring."
++
+     def test_lacksPrivileges(self):
+         request = SimpleRequest(self.site, 'GET', '/protected')
+ 
+@@ -348,12 +350,12 @@
+             davxml.Grant(davxml.Privilege(davxml.All())),
+             davxml.Protected()))
+ 
+-    def __init__(self, uri=None, children=None):
++    def __init__(self, uri=None, children=None, principalCollections=()):
+         """
+         @param uri: A string respresenting the URI of the given resource
+         @param children: a dictionary of names to Resources
+         """
+-
++        DAVResource.__init__(self, principalCollections=principalCollections)
+         self.children = children
+         self.uri = uri
+ 
+@@ -380,8 +382,8 @@
+         return succeed(davPrivilegeSet)
+ 
+     def currentPrincipal(self, request):
+-        if hasattr(request, "user"):
+-            return request.user
++        if hasattr(request, "authzUser"):
++            return request.authzUser
+         else:
+             return davxml.Principal(davxml.Unauthenticated())
+ 
+@@ -399,18 +401,21 @@
+ 
+     def accessControlList(self, request, **kwargs):
+         return succeed(self.acl)
+-    
+ 
+ class AuthAllResource (TestResource):
+-    """Give Authenticated principals all privileges deny everything else
+     """
++    Give Authenticated principals all privileges and deny everyone else.
++    """
+     acl = davxml.ACL(
+         davxml.ACE(
+             davxml.Principal(davxml.Authenticated()),
+             davxml.Grant(davxml.Privilege(davxml.All())),
+-            davxml.Protected()))
+-
++            davxml.Protected()
++        )
++    )
+     
+ class TestDAVPrincipalResource(DAVPrincipalResource, TestResource):
+-    """Get deadProperties from TestResource
+-    """
++    # Get dead properties from TestResource
++
++    def principalURL(self):
++        return self.uri

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_static.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_static.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_static.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_static.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,10 @@
+Index: twisted/web2/dav/test/test_static.py
+===================================================================
+--- twisted/web2/dav/test/test_static.py	(revision 19773)
++++ twisted/web2/dav/test/test_static.py	(working copy)
+@@ -60,3 +60,5 @@
+         d.addCallback(assertListing)
+ 
+         return d
++
++    test_renderPrivileges.todo = "We changed the rules here; test needs an update"

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_stream.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_stream.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_stream.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_stream.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,56 @@
+Index: twisted/web2/dav/test/test_stream.py
+===================================================================
+--- twisted/web2/dav/test/test_stream.py	(revision 0)
++++ twisted/web2/dav/test/test_stream.py	(revision 0)
+@@ -0,0 +1,51 @@
++##
++# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++##
++
++from twisted.trial import unittest
++from twisted.web2.stream import MemoryStream
++from twisted.web2.dav.stream import MD5StreamWrapper
++import md5
++
++class Stream(unittest.TestCase):
++    """
++    MD5StreamWrapper tests.
++    """
++    def test_simple(self):
++        """
++        Simple test
++        """
++        
++        data = """I am sorry Dave, I can't do that.
++--HAL 9000
++"""
++
++        datastream = MemoryStream(data)
++        stream = MD5StreamWrapper(datastream)
++        
++        while stream.read() is not None:
++            pass
++        stream.close()
++
++        m = md5.new()
++        m.update(data)
++        
++        self.assertEqual(m.hexdigest(), stream.getMD5())

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_xml.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_xml.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_xml.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_xml.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,88 @@
+Index: twisted/web2/dav/test/test_xml.py
+===================================================================
+--- twisted/web2/dav/test/test_xml.py	(revision 0)
++++ twisted/web2/dav/test/test_xml.py	(revision 0)
+@@ -0,0 +1,83 @@
++##
++# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++##
++
++from twisted.trial import unittest
++from twisted.web2.dav import davxml
++
++class XML(unittest.TestCase):
++    """
++    XML tests.
++    """
++    def test_parse(self):
++        """
++        Simple parsing
++        """
++        doc = davxml.WebDAVDocument.fromString(
++            """<?xml version="1.0" encoding="utf-8" ?>"""
++            """<D:multistatus xmlns:D="DAV:">"""
++            """  <D:response>"""
++            """    <D:href>http://webdav.sb.aol.com/webdav/secret</D:href>"""
++            """    <D:status>HTTP/1.1 403 Forbidden</D:status>"""
++            """  </D:response>"""
++            """</D:multistatus>"""
++        )
++        self.assertEquals(
++            doc,
++            davxml.WebDAVDocument(
++                davxml.MultiStatus(
++                    davxml.Response(
++                        davxml.HRef("http://webdav.sb.aol.com/webdav/secret"),
++                        davxml.Status("HTTP/1.1 403 Forbidden"),
++                    )
++                )
++            )
++        )
++
++    def test_serialize_unserialize(self):
++        """
++        Serialization and unserialization results in equivalent document.
++        """
++        doc = davxml.WebDAVDocument(
++            davxml.MultiStatus(
++                davxml.Response(
++                    davxml.HRef("http://webdav.sb.aol.com/webdav/secret"),
++                    davxml.Status("HTTP/1.1 403 Forbidden"),
++                )
++            )
++        )
++        self.assertEquals(doc, davxml.WebDAVDocument.fromString(doc.toxml()))
++
++    def test_unknownElement(self):
++        """
++        Serialization and unserialization of unknown element.
++        """
++        doc = davxml.WebDAVDocument.fromString(
++            """<?xml version="1.0" encoding="utf-8" ?>"""
++            """<T:foo xmlns:T="http://twistedmatrix.com/"/>"""
++        )
++
++        foo = davxml.WebDAVUnknownElement()
++        foo.namespace = "http://twistedmatrix.com/"
++        foo.name = "foo"
++
++        self.assertEquals(doc, davxml.WebDAVDocument(foo))
++        self.assertEquals(doc, davxml.WebDAVDocument.fromString(doc.toxml()))

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.tworequest_client.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.tworequest_client.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.tworequest_client.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.tworequest_client.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,52 @@
+Index: twisted/web2/dav/test/tworequest_client.py
+===================================================================
+--- twisted/web2/dav/test/tworequest_client.py	(revision 0)
++++ twisted/web2/dav/test/tworequest_client.py	(revision 0)
+@@ -0,0 +1,47 @@
++import socket, sys
++
++test_type = sys.argv[1]
++port = int(sys.argv[2])
++socket_type = sys.argv[3]
++
++s = socket.socket(socket.AF_INET)
++s.connect(("127.0.0.1", port))
++s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 40000)
++
++if socket_type == 'ssl':
++    s2 = socket.ssl(s)
++    send=s2.write
++    recv=s2.read
++else:
++    send=s.send
++    recv=s.recv
++    
++print >> sys.stderr, ">> Making %s request to port %d" % (socket_type, port)
++
++send("PUT /forbidden HTTP/1.1\r\n")
++send("Host: localhost\r\n")
++
++print >> sys.stderr, ">> Sending lots of data"
++send("Content-Length: 100\r\n\r\n")
++send("X"*100)
++
++send("PUT /forbidden HTTP/1.1\r\n")
++send("Host: localhost\r\n")
++
++print >> sys.stderr, ">> Sending lots of data"
++send("Content-Length: 100\r\n\r\n")
++send("X"*100)
++
++#import time
++#time.sleep(5)
++print >> sys.stderr, ">> Getting data"
++data=''
++while len(data) < 299999:
++    try:
++        x=recv(10000)
++    except:
++        break
++    if x == '':
++        break
++    data+=x
++sys.stdout.write(data)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.util.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.util.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.util.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.util.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,20 @@
+Index: twisted/web2/dav/test/util.py
+===================================================================
+--- twisted/web2/dav/test/util.py	(revision 19773)
++++ twisted/web2/dav/test/util.py	(working copy)
+@@ -184,10 +184,11 @@
+         d.addCallback(lambda resource: resource.renderHTTP(request))
+         d.addCallback(request._cbFinishRender)
+ 
+-        if type(callback) is tuple:
+-            d.addCallbacks(*callback)
+-        else:
+-            d.addCallback(callback)
++        if callback:
++            if type(callback) is tuple:
++                d.addCallbacks(*callback)
++            else:
++                d.addCallback(callback)
+ 
+         return d
+ 

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.util.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.util.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.util.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.util.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,23 @@
+Index: twisted/web2/dav/util.py
+===================================================================
+--- twisted/web2/dav/util.py	(revision 19773)
++++ twisted/web2/dav/util.py	(working copy)
+@@ -37,6 +37,7 @@
+     "normalizeURL",
+     "joinURL",
+     "parentForURL",
++    "unimplemented",
+     "bindMethods",
+ ]
+ 
+@@ -76,7 +77,9 @@
+ 
+     def parse(xml):
+         try:
+-            return davxml.WebDAVDocument.fromString(xml)
++            doc = davxml.WebDAVDocument.fromString(xml)
++            doc.root_element.validate()
++            return doc
+         except ValueError:
+             log.err("Bad XML:\n%s" % (xml,))
+             raise

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,169 @@
+Index: twisted/web2/dav/xattrprops.py
+===================================================================
+--- twisted/web2/dav/xattrprops.py	(revision 19773)
++++ twisted/web2/dav/xattrprops.py	(working copy)
+@@ -33,15 +33,24 @@
+ 
+ import urllib
+ import sys
++import zlib
++from time import sleep
++from random import random
++from errno import EAGAIN
++from zlib import compress, decompress
++from cPickle import loads as unpickle, PicklingError, UnpicklingError
+ 
+ import xattr
+ 
+ if getattr(xattr, 'xattr', None) is None:
+     raise ImportError("wrong xattr package imported")
+ 
++from twisted.python import log
++from twisted.python.failure import Failure
+ from twisted.web2 import responsecode
+ from twisted.web2.http import HTTPError, StatusResponse
+ from twisted.web2.dav import davxml
++from twisted.web2.dav.http import statusForFailure
+ 
+ class xattrPropertyStore (object):
+     """
+@@ -66,16 +75,8 @@
+         deadPropertyXattrPrefix = "user."
+ 
+     def _encode(clazz, name):
+-        #
+-        # FIXME: The xattr API in Mac OS 10.4.2 breaks if you have "/" in an
+-        # attribute name (radar://4202440). We'll quote the strings to get rid
+-        # of "/" characters for now.
+-        #
+-        result = list("{%s}%s" % name)
+-        for i in range(len(result)):
+-            c = result[i]
+-            if c in "%/": result[i] = "%%%02X" % (ord(c),)
+-        r = clazz.deadPropertyXattrPrefix + ''.join(result)
++        result = urllib.quote("{%s}%s" % name, safe='{}:')
++        r = clazz.deadPropertyXattrPrefix + result
+         return r
+ 
+     def _decode(clazz, name):
+@@ -97,19 +98,83 @@
+ 
+     def get(self, qname):
+         try:
+-            value = self.attrs[self._encode(qname)]
++            data = self.attrs[self._encode(qname)]
+         except KeyError:
+             raise HTTPError(StatusResponse(
+                 responsecode.NOT_FOUND,
+                 "No such property: {%s}%s" % qname
+             ))
++        except Exception, e:
++            raise HTTPError(StatusResponse(
++                statusForFailure(Failure()),
++                "Unable to read property: {%s}%s" % qname
++            ))
+ 
+-        doc = davxml.WebDAVDocument.fromString(value)
++        #
++        # Serialize XML data to an xattr.  The storage format has
++        # changed over time:
++        #
++        #  1- Started with XML
++        #  2- Started compressing the XML due to limits on xattr size
++        #  3- Switched to pickle which is faster, still compressing
++        #  4- Back to compressed XML for interoperability, size
++        #
++        # We only write the current format, but we also read the old
++        # ones for compatibility.
++        #
++        legacy = False
+ 
+-        return doc.root_element
++        try:
++            data = decompress(data)
++        except zlib.error:
++            legacy = True
+ 
++        try:
++            doc = davxml.WebDAVDocument.fromString(data)
++            result = doc.root_element
++        except ValueError:
++            legacy = True
++
++            try:
++                result = unpickle(data)
++            except UnpicklingError:
++                msg = ("Invalid property value stored on server: {%s}%s %s"
++                       % (qname[0], qname[1], data))
++                log.err(msg)
++                raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, msg))
++
++        if legacy:
++            # Try to upgrade the property to the current format
++            try:
++                log.msg("Upgrading property %r on resource %r"
++                        % (qname, self.resource.fp.path))
++                self.set(result)
++            except Exception, e:
++                #
++                # Hrm, that failed.  No need to re-raise here, since
++                # we can do what we were asked to do, but let's
++                # complain about it.
++                #
++                log.err("Error while upgrading property %r on resource %r: %s"
++                        % (qname, self.resource.fp.path, e))
++
++        return result
++
+     def set(self, property):
+-        self.attrs[self._encode(property.qname())] = property.toxml()
++        for n in range(20):
++            data = compress(property.toxml())
++            try:
++                self.attrs[self._encode(property.qname())] = data
++            except Exception, e:
++                if e.errno == EAGAIN and n < 19:
++                    sleep(random() / 10) # OMG Brutal Hax
++                else:
++                    raise HTTPError(StatusResponse(
++                        statusForFailure(Failure()),
++                        "Unable to write property: %s" % (property.sname(),)
++                    ))
++            else:
++                break
+ 
+         # Update the resource because we've modified it
+         self.resource.fp.restat()
+@@ -121,15 +186,31 @@
+             # RFC 2518 Section 12.13.1 says that removal of
+             # non-existing property is not an error.
+             pass
++        except Exception, e:
++            raise HTTPError(StatusResponse(
++                statusForFailure(Failure()),
++                "Unable to delete property: {%s}%s" % qname
++            ))
+ 
+     def contains(self, qname):
+         try:
+             return self._encode(qname) in self.attrs
+         except TypeError:
+             return False
++        except Exception, e:
++            raise HTTPError(StatusResponse(
++                statusForFailure(Failure()),
++                "Unable to read properties"
++            ))
+ 
+     def list(self):
+         prefix     = self.deadPropertyXattrPrefix
+         prefix_len = len(prefix)
+ 
+-        return [ self._decode(name) for name in self.attrs if name.startswith(prefix) ]
++        try:
++            return [ self._decode(name) for name in self.attrs if name.startswith(prefix) ]
++        except Exception, e:
++            raise HTTPError(StatusResponse(
++                statusForFailure(Failure()),
++                "Unable to list properties"
++            ))

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.http.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.http.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.http.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.http.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,57 @@
+Index: twisted/web2/http.py
+===================================================================
+--- twisted/web2/http.py	(revision 19773)
++++ twisted/web2/http.py	(working copy)
+@@ -26,7 +26,7 @@
+ from twisted.web2 import http_headers
+ from twisted.web2 import iweb
+ from twisted.web2 import stream
+-from twisted.web2.stream import IByteStream
++from twisted.web2.stream import IByteStream, readAndDiscard
+ 
+ defaultPortForScheme = {'http': 80, 'https':443, 'ftp':21}
+ 
+@@ -66,9 +66,9 @@
+             object.
+         @type codeOrResponse: C{int} or L{http.Response}
+         """
+-        Exception.__init__(self)
+-        self.response = iweb.IResponse(codeOrResponse)
+-        self.args = str(self.response)
++        response = iweb.IResponse(codeOrResponse)
++        Exception.__init__(self, str(response))
++        self.response = response
+ 
+     def __repr__(self):
+         return "<%s %s>" % (self.__class__.__name__, self.response)
+@@ -408,9 +408,22 @@
+     def _sendContinue(self):
+         self.chanRequest.writeIntermediateResponse(responsecode.CONTINUE)
+ 
+-    def _finished(self, x):
++    def _reallyFinished(self, x):
+         """We are finished writing data."""
+         self.chanRequest.finish()
++        
++    def _finished(self, x):
++        """
++        We are finished writing data.
++        But we need to check that we have also finished reading all data as we
++        might have sent a, for example, 401 response before we read any data.
++        To make sure that the stream/producer sequencing works properly we need
++        to discard the remaining data in the request.  
++        """
++        if self.stream.length != 0:
++            return readAndDiscard(self.stream).addCallback(self._reallyFinished).addErrback(self._error)
++        else:
++            self._reallyFinished(x)
+ 
+     def _error(self, reason):
+         if reason.check(error.ConnectionLost):
+@@ -471,5 +484,5 @@
+ else:
+     components.registerAdapter(compat.OldResourceAdapter, resource.IResource, iweb.IOldNevowResource)
+ 
+-__all__ = ['HTTPError', 'NotModifiedResponse', 'Request', 'Response', 'checkIfRange', 'checkPreconditions', 'defaultPortForScheme', 'parseVersion', 'splitHostPort']
++__all__ = ['HTTPError', 'NotModifiedResponse', 'Request', 'Response', 'StatusResponse', 'RedirectResponse', 'checkIfRange', 'checkPreconditions', 'defaultPortForScheme', 'parseVersion', 'splitHostPort']
+ 

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,13 @@
+Index: twisted/web2/log.py
+===================================================================
+--- twisted/web2/log.py	(revision 19773)
++++ twisted/web2/log.py	(working copy)
+@@ -88,7 +88,7 @@
+ class LogWrapperResource(resource.WrapperResource):
+     def hook(self, request):
+         # Insert logger
+-        request.addResponseFilter(logFilter, atEnd=True)
++        request.addResponseFilter(logFilter, atEnd=True, onlyOnce=True)
+ 
+ monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+              'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,129 @@
+Index: twisted/web2/server.py
+===================================================================
+--- twisted/web2/server.py	(revision 19773)
++++ twisted/web2/server.py	(working copy)
+@@ -26,6 +26,7 @@
+ from twisted.web2 import http_headers
+ from twisted.web2.filter.range import rangefilter
+ from twisted.web2 import error
++from twisted.web2.dav.util import joinURL
+ 
+ from twisted.web2 import version as web2_version
+ from twisted import __version__ as twisted_version
+@@ -143,6 +144,9 @@
+                        error.defaultErrorHandler, defaultHeadersFilter]
+     
+     def __init__(self, *args, **kw):
++        
++        self.initTime = time.time()
++
+         if kw.has_key('site'):
+             self.site = kw['site']
+             del kw['site']
+@@ -150,17 +154,36 @@
+             self._initialprepath = kw['prepathuri']
+             del kw['prepathuri']
+ 
++        self._resourcesByURL = {}
++        self._urlsByResource = {}
++
+         # Copy response filters from the class
+         self.responseFilters = self.responseFilters[:]
+         self.files = {}
+         self.resources = []
+         http.Request.__init__(self, *args, **kw)
++        try:
++            self.serverInstance = self.chanRequest.channel.transport.server.port
++        except AttributeError:
++            self.serverInstance = "Unknown"
+ 
+-    def addResponseFilter(self, f, atEnd=False):
++    def addResponseFilter(self, filter, atEnd=False, onlyOnce=False):
++        """
++        Add a response filter to this request.
++        Response filters are applied to the response to this request in order.
++        @param filter: a callable which takes an response argument and returns
++            a response object.
++        @param atEnd: if C{True}, C{filter} is added at the end of the list of
++            response filters; if C{False}, it is added to the beginning.
++        @param onlyOnce: if C{True}, C{filter} is not added to the list of
++            response filters if it already in the list.
++        """
++        if onlyOnce and filter in self.responseFilters:
++            return
+         if atEnd:
+-            self.responseFilters.append(f)
++            self.responseFilters.append(filter)
+         else:
+-            self.responseFilters.insert(0, f)
++            self.responseFilters.insert(0, filter)
+ 
+     def unparseURL(self, scheme=None, host=None, port=None,
+                    path=None, params=None, querystring=None, fragment=None):
+@@ -265,6 +288,7 @@
+         
+         d = defer.Deferred()
+         d.addCallback(self._getChild, self.site.resource, self.postpath)
++        d.addCallback(self._rememberResource, "/" + "/".join(quote(s) for s in self.postpath))
+         d.addCallback(lambda res, req: res.renderHTTP(req), self)
+         d.addCallback(self._cbFinishRender)
+         d.addErrback(self._processingFailed)
+@@ -321,7 +345,6 @@
+         if newpath is StopTraversal:
+             # We need to rethink how to do this.
+             #if newres is res:
+-                self._rememberResource(res, url)
+                 return res
+             #else:
+             #    raise ValueError("locateChild must not return StopTraversal with a resource other than self.")
+@@ -337,7 +360,6 @@
+                 self.prepath.append(self.postpath.pop(0))
+ 
+         child = self._getChild(None, newres, newpath, updatepaths=updatepaths)
+-        self._rememberResource(child, url)
+ 
+         return child
+ 
+@@ -347,6 +369,7 @@
+         """
+         Remember the URL of a visited resource.
+         """
++        self._resourcesByURL[url] = resource
+         self._urlsByResource[resource] = url
+         return resource
+ 
+@@ -386,7 +409,8 @@
+             The contained response will have a status code of
+             L{responsecode.BAD_REQUEST}.
+         """
+-        if url is None: return None
++        if url is None:
++            return defer.succeed(None)
+ 
+         #
+         # Parse the URL
+@@ -407,9 +431,13 @@
+                 "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
+             ))
+ 
+-        segments = path.split("/")
++        # Look for cached value
++        cached = self._resourcesByURL.get(path, None)
++        if cached is not None:
++            return defer.succeed(cached)
++
++        segments = unquote(path).split("/")
+         assert segments[0] == "", "URL path didn't begin with '/': %s" % (path,)
+-        segments = map(unquote, segments[1:])
+ 
+         def notFound(f):
+             f.trap(http.HTTPError)
+@@ -417,7 +445,7 @@
+                 return f
+             return None
+ 
+-        d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
++        d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments[1:], updatepaths=False)
+         d.addCallback(self._rememberResource, path)
+         d.addErrback(notFound)
+         return d

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.static.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.static.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.static.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.static.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,33 @@
+Index: twisted/web2/static.py
+===================================================================
+--- twisted/web2/static.py	(revision 19773)
++++ twisted/web2/static.py	(working copy)
+@@ -9,7 +9,6 @@
+ # System Imports
+ import os, time, stat
+ import tempfile
+-import md5
+ 
+ # Sibling Imports
+ from twisted.web2 import http_headers, resource
+@@ -200,7 +199,10 @@
+         super(File, self).__init__()
+ 
+         self.putChildren = {}
+-        self.fp = filepath.FilePath(path)
++        if isinstance(path, filepath.FilePath):
++            self.fp = path
++        else:
++            self.fp = filepath.FilePath(path)
+         # Remove the dots from the path to split
+         self.defaultType = defaultType
+         self.ignoredExts = list(ignoredExts)
+@@ -383,7 +385,7 @@
+             return responsecode.NOT_FOUND
+ 
+         if self.fp.isdir():
+-            if req.uri[-1] != "/":
++            if req.path[-1] != "/":
+                 # Redirect to include trailing '/' in URI
+                 return http.RedirectResponse(req.unparseURL(path=req.path+'/'))
+             else:

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_http.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_http.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_http.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_http.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,23 @@
+Index: twisted/web2/test/test_http.py
+===================================================================
+--- twisted/web2/test/test_http.py	(revision 19773)
++++ twisted/web2/test/test_http.py	(working copy)
+@@ -324,6 +324,9 @@
+     def connectionLost(self, reason):
+         self.cmds.append(('connectionLost', reason))
+ 
++    def _finished(self, x):
++        self._reallyFinished(x)
++
+ class TestResponse(object):
+     implements(iweb.IResponse)
+ 
+@@ -1017,6 +1020,8 @@
+         response = TestResponse()
+         if self.uri == "/error":
+             response.code=402
++        elif self.uri == "/forbidden":
++            response.code=403
+         else:
+             response.code=404
+             response.write("URI %s unrecognized." % self.uri)

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,243 @@
+Index: twisted/web2/test/test_httpauth.py
+===================================================================
+--- twisted/web2/test/test_httpauth.py	(revision 19773)
++++ twisted/web2/test/test_httpauth.py	(working copy)
+@@ -1,3 +1,4 @@
++from twisted.internet.defer import inlineCallbacks
+ import md5
+ from twisted.internet import address
+ from twisted.trial import unittest
+@@ -41,22 +42,25 @@
+         self.username = 'dreid'
+         self.password = 'S3CuR1Ty'
+ 
++    @inlineCallbacks
+     def testUsernamePassword(self):
+         response = base64.encodestring('%s:%s' % (
+                 self.username,
+                 self.password))
+ 
+-        creds = self.credentialFactory.decode(response, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(response, _trivial_GET))
+         self.failUnless(creds.checkPassword(self.password))
+ 
++    @inlineCallbacks
+     def testIncorrectPassword(self):
+         response = base64.encodestring('%s:%s' % (
+                 self.username,
+                 'incorrectPassword'))
+ 
+-        creds = self.credentialFactory.decode(response, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(response, _trivial_GET))
+         self.failIf(creds.checkPassword(self.password))
+ 
++    @inlineCallbacks
+     def testIncorrectPadding(self):
+         response = base64.encodestring('%s:%s' % (
+                 self.username,
+@@ -64,7 +68,7 @@
+ 
+         response = response.strip('=')
+ 
+-        creds = self.credentialFactory.decode(response, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(response, _trivial_GET))
+         self.failUnless(creds.checkPassword(self.password))
+ 
+     def testInvalidCredentials(self):
+@@ -135,6 +139,7 @@
+             )
+         return expected
+ 
++    @inlineCallbacks
+     def test_getChallenge(self):
+         """
+         Test that all the required fields exist in the challenge,
+@@ -142,42 +147,44 @@
+         DigestCredentialFactory
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+         self.assertEquals(challenge['qop'], 'auth')
+         self.assertEquals(challenge['realm'], 'test realm')
+         self.assertEquals(challenge['algorithm'], 'md5')
+         self.assertTrue(challenge.has_key("nonce"))
+         self.assertTrue(challenge.has_key("opaque"))
+ 
++    @inlineCallbacks
+     def test_response(self):
+         """
+         Test that we can decode a valid response to our challenge
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+         self.failUnless(creds.checkPassword('password'))
+ 
++    @inlineCallbacks
+     def test_multiResponse(self):
+         """
+         Test that multiple responses to to a single challenge are handled
+         successfully.
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+         self.failUnless(creds.checkPassword('password'))
+ 
+         clientResponse = authRequest2 % (
+@@ -185,24 +192,25 @@
+             self.getDigestResponse(challenge, "00000002"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+         self.failUnless(creds.checkPassword('password'))
+ 
++    @inlineCallbacks
+     def test_failsWithDifferentMethod(self):
+         """
+         Test that the response fails if made for a different request method
+         than it is being issued for.
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse,
+-                                              SimpleRequest(None, 'POST', '/'))
++        creds = (yield self.credentialFactory.decode(clientResponse,
++                                              SimpleRequest(None, 'POST', '/')))
+         self.failIf(creds.checkPassword('password'))
+ 
+     def test_noUsername(self):
+@@ -247,20 +255,21 @@
+                               _trivial_GET)
+         self.assertEquals(str(e), "Invalid response, no opaque given.")
+ 
++    @inlineCallbacks
+     def test_checkHash(self):
+         """
+         Check that given a hash of the form 'username:realm:password'
+         we can verify the digest challenge
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+ 
+         self.failUnless(creds.checkHash(
+                 md5.md5('username:test realm:password').hexdigest()))
+@@ -268,6 +277,7 @@
+         self.failIf(creds.checkHash(
+                 md5.md5('username:test realm:bogus').hexdigest()))
+ 
++    @inlineCallbacks
+     def test_invalidOpaque(self):
+         """
+         Test that login fails when the opaque does not contain all the required
+@@ -276,7 +286,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         self.assertRaises(
+             error.LoginFailed,
+@@ -302,6 +312,7 @@
+             challenge['nonce'],
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_incompatibleNonce(self):
+         """
+         Test that login fails when the given nonce from the response, does not
+@@ -310,7 +321,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         badNonceOpaque = credentialFactory.generateOpaque(
+             '1234567890',
+@@ -330,6 +341,7 @@
+             '',
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_incompatibleClientIp(self):
+         """
+         Test that the login fails when the request comes from a client ip
+@@ -338,7 +350,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         badNonceOpaque = credentialFactory.generateOpaque(
+             challenge['nonce'],
+@@ -351,6 +363,7 @@
+             challenge['nonce'],
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_oldNonce(self):
+         """
+         Test that the login fails when the given opaque is older than
+@@ -359,7 +372,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         key = '%s,%s,%s' % (challenge['nonce'],
+                             clientAddress.host,
+@@ -376,6 +389,7 @@
+             challenge['nonce'],
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_mismatchedOpaqueChecksum(self):
+         """
+         Test that login fails when the opaque checksum fails verification
+@@ -383,7 +397,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+ 
+         key = '%s,%s,%s' % (challenge['nonce'],

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.words.protocols.jabber.sasl_mechanisms.patch (from rev 4133, CalendarServer/trunk/lib-patches/Twisted/twisted.words.protocols.jabber.sasl_mechanisms.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.words.protocols.jabber.sasl_mechanisms.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.words.protocols.jabber.sasl_mechanisms.patch	2009-05-01 21:12:23 UTC (rev 4136)
@@ -0,0 +1,86 @@
+Index: twisted/words/protocols/jabber/sasl_mechanisms.py
+===================================================================
+--- twisted/words/protocols/jabber/sasl_mechanisms.py	(revision 19773)
++++ twisted/words/protocols/jabber/sasl_mechanisms.py	(working copy)
+@@ -7,8 +7,13 @@
+ Protocol agnostic implementations of SASL authentication mechanisms.
+ """
+ 
+-import md5, binascii, random, time, os
++import binascii, random, time, os
+ 
++try:
++    from hashlib import md5
++except ImportError:
++    from md5 import new as md5
++
+ from zope.interface import Interface, Attribute, implements
+ 
+ class ISASLMechanism(Interface):
+@@ -108,16 +113,43 @@
+         @return: challenge directives and their values.
+         @rtype: L{dict} of L{str} to L{str}.
+         """
+-        directive_list = challenge.split(',')
+-        directives = {}
+-        for directive in directive_list:
+-            name, value = directive.split('=')
+-            value = value.replace("'","")
+-            value = value.replace('"','')
+-            directives[name] = value
+-        return directives
++        s = challenge
++        paramDict = {}
++        cur = 0
++        remainingParams = True
++        while remainingParams:
++            # Parse a param. We can't just split on commas, because there can
++            # be some commas inside (quoted) param values, e.g.:
++            # qop="auth,auth-int"
+ 
++            middle = s.index("=", cur)
++            name = s[cur:middle].lstrip()
++            middle += 1
++            if s[middle] == '"':
++                middle += 1
++                end = s.index('"', middle)
++                value = s[middle:end]
++                cur = s.find(',', end) + 1
++                if cur == 0:
++                    remainingParams = False
++            else:
++                end = s.find(',', middle)
++                if end == -1:
++                    value = s[middle:].rstrip()
++                    remainingParams = False
++                else:
++                    value = s[middle:end].rstrip()
++                cur = end + 1
++            paramDict[name] = value
+ 
++        for param in ('qop', 'cipher'):
++            if param in paramDict:
++                paramDict[param] = paramDict[param].split(',')
++
++        return paramDict
++
++
++
+     def _unparse(self, directives):
+         """
+         Create message string from directives.
+@@ -153,7 +185,7 @@
+         """
+ 
+         def H(s):
+-            return md5.new(s).digest()
++            return md5(s).digest()
+ 
+         def HEX(n):
+             return binascii.b2a_hex(n)
+@@ -196,4 +228,4 @@
+ 
+ 
+     def _gen_nonce(self):
+-        return md5.new("%s:%s:%s" % (str(random.random()) , str(time.gmtime()),str(os.getpid()))).hexdigest()
++        return md5("%s:%s:%s" % (str(random.random()) , str(time.gmtime()),str(os.getpid()))).hexdigest()

Modified: CalendarServer/trunk/run
===================================================================
--- CalendarServer/trunk/run	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/run	2009-05-01 21:12:23 UTC (rev 4136)
@@ -104,7 +104,7 @@
     'R')       reactor="-R ${OPTARG}"; ;;
     't')  service_type="${OPTARG}"; ;;
     'K')      read_key="${OPTARG}"; ;;
-    'S')       profile="--profiler cprofile -p ${OPTARG}"; ;;
+    'S')       profile="-p ${OPTARG}"; ;;
     'g') do_get="true" ; do_setup="false"; do_run="false"; ;;
     's') do_get="true" ; do_setup="true" ; do_run="false"; ;;
     'p') do_get="false"; do_setup="false"; do_run="false"; print_path="true"; ;;
@@ -655,8 +655,8 @@
     proto="svn";
     ;;
 esac;
-svn_uri="${proto}://svn.twistedmatrix.com/svn/Twisted/branches/dav-take-two-3081-4";
-svn_get "Twisted" "${twisted}" "${svn_uri}" 26741;
+svn_uri="${proto}://svn.twistedmatrix.com/svn/Twisted/branches/dav-acl-1608-4";
+svn_get "Twisted" "${twisted}" "${svn_uri}" 19773;
 
 # No py_build step, since we tend to do edit Twisted, we want the sources in
 # PYTHONPATH, not a build directory.

Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/twistedcaldav/notify.py	2009-05-01 21:12:23 UTC (rev 4136)
@@ -720,8 +720,7 @@
             return
 
         try:
-            # XXX This codepath is not unit tested
-            iq = IQ(self.xmlStream, 'get')
+            iq = IQ(self.xmlStream, type='get')
             child = iq.addElement('pubsub',
                 defaultUri=self.pubsubNS+"#owner")
             child = child.addElement('configure')
@@ -756,7 +755,7 @@
                     formElement = configureElement.firstChildElement()
                     if formElement['type'] == 'form':
                         # We've found the form; start building a response
-                        filledIq = IQ(self.xmlStream, 'set')
+                        filledIq = IQ(self.xmlStream, type='set')
                         filledPubSub = filledIq.addElement('pubsub',
                             defaultUri=self.pubsubNS+"#owner")
                         filledConfigure = filledPubSub.addElement('configure')
@@ -785,8 +784,7 @@
                                         valueElement.addContent(value)
                                         # filledForm.addChild(field)
                         if configMatches:
-                            # XXX This codepath is not unit tested
-                            cancelIq = IQ(self.xmlStream, 'set')
+                            cancelIq = IQ(self.xmlStream, type='set')
                             cancelPubSub = cancelIq.addElement('pubsub',
                                 defaultUri=self.pubsubNS+"#owner")
                             cancelConfig = cancelPubSub.addElement('configure')
@@ -894,7 +892,7 @@
     def requestRoster(self):
         if self.doRoster:
             self.roster = {}
-            rosterIq = IQ(self.xmlStream, 'get')
+            rosterIq = IQ(self.xmlStream, type='get')
             rosterIq.addElement("query", "jabber:iq:roster")
             d = rosterIq.send()
             d.addCallback(self.handleRoster)
@@ -952,8 +950,7 @@
             self.xmlStream.send(response)
 
             # remove from roster as well
-            # XXX This codepath is not unit tested
-            removal = IQ(self.xmlStream, 'set')
+            removal = IQ(self.xmlStream, type='set')
             query = removal.addElement("query", "jabber:iq:roster")
             query.addElement("item")
             query.item["jid"] = iq["from"]

Modified: CalendarServer/trunk/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_index.py	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/twistedcaldav/test/test_index.py	2009-05-01 21:12:23 UTC (rev 4136)
@@ -19,8 +19,7 @@
 import twistedcaldav.test.util
 from twistedcaldav.test.util import InMemoryMemcacheProtocol
 from twistedcaldav.index import ReservationError, MemcachedUIDReserver
-from twisted.internet import reactor
-from twisted.internet.task import deferLater
+from twisted.web2.test.test_http import deferLater
 
 class SQLIndexTests (twistedcaldav.test.util.TestCase):
     """
@@ -81,7 +80,7 @@
         d.addCallback(lambda _: self.db.reserveUID(uid))
         d.addCallback(lambda _: self.db.isReservedUID(uid))
         d.addCallback(self.assertTrue)
-        d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
+        d.addCallback(lambda _: deferLater(2))
         d.addCallback(lambda _: self.db.isReservedUID(uid))
         d.addCallback(self.assertFalse)
         d.addBoth(_finally)

Modified: CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_upgrade.py	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/twistedcaldav/test/test_upgrade.py	2009-05-01 21:12:23 UTC (rev 4136)
@@ -188,7 +188,7 @@
         # Verify these values do require updating:
         #
 
-        expected = "<?xml version='1.0' encoding='UTF-8'?><calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"
+        expected = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>\r\n"
 
         # Uncompressed XML
         value = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/users/wsanchez/calendar</href>\r\n</calendar-free-busy-set>\r\n"
@@ -314,7 +314,7 @@
                                 {
                                     "@xattrs" :
                                     {
-                                        freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?><calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"),
+                                        freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>\r\n"),
                                     },
                                 },
                             },
@@ -430,7 +430,7 @@
                                 {
                                     "@xattrs" :
                                     {
-                                        freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?><calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"),
+                                        freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>\r\n"),
                                     },
                                 },
                             },
@@ -550,7 +550,7 @@
                                 {
                                     "@xattrs" :
                                     {
-                                        freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?><calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>"),
+                                        freeBusyAttr : zlib.compress("<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n  <href xmlns='DAV:'>/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar/</href>\r\n</calendar-free-busy-set>\r\n"),
                                     },
                                 },
                             },

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2009-05-01 20:55:12 UTC (rev 4135)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2009-05-01 21:12:23 UTC (rev 4136)
@@ -117,7 +117,6 @@
                             try:
                                 if xattr.getxattr(childPath, attr) != value:
                                     print "Xattr mismatch:", childPath, attr
-                                    print (xattr.getxattr(childPath, attr), " != ", value)
                                     return False
                             except:
                                 return False
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090501/bb7f0e0a/attachment-0001.html>


More information about the calendarserver-changes mailing list